OSDN Git Service

try edit the code format
authorpaladz <453256728@qq.com>
Mon, 22 Jul 2019 15:21:55 +0000 (23:21 +0800)
committerpaladz <453256728@qq.com>
Mon, 22 Jul 2019 15:21:55 +0000 (23:21 +0800)
16 files changed:
toolbar/api_node/block.go [new file with mode: 0644]
toolbar/api_node/node.go [new file with mode: 0644]
toolbar/api_node/transaction.go [new file with mode: 0644]
toolbar/reward/database/dump_reward.sql [deleted file]
toolbar/reward/database/orm/block_state.go [deleted file]
toolbar/reward/database/orm/vote_utxo.go [deleted file]
toolbar/reward/service/node.go [deleted file]
toolbar/reward/service/node_test.go [deleted file]
toolbar/reward/service/transaction.go [deleted file]
toolbar/reward/synchron/block_keeper.go [deleted file]
toolbar/reward/synchron/errors.go [deleted file]
toolbar/vote_reward/config/config.go [moved from toolbar/reward/config/config.go with 70% similarity]
toolbar/vote_reward/database/dump_reward.sql [new file with mode: 0644]
toolbar/vote_reward/database/orm/block_state.go [new file with mode: 0644]
toolbar/vote_reward/database/orm/utxo.go [new file with mode: 0644]
toolbar/vote_reward/synchron/block_keeper.go [new file with mode: 0644]

