Official Go implementation of the Bytom protocol
Revisão | 11d72f6533eb491db3074772dc2a8e0bb41cb6ff (tree) |
---|---|
Hora | 2021-07-30 12:31:49 |
Autor | DeKaiju <longjinglv@163....> |
Commiter | GitHub |
feat(toolbar): add vote reward tool (#2058)
* feat(toolbar): add vote reward tool
* refactor(toolbar): code refactoring
* refactor(toolbar): code refactoring
* refactor(toolbar): code refactoring
* refactor(toolbar): code refactoring
Co-authored-by: Paladz <yzhu101@uottawa.ca>
@@ -0,0 +1,66 @@ | ||
1 | +## database | |
2 | + | |
3 | +- Create a MySQL database locally or with server installation | |
4 | +- Import table structure to MySQL database, table structure path: bytom/toolbar/vote_reward/database/dump_reward.sql | |
5 | + | |
6 | + | |
7 | + | |
8 | +## configuration file | |
9 | + | |
10 | +- Default file name:reward.json | |
11 | +- A `reward.json` would look like this: | |
12 | + | |
13 | +```json | |
14 | +{ | |
15 | + "node_ip": "http://127.0.0.1:9888", // node API address, replace with self node API address | |
16 | + "chain_id": "mainnet", //Node network type | |
17 | + "mysql": { // Mysql connection information | |
18 | + "connection": { | |
19 | + "host": "192.168.30.186", | |
20 | + "port": 3306, | |
21 | + "username": "root", | |
22 | + "password": "123456", | |
23 | + "database": "reward" | |
24 | + }, | |
25 | + "log_mode": false // default | |
26 | + }, | |
27 | + "reward_config": { | |
28 | + "xpub": "9742a39a0bcfb5b7ac8f56f1894fbb694b53ebf58f9a032c36cc22d57a06e49e94ff7199063fb7a78190624fa3530f611404b56fc9af91dcaf4639614512cb64", // Node public key (from dashboard Settings), replaced with its own | |
29 | + "account_id": "bd775113-49e0-4678-94bf-2b853f1afe80", // accountID | |
30 | + "password": "123456",// The password corresponding to the account ID | |
31 | + "reward_ratio": 20,// The percentage of a reward given to a voter per block | |
32 | + "mining_address": "sp1qfpgjve27gx0r9t7vud8vypplkzytgrvqr74rwz" // The address that receives the block reward, use the get-mining- address for mining address, for example, curl -x POST http://127.0.0.1:9889/get-mining-address -d '{}' | |
33 | + } | |
34 | +} | |
35 | +``` | |
36 | + | |
37 | + | |
38 | + | |
39 | +tool use | |
40 | + | |
41 | +params | |
42 | + | |
43 | +```shell | |
44 | +distribution of reward. | |
45 | + | |
46 | +Usage: | |
47 | + reward [flags] | |
48 | + | |
49 | +Flags: | |
50 | + --config_file string config file. default: reward.json (default "reward.json") | |
51 | + -h, --help help for reward | |
52 | + --reward_end_height uint The end height of the distributive income reward interval, It is a multiple of the dpos consensus cycle(1200). example: 2400 | |
53 | + --reward_start_height uint The starting height of the distributive income reward interval, It is a multiple of the dpos consensus cycle(1200). example: 1200 | |
54 | +``` | |
55 | + | |
56 | +example: | |
57 | + | |
58 | +```shell | |
59 | +./votereward reward --reward_start_height 6000 --reward_end_height 7200 | |
60 | +``` | |
61 | + | |
62 | + | |
63 | + | |
64 | +Note: | |
65 | + | |
66 | +When an error (Gas credit has been spent) is returned, UTXO needs to be merged. | |
\ No newline at end of file |
@@ -0,0 +1,85 @@ | ||
1 | +package main | |
2 | + | |
3 | +import ( | |
4 | + "time" | |
5 | + | |
6 | + log "github.com/sirupsen/logrus" | |
7 | + "github.com/spf13/cobra" | |
8 | + "github.com/tendermint/tmlibs/cli" | |
9 | + | |
10 | + "github.com/bytom/bytom/consensus" | |
11 | + "github.com/bytom/bytom/toolbar/common" | |
12 | + cfg "github.com/bytom/bytom/toolbar/vote_reward/config" | |
13 | + "github.com/bytom/bytom/toolbar/vote_reward/settlementvotereward" | |
14 | + "github.com/bytom/bytom/toolbar/vote_reward/synchron" | |
15 | +) | |
16 | + | |
17 | +const logModule = "reward" | |
18 | + | |
19 | +var ( | |
20 | + rewardStartHeight uint64 | |
21 | + rewardEndHeight uint64 | |
22 | + configFile string | |
23 | +) | |
24 | + | |
25 | +var RootCmd = &cobra.Command{ | |
26 | + Use: "reward", | |
27 | + Short: "distribution of reward.", | |
28 | + RunE: runReward, | |
29 | +} | |
30 | + | |
31 | +func init() { | |
32 | + RootCmd.Flags().Uint64Var(&rewardStartHeight, "reward_start_height", 0, "The starting height of the distributive income reward interval, It is a multiple of the dpos consensus cycle(1200). example: 1200") | |
33 | + RootCmd.Flags().Uint64Var(&rewardEndHeight, "reward_end_height", 0, "The end height of the distributive income reward interval, It is a multiple of the dpos consensus cycle(1200). example: 2400") | |
34 | + RootCmd.Flags().StringVar(&configFile, "config_file", "reward.json", "config file. default: reward.json") | |
35 | +} | |
36 | + | |
37 | +func runReward(cmd *cobra.Command, args []string) error { | |
38 | + log.Info("This tool belongs to an open-source project, we can not guarantee this tool is bug-free. Please check the code before using, developers will not be responsible for any asset loss due to bug!") | |
39 | + startTime := time.Now() | |
40 | + config := &cfg.Config{} | |
41 | + if err := cfg.LoadConfigFile(configFile, config); err != nil { | |
42 | + log.WithFields(log.Fields{"module": logModule, "config": configFile, "error": err}).Fatal("Failded to load config file.") | |
43 | + } | |
44 | + | |
45 | + if err := consensus.InitActiveNetParams(config.ChainID); err != nil { | |
46 | + log.WithFields(log.Fields{"module": logModule, "error": err}).Fatal("Init ActiveNetParams.") | |
47 | + } | |
48 | + if rewardStartHeight >= rewardEndHeight || rewardStartHeight%consensus.ActiveNetParams.BlocksOfEpoch != 0 || rewardEndHeight%consensus.ActiveNetParams.BlocksOfEpoch != 0 { | |
49 | + log.Fatal("Please check the height range, which must be multiple of the number of block rounds.") | |
50 | + } | |
51 | + | |
52 | + db, err := common.NewMySQLDB(config.MySQLConfig) | |
53 | + if err != nil { | |
54 | + log.WithFields(log.Fields{"module": logModule, "error": err}).Fatal("Failded to initialize mysql db.") | |
55 | + } | |
56 | + | |
57 | + db.LogMode(true) | |
58 | + | |
59 | + keeper, err := synchron.NewChainKeeper(db, config, rewardEndHeight) | |
60 | + if err != nil { | |
61 | + log.WithFields(log.Fields{"module": logModule, "error": err}).Fatal("Failded to initialize NewChainKeeper.") | |
62 | + } | |
63 | + | |
64 | + if err := keeper.SyncBlock(); err != nil { | |
65 | + log.WithFields(log.Fields{"module": logModule, "error": err}).Fatal("Failded to sync block.") | |
66 | + } | |
67 | + | |
68 | + s := settlementvotereward.NewSettlementReward(db, config, rewardStartHeight, rewardEndHeight) | |
69 | + | |
70 | + if err := s.Settlement(); err != nil { | |
71 | + log.WithFields(log.Fields{"module": logModule, "error": err}).Fatal("Settlement vote rewards failure.") | |
72 | + } | |
73 | + | |
74 | + log.WithFields(log.Fields{ | |
75 | + "module": logModule, | |
76 | + "duration": time.Since(startTime), | |
77 | + }).Info("Settlement vote reward complete") | |
78 | + | |
79 | + return nil | |
80 | +} | |
81 | + | |
82 | +func main() { | |
83 | + cmd := cli.PrepareBaseCmd(RootCmd, "REWARD", "./") | |
84 | + cmd.Execute() | |
85 | +} |
@@ -2,6 +2,7 @@ package consensus | ||
2 | 2 | |
3 | 3 | import ( |
4 | 4 | "encoding/binary" |
5 | + "fmt" | |
5 | 6 | "strings" |
6 | 7 | |
7 | 8 | "github.com/bytom/bytom/protocol/bc" |
@@ -144,3 +145,12 @@ var SoloNetParams = Params{ | ||
144 | 145 | VotePendingBlockNumber: 10, |
145 | 146 | }, |
146 | 147 | } |
148 | + | |
149 | +// InitActiveNetParams load the config by chain ID | |
150 | +func InitActiveNetParams(chainID string) error { | |
151 | + var exist bool | |
152 | + if ActiveNetParams, exist = NetParams[chainID]; !exist { | |
153 | + return fmt.Errorf("chain_id[%v] don't exist", chainID) | |
154 | + } | |
155 | + return nil | |
156 | +} |
@@ -18,6 +18,7 @@ require ( | ||
18 | 18 | github.com/davecgh/go-spew v1.1.1 |
19 | 19 | github.com/fortytw2/leaktest v1.3.0 // indirect |
20 | 20 | github.com/go-kit/kit v0.10.0 // indirect |
21 | + github.com/go-sql-driver/mysql v1.5.0 | |
21 | 22 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da |
22 | 23 | github.com/golang/protobuf v1.4.3 |
23 | 24 | github.com/golang/snappy v0.0.3 // indirect |
@@ -26,6 +27,7 @@ require ( | ||
26 | 27 | github.com/grandcat/zeroconf v0.0.0-20190424104450-85eadb44205c |
27 | 28 | github.com/hashicorp/go-version v1.3.0 |
28 | 29 | github.com/holiman/uint256 v1.1.1 |
30 | + github.com/jinzhu/gorm v1.9.16 | |
29 | 31 | github.com/johngb/langreg v0.0.0-20150123211413-5c6abc6d19d2 |
30 | 32 | github.com/jonboulle/clockwork v0.2.2 // indirect |
31 | 33 | github.com/kr/secureheader v0.2.0 |
@@ -20,6 +20,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 | ||
20 | 20 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= |
21 | 21 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= |
22 | 22 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= |
23 | +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= | |
23 | 24 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= |
24 | 25 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= |
25 | 26 | github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= |
@@ -29,6 +30,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy | ||
29 | 30 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= |
30 | 31 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= |
31 | 32 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= |
33 | +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= | |
32 | 34 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= |
33 | 35 | github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= |
34 | 36 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= |
@@ -58,6 +60,8 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku | ||
58 | 60 | github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= |
59 | 61 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= |
60 | 62 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= |
63 | +github.com/bytom/vapor v4.8.11+incompatible h1:sO8CbrkiK3I65htiXuml8SCL1ZjbnbKn915CtS6L3HU= | |
64 | +github.com/bytom/vapor v4.8.11+incompatible/go.mod h1:v/ibQL+K6miAS8OcWmfGLJe8274fa0GR0KRdfWpwdnY= | |
61 | 65 | github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= |
62 | 66 | github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= |
63 | 67 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= |
@@ -89,6 +93,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs | ||
89 | 93 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
90 | 94 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
91 | 95 | github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= |
96 | +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= | |
92 | 97 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= |
93 | 98 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= |
94 | 99 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= |
@@ -99,6 +104,7 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB | ||
99 | 104 | github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= |
100 | 105 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= |
101 | 106 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= |
107 | +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= | |
102 | 108 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= |
103 | 109 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= |
104 | 110 | github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= |
@@ -118,7 +124,10 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 | ||
118 | 124 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= |
119 | 125 | github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= |
120 | 126 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= |
127 | +github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= | |
121 | 128 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= |
129 | +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= | |
130 | +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | |
122 | 131 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= |
123 | 132 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= |
124 | 133 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= |
@@ -126,6 +135,7 @@ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFG | ||
126 | 135 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= |
127 | 136 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= |
128 | 137 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= |
138 | +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= | |
129 | 139 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= |
130 | 140 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= |
131 | 141 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= |
@@ -225,6 +235,11 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt | ||
225 | 235 | github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= |
226 | 236 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= |
227 | 237 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= |
238 | +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= | |
239 | +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= | |
240 | +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= | |
241 | +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= | |
242 | +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= | |
228 | 243 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= |
229 | 244 | github.com/johngb/langreg v0.0.0-20150123211413-5c6abc6d19d2 h1:R0Yc1jK2pjDwZeIXmcbELtKLedE+PjuI0S5cguGxTxw= |
230 | 245 | github.com/johngb/langreg v0.0.0-20150123211413-5c6abc6d19d2/go.mod h1:m/usUv5KgruWsRUejHsR568dyOh5pJ1wVoKZKMuEPhI= |
@@ -260,6 +275,7 @@ github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkL | ||
260 | 275 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= |
261 | 276 | github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8= |
262 | 277 | github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= |
278 | +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | |
263 | 279 | github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= |
264 | 280 | github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= |
265 | 281 | github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= |
@@ -270,6 +286,7 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO | ||
270 | 286 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= |
271 | 287 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= |
272 | 288 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= |
289 | +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= | |
273 | 290 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= |
274 | 291 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= |
275 | 292 | github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= |
@@ -0,0 +1,44 @@ | ||
1 | +package apinode | |
2 | + | |
3 | +import ( | |
4 | + "encoding/json" | |
5 | + | |
6 | + "github.com/bytom/bytom/api" | |
7 | + "github.com/bytom/bytom/errors" | |
8 | + "github.com/bytom/bytom/protocol/bc/types" | |
9 | +) | |
10 | + | |
11 | +func (n *Node) GetBlockByHash(hash string) (*types.Block, error) { | |
12 | + return n.getRawBlock(&getRawBlockReq{BlockHash: hash}) | |
13 | +} | |
14 | + | |
15 | +func (n *Node) GetBlockByHeight(height uint64) (*types.Block, error) { | |
16 | + return n.getRawBlock(&getRawBlockReq{BlockHeight: height}) | |
17 | +} | |
18 | + | |
19 | +type getRawBlockReq struct { | |
20 | + BlockHeight uint64 `json:"block_height"` | |
21 | + BlockHash string `json:"block_hash"` | |
22 | +} | |
23 | + | |
24 | +func (n *Node) getRawBlock(req *getRawBlockReq) (*types.Block, error) { | |
25 | + url := "/get-raw-block" | |
26 | + payload, err := json.Marshal(req) | |
27 | + if err != nil { | |
28 | + return nil, errors.Wrap(err, "json marshal") | |
29 | + } | |
30 | + resp := &api.GetRawBlockResp{} | |
31 | + return resp.RawBlock, n.request(url, payload, resp) | |
32 | +} | |
33 | + | |
34 | +// bytomChainStatusResp is the response of bytom chain status | |
35 | +type bytomChainStatusResp struct { | |
36 | + FinalizedHeight uint64 `json:"finalized_height"` | |
37 | +} | |
38 | + | |
39 | +// GetFinalizedHeight return the finalized block height of connected node | |
40 | +func (n *Node) GetFinalizedHeight() (uint64, error) { | |
41 | + url := "/chain-status" | |
42 | + res := &bytomChainStatusResp{} | |
43 | + return res.FinalizedHeight, n.request(url, nil, res) | |
44 | +} |
@@ -0,0 +1,41 @@ | ||
1 | +package apinode | |
2 | + | |
3 | +import ( | |
4 | + "encoding/json" | |
5 | + | |
6 | + "github.com/bytom/bytom/errors" | |
7 | + "github.com/bytom/bytom/toolbar/common" | |
8 | +) | |
9 | + | |
10 | +// Node can invoke the api which provide by the full node server | |
11 | +type Node struct { | |
12 | + hostPort string | |
13 | +} | |
14 | + | |
15 | +// NewNode create a api client with target server | |
16 | +func NewNode(hostPort string) *Node { | |
17 | + return &Node{hostPort: hostPort} | |
18 | +} | |
19 | + | |
20 | +type response struct { | |
21 | + Status string `json:"status"` | |
22 | + Data json.RawMessage `json:"data"` | |
23 | + ErrDetail string `json:"error_detail"` | |
24 | +} | |
25 | + | |
26 | +func (n *Node) request(path string, payload []byte, respData interface{}) error { | |
27 | + resp := &response{} | |
28 | + if err := common.Post(n.hostPort+path, payload, resp); err != nil { | |
29 | + return err | |
30 | + } | |
31 | + | |
32 | + if resp.Status != "success" { | |
33 | + return errors.New(resp.ErrDetail) | |
34 | + } | |
35 | + | |
36 | + if resp.Data == nil { | |
37 | + return nil | |
38 | + } | |
39 | + | |
40 | + return json.Unmarshal(resp.Data, respData) | |
41 | +} |
@@ -0,0 +1,162 @@ | ||
1 | +package apinode | |
2 | + | |
3 | +import ( | |
4 | + "encoding/hex" | |
5 | + "encoding/json" | |
6 | + | |
7 | + "github.com/bytom/bytom/blockchain/txbuilder" | |
8 | + "github.com/bytom/bytom/consensus" | |
9 | + "github.com/bytom/bytom/errors" | |
10 | + "github.com/bytom/bytom/protocol/bc" | |
11 | + "github.com/bytom/bytom/protocol/bc/types" | |
12 | +) | |
13 | + | |
14 | +type SpendAccountAction struct { | |
15 | + AccountID string `json:"account_id"` | |
16 | + *bc.AssetAmount | |
17 | +} | |
18 | + | |
19 | +func (s *SpendAccountAction) MarshalJSON() ([]byte, error) { | |
20 | + return json.Marshal(&struct { | |
21 | + Type string `json:"type"` | |
22 | + AccountID string `json:"account_id"` | |
23 | + *bc.AssetAmount | |
24 | + }{ | |
25 | + Type: "spend_account", | |
26 | + AccountID: s.AccountID, | |
27 | + AssetAmount: s.AssetAmount, | |
28 | + }) | |
29 | +} | |
30 | + | |
31 | +type ControlAddressAction struct { | |
32 | + Address string `json:"address"` | |
33 | + *bc.AssetAmount | |
34 | +} | |
35 | + | |
36 | +func (c *ControlAddressAction) MarshalJSON() ([]byte, error) { | |
37 | + return json.Marshal(&struct { | |
38 | + Type string `json:"type"` | |
39 | + Address string `json:"address"` | |
40 | + *bc.AssetAmount | |
41 | + }{ | |
42 | + Type: "control_address", | |
43 | + Address: c.Address, | |
44 | + AssetAmount: c.AssetAmount, | |
45 | + }) | |
46 | +} | |
47 | + | |
48 | +type RetireAction struct { | |
49 | + *bc.AssetAmount | |
50 | + Arbitrary []byte | |
51 | +} | |
52 | + | |
53 | +func (r *RetireAction) MarshalJSON() ([]byte, error) { | |
54 | + return json.Marshal(&struct { | |
55 | + Type string `json:"type"` | |
56 | + Arbitrary string `json:"arbitrary"` | |
57 | + *bc.AssetAmount | |
58 | + }{ | |
59 | + Type: "retire", | |
60 | + Arbitrary: hex.EncodeToString(r.Arbitrary), | |
61 | + AssetAmount: r.AssetAmount, | |
62 | + }) | |
63 | +} | |
64 | + | |
65 | +func (n *Node) BatchSendBTM(accountID, password string, outputs map[string]uint64, memo []byte) (string, error) { | |
66 | + totalBTM := uint64(10000000) | |
67 | + actions := []interface{}{} | |
68 | + if len(memo) > 0 { | |
69 | + actions = append(actions, &RetireAction{ | |
70 | + Arbitrary: memo, | |
71 | + AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: 1}, | |
72 | + }) | |
73 | + } | |
74 | + | |
75 | + for address, amount := range outputs { | |
76 | + actions = append(actions, &ControlAddressAction{ | |
77 | + Address: address, | |
78 | + AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: amount}, | |
79 | + }) | |
80 | + totalBTM += amount | |
81 | + } | |
82 | + | |
83 | + actions = append(actions, &SpendAccountAction{ | |
84 | + AccountID: accountID, | |
85 | + AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: totalBTM}, | |
86 | + }) | |
87 | + | |
88 | + tpl, err := n.buildTx(actions) | |
89 | + if err != nil { | |
90 | + return "", err | |
91 | + } | |
92 | + | |
93 | + tpl, err = n.signTx(tpl, password) | |
94 | + if err != nil { | |
95 | + return "", err | |
96 | + } | |
97 | + | |
98 | + return n.SubmitTx(tpl.Transaction) | |
99 | +} | |
100 | + | |
101 | +type buildTxReq struct { | |
102 | + Actions []interface{} `json:"actions"` | |
103 | +} | |
104 | + | |
105 | +func (n *Node) buildTx(actions []interface{}) (*txbuilder.Template, error) { | |
106 | + url := "/build-transaction" | |
107 | + payload, err := json.Marshal(&buildTxReq{Actions: actions}) | |
108 | + if err != nil { | |
109 | + return nil, errors.Wrap(err, "Marshal spend request") | |
110 | + } | |
111 | + | |
112 | + result := &txbuilder.Template{} | |
113 | + return result, n.request(url, payload, result) | |
114 | +} | |
115 | + | |
116 | +type signTxReq struct { | |
117 | + Tx *txbuilder.Template `json:"transaction"` | |
118 | + Password string `json:"password"` | |
119 | +} | |
120 | + | |
121 | +type signTxResp struct { | |
122 | + Tx *txbuilder.Template `json:"transaction"` | |
123 | + SignComplete bool `json:"sign_complete"` | |
124 | +} | |
125 | + | |
126 | +func (n *Node) signTx(tpl *txbuilder.Template, password string) (*txbuilder.Template, error) { | |
127 | + url := "/sign-transaction" | |
128 | + payload, err := json.Marshal(&signTxReq{Tx: tpl, Password: password}) | |
129 | + if err != nil { | |
130 | + return nil, errors.Wrap(err, "json marshal") | |
131 | + } | |
132 | + | |
133 | + resp := &signTxResp{} | |
134 | + if err := n.request(url, payload, resp); err != nil { | |
135 | + return nil, err | |
136 | + } | |
137 | + | |
138 | + if !resp.SignComplete { | |
139 | + return nil, errors.New("sign fail") | |
140 | + } | |
141 | + | |
142 | + return resp.Tx, nil | |
143 | +} | |
144 | + | |
145 | +type submitTxReq struct { | |
146 | + Tx *types.Tx `json:"raw_transaction"` | |
147 | +} | |
148 | + | |
149 | +type submitTxResp struct { | |
150 | + TxID string `json:"tx_id"` | |
151 | +} | |
152 | + | |
153 | +func (n *Node) SubmitTx(tx *types.Tx) (string, error) { | |
154 | + url := "/submit-transaction" | |
155 | + payload, err := json.Marshal(submitTxReq{Tx: tx}) | |
156 | + if err != nil { | |
157 | + return "", errors.Wrap(err, "json marshal") | |
158 | + } | |
159 | + | |
160 | + res := &submitTxResp{} | |
161 | + return res.TxID, n.request(url, payload, res) | |
162 | +} |
@@ -0,0 +1,64 @@ | ||
1 | +package common | |
2 | + | |
3 | +import ( | |
4 | + "errors" | |
5 | + | |
6 | + "github.com/bytom/bytom/common" | |
7 | + "github.com/bytom/bytom/consensus" | |
8 | + "github.com/bytom/bytom/consensus/segwit" | |
9 | + "github.com/bytom/bytom/protocol/vm/vmutil" | |
10 | +) | |
11 | + | |
12 | +func GetAddressFromControlProgram(prog []byte) string { | |
13 | + if segwit.IsP2WPKHScript(prog) { | |
14 | + if pubHash, err := segwit.GetHashFromStandardProg(prog); err == nil { | |
15 | + return buildP2PKHAddress(pubHash) | |
16 | + } | |
17 | + } else if segwit.IsP2WSHScript(prog) { | |
18 | + if scriptHash, err := segwit.GetHashFromStandardProg(prog); err == nil { | |
19 | + return buildP2SHAddress(scriptHash) | |
20 | + } | |
21 | + } | |
22 | + | |
23 | + return "" | |
24 | +} | |
25 | + | |
26 | +func buildP2PKHAddress(pubHash []byte) string { | |
27 | + address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams) | |
28 | + if err != nil { | |
29 | + return "" | |
30 | + } | |
31 | + | |
32 | + return address.EncodeAddress() | |
33 | +} | |
34 | + | |
35 | +func buildP2SHAddress(scriptHash []byte) string { | |
36 | + address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams) | |
37 | + if err != nil { | |
38 | + return "" | |
39 | + } | |
40 | + | |
41 | + return address.EncodeAddress() | |
42 | +} | |
43 | + | |
44 | +func GetControlProgramFromAddress(address string) ([]byte, error) { | |
45 | + decodeaddress, err := common.DecodeAddress(address, &consensus.ActiveNetParams) | |
46 | + if err != nil { | |
47 | + return nil, err | |
48 | + } | |
49 | + | |
50 | + redeemContract := decodeaddress.ScriptAddress() | |
51 | + program := []byte{} | |
52 | + switch decodeaddress.(type) { | |
53 | + case *common.AddressWitnessPubKeyHash: | |
54 | + program, err = vmutil.P2WPKHProgram(redeemContract) | |
55 | + case *common.AddressWitnessScriptHash: | |
56 | + program, err = vmutil.P2WSHProgram(redeemContract) | |
57 | + default: | |
58 | + return nil, errors.New("Invalid address") | |
59 | + } | |
60 | + if err != nil { | |
61 | + return nil, err | |
62 | + } | |
63 | + return program, nil | |
64 | +} |
@@ -0,0 +1,14 @@ | ||
1 | +package common | |
2 | + | |
3 | +type MySQLConfig struct { | |
4 | + Connection MySQLConnection `json:"connection"` | |
5 | + LogMode bool `json:"log_mode"` | |
6 | +} | |
7 | + | |
8 | +type MySQLConnection struct { | |
9 | + Host string `json:"host"` | |
10 | + Port uint `json:"port"` | |
11 | + Username string `json:"username"` | |
12 | + Password string `json:"password"` | |
13 | + DbName string `json:"database"` | |
14 | +} |
@@ -0,0 +1,26 @@ | ||
1 | +package common | |
2 | + | |
3 | +import ( | |
4 | + "fmt" | |
5 | + | |
6 | + _ "github.com/go-sql-driver/mysql" | |
7 | + "github.com/jinzhu/gorm" | |
8 | + | |
9 | + "github.com/bytom/bytom/errors" | |
10 | +) | |
11 | + | |
12 | +func NewMySQLDB(cfg MySQLConfig) (*gorm.DB, error) { | |
13 | + dsnTemplate := "%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=true&loc=Local" | |
14 | + dsn := fmt.Sprintf(dsnTemplate, cfg.Connection.Username, cfg.Connection.Password, cfg.Connection.Host, cfg.Connection.Port, cfg.Connection.DbName) | |
15 | + db, err := gorm.Open("mysql", dsn) | |
16 | + if err != nil { | |
17 | + return nil, errors.Wrap(err, "open db cluster") | |
18 | + } | |
19 | + | |
20 | + db.LogMode(cfg.LogMode) | |
21 | + if err = db.DB().Ping(); err != nil { | |
22 | + return nil, errors.Wrap(err, "ping db") | |
23 | + } | |
24 | + | |
25 | + return db, nil | |
26 | +} |
@@ -0,0 +1,50 @@ | ||
1 | +package common | |
2 | + | |
3 | +import ( | |
4 | + "bytes" | |
5 | + "encoding/json" | |
6 | + "io/ioutil" | |
7 | + "net/http" | |
8 | +) | |
9 | + | |
10 | +func Get(url string, result interface{}) error { | |
11 | + client := &http.Client{} | |
12 | + resp, err := client.Get(url) | |
13 | + if err != nil { | |
14 | + return err | |
15 | + } | |
16 | + | |
17 | + defer resp.Body.Close() | |
18 | + body, err := ioutil.ReadAll(resp.Body) | |
19 | + if err != nil { | |
20 | + return err | |
21 | + } | |
22 | + | |
23 | + return json.Unmarshal(body, result) | |
24 | +} | |
25 | + | |
26 | +func Post(url string, payload []byte, result interface{}) error { | |
27 | + req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload)) | |
28 | + if err != nil { | |
29 | + return err | |
30 | + } | |
31 | + | |
32 | + req.Header.Set("Content-Type", "application/json") | |
33 | + client := &http.Client{} | |
34 | + resp, err := client.Do(req) | |
35 | + if err != nil { | |
36 | + return err | |
37 | + } | |
38 | + | |
39 | + defer resp.Body.Close() | |
40 | + if result == nil { | |
41 | + return nil | |
42 | + } | |
43 | + | |
44 | + body, err := ioutil.ReadAll(resp.Body) | |
45 | + if err != nil { | |
46 | + return err | |
47 | + } | |
48 | + | |
49 | + return json.Unmarshal(body, result) | |
50 | +} |
@@ -0,0 +1,33 @@ | ||
1 | +package config | |
2 | + | |
3 | +import ( | |
4 | + "encoding/json" | |
5 | + "os" | |
6 | + | |
7 | + "github.com/bytom/bytom/toolbar/common" | |
8 | +) | |
9 | + | |
10 | +type Config struct { | |
11 | + NodeIP string `json:"node_ip"` | |
12 | + ChainID string `json:"chain_id"` | |
13 | + MySQLConfig common.MySQLConfig `json:"mysql"` | |
14 | + RewardConf *RewardConfig `json:"reward_config"` | |
15 | +} | |
16 | + | |
17 | +type RewardConfig struct { | |
18 | + XPub string `json:"xpub"` | |
19 | + AccountID string `json:"account_id"` | |
20 | + Password string `json:"password"` | |
21 | + MiningAddress string `json:"mining_address"` | |
22 | + RewardRatio uint64 `json:"reward_ratio"` | |
23 | +} | |
24 | + | |
25 | +func LoadConfigFile(configFile string, config *Config) error { | |
26 | + file, err := os.Open(configFile) | |
27 | + if err != nil { | |
28 | + return err | |
29 | + } | |
30 | + defer file.Close() | |
31 | + | |
32 | + return json.NewDecoder(file).Decode(config) | |
33 | +} |
@@ -0,0 +1,63 @@ | ||
1 | +# ************************************************************ | |
2 | +# Sequel Pro SQL dump | |
3 | +# Version 4541 | |
4 | +# | |
5 | +# http://www.sequelpro.com/ | |
6 | +# https://github.com/sequelpro/sequelpro | |
7 | +# | |
8 | +# Host: 127.0.0.1 (MySQL 5.7.24) | |
9 | +# Database: vote_reward | |
10 | +# Generation Time: 2019-07-22 13:41:50 +0000 | |
11 | +# ************************************************************ | |
12 | + | |
13 | + | |
14 | +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; | |
15 | +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; | |
16 | +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; | |
17 | +/*!40101 SET NAMES utf8 */; | |
18 | +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; | |
19 | +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; | |
20 | +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; | |
21 | + | |
22 | + | |
23 | +# Dump of table chain_statuses | |
24 | +# ------------------------------------------------------------ | |
25 | + | |
26 | +DROP TABLE IF EXISTS `chain_statuses`; | |
27 | + | |
28 | +CREATE TABLE `chain_statuses` ( | |
29 | + `block_height` int(11) NOT NULL, | |
30 | + `block_hash` varchar(64) NOT NULL | |
31 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8; | |
32 | + | |
33 | + | |
34 | + | |
35 | +# Dump of table utxos | |
36 | +# ------------------------------------------------------------ | |
37 | + | |
38 | +DROP TABLE IF EXISTS `utxos`; | |
39 | + | |
40 | +CREATE TABLE `utxos` ( | |
41 | + `id` int(11) NOT NULL AUTO_INCREMENT, | |
42 | + `output_id` varchar(64) NOT NULL, | |
43 | + `xpub` varchar(128) NOT NULL, | |
44 | + `vote_address` varchar(62) NOT NULL, | |
45 | + `vote_num` bigint(21) NOT NULL, | |
46 | + `vote_height` int(11) NOT NULL, | |
47 | + `veto_height` int(11) NOT NULL, | |
48 | + PRIMARY KEY (`id`), | |
49 | + UNIQUE KEY `output_id` (`output_id`), | |
50 | + KEY `xpub` (`xpub`), | |
51 | + KEY `vote_height` (`vote_height`), | |
52 | + KEY `veto_height` (`veto_height`) | |
53 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8; | |
54 | + | |
55 | + | |
56 | + | |
57 | + | |
58 | +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; | |
59 | +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; | |
60 | +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; | |
61 | +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; | |
62 | +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; | |
63 | +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; |
@@ -0,0 +1,6 @@ | ||
1 | +package orm | |
2 | + | |
3 | +type ChainStatus struct { | |
4 | + BlockHeight uint64 | |
5 | + BlockHash string | |
6 | +} |
@@ -0,0 +1,11 @@ | ||
1 | +package orm | |
2 | + | |
3 | +type Utxo struct { | |
4 | + ID uint64 `gorm:"primary_key"` | |
5 | + OutputID string | |
6 | + Xpub string | |
7 | + VoteAddress string | |
8 | + VoteNum uint64 | |
9 | + VoteHeight uint64 | |
10 | + VetoHeight uint64 | |
11 | +} |
@@ -0,0 +1,148 @@ | ||
1 | +package settlementvotereward | |
2 | + | |
3 | +import ( | |
4 | + "bytes" | |
5 | + "encoding/json" | |
6 | + "math/big" | |
7 | + | |
8 | + "github.com/jinzhu/gorm" | |
9 | + | |
10 | + "github.com/bytom/bytom/consensus" | |
11 | + "github.com/bytom/bytom/errors" | |
12 | + "github.com/bytom/bytom/toolbar/apinode" | |
13 | + "github.com/bytom/bytom/toolbar/common" | |
14 | + "github.com/bytom/bytom/toolbar/vote_reward/config" | |
15 | +) | |
16 | + | |
17 | +var ( | |
18 | + errNotFoundReward = errors.New("No reward found") | |
19 | + errNotRewardTx = errors.New("No reward transaction") | |
20 | +) | |
21 | + | |
22 | +type voteResult struct { | |
23 | + VoteAddress string | |
24 | + VoteNum uint64 | |
25 | +} | |
26 | + | |
27 | +type SettlementReward struct { | |
28 | + rewardCfg *config.RewardConfig | |
29 | + node *apinode.Node | |
30 | + db *gorm.DB | |
31 | + rewards map[string]uint64 | |
32 | + startHeight uint64 | |
33 | + endHeight uint64 | |
34 | +} | |
35 | + | |
36 | +type memo struct { | |
37 | + StartHeight uint64 `json:"start_height"` | |
38 | + EndHeight uint64 `json:"end_height"` | |
39 | + NodePubkey string `json:"node_pubkey"` | |
40 | + RewardRatio uint64 `json:"reward_ratio"` | |
41 | +} | |
42 | + | |
43 | +func NewSettlementReward(db *gorm.DB, cfg *config.Config, startHeight, endHeight uint64) *SettlementReward { | |
44 | + return &SettlementReward{ | |
45 | + db: db, | |
46 | + rewardCfg: cfg.RewardConf, | |
47 | + node: apinode.NewNode(cfg.NodeIP), | |
48 | + rewards: make(map[string]uint64), | |
49 | + startHeight: startHeight, | |
50 | + endHeight: endHeight, | |
51 | + } | |
52 | +} | |
53 | + | |
54 | +func (s *SettlementReward) getVoteResultFromDB(height uint64) (voteResults []*voteResult, err error) { | |
55 | + query := s.db.Table("utxos").Select("vote_address, sum(vote_num) as vote_num") | |
56 | + query = query.Where("(veto_height >= ? or veto_height = 0) and vote_height <= ? and xpub = ?", height-consensus.ActiveNetParams.BlocksOfEpoch+1, height-consensus.ActiveNetParams.BlocksOfEpoch, s.rewardCfg.XPub) | |
57 | + query = query.Group("vote_address") | |
58 | + if err := query.Scan(&voteResults).Error; err != nil { | |
59 | + return nil, err | |
60 | + } | |
61 | + | |
62 | + return voteResults, nil | |
63 | +} | |
64 | + | |
65 | +func (s *SettlementReward) Settlement() error { | |
66 | + for height := s.startHeight + consensus.ActiveNetParams.BlocksOfEpoch; height <= s.endHeight; height += consensus.ActiveNetParams.BlocksOfEpoch { | |
67 | + totalReward, err := s.getCoinbaseReward(height + 1) | |
68 | + if err == errNotFoundReward { | |
69 | + continue | |
70 | + } | |
71 | + | |
72 | + if err != nil { | |
73 | + return errors.Wrapf(err, "get total reward at height: %d", height) | |
74 | + } | |
75 | + | |
76 | + voteResults, err := s.getVoteResultFromDB(height) | |
77 | + if err != nil { | |
78 | + return err | |
79 | + } | |
80 | + | |
81 | + s.calcVoterRewards(voteResults, totalReward) | |
82 | + } | |
83 | + | |
84 | + if len(s.rewards) == 0 { | |
85 | + return errNotRewardTx | |
86 | + } | |
87 | + | |
88 | + data, err := json.Marshal(&memo{ | |
89 | + StartHeight: s.startHeight, | |
90 | + EndHeight: s.endHeight, | |
91 | + NodePubkey: s.rewardCfg.XPub, | |
92 | + RewardRatio: s.rewardCfg.RewardRatio, | |
93 | + }) | |
94 | + if err != nil { | |
95 | + return err | |
96 | + } | |
97 | + | |
98 | + // send transactions | |
99 | + _, err = s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards, data) | |
100 | + return err | |
101 | +} | |
102 | + | |
103 | +func (s *SettlementReward) getCoinbaseReward(height uint64) (uint64, error) { | |
104 | + block, err := s.node.GetBlockByHeight(height) | |
105 | + if err != nil { | |
106 | + return 0, err | |
107 | + } | |
108 | + | |
109 | + miningControl, err := common.GetControlProgramFromAddress(s.rewardCfg.MiningAddress) | |
110 | + if err != nil { | |
111 | + return 0, err | |
112 | + } | |
113 | + | |
114 | + for _, output := range block.Transactions[0].Outputs { | |
115 | + if output.Amount == 0 { | |
116 | + continue | |
117 | + } | |
118 | + | |
119 | + if bytes.Equal(miningControl, output.ControlProgram) { | |
120 | + amount := big.NewInt(0).SetUint64(output.Amount) | |
121 | + rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio) | |
122 | + amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100)) | |
123 | + | |
124 | + return amount.Uint64(), nil | |
125 | + } | |
126 | + } | |
127 | + return 0, errNotFoundReward | |
128 | +} | |
129 | + | |
130 | +func (s *SettlementReward) calcVoterRewards(voteResults []*voteResult, totalReward uint64) { | |
131 | + totalVoteNum := uint64(0) | |
132 | + for _, voteResult := range voteResults { | |
133 | + totalVoteNum += voteResult.VoteNum | |
134 | + } | |
135 | + | |
136 | + for _, voteResult := range voteResults { | |
137 | + // voteNum / totalVoteNum * totalReward | |
138 | + voteNum := big.NewInt(0).SetUint64(voteResult.VoteNum) | |
139 | + total := big.NewInt(0).SetUint64(totalVoteNum) | |
140 | + reward := big.NewInt(0).SetUint64(totalReward) | |
141 | + | |
142 | + amount := voteNum.Mul(voteNum, reward).Div(voteNum, total).Uint64() | |
143 | + | |
144 | + if amount != 0 { | |
145 | + s.rewards[voteResult.VoteAddress] += amount | |
146 | + } | |
147 | + } | |
148 | +} |
@@ -0,0 +1,155 @@ | ||
1 | +package synchron | |
2 | + | |
3 | +import ( | |
4 | + "encoding/hex" | |
5 | + | |
6 | + "github.com/jinzhu/gorm" | |
7 | + | |
8 | + "github.com/bytom/bytom/errors" | |
9 | + "github.com/bytom/bytom/protocol/bc/types" | |
10 | + "github.com/bytom/bytom/toolbar/apinode" | |
11 | + "github.com/bytom/bytom/toolbar/common" | |
12 | + "github.com/bytom/bytom/toolbar/vote_reward/config" | |
13 | + "github.com/bytom/bytom/toolbar/vote_reward/database/orm" | |
14 | +) | |
15 | + | |
16 | +var ErrInconsistentDB = errors.New("inconsistent db status") | |
17 | + | |
18 | +type ChainKeeper struct { | |
19 | + db *gorm.DB | |
20 | + node *apinode.Node | |
21 | + targetHeight uint64 | |
22 | +} | |
23 | + | |
24 | +func NewChainKeeper(db *gorm.DB, cfg *config.Config, targetHeight uint64) (*ChainKeeper, error) { | |
25 | + keeper := &ChainKeeper{ | |
26 | + db: db, | |
27 | + node: apinode.NewNode(cfg.NodeIP), | |
28 | + targetHeight: targetHeight, | |
29 | + } | |
30 | + | |
31 | + finalizedHeight, err := keeper.node.GetFinalizedHeight() | |
32 | + if err != nil { | |
33 | + return nil, errors.Wrap(err, "fail on get finalized height") | |
34 | + } | |
35 | + | |
36 | + if targetHeight > finalizedHeight { | |
37 | + return nil, errors.New("reward end height is more than finalized height") | |
38 | + } | |
39 | + | |
40 | + chainStatus := &orm.ChainStatus{} | |
41 | + if err := db.First(chainStatus).Error; err == nil { | |
42 | + return keeper, nil | |
43 | + } else if err != gorm.ErrRecordNotFound { | |
44 | + return nil, errors.Wrap(err, "fail on get chainStatus") | |
45 | + } | |
46 | + | |
47 | + if err := keeper.initBlockState(); err != nil { | |
48 | + return nil, errors.Wrap(err, "fail on init chainStatus") | |
49 | + } | |
50 | + return keeper, nil | |
51 | +} | |
52 | + | |
53 | +func (c *ChainKeeper) SyncBlock() error { | |
54 | + for { | |
55 | + chainStatus := &orm.ChainStatus{} | |
56 | + if err := c.db.First(chainStatus).Error; err != nil { | |
57 | + return errors.Wrap(err, "fail on syncBlock query chainStatus") | |
58 | + } | |
59 | + | |
60 | + if chainStatus.BlockHeight >= c.targetHeight { | |
61 | + break | |
62 | + } | |
63 | + | |
64 | + dbTX := c.db.Begin() | |
65 | + if err := c.syncChainStatus(dbTX, chainStatus); err != nil { | |
66 | + dbTX.Rollback() | |
67 | + return err | |
68 | + } | |
69 | + | |
70 | + if err := dbTX.Commit().Error; err != nil { | |
71 | + return err | |
72 | + } | |
73 | + } | |
74 | + return nil | |
75 | +} | |
76 | + | |
77 | +func (c *ChainKeeper) syncChainStatus(db *gorm.DB, chainStatus *orm.ChainStatus) error { | |
78 | + nextBlock, err := c.node.GetBlockByHeight(chainStatus.BlockHeight + 1) | |
79 | + if err != nil { | |
80 | + return err | |
81 | + } | |
82 | + | |
83 | + return c.AttachBlock(db, chainStatus, nextBlock) | |
84 | +} | |
85 | + | |
86 | +func (c *ChainKeeper) AttachBlock(db *gorm.DB, chainStatus *orm.ChainStatus, block *types.Block) error { | |
87 | + for _, tx := range block.Transactions { | |
88 | + for _, input := range tx.Inputs { | |
89 | + if input.TypedInput.InputType() != types.VetoInputType { | |
90 | + continue | |
91 | + } | |
92 | + | |
93 | + outputID, err := input.SpentOutputID() | |
94 | + if err != nil { | |
95 | + return err | |
96 | + } | |
97 | + | |
98 | + result := db.Model(&orm.Utxo{}).Where(&orm.Utxo{OutputID: outputID.String()}).Update("veto_height", block.Height) | |
99 | + if err := result.Error; err != nil { | |
100 | + return err | |
101 | + } else if result.RowsAffected != 1 { | |
102 | + return ErrInconsistentDB | |
103 | + } | |
104 | + } | |
105 | + | |
106 | + for i, output := range tx.Outputs { | |
107 | + voteOutput, ok := output.TypedOutput.(*types.VoteOutput) | |
108 | + if !ok { | |
109 | + continue | |
110 | + } | |
111 | + | |
112 | + utxo := &orm.Utxo{ | |
113 | + Xpub: hex.EncodeToString(voteOutput.Vote), | |
114 | + VoteAddress: common.GetAddressFromControlProgram(output.ControlProgram), | |
115 | + VoteHeight: block.Height, | |
116 | + VoteNum: output.Amount, | |
117 | + OutputID: tx.OutputID(i).String(), | |
118 | + } | |
119 | + | |
120 | + if err := db.Save(utxo).Error; err != nil { | |
121 | + return err | |
122 | + } | |
123 | + } | |
124 | + } | |
125 | + | |
126 | + return c.updateChainStatus(db, chainStatus, block) | |
127 | +} | |
128 | + | |
129 | +func (c *ChainKeeper) initBlockState() error { | |
130 | + block, err := c.node.GetBlockByHeight(0) | |
131 | + if err != nil { | |
132 | + return errors.Wrap(err, "fail on get genenis block") | |
133 | + } | |
134 | + | |
135 | + blockHash := block.Hash() | |
136 | + chainStatus := &orm.ChainStatus{ | |
137 | + BlockHeight: block.Height, | |
138 | + BlockHash: blockHash.String(), | |
139 | + } | |
140 | + return c.db.Save(chainStatus).Error | |
141 | +} | |
142 | + | |
143 | +func (c *ChainKeeper) updateChainStatus(db *gorm.DB, chainStatus *orm.ChainStatus, block *types.Block) error { | |
144 | + blockHash := block.Hash() | |
145 | + result := db.Model(&orm.ChainStatus{}).Where(chainStatus).Updates(&orm.ChainStatus{ | |
146 | + BlockHeight: block.Height, | |
147 | + BlockHash: blockHash.String(), | |
148 | + }) | |
149 | + if err := result.Error; err != nil { | |
150 | + return err | |
151 | + } else if result.RowsAffected != 1 { | |
152 | + return ErrInconsistentDB | |
153 | + } | |
154 | + return nil | |
155 | +} |