--- /dev/null
+package main
+
+import (
+ "os"
+ "time"
+
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+ "github.com/tendermint/tmlibs/cli"
+
+ "github.com/vapor/consensus"
+ "github.com/vapor/toolbar/consensusreward"
+ cfg "github.com/vapor/toolbar/consensusreward/config"
+)
+
+const logModule = "reward"
+
+var (
+ rewardStartHeight uint64
+ rewardEndHeight uint64
+ configFile string
+)
+
+var RootCmd = &cobra.Command{
+ Use: "consensusreward",
+ 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 := os.Stat(configFile); os.IsNotExist(err) {
+ if err := cfg.ExportConfigFile(configFile, cfg.Default()); err != nil {
+ log.WithFields(log.Fields{"module": logModule, "config": configFile, "error": err}).Fatal("fail on export federation file")
+ }
+ return nil
+ }
+ 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.")
+ }
+
+ s := consensusreward.NewStandbyNodeReward(config, rewardStartHeight, rewardEndHeight)
+ if err := s.Settlement(); err != nil {
+ log.WithFields(log.Fields{"module": logModule, "error": err}).Fatal("Standby node rewards failure.")
+ }
+
+ log.WithFields(log.Fields{
+ "module": logModule,
+ "duration": time.Since(startTime),
+ }).Info("Standby node reward complete")
+
+ return nil
+}
+
+func main() {
+ cmd := cli.PrepareBaseCmd(RootCmd, "REWARD", "./")
+ cmd.Execute()
+}
--- /dev/null
+package config
+
+import (
+ "bytes"
+ "encoding/json"
+ "io/ioutil"
+ "os"
+)
+
+func ExportConfigFile(configFile string, config *Config) error {
+ buf := new(bytes.Buffer)
+
+ encoder := json.NewEncoder(buf)
+ encoder.SetIndent("", " ")
+ if err := encoder.Encode(config); err != nil {
+ return err
+ }
+
+ return ioutil.WriteFile(configFile, buf.Bytes(), 0644)
+}
+
+func LoadConfigFile(configFile string, config *Config) error {
+ file, err := os.Open(configFile)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ return json.NewDecoder(file).Decode(config)
+}
+
+type Config struct {
+ NodeIP string `json:"node_ip"`
+ ChainID string `json:"chain_id"`
+ RewardConf *RewardConfig `json:"reward_config"`
+}
+
+func Default() *Config {
+ return &Config{
+ RewardConf: &RewardConfig{
+ Node: []NodeConfig{
+ {
+ XPub: "",
+ Address: "",
+ },
+ },
+ },
+ }
+}
+
+type RewardConfig struct {
+ Node []NodeConfig `json:"node"`
+ AccountID string `json:"account_id"`
+ Password string `json:"password"`
+}
+
+type NodeConfig struct {
+ XPub string `json:"xpub"`
+ Address string `json:"address"`
+}
--- /dev/null
+package consensusreward
+
+import (
+ "math/big"
+
+ "github.com/vapor/consensus"
+ "github.com/vapor/errors"
+ apinode "github.com/vapor/toolbar/apinode"
+ "github.com/vapor/toolbar/common"
+ "github.com/vapor/toolbar/consensusreward/config"
+)
+
+var (
+ errNotStandbyNode = errors.New("No Standby Node")
+ errNotRewardTx = errors.New("No reward transaction")
+)
+
+const standbyNodesRewardForConsensusCycle = 7610350076 // 400000000000000 / (365 * 24 * 60 / (500 * 1200 / 1000 / 60))
+
+type StandbyNodeReward struct {
+ cfg *config.Config
+ node *apinode.Node
+ rewards map[string]uint64
+ xpubAddress map[string]string
+ startHeight uint64
+ endHeight uint64
+}
+
+func NewStandbyNodeReward(cfg *config.Config, startHeight, endHeight uint64) *StandbyNodeReward {
+ s := &StandbyNodeReward{
+ cfg: cfg,
+ node: apinode.NewNode(cfg.NodeIP),
+ rewards: make(map[string]uint64),
+ xpubAddress: make(map[string]string),
+ startHeight: startHeight,
+ endHeight: endHeight,
+ }
+ for _, item := range cfg.RewardConf.Node {
+ s.xpubAddress[item.XPub] = item.Address
+ }
+ return s
+}
+
+func (s *StandbyNodeReward) getStandbyNodeReward(height uint64) (map[string]uint64, error) {
+ voteInfos, err := s.node.GetVoteByHeight(height)
+ if err != nil {
+ return nil, errors.Wrapf(err, "get alternative node reward")
+ }
+ voteInfos = common.CalcStandByNodes(voteInfos)
+ if len(voteInfos) == 0 {
+ return nil, errNotStandbyNode
+ }
+ totalVoteNum := uint64(0)
+ for _, voteInfo := range voteInfos {
+ totalVoteNum += voteInfo.VoteNum
+ }
+ total := big.NewInt(0).SetUint64(totalVoteNum)
+ xpubReward := make(map[string]uint64)
+ for _, voteInfo := range voteInfos {
+ amount := big.NewInt(0).SetUint64(standbyNodesRewardForConsensusCycle)
+ voteNum := big.NewInt(0).SetUint64(voteInfo.VoteNum)
+ xpubReward[voteInfo.Vote] = amount.Mul(amount, voteNum).Div(amount, total).Uint64()
+ }
+ return xpubReward, nil
+}
+
+func (s *StandbyNodeReward) Settlement() error {
+ if err := s.calcAllReward(); err != nil {
+ return err
+ }
+ return s.node.BatchSendBTM(s.cfg.RewardConf.AccountID, s.cfg.RewardConf.Password, s.rewards)
+}
+
+func (s *StandbyNodeReward) calcAllReward() error {
+ for height := s.startHeight; height <= s.endHeight; height += consensus.ActiveNetParams.RoundVoteBlockNums {
+ xpubReward, err := s.getStandbyNodeReward(height - consensus.ActiveNetParams.RoundVoteBlockNums)
+ if err == errNotStandbyNode {
+ continue
+ }
+ if err != nil {
+ return err
+ }
+ for xpub, amount := range xpubReward {
+ if address, ok := s.xpubAddress[xpub]; ok {
+ s.rewards[address] += amount
+ }
+ }
+ }
+ if len(s.rewards) == 0 {
+ return errNotRewardTx
+ }
+ return nil
+}