• R/O
  • HTTP
  • SSH
  • HTTPS

vapor: Commit

Golang implemented sidechain for Bytom


Commit MetaInfo

Revision3df751629b0461637eef6d863f5f679c908c7eb4 (tree)
Time2019-07-23 14:38:16
Authorwz <mars@byto...>
CommiterPaladz

Log Message

Reward util (#342)

* add vote reward

* add config

* fix

* modify

* modify sql

* fix review

* modify sql

* add roundVoteBlockNums

* recover code

* modify format

* modify code

* optimized code

* modify format

* fix review

* fix review

* fix review

* single xpub

* modifu

* fix review

* fix

* modify format

* fix review

* modify coinbase

* fix bug

* fix

* fix review

* add Rollback

* modify

* delete code

* add util

* modify sync

* add test

* modify db

* restore file

* fix review

* modify error

* add block hash

* try edit the code format

* add unit test

* modify table name

* fix review

* fix review

Change Summary

Incremental Difference

--- /dev/null
+++ b/toolbar/api_node/block.go
@@ -0,0 +1,33 @@
1+package apinode
2+
3+import (
4+ "encoding/json"
5+
6+ "github.com/vapor/api"
7+ "github.com/vapor/errors"
8+ "github.com/vapor/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+
31+ resp := &api.GetRawBlockResp{}
32+ return resp.RawBlock, n.request(url, payload, resp)
33+}
--- /dev/null
+++ b/toolbar/api_node/node.go
@@ -0,0 +1,37 @@
1+package apinode
2+
3+import (
4+ "encoding/json"
5+
6+ "github.com/vapor/errors"
7+ "github.com/vapor/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+ return json.Unmarshal(resp.Data, respData)
37+}
--- /dev/null
+++ b/toolbar/api_node/node_test.go
@@ -0,0 +1,70 @@
1+package apinode
2+
3+import (
4+ "encoding/json"
5+ "testing"
6+
7+ "github.com/vapor/consensus"
8+ "github.com/vapor/errors"
9+ "github.com/vapor/protocol/bc"
10+)
11+
12+func buildTxRequest(accountID string, outputs map[string]uint64) ([]byte, error) {
13+ totalBTM := uint64(10000000)
14+ actions := []interface{}{}
15+ for address, amount := range outputs {
16+ actions = append(actions, &ControlAddressAction{
17+ Address: address,
18+ AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: amount},
19+ })
20+ totalBTM += amount
21+ }
22+
23+ actions = append(actions, &SpendAccountAction{
24+ AccountID: accountID,
25+ AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: totalBTM},
26+ })
27+ payload, err := json.Marshal(&buildTxReq{Actions: actions})
28+ if err != nil {
29+ return nil, errors.Wrap(err, "Marshal spend request")
30+ }
31+
32+ return payload, nil
33+}
34+
35+type args struct {
36+ accountID string
37+ outputs map[string]uint64
38+}
39+
40+func TestBuildTxRequest(t *testing.T) {
41+ cases := []struct {
42+ args args
43+ want string
44+ }{
45+ {
46+ args: args{
47+ accountID: "9bb77612-350e-4d53-81e2-525b28247ba5",
48+ outputs: map[string]uint64{"sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf": 100},
49+ },
50+ want: `{"actions":[{"type":"control_address","address":"sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":100},{"type":"spend_account","account_id":"9bb77612-350e-4d53-81e2-525b28247ba5","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":10000100}]}`,
51+ },
52+ {
53+ args: args{
54+ accountID: "9bb77612-350e-4d53-81e2-525b28247ba5",
55+ outputs: map[string]uint64{"sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf": 100, "sp1qcgtxkhfzytul4lfttwex3skfqhm0tg6ms9da28": 200},
56+ },
57+ want: `{"actions":[{"type":"control_address","address":"sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":100},{"type":"control_address","address":"sp1qcgtxkhfzytul4lfttwex3skfqhm0tg6ms9da28","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":200},{"type":"spend_account","account_id":"9bb77612-350e-4d53-81e2-525b28247ba5","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":10000300}]}`,
58+ },
59+ }
60+
61+ for i, c := range cases {
62+ tx, err := buildTxRequest(c.args.accountID, c.args.outputs)
63+ if err != nil {
64+ t.Fatal(err)
65+ }
66+ if string(tx) != string(c.want) {
67+ t.Fatal(i, string(tx))
68+ }
69+ }
70+}
--- /dev/null
+++ b/toolbar/api_node/transaction.go
@@ -0,0 +1,138 @@
1+package apinode
2+
3+import (
4+ "encoding/json"
5+
6+ "github.com/vapor/blockchain/txbuilder"
7+ "github.com/vapor/consensus"
8+ "github.com/vapor/errors"
9+ "github.com/vapor/protocol/bc"
10+ "github.com/vapor/protocol/bc/types"
11+)
12+
13+type SpendAccountAction struct {
14+ AccountID string `json:"account_id"`
15+ *bc.AssetAmount
16+}
17+
18+func (s *SpendAccountAction) MarshalJSON() ([]byte, error) {
19+ return json.Marshal(&struct {
20+ Type string `json:"type"`
21+ AccountID string `json:"account_id"`
22+ *bc.AssetAmount
23+ }{
24+ Type: "spend_account",
25+ AccountID: s.AccountID,
26+ AssetAmount: s.AssetAmount,
27+ })
28+}
29+
30+type ControlAddressAction struct {
31+ Address string `json:"address"`
32+ *bc.AssetAmount
33+}
34+
35+func (c *ControlAddressAction) MarshalJSON() ([]byte, error) {
36+ return json.Marshal(&struct {
37+ Type string `json:"type"`
38+ Address string `json:"address"`
39+ *bc.AssetAmount
40+ }{
41+ Type: "control_address",
42+ Address: c.Address,
43+ AssetAmount: c.AssetAmount,
44+ })
45+}
46+
47+func (n *Node) BatchSendBTM(accountID, password string, outputs map[string]uint64) error {
48+ totalBTM := uint64(10000000)
49+ actions := []interface{}{}
50+ for address, amount := range outputs {
51+ actions = append(actions, &ControlAddressAction{
52+ Address: address,
53+ AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: amount},
54+ })
55+ totalBTM += amount
56+ }
57+
58+ actions = append(actions, &SpendAccountAction{
59+ AccountID: accountID,
60+ AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: totalBTM},
61+ })
62+
63+ tpl, err := n.buildTx(actions)
64+ if err != nil {
65+ return err
66+ }
67+
68+ tpl, err = n.signTx(tpl, password)
69+ if err != nil {
70+ return err
71+ }
72+
73+ _, err = n.SubmitTx(tpl.Transaction)
74+ return err
75+}
76+
77+type buildTxReq struct {
78+ Actions []interface{} `json:"actions"`
79+}
80+
81+func (n *Node) buildTx(actions []interface{}) (*txbuilder.Template, error) {
82+ url := "/build-transaction"
83+ payload, err := json.Marshal(&buildTxReq{Actions: actions})
84+ if err != nil {
85+ return nil, errors.Wrap(err, "Marshal spend request")
86+ }
87+
88+ result := &txbuilder.Template{}
89+ return result, n.request(url, payload, result)
90+}
91+
92+type signTxReq struct {
93+ Tx *txbuilder.Template `json:"transaction"`
94+ Password string `json:"password"`
95+}
96+
97+type signTxResp struct {
98+ Tx *txbuilder.Template `json:"transaction"`
99+ SignComplete bool `json:"sign_complete"`
100+}
101+
102+func (n *Node) signTx(tpl *txbuilder.Template, password string) (*txbuilder.Template, error) {
103+ url := "/sign-transaction"
104+ payload, err := json.Marshal(&signTxReq{Tx: tpl, Password: password})
105+ if err != nil {
106+ return nil, errors.Wrap(err, "json marshal")
107+ }
108+
109+ resp := &signTxResp{}
110+ if err := n.request(url, payload, resp); err != nil {
111+ return nil, err
112+ }
113+
114+ if !resp.SignComplete {
115+ return nil, errors.New("sign fail")
116+ }
117+
118+ return resp.Tx, nil
119+}
120+
121+type submitTxReq struct {
122+ Tx *types.Tx `json:"raw_transaction"`
123+}
124+
125+type submitTxResp struct {
126+ TxID string `json:"tx_id"`
127+}
128+
129+func (n *Node) SubmitTx(tx *types.Tx) (string, error) {
130+ url := "/submit-transaction"
131+ payload, err := json.Marshal(submitTxReq{Tx: tx})
132+ if err != nil {
133+ return "", errors.Wrap(err, "json marshal")
134+ }
135+
136+ res := &submitTxResp{}
137+ return res.TxID, n.request(url, payload, res)
138+}
--- a/toolbar/federation/util/http_util.go
+++ b/toolbar/common/http_util.go
@@ -1,4 +1,4 @@
1-package util
1+package common
22
33 import (
44 "bytes"
--- a/toolbar/common/service/node.go
+++ b/toolbar/federation/service/node.go
@@ -4,8 +4,8 @@ import (
44 "encoding/json"
55
66 "github.com/vapor/errors"
7- "github.com/vapor/toolbar/federation/util"
87 "github.com/vapor/protocol/bc"
8+ "github.com/vapor/toolbar/common"
99 )
1010
1111 // Node can invoke the api which provide by the full node server
@@ -66,7 +66,7 @@ type response struct {
6666
6767 func (n *Node) request(path string, payload []byte, respData interface{}) error {
6868 resp := &response{}
69- if err := util.Post(n.hostPort+path, payload, resp); err != nil {
69+ if err := common.Post(n.hostPort+path, payload, resp); err != nil {
7070 return err
7171 }
7272
--- a/toolbar/federation/synchron/mainchain_keeper.go
+++ b/toolbar/federation/synchron/mainchain_keeper.go
@@ -17,11 +17,11 @@ import (
1717 "github.com/vapor/consensus"
1818 "github.com/vapor/errors"
1919 "github.com/vapor/protocol/bc"
20- "github.com/vapor/toolbar/common/service"
2120 "github.com/vapor/toolbar/federation/common"
2221 "github.com/vapor/toolbar/federation/config"
2322 "github.com/vapor/toolbar/federation/database"
2423 "github.com/vapor/toolbar/federation/database/orm"
24+ "github.com/vapor/toolbar/federation/service"
2525 )
2626
2727 type mainchainKeeper struct {
--- a/toolbar/federation/synchron/sidechain_keeper.go
+++ b/toolbar/federation/synchron/sidechain_keeper.go
@@ -13,11 +13,11 @@ import (
1313 "github.com/vapor/errors"
1414 "github.com/vapor/protocol/bc"
1515 "github.com/vapor/protocol/bc/types"
16- "github.com/vapor/toolbar/common/service"
1716 "github.com/vapor/toolbar/federation/common"
1817 "github.com/vapor/toolbar/federation/config"
1918 "github.com/vapor/toolbar/federation/database"
2019 "github.com/vapor/toolbar/federation/database/orm"
20+ "github.com/vapor/toolbar/federation/service"
2121 )
2222
2323 type sidechainKeeper struct {
--- a/toolbar/reward/config/config.go
+++ /dev/null
@@ -1,46 +0,0 @@
1-package config
2-
3-import (
4- "encoding/json"
5- "os"
6-
7- log "github.com/sirupsen/logrus"
8-
9- "github.com/vapor/crypto/ed25519/chainkd"
10- "github.com/vapor/toolbar/common"
11-)
12-
13-func NewConfig() *Config {
14- if len(os.Args) <= 1 {
15- log.Fatal("Please setup the config file path")
16- }
17-
18- return NewConfigWithPath(os.Args[1])
19-}
20-
21-func NewConfigWithPath(path string) *Config {
22- configFile, err := os.Open(path)
23- if err != nil {
24- log.WithFields(log.Fields{"err": err, "file_path": os.Args[1]}).Fatal("fail to open config file")
25- }
26- defer configFile.Close()
27-
28- cfg := &Config{}
29- if err := json.NewDecoder(configFile).Decode(cfg); err != nil {
30- log.WithField("err", err).Fatal("fail to decode config file")
31- }
32-
33- return cfg
34-}
35-
36-type Config struct {
37- MySQLConfig common.MySQLConfig `json:"mysql"`
38- Chain Chain `json:"chain"`
39- XPubs []chainkd.XPub `json:"xpubs"`
40-}
41-
42-type Chain struct {
43- Name string `json:"name"`
44- Upstream string `json:"upstream"`
45- SyncSeconds uint64 `json:"sync_seconds"`
46-}
--- a/toolbar/reward/database/dump_reward.sql
+++ /dev/null
@@ -1,29 +0,0 @@
1-SET NAMES utf8mb4;
2-SET FOREIGN_KEY_CHECKS = 0;
3-
4--- ----------------------------
5--- Table structure for block_state
6--- ----------------------------
7-DROP TABLE IF EXISTS `block_state`;
8-CREATE TABLE `block_state` (
9- `height` int(11) NOT NULL,
10- `block_hash` varchar(64) NOT NULL
11-) ENGINE = InnoDB DEFAULT CHARSET=utf8;
12-
13--- ----------------------------
14--- Table structure for vote
15--- ----------------------------
16-DROP TABLE IF EXISTS `vote`;
17-CREATE TABLE `vote` (
18- `id` int(11) NOT NULL AUTO_INCREMENT,
19- `xpub` varchar(128) NOT NULL,
20- `voter_address` varchar(62) NOT NULL,
21- `vote_height` int(11) NOT NULL,
22- `vote_num` int(11) NOT NULL,
23- `veto_height` int(11) NOT NULL,
24- `output_id` varchar(64) NOT NULL,
25- PRIMARY KEY (`id`) USING BTREE,
26- UNIQUE INDEX `xpub`(`xpub`, `vote_height`, `output_id`) USING BTREE
27-) ENGINE = InnoDB AUTO_INCREMENT = 6 DEFAULT CHARSET=utf8;
28-
29-SET FOREIGN_KEY_CHECKS = 1;
--- a/toolbar/reward/database/orm/block_state.go
+++ /dev/null
@@ -1,6 +0,0 @@
1-package orm
2-
3-type BlockState struct {
4- Height uint64
5- BlockHash string
6-}
--- a/toolbar/reward/database/orm/vote_utxo.go
+++ /dev/null
@@ -1,11 +0,0 @@
1-package orm
2-
3-type Utxo struct {
4- ID uint64 `gorm:"primary_key"`
5- Xpub string
6- VoterAddress string
7- VoteHeight uint64
8- VoteNum uint64
9- VetoHeight uint64
10- OutputID string
11-}
--- a/toolbar/reward/reward.go
+++ /dev/null
@@ -1,4 +0,0 @@
1-package reward
2-
3-type Reward interface {
4-}
--- a/toolbar/reward/synchron/block_keeper.go
+++ /dev/null
@@ -1,185 +0,0 @@
1-package synchron
2-
3-import (
4- "encoding/hex"
5- "time"
6-
7- "github.com/jinzhu/gorm"
8- log "github.com/sirupsen/logrus"
9-
10- "github.com/vapor/errors"
11- "github.com/vapor/protocol/bc"
12- "github.com/vapor/protocol/bc/types"
13- "github.com/vapor/toolbar/common"
14- "github.com/vapor/toolbar/common/service"
15- "github.com/vapor/toolbar/reward/config"
16- "github.com/vapor/toolbar/reward/database/orm"
17-)
18-
19-type ChainKeeper struct {
20- cfg *config.Chain
21- db *gorm.DB
22- node *service.Node
23-}
24-
25-func NewChainKeeper(db *gorm.DB, cfg *config.Config) *ChainKeeper {
26- return &ChainKeeper{
27- cfg: &cfg.Chain,
28- db: db,
29- node: service.NewNode(cfg.Chain.Upstream),
30- }
31-}
32-
33-func (c *ChainKeeper) Run() {
34- ticker := time.NewTicker(time.Duration(c.cfg.SyncSeconds) * time.Second)
35- for ; true; <-ticker.C {
36- for {
37- isUpdate, err := c.syncBlock()
38- if err != nil {
39- log.WithField("error", err).Errorln("blockKeeper fail on process block")
40- break
41- }
42-
43- if !isUpdate {
44- break
45- }
46- }
47- }
48-}
49-
50-func (c *ChainKeeper) syncBlock() (bool, error) {
51- blockState := &orm.BlockState{}
52- if err := c.db.First(blockState).Error; err != nil {
53- return false, errors.Wrap(err, "query chain")
54- }
55-
56- height, err := c.node.GetBlockCount()
57- if err != nil {
58- return false, err
59- }
60-
61- if height == blockState.Height {
62- return false, nil
63- }
64-
65- nextBlockStr, txStatus, err := c.node.GetBlockByHeight(blockState.Height + 1)
66- if err != nil {
67- return false, err
68- }
69-
70- nextBlock := &types.Block{}
71- if err := nextBlock.UnmarshalText([]byte(nextBlockStr)); err != nil {
72- return false, errors.New("Unmarshal nextBlock")
73- }
74-
75- // Normal case, the previous hash of next block equals to the hash of current block,
76- // just sync to database directly.
77- if nextBlock.PreviousBlockHash.String() == blockState.BlockHash {
78- return true, c.AttachBlock(nextBlock, txStatus)
79- }
80-
81- log.WithField("block height", blockState.Height).Debug("the prev hash of remote is not equals the hash of current best block, must rollback")
82- currentBlockStr, txStatus, err := c.node.GetBlockByHash(blockState.BlockHash)
83- if err != nil {
84- return false, err
85- }
86-
87- currentBlock := &types.Block{}
88- if err := nextBlock.UnmarshalText([]byte(currentBlockStr)); err != nil {
89- return false, errors.New("Unmarshal currentBlock")
90- }
91-
92- return true, c.DetachBlock(currentBlock, txStatus)
93-}
94-
95-func (c *ChainKeeper) AttachBlock(block *types.Block, txStatus *bc.TransactionStatus) error {
96- ormDB := c.db.Begin()
97- for pos, tx := range block.Transactions {
98- statusFail, err := txStatus.GetStatus(pos)
99- if err != nil {
100- return err
101- }
102-
103- if statusFail {
104- log.WithFields(log.Fields{"block height": block.Height, "statusFail": statusFail}).Debug("AttachBlock")
105- continue
106- }
107-
108- for _, input := range tx.Inputs {
109- vetoInput, ok := input.TypedInput.(*types.VetoInput)
110- if !ok {
111- continue
112- }
113-
114- outputID, err := input.SpentOutputID()
115- if err != nil {
116- return err
117- }
118- utxo := &orm.Utxo{
119- VoterAddress: common.GetAddressFromControlProgram(vetoInput.ControlProgram),
120- OutputID: outputID.String(),
121- }
122- // update data
123- db := ormDB.Where(utxo).Update("veto_height", block.Height)
124- if err := db.Error; err != nil {
125- ormDB.Rollback()
126- return err
127- }
128-
129- if db.RowsAffected != 1 {
130- ormDB.Rollback()
131- return ErrInconsistentDB
132- }
133-
134- }
135-
136- for index, output := range tx.Outputs {
137- voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
138- if !ok {
139- continue
140- }
141- pubkey := hex.EncodeToString(voteOutput.Vote)
142- outputID := tx.OutputID(index)
143- utxo := &orm.Utxo{
144- Xpub: pubkey,
145- VoterAddress: common.GetAddressFromControlProgram(voteOutput.ControlProgram),
146- VoteHeight: block.Height,
147- VoteNum: voteOutput.Amount,
148- VetoHeight: 0,
149- OutputID: outputID.String(),
150- }
151- // insert data
152- if err := ormDB.Save(utxo).Error; err != nil {
153- ormDB.Rollback()
154- return err
155- }
156- }
157- }
158-
159- return ormDB.Commit().Error
160-}
161-
162-func (c *ChainKeeper) DetachBlock(block *types.Block, txStatus *bc.TransactionStatus) error {
163- ormDB := c.db.Begin()
164-
165- utxo := &orm.Utxo{
166- VoteHeight: block.Height,
167- }
168- // insert data
169- if err := ormDB.Where(utxo).Delete(&orm.Utxo{}).Error; err != nil {
170- ormDB.Rollback()
171- return err
172- }
173-
174- utxo = &orm.Utxo{
175- VetoHeight: block.Height,
176- }
177-
178- // update data
179- if err := ormDB.Where(utxo).Update("veto_height", 0).Error; err != nil {
180- ormDB.Rollback()
181- return err
182- }
183-
184- return ormDB.Commit().Error
185-}
--- a/toolbar/reward/synchron/errors.go
+++ /dev/null
@@ -1,10 +0,0 @@
1-package synchron
2-
3-import (
4- "github.com/vapor/errors"
5-)
6-
7-var (
8- ErrInconsistentDB = errors.New("inconsistent db status")
9- ErrOutputType = errors.New("error output type")
10-)
--- /dev/null
+++ b/toolbar/vote_reward/config/config.go
@@ -0,0 +1,37 @@
1+package config
2+
3+import (
4+ "bytes"
5+ "encoding/json"
6+ "io/ioutil"
7+ "os"
8+
9+ "github.com/vapor/toolbar/common"
10+)
11+
12+type Config struct {
13+ MySQLConfig common.MySQLConfig `json:"mysql"`
14+ NodeIP string `json:"node_ip"`
15+}
16+
17+func ExportConfigFile(configFile string, config *Config) error {
18+ buf := new(bytes.Buffer)
19+
20+ encoder := json.NewEncoder(buf)
21+ encoder.SetIndent("", " ")
22+ if err := encoder.Encode(config); err != nil {
23+ return err
24+ }
25+
26+ return ioutil.WriteFile(configFile, buf.Bytes(), 0644)
27+}
28+
29+func LoadConfigFile(configFile string, config *Config) error {
30+ file, err := os.Open(configFile)
31+ if err != nil {
32+ return err
33+ }
34+ defer file.Close()
35+
36+ return json.NewDecoder(file).Decode(config)
37+}
--- /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/synchron/block_keeper.go
@@ -0,0 +1,171 @@
1+package synchron
2+
3+import (
4+ "encoding/hex"
5+
6+ "github.com/jinzhu/gorm"
7+ log "github.com/sirupsen/logrus"
8+
9+ "github.com/vapor/errors"
10+ "github.com/vapor/protocol/bc/types"
11+ apinode "github.com/vapor/toolbar/api_node"
12+ "github.com/vapor/toolbar/common"
13+ "github.com/vapor/toolbar/vote_reward/config"
14+ "github.com/vapor/toolbar/vote_reward/database/orm"
15+)
16+
17+var ErrInconsistentDB = errors.New("inconsistent db status")
18+
19+type ChainKeeper struct {
20+ db *gorm.DB
21+ node *apinode.Node
22+ targetHeight uint64
23+}
24+
25+func NewChainKeeper(db *gorm.DB, cfg *config.Config, targetHeight uint64) (*ChainKeeper, error) {
26+ keeper := &ChainKeeper{
27+ db: db,
28+ node: apinode.NewNode(cfg.NodeIP),
29+ targetHeight: targetHeight,
30+ }
31+
32+ chainStatus := &orm.ChainStatus{}
33+ if err := db.First(chainStatus).Error; err == nil {
34+ return keeper, nil
35+ } else if err != gorm.ErrRecordNotFound {
36+ return nil, errors.Wrap(err, "fail on get chainStatus")
37+ }
38+
39+ if err := keeper.initBlockState(); err != nil {
40+ return nil, errors.Wrap(err, "fail on init chainStatus")
41+ }
42+ return keeper, nil
43+}
44+
45+func (c *ChainKeeper) SyncBlock() error {
46+ for {
47+ chainStatus := &orm.ChainStatus{}
48+ if err := c.db.First(chainStatus).Error; err != nil {
49+ return errors.Wrap(err, "fail on syncBlock query chainStatus")
50+ }
51+
52+ if chainStatus.BlockHeight >= c.targetHeight {
53+ break
54+ }
55+
56+ dbTX := c.db.Begin()
57+ if err := c.syncChainStatus(dbTX, chainStatus); err != nil {
58+ dbTX.Rollback()
59+ return err
60+ }
61+
62+ if err := dbTX.Commit().Error; err != nil {
63+ return err
64+ }
65+ }
66+ return nil
67+}
68+
69+func (c *ChainKeeper) syncChainStatus(db *gorm.DB, chainStatus *orm.ChainStatus) error {
70+ nextBlock, err := c.node.GetBlockByHeight(chainStatus.BlockHeight + 1)
71+ if err != nil {
72+ return err
73+ }
74+
75+ // Normal case, the previous hash of next block equals to the hash of current block,
76+ // just sync to database directly.
77+ if nextBlock.PreviousBlockHash.String() == chainStatus.BlockHash {
78+ return c.AttachBlock(db, chainStatus, nextBlock)
79+ }
80+
81+ log.WithField("block height", chainStatus.BlockHeight).Debug("the prev hash of remote is not equals the hash of current best block, must rollback")
82+ currentBlock, err := c.node.GetBlockByHash(chainStatus.BlockHash)
83+ if err != nil {
84+ return err
85+ }
86+
87+ return c.DetachBlock(db, chainStatus, currentBlock)
88+}
89+
90+func (c *ChainKeeper) AttachBlock(db *gorm.DB, chainStatus *orm.ChainStatus, block *types.Block) error {
91+ for _, tx := range block.Transactions {
92+ for _, input := range tx.Inputs {
93+ if input.TypedInput.InputType() != types.VetoInputType {
94+ continue
95+ }
96+
97+ outputID, err := input.SpentOutputID()
98+ if err != nil {
99+ return err
100+ }
101+
102+ result := db.Model(&orm.Utxo{}).Where(&orm.Utxo{OutputID: outputID.String()}).Update("veto_height", block.Height)
103+ if err := result.Error; err != nil {
104+ return err
105+ } else if result.RowsAffected != 1 {
106+ return ErrInconsistentDB
107+ }
108+ }
109+
110+ for i, output := range tx.Outputs {
111+ voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
112+ if !ok {
113+ continue
114+ }
115+
116+ utxo := &orm.Utxo{
117+ Xpub: hex.EncodeToString(voteOutput.Vote),
118+ VoteAddress: common.GetAddressFromControlProgram(voteOutput.ControlProgram),
119+ VoteHeight: block.Height,
120+ VoteNum: voteOutput.Amount,
121+ OutputID: tx.OutputID(i).String(),
122+ }
123+
124+ if err := db.Save(utxo).Error; err != nil {
125+ return err
126+ }
127+ }
128+ }
129+
130+ return c.updateChainStatus(db, chainStatus, block)
131+}
132+
133+func (c *ChainKeeper) DetachBlock(db *gorm.DB, chainStatus *orm.ChainStatus, block *types.Block) error {
134+ if err := db.Where(&orm.Utxo{VoteHeight: block.Height}).Delete(&orm.Utxo{}).Error; err != nil {
135+ return err
136+ }
137+
138+ if err := db.Where(&orm.Utxo{VetoHeight: block.Height}).Update("veto_height", 0).Error; err != nil {
139+ return err
140+ }
141+
142+ return c.updateChainStatus(db, chainStatus, block)
143+}
144+
145+func (c *ChainKeeper) initBlockState() error {
146+ block, err := c.node.GetBlockByHeight(0)
147+ if err != nil {
148+ return errors.Wrap(err, "fail on get genenis block")
149+ }
150+
151+ blockHash := block.Hash()
152+ chainStatus := &orm.ChainStatus{
153+ BlockHeight: block.Height,
154+ BlockHash: blockHash.String(),
155+ }
156+ return c.db.Save(chainStatus).Error
157+}
158+
159+func (c *ChainKeeper) updateChainStatus(db *gorm.DB, chainStatus *orm.ChainStatus, block *types.Block) error {
160+ blockHash := block.Hash()
161+ result := db.Model(&orm.ChainStatus{}).Where(chainStatus).Updates(&orm.ChainStatus{
162+ BlockHeight: block.Height,
163+ BlockHash: blockHash.String(),
164+ })
165+ if err := result.Error; err != nil {
166+ return err
167+ } else if result.RowsAffected != 1 {
168+ return ErrInconsistentDB
169+ }
170+ return nil
171+}
Show on old repository browser