+++ /dev/null
-package main
-
-func main() {
-
-}
--- /dev/null
+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
+```
+
--- /dev/null
+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()
+}
import (
"encoding/binary"
+ "fmt"
"github.com/vapor/protocol/bc"
)
}
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
+}
}).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
}
}
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))
}
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
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 {
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
+}
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}
}
"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 {
--- /dev/null
+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
+ }
+ }
+}
"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"