From: wz Date: Wed, 24 Jul 2019 07:08:19 +0000 (+0800) Subject: Voter reward (#344) X-Git-Tag: v1.0.5~81 X-Git-Url: http://git.osdn.net/view?p=bytom%2Fvapor.git;a=commitdiff_plain;h=1362b9a101258e2f2a59f79877231ac6b256f932 Voter reward (#344) * 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 * fix * fix * fix voter rewward * modify * fix review * fix review * remove code file * fix * add readme * fix * rm file * fix review * rm chain_id * modify readme --- diff --git a/cmd/reward/main.go b/cmd/reward/main.go deleted file mode 100644 index 79058077..00000000 --- a/cmd/reward/main.go +++ /dev/null @@ -1,5 +0,0 @@ -package main - -func main() { - -} diff --git a/cmd/votereward/README.md b/cmd/votereward/README.md new file mode 100644 index 00000000..ee6c5d36 --- /dev/null +++ b/cmd/votereward/README.md @@ -0,0 +1,51 @@ +A `reward.json` would look like this: + +```json +{ + "node_ip": "http://127.0.0.1:9889", + "chain_id": "solonet", + "mysql": { + "connection": { + "host": "192.168.30.186", + "port": 3306, + "username": "root", + "password": "123456", + "database": "reward" + }, + "log_mode": false + }, + "reward_config": { + "xpub": "9742a39a0bcfb5b7ac8f56f1894fbb694b53ebf58f9a032c36cc22d57a06e49e94ff7199063fb7a78190624fa3530f611404b56fc9af91dcaf4639614512cb64", + "account_id": "bd775113-49e0-4678-94bf-2b853f1afe80", + "password": "123456", + "reward_ratio": 20, + "mining_address": "sp1qfpgjve27gx0r9t7vud8vypplkzytgrvqr74rwz" + } +} +``` + + + +tool use + +params + +```shell +distribution of reward. + +Usage: + reward [flags] + +Flags: + --config_file string config file. default: reward.json (default "reward.json") + -h, --help help for reward + --reward_end_height uint The end height of the distributive income reward interval, It is a multiple of the dpos consensus cycle(1200). example: 2400 + --reward_start_height uint The starting height of the distributive income reward interval, It is a multiple of the dpos consensus cycle(1200). example: 1200 +``` + +example: + +```shell +./votereward reward --reward_start_height 6000 --reward_end_height 7200 +``` + diff --git a/cmd/votereward/main.go b/cmd/votereward/main.go new file mode 100644 index 00000000..fd42aa11 --- /dev/null +++ b/cmd/votereward/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "time" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/tendermint/tmlibs/cli" + + "github.com/vapor/consensus" + "github.com/vapor/toolbar/common" + cfg "github.com/vapor/toolbar/vote_reward/config" + "github.com/vapor/toolbar/vote_reward/settlementvotereward" + "github.com/vapor/toolbar/vote_reward/synchron" +) + +const logModule = "reward" + +var ( + rewardStartHeight uint64 + rewardEndHeight uint64 + configFile string +) + +var RootCmd = &cobra.Command{ + Use: "reward", + Short: "distribution of reward.", + RunE: runReward, +} + +func init() { + RootCmd.Flags().Uint64Var(&rewardStartHeight, "reward_start_height", 0, "The starting height of the distributive income reward interval, It is a multiple of the dpos consensus cycle(1200). example: 1200") + RootCmd.Flags().Uint64Var(&rewardEndHeight, "reward_end_height", 0, "The end height of the distributive income reward interval, It is a multiple of the dpos consensus cycle(1200). example: 2400") + RootCmd.Flags().StringVar(&configFile, "config_file", "reward.json", "config file. default: reward.json") +} + +func runReward(cmd *cobra.Command, args []string) error { + startTime := time.Now() + config := &cfg.Config{} + if err := cfg.LoadConfigFile(configFile, config); err != nil { + log.WithFields(log.Fields{"module": logModule, "config": configFile, "error": err}).Fatal("Failded to load config file.") + } + + if err := consensus.InitActiveNetParams(config.ChainID); err != nil { + log.WithFields(log.Fields{"module": logModule, "error": err}).Fatal("Init ActiveNetParams.") + } + if rewardStartHeight >= rewardEndHeight || rewardStartHeight%consensus.ActiveNetParams.RoundVoteBlockNums != 0 || rewardEndHeight%consensus.ActiveNetParams.RoundVoteBlockNums != 0 { + log.Fatal("Please check the height range, which must be multiple of the number of block rounds.") + } + + db, err := common.NewMySQLDB(config.MySQLConfig) + if err != nil { + log.WithFields(log.Fields{"module": logModule, "error": err}).Fatal("Failded to initialize mysql db.") + } + + sync, err := synchron.NewChainKeeper(db, config, rewardEndHeight) + if err != nil { + log.WithFields(log.Fields{"module": logModule, "error": err}).Fatal("Failded to initialize NewChainKeeper.") + } + + if err := sync.SyncBlock(); err != nil { + log.WithFields(log.Fields{"module": logModule, "error": err}).Fatal("Failded to sync block.") + } + + s := settlementvotereward.NewSettlementReward(db, config, rewardStartHeight, rewardEndHeight) + + if err := s.Settlement(); err != nil { + log.WithFields(log.Fields{"module": logModule, "error": err}).Fatal("Settlement vote rewards failure.") + } + + log.WithFields(log.Fields{ + "module": logModule, + "duration": time.Since(startTime), + }).Info("Settlement vote reward complete") + + return nil +} + +func main() { + cmd := cli.PrepareBaseCmd(RootCmd, "REWARD", "./") + cmd.Execute() +} diff --git a/consensus/general.go b/consensus/general.go index 32620ac1..f3bab4e0 100644 --- a/consensus/general.go +++ b/consensus/general.go @@ -2,6 +2,7 @@ package consensus import ( "encoding/binary" + "fmt" "github.com/vapor/protocol/bc" ) @@ -225,3 +226,11 @@ func BytomMainNetParams(vaporParam *Params) *Params { } return &Params{Bech32HRPSegwit: bech32HRPSegwit} } + +func InitActiveNetParams(chainID string) error { + var exist bool + if ActiveNetParams, exist = NetParams[chainID]; !exist { + return fmt.Errorf("chain_id[%v] don't exist", chainID) + } + return nil +} diff --git a/node/node.go b/node/node.go index 69948239..531ff832 100644 --- a/node/node.go +++ b/node/node.go @@ -75,7 +75,9 @@ func NewNode(config *cfg.Config) *Node { }).Info() initLogFile(config) - initActiveNetParams(config) + if err := consensus.InitActiveNetParams(config.ChainID); err != nil { + log.Fatalf("Failed to init ActiveNetParams:[%s]", err.Error()) + } initCommonConfig(config) // Get store @@ -125,7 +127,7 @@ func NewNode(config *cfg.Config) *Node { } } fastSyncDB := dbm.NewDB("fastsync", config.DBBackend, config.DBDir()) - syncManager, err := netsync.NewSyncManager(config, chain, txPool, dispatcher,fastSyncDB) + syncManager, err := netsync.NewSyncManager(config, chain, txPool, dispatcher, fastSyncDB) if err != nil { cmn.Exit(cmn.Fmt("Failed to create sync manager: %v", err)) } @@ -186,14 +188,6 @@ func lockDataDirectory(config *cfg.Config) error { return nil } -func initActiveNetParams(config *cfg.Config) { - var exist bool - consensus.ActiveNetParams, exist = consensus.NetParams[config.ChainID] - if !exist { - cmn.Exit(cmn.Fmt("chain_id[%v] don't exist", config.ChainID)) - } -} - func initLogFile(config *cfg.Config) { if config.LogFile == "" { return diff --git a/toolbar/api_node/block.go b/toolbar/apinode/block.go similarity index 100% rename from toolbar/api_node/block.go rename to toolbar/apinode/block.go diff --git a/toolbar/api_node/node.go b/toolbar/apinode/node.go similarity index 100% rename from toolbar/api_node/node.go rename to toolbar/apinode/node.go diff --git a/toolbar/api_node/node_test.go b/toolbar/apinode/node_test.go similarity index 100% rename from toolbar/api_node/node_test.go rename to toolbar/apinode/node_test.go diff --git a/toolbar/api_node/transaction.go b/toolbar/apinode/transaction.go similarity index 100% rename from toolbar/api_node/transaction.go rename to toolbar/apinode/transaction.go diff --git a/toolbar/common/address.go b/toolbar/common/address.go index 53b6c9b5..d6f7193a 100644 --- a/toolbar/common/address.go +++ b/toolbar/common/address.go @@ -1,9 +1,12 @@ package common import ( + "errors" + "github.com/vapor/common" "github.com/vapor/consensus" "github.com/vapor/consensus/segwit" + "github.com/vapor/protocol/vm/vmutil" ) func GetAddressFromControlProgram(prog []byte) string { @@ -37,3 +40,25 @@ func buildP2SHAddress(scriptHash []byte) string { return address.EncodeAddress() } + +func GetControlProgramFromAddress(address string) ([]byte, error) { + decodeaddress, err := common.DecodeAddress(address, &consensus.ActiveNetParams) + if err != nil { + return nil, err + } + + redeemContract := decodeaddress.ScriptAddress() + program := []byte{} + switch decodeaddress.(type) { + case *common.AddressWitnessPubKeyHash: + program, err = vmutil.P2WPKHProgram(redeemContract) + case *common.AddressWitnessScriptHash: + program, err = vmutil.P2WSHProgram(redeemContract) + default: + return nil, errors.New("Invalid address") + } + if err != nil { + return nil, err + } + return program, nil +} diff --git a/toolbar/federation/service/node.go b/toolbar/federation/service/node.go index 2775e469..9b5a8568 100644 --- a/toolbar/federation/service/node.go +++ b/toolbar/federation/service/node.go @@ -13,7 +13,7 @@ type Node struct { hostPort string } -// Node create a api client with target server +// NewNode create a api client with target server func NewNode(hostPort string) *Node { return &Node{hostPort: hostPort} } diff --git a/toolbar/vote_reward/config/config.go b/toolbar/vote_reward/config/config.go index b73bb95f..d15b3c6b 100644 --- a/toolbar/vote_reward/config/config.go +++ b/toolbar/vote_reward/config/config.go @@ -5,13 +5,28 @@ import ( "encoding/json" "io/ioutil" "os" + "path" "github.com/vapor/toolbar/common" ) type Config struct { - MySQLConfig common.MySQLConfig `json:"mysql"` NodeIP string `json:"node_ip"` + ChainID string `json:"chain_id"` + MySQLConfig common.MySQLConfig `json:"mysql"` + RewardConf *RewardConfig `json:"reward_config"` +} + +func ConfigFile() string { + return path.Join("./", "reward.json") +} + +type RewardConfig struct { + XPub string `json:"xpub"` + AccountID string `json:"account_id"` + Password string `json:"password"` + MiningAddress string `json:"mining_address"` + RewardRatio uint64 `json:"reward_ratio"` } func ExportConfigFile(configFile string, config *Config) error { diff --git a/toolbar/vote_reward/settlementvotereward/settlementreward.go b/toolbar/vote_reward/settlementvotereward/settlementreward.go new file mode 100644 index 00000000..c367daeb --- /dev/null +++ b/toolbar/vote_reward/settlementvotereward/settlementreward.go @@ -0,0 +1,123 @@ +package settlementvotereward + +import ( + "bytes" + "math/big" + + "github.com/jinzhu/gorm" + + "github.com/vapor/consensus" + "github.com/vapor/errors" + "github.com/vapor/protocol/bc/types" + "github.com/vapor/toolbar/apinode" + "github.com/vapor/toolbar/common" + "github.com/vapor/toolbar/vote_reward/config" +) + +type voteResult struct { + VoteAddress string + VoteNum uint64 +} + +type SettlementReward struct { + rewardCfg *config.RewardConfig + node *apinode.Node + db *gorm.DB + rewards map[string]uint64 + startHeight uint64 + endHeight uint64 +} + +func NewSettlementReward(db *gorm.DB, cfg *config.Config, startHeight, endHeight uint64) *SettlementReward { + return &SettlementReward{ + db: db, + rewardCfg: cfg.RewardConf, + node: apinode.NewNode(cfg.NodeIP), + rewards: make(map[string]uint64), + startHeight: startHeight, + endHeight: endHeight, + } +} + +func (s *SettlementReward) getVoteResultFromDB(height uint64) (voteResults []*voteResult, err error) { + query := s.db.Table("utxos").Select("vote_address, sum(vote_num) as vote_num") + query = query.Where("(veto_height >= ? or veto_height = 0) and vote_height <= ? and xpub = ?", height-consensus.ActiveNetParams.RoundVoteBlockNums+1, height-consensus.ActiveNetParams.RoundVoteBlockNums, s.rewardCfg.XPub) + query = query.Group("vote_address") + if err := query.Scan(&voteResults).Error; err != nil { + return nil, err + } + + return voteResults, nil +} + +func (s *SettlementReward) Settlement() error { + for height := s.startHeight + consensus.ActiveNetParams.RoundVoteBlockNums; height <= s.endHeight; height += consensus.ActiveNetParams.RoundVoteBlockNums { + coinbaseHeight := height + 1 + totalReward, err := s.getCoinbaseReward(coinbaseHeight) + if err != nil { + return errors.Wrapf(err, "get total reward at coinbase_height: %d", coinbaseHeight) + } + + voteResults, err := s.getVoteResultFromDB(height) + if err != nil { + return err + } + + s.calcVoterRewards(voteResults, totalReward) + } + + // send transactions + return s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards) +} + +func (s *SettlementReward) getCoinbaseReward(height uint64) (uint64, error) { + block, err := s.node.GetBlockByHeight(height) + if err != nil { + return 0, err + } + + miningControl, err := common.GetControlProgramFromAddress(s.rewardCfg.MiningAddress) + if err != nil { + return 0, err + } + + for _, output := range block.Transactions[0].Outputs { + output, ok := output.TypedOutput.(*types.IntraChainOutput) + if !ok { + return 0, errors.New("Output type error") + } + + if output.Amount == 0 { + continue + } + + if bytes.Equal(miningControl, output.ControlProgram) { + amount := big.NewInt(0).SetUint64(output.Amount) + rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio) + amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100)) + + return amount.Uint64(), nil + } + } + return 0, errors.New("No reward found") +} + +func (s *SettlementReward) calcVoterRewards(voteResults []*voteResult, totalReward uint64) { + totalVoteNum := uint64(0) + for _, voteResult := range voteResults { + totalVoteNum += voteResult.VoteNum + } + + for _, voteResult := range voteResults { + // voteNum / totalVoteNum * totalReward + voteNum := big.NewInt(0).SetUint64(voteResult.VoteNum) + total := big.NewInt(0).SetUint64(totalVoteNum) + reward := big.NewInt(0).SetUint64(totalReward) + + amount := voteNum.Mul(voteNum, reward).Div(voteNum, total).Uint64() + + if amount != 0 { + s.rewards[voteResult.VoteAddress] += amount + } + } +} diff --git a/toolbar/vote_reward/synchron/block_keeper.go b/toolbar/vote_reward/synchron/block_keeper.go index 2b78a663..99034809 100644 --- a/toolbar/vote_reward/synchron/block_keeper.go +++ b/toolbar/vote_reward/synchron/block_keeper.go @@ -8,7 +8,7 @@ import ( "github.com/vapor/errors" "github.com/vapor/protocol/bc/types" - apinode "github.com/vapor/toolbar/api_node" + "github.com/vapor/toolbar/apinode" "github.com/vapor/toolbar/common" "github.com/vapor/toolbar/vote_reward/config" "github.com/vapor/toolbar/vote_reward/database/orm"