• R/O
  • HTTP
  • SSH
  • HTTPS

vapor: Commit

Golang implemented sidechain for Bytom


Commit MetaInfo

Revision2b4b27796b86f47f04ccb6a046a6ed763a34963b (tree)
Time2019-07-23 00:21:55
Authorpaladz <453256728@qq.c...>
Commiterpaladz

Log Message

try edit the code format

Change Summary

Incremental Difference

--- /dev/null
+++ b/toolbar/api_node/block.go
@@ -0,0 +1,33 @@
1+package api_node
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 api_node
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/transaction.go
@@ -0,0 +1,138 @@
1+package api_node
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/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_states`;
8-CREATE TABLE `block_states` (
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 `utxos`;
17-CREATE TABLE `utxos` (
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` bigint(21) 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/service/node.go
+++ /dev/null
@@ -1,73 +0,0 @@
1-package service
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- "github.com/vapor/toolbar/common"
10-)
11-
12-// Node can invoke the api which provide by the full node server
13-type Node struct {
14- hostPort string
15-}
16-
17-// NewNode create a api client with target server
18-func NewNode(hostPort string) *Node {
19- return &Node{hostPort: hostPort}
20-}
21-
22-func (n *Node) GetBlockByHash(hash string) (*types.Block, error) {
23- return n.getRawBlock(&getRawBlockReq{BlockHash: hash})
24-}
25-
26-func (n *Node) GetBlockByHeight(height uint64) (*types.Block, error) {
27- return n.getRawBlock(&getRawBlockReq{BlockHeight: height})
28-}
29-
30-type getBlockCountResp struct {
31- BlockCount uint64 `json:"block_count"`
32-}
33-
34-func (n *Node) GetBlockCount() (uint64, error) {
35- url := "/get-block-count"
36- res := &getBlockCountResp{}
37- return res.BlockCount, n.request(url, nil, res)
38-}
39-
40-type getRawBlockReq struct {
41- BlockHeight uint64 `json:"block_height"`
42- BlockHash string `json:"block_hash"`
43-}
44-
45-func (n *Node) getRawBlock(req *getRawBlockReq) (*types.Block, error) {
46- url := "/get-raw-block"
47- payload, err := json.Marshal(req)
48- if err != nil {
49- return nil, errors.Wrap(err, "json marshal")
50- }
51-
52- resp := &api.GetRawBlockResp{}
53- return resp.RawBlock, n.request(url, payload, resp)
54-}
55-
56-type response struct {
57- Status string `json:"status"`
58- Data json.RawMessage `json:"data"`
59- ErrDetail string `json:"error_detail"`
60-}
61-
62-func (n *Node) request(path string, payload []byte, respData interface{}) error {
63- resp := &response{}
64- if err := common.Post(n.hostPort+path, payload, resp); err != nil {
65- return err
66- }
67-
68- if resp.Status != "success" {
69- return errors.New(resp.ErrDetail)
70- }
71-
72- return json.Unmarshal(resp.Data, respData)
73-}
--- a/toolbar/reward/service/node_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
1-package service
2-
3-import (
4- "encoding/json"
5- "testing"
6-
7- "github.com/vapor/consensus"
8- "github.com/vapor/protocol/bc"
9-)
10-
11-func TestBuildRequest(t *testing.T) {
12- cases := []struct {
13- input InputAction
14- outputs []OutputAction
15- want string
16- err error
17- }{
18- {
19- input: InputAction{
20- Type: "spend_account",
21- AccountID: "9bb77612-350e-4d53-81e2-525b28247ba5",
22- AssetAmount: bc.AssetAmount{
23- AssetId: consensus.BTMAssetID,
24- Amount: 100,
25- },
26- },
27- outputs: []OutputAction{
28- OutputAction{
29- Type: "control_address",
30- Address: "sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf",
31- AssetAmount: bc.AssetAmount{
32- AssetId: consensus.BTMAssetID,
33- Amount: 100,
34- },
35- },
36- },
37- want: `{"actions":[{"type":"spend_account","account_id":"9bb77612-350e-4d53-81e2-525b28247ba5","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":100},{"type":"control_address","address":"sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":100}]}`,
38- },
39- {
40- input: InputAction{
41- Type: "spend_account",
42- AccountID: "9bb77612-350e-4d53-81e2-525b28247ba5",
43- AssetAmount: bc.AssetAmount{
44- AssetId: consensus.BTMAssetID,
45- Amount: 100,
46- },
47- },
48- outputs: []OutputAction{
49- OutputAction{
50- Type: "control_address",
51- Address: "sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf",
52- AssetAmount: bc.AssetAmount{
53- AssetId: consensus.BTMAssetID,
54- Amount: 50,
55- },
56- },
57- OutputAction{
58- Type: "control_address",
59- Address: "sp1qklmexrd32ch8yc8xhkpkdx05wye75pvzuy2gch",
60- AssetAmount: bc.AssetAmount{
61- AssetId: consensus.BTMAssetID,
62- Amount: 50,
63- },
64- },
65- },
66- want: `{"actions":[{"type":"spend_account","account_id":"9bb77612-350e-4d53-81e2-525b28247ba5","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":100},{"type":"control_address","address":"sp1qklmexrd32ch8yc8xhkpkdx05wye75pvzuy2gch","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":50},{"type":"control_address","address":"sp1qklmexrd32ch8yc8xhkpkdx05wye75pvzuy2gch","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":50}]}`,
67- },
68- }
69-
70- for i, c := range cases {
71- n := &Node{}
72- req := &buildSpendReq{}
73- if err := n.buildRequest(c.input, c.outputs, req); err != nil {
74- t.Fatal(err)
75- }
76-
77- buildReq, err := json.Marshal(req)
78- if err != nil {
79- t.Fatal(err)
80- }
81-
82- if string(buildReq) != string(c.want) {
83- t.Fatal(i, string(buildReq))
84- }
85-
86- }
87-}
--- a/toolbar/reward/service/transaction.go
+++ /dev/null
@@ -1,129 +0,0 @@
1-package service
2-
3-import (
4- "encoding/json"
5-
6- "github.com/vapor/blockchain/txbuilder"
7- "github.com/vapor/errors"
8- "github.com/vapor/protocol/bc"
9-)
10-
11-type buildSpendReq struct {
12- Actions []interface{} `json:"actions"`
13-}
14-
15-type Action struct {
16- InputAction
17- OutputActions []OutputAction
18-}
19-
20-type InputAction struct {
21- Type string `json:"type"`
22- AccountID string `json:"account_id"`
23- bc.AssetAmount
24-}
25-
26-type OutputAction struct {
27- Type string `json:"type"`
28- Address string `json:"address"`
29- bc.AssetAmount
30-}
31-
32-func (n *Node) SendTransaction(inputAction InputAction, outputActions []OutputAction, passwd string) (string, error) {
33- tmpl, err := n.buildTx(inputAction, outputActions)
34- if err != nil {
35- return "", err
36- }
37-
38- tmpl, err = n.signTx(passwd, *tmpl)
39- if err != nil {
40- return "", err
41- }
42-
43- return n.SubmitTx(tmpl.Transaction)
44-}
45-
46-func (n *Node) buildRequest(inputAction InputAction, outputActions []OutputAction, req *buildSpendReq) error {
47- if len(outputActions) == 0 {
48- return errors.New("output is empty")
49- }
50- req.Actions = append(req.Actions, &inputAction)
51-
52- for _, outputAction := range outputActions {
53- req.Actions = append(req.Actions, &outputAction)
54- }
55-
56- return nil
57-}
58-
59-func (n *Node) buildTx(inputAction InputAction, outputActions []OutputAction) (*txbuilder.Template, error) {
60- url := "/build-transaction"
61-
62- req := &buildSpendReq{}
63- err := n.buildRequest(inputAction, outputActions, req)
64- if err != nil {
65- return nil, errors.Wrap(err, "build spend request")
66- }
67-
68- buildReq, err := json.Marshal(req)
69- if err != nil {
70- return nil, errors.Wrap(err, "Marshal spend request")
71- }
72-
73- tmpl := &txbuilder.Template{}
74- return tmpl, n.request(url, buildReq, tmpl)
75-}
76-
77-type signTxReq struct {
78- Password string `json:"password"`
79- Txs txbuilder.Template `json:"transaction"`
80-}
81-
82-type signTemplateResp struct {
83- Tx *txbuilder.Template `json:"transaction"`
84- SignComplete bool `json:"sign_complete"`
85-}
86-
87-func (n *Node) signTx(passwd string, tmpl txbuilder.Template) (*txbuilder.Template, error) {
88- url := "/sign-transaction"
89- req := &signTxReq{
90- Password: passwd,
91- Txs: tmpl,
92- }
93-
94- payload, err := json.Marshal(req)
95- if err != nil {
96- return nil, errors.Wrap(err, "json marshal")
97- }
98-
99- resp := &signTemplateResp{}
100-
101- if err := n.request(url, payload, resp); err != nil {
102- return nil, err
103- }
104-
105- if !resp.SignComplete {
106- return nil, errors.New("sign fail")
107- }
108-
109- return resp.Tx, nil
110-}
111-
112-type submitTxReq struct {
113- Tx interface{} `json:"raw_transaction"`
114-}
115-
116-type submitTxResp struct {
117- TxID string `json:"tx_id"`
118-}
119-
120-func (n *Node) SubmitTx(tx interface{}) (string, error) {
121- url := "/submit-transaction"
122- payload, err := json.Marshal(submitTxReq{Tx: tx})
123- if err != nil {
124- return "", errors.Wrap(err, "json marshal")
125- }
126-
127- res := &submitTxResp{}
128- return res.TxID, n.request(url, payload, res)
129-}
--- a/toolbar/reward/synchron/block_keeper.go
+++ /dev/null
@@ -1,211 +0,0 @@
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- "github.com/vapor/toolbar/common"
12- "github.com/vapor/toolbar/reward/config"
13- "github.com/vapor/toolbar/reward/database/orm"
14- "github.com/vapor/toolbar/reward/service"
15-)
16-
17-type ChainKeeper struct {
18- cfg *config.Chain
19- db *gorm.DB
20- node *service.Node
21- syncHeight uint64
22-}
23-
24-func NewChainKeeper(db *gorm.DB, cfg *config.Config, syncHeight uint64) (*ChainKeeper, error) {
25- keeper := &ChainKeeper{
26- cfg: &cfg.Chain,
27- db: db,
28- node: service.NewNode(cfg.Chain.Upstream),
29- syncHeight: syncHeight,
30- }
31-
32- blockState := &orm.BlockState{}
33- err := db.First(blockState).Error
34- if err == nil {
35- return keeper, nil
36- }
37-
38- if err != gorm.ErrRecordNotFound {
39- return nil, errors.Wrap(err, "Failed to get blockState")
40- }
41-
42- block, err := keeper.node.GetBlockByHeight(0)
43- if err != nil {
44- return nil, errors.Wrap(err, "Failed to get genenis block")
45- }
46-
47- if err := keeper.initBlockState(db, block); err != nil {
48- return nil, errors.Wrap(err, "Failed to insert blockState")
49- }
50-
51- return keeper, nil
52-}
53-
54-func (c *ChainKeeper) SyncBlock() error {
55- for {
56- blockState := &orm.BlockState{}
57- if err := c.db.First(blockState).Error; err != nil {
58- return errors.Wrap(err, "The query blockState record is empty empty on process block")
59- }
60-
61- if blockState.Height >= c.syncHeight {
62- break
63- }
64- ormDB := c.db.Begin()
65- if err := c.syncBlock(ormDB, blockState); err != nil {
66- ormDB.Rollback()
67- return err
68- }
69-
70- if err := ormDB.Commit().Error; err != nil {
71- return err
72- }
73- }
74- return nil
75-}
76-
77-func (c *ChainKeeper) syncBlock(ormDB *gorm.DB, blockState *orm.BlockState) error {
78- height, err := c.node.GetBlockCount()
79- if err != nil {
80- return err
81- }
82-
83- if height == blockState.Height {
84- return nil
85- }
86-
87- nextBlock, err := c.node.GetBlockByHeight(blockState.Height + 1)
88- if err != nil {
89- return err
90- }
91-
92- // Normal case, the previous hash of next block equals to the hash of current block,
93- // just sync to database directly.
94- if nextBlock.PreviousBlockHash.String() == blockState.BlockHash {
95- return c.AttachBlock(ormDB, nextBlock)
96- }
97-
98- log.WithField("block height", blockState.Height).Debug("the prev hash of remote is not equals the hash of current best block, must rollback")
99- currentBlock, err := c.node.GetBlockByHash(blockState.BlockHash)
100- if err != nil {
101- return err
102- }
103-
104- return c.DetachBlock(ormDB, currentBlock)
105-}
106-
107-func (c *ChainKeeper) AttachBlock(ormDB *gorm.DB, block *types.Block) error {
108- for _, tx := range block.Transactions {
109- for _, input := range tx.Inputs {
110- if _, ok := input.TypedInput.(*types.VetoInput); !ok {
111- continue
112- }
113-
114- outputID, err := input.SpentOutputID()
115- if err != nil {
116- return err
117- }
118- utxo := &orm.Utxo{
119- OutputID: outputID.String(),
120- }
121- // update data
122- db := ormDB.Model(&orm.Utxo{}).Where(utxo).Update("veto_height", block.Height)
123- if err := db.Error; err != nil {
124- return err
125- }
126-
127- if db.RowsAffected != 1 {
128- return ErrInconsistentDB
129- }
130-
131- }
132-
133- for index, output := range tx.Outputs {
134- voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
135- if !ok {
136- continue
137- }
138- pubkey := hex.EncodeToString(voteOutput.Vote)
139- outputID := tx.OutputID(index)
140- utxo := &orm.Utxo{
141- Xpub: pubkey,
142- VoterAddress: common.GetAddressFromControlProgram(voteOutput.ControlProgram),
143- VoteHeight: block.Height,
144- VoteNum: voteOutput.Amount,
145- VetoHeight: 0,
146- OutputID: outputID.String(),
147- }
148- // insert data
149- if err := ormDB.Save(utxo).Error; err != nil {
150- return err
151- }
152- }
153- }
154-
155- blockHash := block.Hash()
156- blockState := &orm.BlockState{
157- Height: block.Height,
158- BlockHash: blockHash.String(),
159- }
160-
161- return c.updateBlockState(ormDB, blockState)
162-}
163-
164-func (c *ChainKeeper) DetachBlock(ormDB *gorm.DB, block *types.Block) error {
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- return err
171- }
172-
173- utxo = &orm.Utxo{
174- VetoHeight: block.Height,
175- }
176-
177- // update data
178- if err := ormDB.Where(utxo).Update("veto_height", 0).Error; err != nil {
179- return err
180- }
181-
182- blockState := &orm.BlockState{
183- Height: block.Height - 1,
184- BlockHash: block.PreviousBlockHash.String(),
185- }
186-
187- return c.updateBlockState(ormDB, blockState)
188-}
189-
190-func (c *ChainKeeper) initBlockState(db *gorm.DB, block *types.Block) error {
191- blockHash := block.Hash()
192- blockState := &orm.BlockState{
193- Height: block.Height,
194- BlockHash: blockHash.String(),
195- }
196-
197- return db.Save(blockState).Error
198-}
199-
200-func (c *ChainKeeper) updateBlockState(db *gorm.DB, blockState *orm.BlockState) error {
201- // update blockState
202- u := db.Model(&orm.BlockState{}).Updates(blockState)
203- if err := u.Error; err != nil {
204- return err
205- }
206-
207- if u.RowsAffected != 1 {
208- return ErrInconsistentDB
209- }
210- return nil
211-}
--- 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-)
--- a/toolbar/reward/config/config.go
+++ b/toolbar/vote_reward/config/config.go
@@ -6,20 +6,12 @@ import (
66 "io/ioutil"
77 "os"
88
9- "github.com/vapor/crypto/ed25519/chainkd"
109 "github.com/vapor/toolbar/common"
1110 )
1211
1312 type Config struct {
1413 MySQLConfig common.MySQLConfig `json:"mysql"`
15- Chain Chain `json:"chain"`
16- XPubs []chainkd.XPub `json:"xpubs"`
17-}
18-
19-type Chain struct {
20- Name string `json:"name"`
21- Upstream string `json:"upstream"`
22- SyncSeconds uint64 `json:"sync_seconds"`
14+ NodeIP string `json:"node_ip"`
2315 }
2416
2517 func ExportConfigFile(fedFile string, config *Config) error {
--- /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_status
24+# ------------------------------------------------------------
25+
26+DROP TABLE IF EXISTS `chain_status`;
27+
28+CREATE TABLE `chain_status` (
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+ "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 *api_node.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: api_node.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