diff --git a/toolbar/api_node/block.go b/toolbar/api_node/block.go
new file mode 100644 (file)
index 0000000..06fafbd
--- /dev/null
@@ -0,0 +1,33 @@
+package api_node
+
+import (
+       "encoding/json"
+
+       "github.com/vapor/api"
+       "github.com/vapor/errors"
+       "github.com/vapor/protocol/bc/types"
+)
+
+func (n *Node) GetBlockByHash(hash string) (*types.Block, error) {
+       return n.getRawBlock(&getRawBlockReq{BlockHash: hash})
+}
+
+func (n *Node) GetBlockByHeight(height uint64) (*types.Block, error) {
+       return n.getRawBlock(&getRawBlockReq{BlockHeight: height})
+}
+
+type getRawBlockReq struct {
+       BlockHeight uint64 `json:"block_height"`
+       BlockHash   string `json:"block_hash"`
+}
+
+func (n *Node) getRawBlock(req *getRawBlockReq) (*types.Block, error) {
+       url := "/get-raw-block"
+       payload, err := json.Marshal(req)
+       if err != nil {
+               return nil, errors.Wrap(err, "json marshal")
+       }
+
+       resp := &api.GetRawBlockResp{}
+       return resp.RawBlock, n.request(url, payload, resp)
+}
diff --git a/toolbar/api_node/node.go b/toolbar/api_node/node.go
new file mode 100644 (file)
index 0000000..203e7b7
--- /dev/null
@@ -0,0 +1,37 @@
+package api_node
+
+import (
+       "encoding/json"
+
+       "github.com/vapor/errors"
+       "github.com/vapor/toolbar/common"
+)
+
+// Node can invoke the api which provide by the full node server
+type Node struct {
+       hostPort string
+}
+
+// NewNode create a api client with target server
+func NewNode(hostPort string) *Node {
+       return &Node{hostPort: hostPort}
+}
+
+type response struct {
+       Status    string          `json:"status"`
+       Data      json.RawMessage `json:"data"`
+       ErrDetail string          `json:"error_detail"`
+}
+
+func (n *Node) request(path string, payload []byte, respData interface{}) error {
+       resp := &response{}
+       if err := common.Post(n.hostPort+path, payload, resp); err != nil {
+               return err
+       }
+
+       if resp.Status != "success" {
+               return errors.New(resp.ErrDetail)
+       }
+
+       return json.Unmarshal(resp.Data, respData)
+}
diff --git a/toolbar/api_node/transaction.go b/toolbar/api_node/transaction.go
new file mode 100644 (file)
index 0000000..0e83e1a
--- /dev/null
@@ -0,0 +1,138 @@
+package api_node
+
+import (
+       "encoding/json"
+
+       "github.com/vapor/blockchain/txbuilder"
+       "github.com/vapor/consensus"
+       "github.com/vapor/errors"
+       "github.com/vapor/protocol/bc"
+       "github.com/vapor/protocol/bc/types"
+)
+
+type SpendAccountAction struct {
+       AccountID string `json:"account_id"`
+       *bc.AssetAmount
+}
+
+func (s *SpendAccountAction) MarshalJSON() ([]byte, error) {
+       return json.Marshal(&struct {
+               Type      string `json:"type"`
+               AccountID string `json:"account_id"`
+               *bc.AssetAmount
+       }{
+               Type:        "spend_account",
+               AccountID:   s.AccountID,
+               AssetAmount: s.AssetAmount,
+       })
+}
+
+type ControlAddressAction struct {
+       Address string `json:"address"`
+       *bc.AssetAmount
+}
+
+func (c *ControlAddressAction) MarshalJSON() ([]byte, error) {
+       return json.Marshal(&struct {
+               Type    string `json:"type"`
+               Address string `json:"address"`
+               *bc.AssetAmount
+       }{
+               Type:        "control_address",
+               Address:     c.Address,
+               AssetAmount: c.AssetAmount,
+       })
+}
+
+func (n *Node) BatchSendBTM(accountID, password string, outputs map[string]uint64) error {
+       totalBTM := uint64(10000000)
+       actions := []interface{}{}
+       for address, amount := range outputs {
+               actions = append(actions, &ControlAddressAction{
+                       Address:     address,
+                       AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: amount},
+               })
+               totalBTM += amount
+       }
+
+       actions = append(actions, &SpendAccountAction{
+               AccountID:   accountID,
+               AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: totalBTM},
+       })
+
+       tpl, err := n.buildTx(actions)
+       if err != nil {
+               return err
+       }
+
+       tpl, err = n.signTx(tpl, password)
+       if err != nil {
+               return err
+       }
+
+       _, err = n.SubmitTx(tpl.Transaction)
+       return err
+}
+
+type buildTxReq struct {
+       Actions []interface{} `json:"actions"`
+}
+
+func (n *Node) buildTx(actions []interface{}) (*txbuilder.Template, error) {
+       url := "/build-transaction"
+       payload, err := json.Marshal(&buildTxReq{Actions: actions})
+       if err != nil {
+               return nil, errors.Wrap(err, "Marshal spend request")
+       }
+
+       result := &txbuilder.Template{}
+       return result, n.request(url, payload, result)
+}
+
+type signTxReq struct {
+       Tx       *txbuilder.Template `json:"transaction"`
+       Password string              `json:"password"`
+}
+
+type signTxResp struct {
+       Tx           *txbuilder.Template `json:"transaction"`
+       SignComplete bool                `json:"sign_complete"`
+}
+
+func (n *Node) signTx(tpl *txbuilder.Template, password string) (*txbuilder.Template, error) {
+       url := "/sign-transaction"
+       payload, err := json.Marshal(&signTxReq{Tx: tpl, Password: password})
+       if err != nil {
+               return nil, errors.Wrap(err, "json marshal")
+       }
+
+       resp := &signTxResp{}
+       if err := n.request(url, payload, resp); err != nil {
+               return nil, err
+       }
+
+       if !resp.SignComplete {
+               return nil, errors.New("sign fail")
+       }
+
+       return resp.Tx, nil
+}
+
+type submitTxReq struct {
+       Tx *types.Tx `json:"raw_transaction"`
+}
+
+type submitTxResp struct {
+       TxID string `json:"tx_id"`
+}
+
+func (n *Node) SubmitTx(tx *types.Tx) (string, error) {
+       url := "/submit-transaction"
+       payload, err := json.Marshal(submitTxReq{Tx: tx})
+       if err != nil {
+               return "", errors.Wrap(err, "json marshal")
+       }
+
+       res := &submitTxResp{}
+       return res.TxID, n.request(url, payload, res)
+}
diff --git a/toolbar/reward/database/dump_reward.sql b/toolbar/reward/database/dump_reward.sql
deleted file mode 100644 (file)
index 330754b..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-SET NAMES utf8mb4;
-SET FOREIGN_KEY_CHECKS = 0;
-
--- ----------------------------
--- Table structure for block_state
--- ----------------------------
-DROP TABLE IF EXISTS `block_states`;
-CREATE TABLE `block_states`  (
-  `height` int(11) NOT NULL,
-  `block_hash` varchar(64) NOT NULL
-) ENGINE = InnoDB DEFAULT CHARSET=utf8;
-
--- ----------------------------
--- Table structure for vote
--- ----------------------------
-DROP TABLE IF EXISTS `utxos`;
-CREATE TABLE `utxos`  (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `xpub` varchar(128) NOT NULL,
-  `voter_address` varchar(62) NOT NULL,
-  `vote_height` int(11) NOT NULL,
-  `vote_num` bigint(21) NOT NULL,
-  `veto_height` int(11) NOT NULL,
-  `output_id` varchar(64) NOT NULL,
-  PRIMARY KEY (`id`) USING BTREE,
-  UNIQUE INDEX `xpub`(`xpub`, `vote_height`, `output_id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 6 DEFAULT CHARSET=utf8;
-
-SET FOREIGN_KEY_CHECKS = 1;
diff --git a/toolbar/reward/database/orm/block_state.go b/toolbar/reward/database/orm/block_state.go
deleted file mode 100644 (file)
index 6fdd155..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-package orm
-
-type BlockState struct {
-       Height    uint64
-       BlockHash string
-}
diff --git a/toolbar/reward/database/orm/vote_utxo.go b/toolbar/reward/database/orm/vote_utxo.go
deleted file mode 100644 (file)
index 4395181..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-package orm
-
-type Utxo struct {
-       ID           uint64 `gorm:"primary_key"`
-       Xpub         string
-       VoterAddress string
-       VoteHeight   uint64
-       VoteNum      uint64
-       VetoHeight   uint64
-       OutputID     string
-}
diff --git a/toolbar/reward/service/node.go b/toolbar/reward/service/node.go
deleted file mode 100644 (file)
index af7309a..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-package service
-
-import (
-       "encoding/json"
-
-       "github.com/vapor/api"
-       "github.com/vapor/errors"
-       "github.com/vapor/protocol/bc/types"
-       "github.com/vapor/toolbar/common"
-)
-
-// Node can invoke the api which provide by the full node server
-type Node struct {
-       hostPort string
-}
-
-// NewNode create a api client with target server
-func NewNode(hostPort string) *Node {
-       return &Node{hostPort: hostPort}
-}
-
-func (n *Node) GetBlockByHash(hash string) (*types.Block, error) {
-       return n.getRawBlock(&getRawBlockReq{BlockHash: hash})
-}
-
-func (n *Node) GetBlockByHeight(height uint64) (*types.Block, error) {
-       return n.getRawBlock(&getRawBlockReq{BlockHeight: height})
-}
-
-type getBlockCountResp struct {
-       BlockCount uint64 `json:"block_count"`
-}
-
-func (n *Node) GetBlockCount() (uint64, error) {
-       url := "/get-block-count"
-       res := &getBlockCountResp{}
-       return res.BlockCount, n.request(url, nil, res)
-}
-
-type getRawBlockReq struct {
-       BlockHeight uint64 `json:"block_height"`
-       BlockHash   string `json:"block_hash"`
-}
-
-func (n *Node) getRawBlock(req *getRawBlockReq) (*types.Block, error) {
-       url := "/get-raw-block"
-       payload, err := json.Marshal(req)
-       if err != nil {
-               return nil, errors.Wrap(err, "json marshal")
-       }
-
-       resp := &api.GetRawBlockResp{}
-       return resp.RawBlock, n.request(url, payload, resp)
-}
-
-type response struct {
-       Status    string          `json:"status"`
-       Data      json.RawMessage `json:"data"`
-       ErrDetail string          `json:"error_detail"`
-}
-
-func (n *Node) request(path string, payload []byte, respData interface{}) error {
-       resp := &response{}
-       if err := common.Post(n.hostPort+path, payload, resp); err != nil {
-               return err
-       }
-
-       if resp.Status != "success" {
-               return errors.New(resp.ErrDetail)
-       }
-
-       return json.Unmarshal(resp.Data, respData)
-}
diff --git a/toolbar/reward/service/node_test.go b/toolbar/reward/service/node_test.go
deleted file mode 100644 (file)
index 92f92a5..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-package service
-
-import (
-       "encoding/json"
-       "testing"
-
-       "github.com/vapor/consensus"
-       "github.com/vapor/protocol/bc"
-)
-
-func TestBuildRequest(t *testing.T) {
-       cases := []struct {
-               input   InputAction
-               outputs []OutputAction
-               want    string
-               err     error
-       }{
-               {
-                       input: InputAction{
-                               Type:      "spend_account",
-                               AccountID: "9bb77612-350e-4d53-81e2-525b28247ba5",
-                               AssetAmount: bc.AssetAmount{
-                                       AssetId: consensus.BTMAssetID,
-                                       Amount:  100,
-                               },
-                       },
-                       outputs: []OutputAction{
-                               OutputAction{
-                                       Type:    "control_address",
-                                       Address: "sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf",
-                                       AssetAmount: bc.AssetAmount{
-                                               AssetId: consensus.BTMAssetID,
-                                               Amount:  100,
-                                       },
-                               },
-                       },
-                       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}]}`,
-               },
-               {
-                       input: InputAction{
-                               Type:      "spend_account",
-                               AccountID: "9bb77612-350e-4d53-81e2-525b28247ba5",
-                               AssetAmount: bc.AssetAmount{
-                                       AssetId: consensus.BTMAssetID,
-                                       Amount:  100,
-                               },
-                       },
-                       outputs: []OutputAction{
-                               OutputAction{
-                                       Type:    "control_address",
-                                       Address: "sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf",
-                                       AssetAmount: bc.AssetAmount{
-                                               AssetId: consensus.BTMAssetID,
-                                               Amount:  50,
-                                       },
-                               },
-                               OutputAction{
-                                       Type:    "control_address",
-                                       Address: "sp1qklmexrd32ch8yc8xhkpkdx05wye75pvzuy2gch",
-                                       AssetAmount: bc.AssetAmount{
-                                               AssetId: consensus.BTMAssetID,
-                                               Amount:  50,
-                                       },
-                               },
-                       },
-                       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}]}`,
-               },
-       }
-
-       for i, c := range cases {
-               n := &Node{}
-               req := &buildSpendReq{}
-               if err := n.buildRequest(c.input, c.outputs, req); err != nil {
-                       t.Fatal(err)
-               }
-
-               buildReq, err := json.Marshal(req)
-               if err != nil {
-                       t.Fatal(err)
-               }
-
-               if string(buildReq) != string(c.want) {
-                       t.Fatal(i, string(buildReq))
-               }
-
-       }
-}
diff --git a/toolbar/reward/service/transaction.go b/toolbar/reward/service/transaction.go
deleted file mode 100644 (file)
index b2d4eb1..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-package service
-
-import (
-       "encoding/json"
-
-       "github.com/vapor/blockchain/txbuilder"
-       "github.com/vapor/errors"
-       "github.com/vapor/protocol/bc"
-)
-
-type buildSpendReq struct {
-       Actions []interface{} `json:"actions"`
-}
-
-type Action struct {
-       InputAction
-       OutputActions []OutputAction
-}
-
-type InputAction struct {
-       Type      string `json:"type"`
-       AccountID string `json:"account_id"`
-       bc.AssetAmount
-}
-
-type OutputAction struct {
-       Type    string `json:"type"`
-       Address string `json:"address"`
-       bc.AssetAmount
-}
-
-func (n *Node) SendTransaction(inputAction InputAction, outputActions []OutputAction, passwd string) (string, error) {
-       tmpl, err := n.buildTx(inputAction, outputActions)
-       if err != nil {
-               return "", err
-       }
-
-       tmpl, err = n.signTx(passwd, *tmpl)
-       if err != nil {
-               return "", err
-       }
-
-       return n.SubmitTx(tmpl.Transaction)
-}
-
-func (n *Node) buildRequest(inputAction InputAction, outputActions []OutputAction, req *buildSpendReq) error {
-       if len(outputActions) == 0 {
-               return errors.New("output is empty")
-       }
-       req.Actions = append(req.Actions, &inputAction)
-
-       for _, outputAction := range outputActions {
-               req.Actions = append(req.Actions, &outputAction)
-       }
-
-       return nil
-}
-
-func (n *Node) buildTx(inputAction InputAction, outputActions []OutputAction) (*txbuilder.Template, error) {
-       url := "/build-transaction"
-
-       req := &buildSpendReq{}
-       err := n.buildRequest(inputAction, outputActions, req)
-       if err != nil {
-               return nil, errors.Wrap(err, "build spend request")
-       }
-
-       buildReq, err := json.Marshal(req)
-       if err != nil {
-               return nil, errors.Wrap(err, "Marshal spend request")
-       }
-
-       tmpl := &txbuilder.Template{}
-       return tmpl, n.request(url, buildReq, tmpl)
-}
-
-type signTxReq struct {
-       Password string             `json:"password"`
-       Txs      txbuilder.Template `json:"transaction"`
-}
-
-type signTemplateResp struct {
-       Tx           *txbuilder.Template `json:"transaction"`
-       SignComplete bool                `json:"sign_complete"`
-}
-
-func (n *Node) signTx(passwd string, tmpl txbuilder.Template) (*txbuilder.Template, error) {
-       url := "/sign-transaction"
-       req := &signTxReq{
-               Password: passwd,
-               Txs:      tmpl,
-       }
-
-       payload, err := json.Marshal(req)
-       if err != nil {
-               return nil, errors.Wrap(err, "json marshal")
-       }
-
-       resp := &signTemplateResp{}
-
-       if err := n.request(url, payload, resp); err != nil {
-               return nil, err
-       }
-
-       if !resp.SignComplete {
-               return nil, errors.New("sign fail")
-       }
-
-       return resp.Tx, nil
-}
-
-type submitTxReq struct {
-       Tx interface{} `json:"raw_transaction"`
-}
-
-type submitTxResp struct {
-       TxID string `json:"tx_id"`
-}
-
-func (n *Node) SubmitTx(tx interface{}) (string, error) {
-       url := "/submit-transaction"
-       payload, err := json.Marshal(submitTxReq{Tx: tx})
-       if err != nil {
-               return "", errors.Wrap(err, "json marshal")
-       }
-
-       res := &submitTxResp{}
-       return res.TxID, n.request(url, payload, res)
-}
diff --git a/toolbar/reward/synchron/block_keeper.go b/toolbar/reward/synchron/block_keeper.go
deleted file mode 100644 (file)
index 1ec1f89..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-package synchron
-
-import (
-       "encoding/hex"
-
-       "github.com/jinzhu/gorm"
-       log "github.com/sirupsen/logrus"
-
-       "github.com/vapor/errors"
-       "github.com/vapor/protocol/bc/types"
-       "github.com/vapor/toolbar/common"
-       "github.com/vapor/toolbar/reward/config"
-       "github.com/vapor/toolbar/reward/database/orm"
-       "github.com/vapor/toolbar/reward/service"
-)
-
-type ChainKeeper struct {
-       cfg        *config.Chain
-       db         *gorm.DB
-       node       *service.Node
-       syncHeight uint64
-}
-
-func NewChainKeeper(db *gorm.DB, cfg *config.Config, syncHeight uint64) (*ChainKeeper, error) {
-       keeper := &ChainKeeper{
-               cfg:        &cfg.Chain,
-               db:         db,
-               node:       service.NewNode(cfg.Chain.Upstream),
-               syncHeight: syncHeight,
-       }
-
-       blockState := &orm.BlockState{}
-       err := db.First(blockState).Error
-       if err == nil {
-               return keeper, nil
-       }
-
-       if err != gorm.ErrRecordNotFound {
-               return nil, errors.Wrap(err, "Failed to get blockState")
-       }
-
-       block, err := keeper.node.GetBlockByHeight(0)
-       if err != nil {
-               return nil, errors.Wrap(err, "Failed to get genenis block")
-       }
-
-       if err := keeper.initBlockState(db, block); err != nil {
-               return nil, errors.Wrap(err, "Failed to insert blockState")
-       }
-
-       return keeper, nil
-}
-
-func (c *ChainKeeper) SyncBlock() error {
-       for {
-               blockState := &orm.BlockState{}
-               if err := c.db.First(blockState).Error; err != nil {
-                       return errors.Wrap(err, "The query blockState record is empty empty on process block")
-               }
-
-               if blockState.Height >= c.syncHeight {
-                       break
-               }
-               ormDB := c.db.Begin()
-               if err := c.syncBlock(ormDB, blockState); err != nil {
-                       ormDB.Rollback()
-                       return err
-               }
-
-               if err := ormDB.Commit().Error; err != nil {
-                       return err
-               }
-       }
-       return nil
-}
-
-func (c *ChainKeeper) syncBlock(ormDB *gorm.DB, blockState *orm.BlockState) error {
-       height, err := c.node.GetBlockCount()
-       if err != nil {
-               return err
-       }
-
-       if height == blockState.Height {
-               return nil
-       }
-
-       nextBlock, err := c.node.GetBlockByHeight(blockState.Height + 1)
-       if err != nil {
-               return err
-       }
-
-       // Normal case, the previous hash of next block equals to the hash of current block,
-       // just sync to database directly.
-       if nextBlock.PreviousBlockHash.String() == blockState.BlockHash {
-               return c.AttachBlock(ormDB, nextBlock)
-       }
-
-       log.WithField("block height", blockState.Height).Debug("the prev hash of remote is not equals the hash of current best block, must rollback")
-       currentBlock, err := c.node.GetBlockByHash(blockState.BlockHash)
-       if err != nil {
-               return err
-       }
-
-       return c.DetachBlock(ormDB, currentBlock)
-}
-
-func (c *ChainKeeper) AttachBlock(ormDB *gorm.DB, block *types.Block) error {
-       for _, tx := range block.Transactions {
-               for _, input := range tx.Inputs {
-                       if _, ok := input.TypedInput.(*types.VetoInput); !ok {
-                               continue
-                       }
-
-                       outputID, err := input.SpentOutputID()
-                       if err != nil {
-                               return err
-                       }
-                       utxo := &orm.Utxo{
-                               OutputID: outputID.String(),
-                       }
-                       // update data
-                       db := ormDB.Model(&orm.Utxo{}).Where(utxo).Update("veto_height", block.Height)
-                       if err := db.Error; err != nil {
-                               return err
-                       }
-
-                       if db.RowsAffected != 1 {
-                               return ErrInconsistentDB
-                       }
-
-               }
-
-               for index, output := range tx.Outputs {
-                       voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
-                       if !ok {
-                               continue
-                       }
-                       pubkey := hex.EncodeToString(voteOutput.Vote)
-                       outputID := tx.OutputID(index)
-                       utxo := &orm.Utxo{
-                               Xpub:         pubkey,
-                               VoterAddress: common.GetAddressFromControlProgram(voteOutput.ControlProgram),
-                               VoteHeight:   block.Height,
-                               VoteNum:      voteOutput.Amount,
-                               VetoHeight:   0,
-                               OutputID:     outputID.String(),
-                       }
-                       // insert data
-                       if err := ormDB.Save(utxo).Error; err != nil {
-                               return err
-                       }
-               }
-       }
-
-       blockHash := block.Hash()
-       blockState := &orm.BlockState{
-               Height:    block.Height,
-               BlockHash: blockHash.String(),
-       }
-
-       return c.updateBlockState(ormDB, blockState)
-}
-
-func (c *ChainKeeper) DetachBlock(ormDB *gorm.DB, block *types.Block) error {
-       utxo := &orm.Utxo{
-               VoteHeight: block.Height,
-       }
-       // insert data
-       if err := ormDB.Where(utxo).Delete(&orm.Utxo{}).Error; err != nil {
-               return err
-       }
-
-       utxo = &orm.Utxo{
-               VetoHeight: block.Height,
-       }
-
-       // update data
-       if err := ormDB.Where(utxo).Update("veto_height", 0).Error; err != nil {
-               return err
-       }
-
-       blockState := &orm.BlockState{
-               Height:    block.Height - 1,
-               BlockHash: block.PreviousBlockHash.String(),
-       }
-
-       return c.updateBlockState(ormDB, blockState)
-}
-
-func (c *ChainKeeper) initBlockState(db *gorm.DB, block *types.Block) error {
-       blockHash := block.Hash()
-       blockState := &orm.BlockState{
-               Height:    block.Height,
-               BlockHash: blockHash.String(),
-       }
-
-       return db.Save(blockState).Error
-}
-
-func (c *ChainKeeper) updateBlockState(db *gorm.DB, blockState *orm.BlockState) error {
-       // update blockState
-       u := db.Model(&orm.BlockState{}).Updates(blockState)
-       if err := u.Error; err != nil {
-               return err
-       }
-
-       if u.RowsAffected != 1 {
-               return ErrInconsistentDB
-       }
-       return nil
-}
diff --git a/toolbar/reward/synchron/errors.go b/toolbar/reward/synchron/errors.go
deleted file mode 100644 (file)
index 5e58fde..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package synchron
-
-import (
-       "github.com/vapor/errors"
-)
-
-var (
-       ErrInconsistentDB = errors.New("inconsistent db status")
-       ErrOutputType     = errors.New("error output type")
-)
similarity index 70%
rename from toolbar/reward/config/config.go
rename to toolbar/vote_reward/config/config.go
index d94daad..3b18a38 100644 (file)
@@ -6,20 +6,12 @@ import (
        "io/ioutil"
        "os"
 
-       "github.com/vapor/crypto/ed25519/chainkd"
        "github.com/vapor/toolbar/common"
 )
 
 type Config struct {
        MySQLConfig common.MySQLConfig `json:"mysql"`
-       Chain       Chain              `json:"chain"`
-       XPubs       []chainkd.XPub     `json:"xpubs"`
-}
-
-type Chain struct {
-       Name        string `json:"name"`
-       Upstream    string `json:"upstream"`
-       SyncSeconds uint64 `json:"sync_seconds"`
+       NodeIP      string             `json:"node_ip"`
 }
 
 func ExportConfigFile(fedFile string, config *Config) error {
diff --git a/toolbar/vote_reward/database/dump_reward.sql b/toolbar/vote_reward/database/dump_reward.sql
new file mode 100644 (file)
index 0000000..703bdf1
--- /dev/null
@@ -0,0 +1,63 @@
+# ************************************************************
+# Sequel Pro SQL dump
+# Version 4541
+#
+# http://www.sequelpro.com/
+# https://github.com/sequelpro/sequelpro
+#
+# Host: 127.0.0.1 (MySQL 5.7.24)
+# Database: vote_reward
+# Generation Time: 2019-07-22 13:41:50 +0000
+# ************************************************************
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+
+# Dump of table chain_status
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `chain_status`;
+
+CREATE TABLE `chain_status` (
+  `block_height` int(11) NOT NULL,
+  `block_hash` varchar(64) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+
+# Dump of table utxos
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `utxos`;
+
+CREATE TABLE `utxos` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `output_id` varchar(64) NOT NULL,
+  `xpub` varchar(128) NOT NULL,
+  `vote_address` varchar(62) NOT NULL,
+  `vote_num` bigint(21) NOT NULL,
+  `vote_height` int(11) NOT NULL,
+  `veto_height` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `output_id` (`output_id`),
+  KEY `xpub` (`xpub`),
+  KEY `vote_height` (`vote_height`),
+  KEY `veto_height` (`veto_height`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+
+
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/toolbar/vote_reward/database/orm/block_state.go b/toolbar/vote_reward/database/orm/block_state.go
new file mode 100644 (file)
index 0000000..75d1369
--- /dev/null
@@ -0,0 +1,6 @@
+package orm
+
+type ChainStatus struct {
+       BlockHeight uint64
+       BlockHash   string
+}
diff --git a/toolbar/vote_reward/database/orm/utxo.go b/toolbar/vote_reward/database/orm/utxo.go
new file mode 100644 (file)
index 0000000..d47ea55
--- /dev/null
@@ -0,0 +1,11 @@
+package orm
+
+type Utxo struct {
+       ID          uint64 `gorm:"primary_key"`
+       OutputID    string
+       Xpub        string
+       VoteAddress string
+       VoteNum     uint64
+       VoteHeight  uint64
+       VetoHeight  uint64
+}
diff --git a/toolbar/vote_reward/synchron/block_keeper.go b/toolbar/vote_reward/synchron/block_keeper.go
new file mode 100644 (file)
index 0000000..ec2cbc9
--- /dev/null
@@ -0,0 +1,171 @@
+package synchron
+
+import (
+       "encoding/hex"
+
+       "github.com/jinzhu/gorm"
+       log "github.com/sirupsen/logrus"
+
+       "github.com/vapor/errors"
+       "github.com/vapor/protocol/bc/types"
+       "github.com/vapor/toolbar/api_node"
+       "github.com/vapor/toolbar/common"
+       "github.com/vapor/toolbar/vote_reward/config"
+       "github.com/vapor/toolbar/vote_reward/database/orm"
+)
+
+var ErrInconsistentDB = errors.New("inconsistent db status")
+
+type ChainKeeper struct {
+       db           *gorm.DB
+       node         *api_node.Node
+       targetHeight uint64
+}
+
+func NewChainKeeper(db *gorm.DB, cfg *config.Config, targetHeight uint64) (*ChainKeeper, error) {
+       keeper := &ChainKeeper{
+               db:           db,
+               node:         api_node.NewNode(cfg.NodeIP),
+               targetHeight: targetHeight,
+       }
+
+       chainStatus := &orm.ChainStatus{}
+       if err := db.First(chainStatus).Error; err == nil {
+               return keeper, nil
+       } else if err != gorm.ErrRecordNotFound {
+               return nil, errors.Wrap(err, "fail on get chainStatus")
+       }
+
+       if err := keeper.initBlockState(); err != nil {
+               return nil, errors.Wrap(err, "fail on init chainStatus")
+       }
+       return keeper, nil
+}
+
+func (c *ChainKeeper) SyncBlock() error {
+       for {
+               chainStatus := &orm.ChainStatus{}
+               if err := c.db.First(chainStatus).Error; err != nil {
+                       return errors.Wrap(err, "fail on syncBlock query chainStatus")
+               }
+
+               if chainStatus.BlockHeight >= c.targetHeight {
+                       break
+               }
+
+               dbTX := c.db.Begin()
+               if err := c.syncChainStatus(dbTX, chainStatus); err != nil {
+                       dbTX.Rollback()
+                       return err
+               }
+
+               if err := dbTX.Commit().Error; err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func (c *ChainKeeper) syncChainStatus(db *gorm.DB, chainStatus *orm.ChainStatus) error {
+       nextBlock, err := c.node.GetBlockByHeight(chainStatus.BlockHeight + 1)
+       if err != nil {
+               return err
+       }
+
+       // Normal case, the previous hash of next block equals to the hash of current block,
+       // just sync to database directly.
+       if nextBlock.PreviousBlockHash.String() == chainStatus.BlockHash {
+               return c.AttachBlock(db, chainStatus, nextBlock)
+       }
+
+       log.WithField("block height", chainStatus.BlockHeight).Debug("the prev hash of remote is not equals the hash of current best block, must rollback")
+       currentBlock, err := c.node.GetBlockByHash(chainStatus.BlockHash)
+       if err != nil {
+               return err
+       }
+
+       return c.DetachBlock(db, chainStatus, currentBlock)
+}
+
+func (c *ChainKeeper) AttachBlock(db *gorm.DB, chainStatus *orm.ChainStatus, block *types.Block) error {
+       for _, tx := range block.Transactions {
+               for _, input := range tx.Inputs {
+                       if input.TypedInput.InputType() != types.VetoInputType {
+                               continue
+                       }
+
+                       outputID, err := input.SpentOutputID()
+                       if err != nil {
+                               return err
+                       }
+
+                       result := db.Model(&orm.Utxo{}).Where(&orm.Utxo{OutputID: outputID.String()}).Update("veto_height", block.Height)
+                       if err := result.Error; err != nil {
+                               return err
+                       } else if result.RowsAffected != 1 {
+                               return ErrInconsistentDB
+                       }
+               }
+
+               for i, output := range tx.Outputs {
+                       voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
+                       if !ok {
+                               continue
+                       }
+
+                       utxo := &orm.Utxo{
+                               Xpub:        hex.EncodeToString(voteOutput.Vote),
+                               VoteAddress: common.GetAddressFromControlProgram(voteOutput.ControlProgram),
+                               VoteHeight:  block.Height,
+                               VoteNum:     voteOutput.Amount,
+                               OutputID:    tx.OutputID(i).String(),
+                       }
+
+                       if err := db.Save(utxo).Error; err != nil {
+                               return err
+                       }
+               }
+       }
+
+       return c.updateChainStatus(db, chainStatus, block)
+}
+
+func (c *ChainKeeper) DetachBlock(db *gorm.DB, chainStatus *orm.ChainStatus, block *types.Block) error {
+       if err := db.Where(&orm.Utxo{VoteHeight: block.Height}).Delete(&orm.Utxo{}).Error; err != nil {
+               return err
+       }
+
+       if err := db.Where(&orm.Utxo{VetoHeight: block.Height}).Update("veto_height", 0).Error; err != nil {
+               return err
+       }
+
+       return c.updateChainStatus(db, chainStatus, block)
+}
+
+func (c *ChainKeeper) initBlockState() error {
+       block, err := c.node.GetBlockByHeight(0)
+       if err != nil {
+               return errors.Wrap(err, "fail on get genenis block")
+       }
+
+       blockHash := block.Hash()
+       chainStatus := &orm.ChainStatus{
+               BlockHeight: block.Height,
+               BlockHash:   blockHash.String(),
+       }
+       return c.db.Save(chainStatus).Error
+}
+
+func (c *ChainKeeper) updateChainStatus(db *gorm.DB, chainStatus *orm.ChainStatus, block *types.Block) error {
+       blockHash := block.Hash()
+       result := db.Model(&orm.ChainStatus{}).Where(chainStatus).Updates(&orm.ChainStatus{
+               BlockHeight: block.Height,
+               BlockHash:   blockHash.String(),
+       })
+       if err := result.Error; err != nil {
+               return err
+       } else if result.RowsAffected != 1 {
+               return ErrInconsistentDB
+       }
+       return nil
+}