From: wz Date: Tue, 16 Jul 2019 07:55:36 +0000 (+0800) Subject: sync vote info for reward (#292) X-Git-Tag: v1.0.5~130 X-Git-Url: http://git.osdn.net/view?p=bytom%2Fvapor.git;a=commitdiff_plain;h=259fee232ebced1fa0d60b04b6a69a4bf38ca50e sync vote info for reward (#292) * modify CHARSET * add * add sync block * add sync block * fix * fix * fix review * modify logic --- diff --git a/toolbar/common/address.go b/toolbar/common/address.go new file mode 100644 index 00000000..53b6c9b5 --- /dev/null +++ b/toolbar/common/address.go @@ -0,0 +1,39 @@ +package common + +import ( + "github.com/vapor/common" + "github.com/vapor/consensus" + "github.com/vapor/consensus/segwit" +) + +func GetAddressFromControlProgram(prog []byte) string { + if segwit.IsP2WPKHScript(prog) { + if pubHash, err := segwit.GetHashFromStandardProg(prog); err == nil { + return buildP2PKHAddress(pubHash) + } + } else if segwit.IsP2WSHScript(prog) { + if scriptHash, err := segwit.GetHashFromStandardProg(prog); err == nil { + return buildP2SHAddress(scriptHash) + } + } + + return "" +} + +func buildP2PKHAddress(pubHash []byte) string { + address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams) + if err != nil { + return "" + } + + return address.EncodeAddress() +} + +func buildP2SHAddress(scriptHash []byte) string { + address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams) + if err != nil { + return "" + } + + return address.EncodeAddress() +} diff --git a/toolbar/federation/service/node.go b/toolbar/common/service/node.go similarity index 100% rename from toolbar/federation/service/node.go rename to toolbar/common/service/node.go diff --git a/toolbar/federation/synchron/mainchain_keeper.go b/toolbar/federation/synchron/mainchain_keeper.go index 31352a70..af90a7df 100644 --- a/toolbar/federation/synchron/mainchain_keeper.go +++ b/toolbar/federation/synchron/mainchain_keeper.go @@ -16,12 +16,12 @@ import ( "github.com/vapor/consensus" "github.com/vapor/errors" + "github.com/vapor/protocol/bc" + "github.com/vapor/toolbar/common/service" "github.com/vapor/toolbar/federation/common" "github.com/vapor/toolbar/federation/config" "github.com/vapor/toolbar/federation/database" "github.com/vapor/toolbar/federation/database/orm" - "github.com/vapor/toolbar/federation/service" - "github.com/vapor/protocol/bc" ) type mainchainKeeper struct { diff --git a/toolbar/federation/synchron/sidechain_keeper.go b/toolbar/federation/synchron/sidechain_keeper.go index c17caf0d..f60b4af0 100644 --- a/toolbar/federation/synchron/sidechain_keeper.go +++ b/toolbar/federation/synchron/sidechain_keeper.go @@ -11,13 +11,13 @@ import ( "github.com/vapor/consensus" "github.com/vapor/errors" + "github.com/vapor/protocol/bc" + "github.com/vapor/protocol/bc/types" + "github.com/vapor/toolbar/common/service" "github.com/vapor/toolbar/federation/common" "github.com/vapor/toolbar/federation/config" "github.com/vapor/toolbar/federation/database" "github.com/vapor/toolbar/federation/database/orm" - "github.com/vapor/toolbar/federation/service" - "github.com/vapor/protocol/bc" - "github.com/vapor/protocol/bc/types" ) type sidechainKeeper struct { diff --git a/toolbar/reward/config/config.go b/toolbar/reward/config/config.go index d912156b..95ed77ad 100644 --- a/toolbar/reward/config/config.go +++ b/toolbar/reward/config/config.go @@ -1 +1,46 @@ package config + +import ( + "encoding/json" + "os" + + log "github.com/sirupsen/logrus" + + "github.com/vapor/crypto/ed25519/chainkd" + "github.com/vapor/toolbar/common" +) + +func NewConfig() *Config { + if len(os.Args) <= 1 { + log.Fatal("Please setup the config file path") + } + + return NewConfigWithPath(os.Args[1]) +} + +func NewConfigWithPath(path string) *Config { + configFile, err := os.Open(path) + if err != nil { + log.WithFields(log.Fields{"err": err, "file_path": os.Args[1]}).Fatal("fail to open config file") + } + defer configFile.Close() + + cfg := &Config{} + if err := json.NewDecoder(configFile).Decode(cfg); err != nil { + log.WithField("err", err).Fatal("fail to decode config file") + } + + return cfg +} + +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"` +} diff --git a/toolbar/reward/database/dump_reward.sql b/toolbar/reward/database/dump_reward.sql index 807c67d5..59938678 100644 --- a/toolbar/reward/database/dump_reward.sql +++ b/toolbar/reward/database/dump_reward.sql @@ -7,7 +7,7 @@ SET FOREIGN_KEY_CHECKS = 0; DROP TABLE IF EXISTS `block_state`; CREATE TABLE `block_state` ( `height` int(11) NOT NULL, - `block_hash` varchar(64) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL + `block_hash` varchar(64) NOT NULL ) ENGINE = InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- @@ -16,12 +16,12 @@ CREATE TABLE `block_state` ( DROP TABLE IF EXISTS `vote`; CREATE TABLE `vote` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `xpub` varchar(128) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL, - `voter_address` varchar(62) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL, + `xpub` varchar(128) NOT NULL, + `voter_address` varchar(62) NOT NULL, `vote_height` int(11) NOT NULL, `vote_num` int(11) NOT NULL, `veto_height` int(11) NOT NULL, - `output_id` varchar(64) CHARACTER SET latin1 COLLATE latin1_swedish_ci 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; diff --git a/toolbar/reward/reward.go b/toolbar/reward/reward.go new file mode 100644 index 00000000..4625b5bd --- /dev/null +++ b/toolbar/reward/reward.go @@ -0,0 +1,4 @@ +package reward + +type Reward interface { +} diff --git a/toolbar/reward/synchron/block_keeper.go b/toolbar/reward/synchron/block_keeper.go new file mode 100644 index 00000000..6a3bda38 --- /dev/null +++ b/toolbar/reward/synchron/block_keeper.go @@ -0,0 +1,185 @@ +package synchron + +import ( + "encoding/hex" + "time" + + "github.com/jinzhu/gorm" + log "github.com/sirupsen/logrus" + + "github.com/vapor/errors" + "github.com/vapor/protocol/bc" + "github.com/vapor/protocol/bc/types" + "github.com/vapor/toolbar/common" + "github.com/vapor/toolbar/common/service" + "github.com/vapor/toolbar/reward/config" + "github.com/vapor/toolbar/reward/database/orm" +) + +type ChainKeeper struct { + cfg *config.Chain + db *gorm.DB + node *service.Node +} + +func NewChainKeeper(db *gorm.DB, cfg *config.Config) *ChainKeeper { + return &ChainKeeper{ + cfg: &cfg.Chain, + db: db, + node: service.NewNode(cfg.Chain.Upstream), + } +} + +func (c *ChainKeeper) Run() { + ticker := time.NewTicker(time.Duration(c.cfg.SyncSeconds) * time.Second) + for ; true; <-ticker.C { + for { + isUpdate, err := c.syncBlock() + if err != nil { + log.WithField("error", err).Errorln("blockKeeper fail on process block") + break + } + + if !isUpdate { + break + } + } + } +} + +func (c *ChainKeeper) syncBlock() (bool, error) { + blockState := &orm.BlockState{} + if err := c.db.First(blockState).Error; err != nil { + return false, errors.Wrap(err, "query chain") + } + + height, err := c.node.GetBlockCount() + if err != nil { + return false, err + } + + if height == blockState.Height { + return false, nil + } + + nextBlockStr, txStatus, err := c.node.GetBlockByHeight(blockState.Height + 1) + if err != nil { + return false, err + } + + nextBlock := &types.Block{} + if err := nextBlock.UnmarshalText([]byte(nextBlockStr)); err != nil { + return false, errors.New("Unmarshal nextBlock") + } + + // 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 true, c.AttachBlock(nextBlock, txStatus) + } + + log.WithField("block height", blockState.Height).Debug("the prev hash of remote is not equals the hash of current best block, must rollback") + currentBlockStr, txStatus, err := c.node.GetBlockByHash(blockState.BlockHash) + if err != nil { + return false, err + } + + currentBlock := &types.Block{} + if err := nextBlock.UnmarshalText([]byte(currentBlockStr)); err != nil { + return false, errors.New("Unmarshal currentBlock") + } + + return true, c.DetachBlock(currentBlock, txStatus) +} + +func (c *ChainKeeper) AttachBlock(block *types.Block, txStatus *bc.TransactionStatus) error { + ormDB := c.db.Begin() + for pos, tx := range block.Transactions { + statusFail, err := txStatus.GetStatus(pos) + if err != nil { + return err + } + + if statusFail { + log.WithFields(log.Fields{"block height": block.Height, "statusFail": statusFail}).Debug("AttachBlock") + continue + } + + for _, input := range tx.Inputs { + vetoInput, ok := input.TypedInput.(*types.VetoInput) + if !ok { + continue + } + + outputID, err := input.SpentOutputID() + if err != nil { + return err + } + utxo := &orm.Utxo{ + VoterAddress: common.GetAddressFromControlProgram(vetoInput.ControlProgram), + OutputID: outputID.String(), + } + // update data + db := ormDB.Where(utxo).Update("veto_height", block.Height) + if err := db.Error; err != nil { + ormDB.Rollback() + return err + } + + if db.RowsAffected != 1 { + ormDB.Rollback() + 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 { + ormDB.Rollback() + return err + } + } + } + + return ormDB.Commit().Error +} + +func (c *ChainKeeper) DetachBlock(block *types.Block, txStatus *bc.TransactionStatus) error { + ormDB := c.db.Begin() + + utxo := &orm.Utxo{ + VoteHeight: block.Height, + } + // insert data + if err := ormDB.Where(utxo).Delete(&orm.Utxo{}).Error; err != nil { + ormDB.Rollback() + return err + } + + utxo = &orm.Utxo{ + VetoHeight: block.Height, + } + + // update data + if err := ormDB.Where(utxo).Update("veto_height", 0).Error; err != nil { + ormDB.Rollback() + return err + } + + return ormDB.Commit().Error +} diff --git a/toolbar/reward/synchron/errors.go b/toolbar/reward/synchron/errors.go new file mode 100644 index 00000000..5e58fde8 --- /dev/null +++ b/toolbar/reward/synchron/errors.go @@ -0,0 +1,10 @@ +package synchron + +import ( + "github.com/vapor/errors" +) + +var ( + ErrInconsistentDB = errors.New("inconsistent db status") + ErrOutputType = errors.New("error output type") +)