• R/O
  • HTTP
  • SSH
  • HTTPS

bytom: Commit

Official Go implementation of the Bytom protocol


Commit MetaInfo

Revisão11d72f6533eb491db3074772dc2a8e0bb41cb6ff (tree)
Hora2021-07-30 12:31:49
AutorDeKaiju <longjinglv@163....>
CommiterGitHub

Mensagem de Log

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>

Mudança Sumário

Diff

--- /dev/null
+++ b/cmd/votereward/README.md
@@ -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
--- /dev/null
+++ b/cmd/votereward/main.go
@@ -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+}
--- a/consensus/general.go
+++ b/consensus/general.go
@@ -2,6 +2,7 @@ package consensus
22
33 import (
44 "encoding/binary"
5+ "fmt"
56 "strings"
67
78 "github.com/bytom/bytom/protocol/bc"
@@ -144,3 +145,12 @@ var SoloNetParams = Params{
144145 VotePendingBlockNumber: 10,
145146 },
146147 }
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+}
--- a/go.mod
+++ b/go.mod
@@ -18,6 +18,7 @@ require (
1818 github.com/davecgh/go-spew v1.1.1
1919 github.com/fortytw2/leaktest v1.3.0 // indirect
2020 github.com/go-kit/kit v0.10.0 // indirect
21+ github.com/go-sql-driver/mysql v1.5.0
2122 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
2223 github.com/golang/protobuf v1.4.3
2324 github.com/golang/snappy v0.0.3 // indirect
@@ -26,6 +27,7 @@ require (
2627 github.com/grandcat/zeroconf v0.0.0-20190424104450-85eadb44205c
2728 github.com/hashicorp/go-version v1.3.0
2829 github.com/holiman/uint256 v1.1.1
30+ github.com/jinzhu/gorm v1.9.16
2931 github.com/johngb/langreg v0.0.0-20150123211413-5c6abc6d19d2
3032 github.com/jonboulle/clockwork v0.2.2 // indirect
3133 github.com/kr/secureheader v0.2.0
--- a/go.sum
+++ b/go.sum
@@ -20,6 +20,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
2020 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
2121 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
2222 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=
2324 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
2425 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
2526 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
2930 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
3031 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
3132 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=
3234 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
3335 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
3436 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
5860 github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
5961 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
6062 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=
6165 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
6266 github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
6367 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
8993 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
9094 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9195 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=
9297 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
9398 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
9499 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
99104 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
100105 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
101106 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=
102108 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
103109 github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
104110 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
118124 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
119125 github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
120126 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=
121128 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=
122131 github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
123132 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
124133 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
126135 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
127136 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
128137 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=
129139 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
130140 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
131141 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
225235 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
226236 github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
227237 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=
228243 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
229244 github.com/johngb/langreg v0.0.0-20150123211413-5c6abc6d19d2 h1:R0Yc1jK2pjDwZeIXmcbELtKLedE+PjuI0S5cguGxTxw=
230245 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
260275 github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
261276 github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8=
262277 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=
263279 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
264280 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
265281 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
270286 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
271287 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
272288 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=
273290 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
274291 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
275292 github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
--- /dev/null
+++ b/toolbar/apinode/block.go
@@ -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+}
--- /dev/null
+++ b/toolbar/apinode/node.go
@@ -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+}
--- /dev/null
+++ b/toolbar/apinode/transaction.go
@@ -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+}
--- /dev/null
+++ b/toolbar/common/address.go
@@ -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+}
--- /dev/null
+++ b/toolbar/common/config.go
@@ -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+}
--- /dev/null
+++ b/toolbar/common/db.go
@@ -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+}
--- /dev/null
+++ b/toolbar/common/http_util.go
@@ -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+}
--- /dev/null
+++ b/toolbar/vote_reward/config/config.go
@@ -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+}
--- /dev/null
+++ b/toolbar/vote_reward/database/dump_reward.sql
@@ -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 */;
--- /dev/null
+++ b/toolbar/vote_reward/database/orm/block_state.go
@@ -0,0 +1,6 @@
1+package orm
2+
3+type ChainStatus struct {
4+ BlockHeight uint64
5+ BlockHash string
6+}
--- /dev/null
+++ b/toolbar/vote_reward/database/orm/utxo.go
@@ -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+}
--- /dev/null
+++ b/toolbar/vote_reward/settlementvotereward/settlementreward.go
@@ -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+}
--- /dev/null
+++ b/toolbar/vote_reward/synchron/block_keeper.go
@@ -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+}
Show on old repository browser