1 package settlementvotereward
8 "github.com/jinzhu/gorm"
10 "github.com/bytom/vapor/consensus"
11 "github.com/bytom/vapor/errors"
12 "github.com/bytom/vapor/protocol/bc/types"
13 "github.com/bytom/vapor/toolbar/apinode"
14 "github.com/bytom/vapor/toolbar/common"
15 "github.com/bytom/vapor/toolbar/vote_reward/config"
19 errNotFoundReward = errors.New("No reward found")
20 errNotStandbyNode = errors.New("No Standby Node")
21 errNotRewardTx = errors.New("No reward transaction")
25 standbyNodesRewardForConsensusCycle = 7610350076 // 400000000000000 / (365 * 24 * 60 / (500 * 1200 / 1000 / 60))
29 type voteResult struct {
34 type SettlementReward struct {
35 rewardCfg *config.RewardConfig
38 rewards map[string]uint64
44 StartHeight uint64 `json:"start_height"`
45 EndHeight uint64 `json:"end_height"`
46 NodePubkey string `json:"node_pubkey"`
47 RewardRatio uint64 `json:"reward_ratio"`
50 func NewSettlementReward(db *gorm.DB, cfg *config.Config, startHeight, endHeight uint64) *SettlementReward {
51 return &SettlementReward{
53 rewardCfg: cfg.RewardConf,
54 node: apinode.NewNode(cfg.NodeIP),
55 rewards: make(map[string]uint64),
56 startHeight: startHeight,
61 func (s *SettlementReward) getVoteResultFromDB(height uint64) (voteResults []*voteResult, err error) {
62 query := s.db.Table("utxos").Select("vote_address, sum(vote_num) as vote_num")
63 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)
64 query = query.Group("vote_address")
65 if err := query.Scan(&voteResults).Error; err != nil {
69 return voteResults, nil
72 func (s *SettlementReward) Settlement() error {
73 for height := s.startHeight + consensus.ActiveNetParams.RoundVoteBlockNums; height <= s.endHeight; height += consensus.ActiveNetParams.RoundVoteBlockNums {
74 totalReward, err := s.getCoinbaseReward(height + 1)
75 if err == errNotFoundReward {
76 totalReward, err = s.getStandbyNodeReward(height - consensus.ActiveNetParams.RoundVoteBlockNums)
79 if err == errNotStandbyNode {
84 return errors.Wrapf(err, "get total reward at height: %d", height)
87 voteResults, err := s.getVoteResultFromDB(height)
92 s.calcVoterRewards(voteResults, totalReward)
95 if len(s.rewards) == 0 {
99 data, err := json.Marshal(&memo{
100 StartHeight: s.startHeight,
101 EndHeight: s.endHeight,
102 NodePubkey: s.rewardCfg.XPub,
103 RewardRatio: s.rewardCfg.RewardRatio,
110 _, err = s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards, data)
114 func (s *SettlementReward) getStandbyNodeReward(height uint64) (uint64, error) {
115 voteInfos, err := s.node.GetVoteByHeight(height)
117 return 0, errors.Wrapf(err, "get alternative node reward")
120 voteInfos = common.CalcStandByNodes(voteInfos)
121 xpubVoteNum := uint64(0)
122 for _, voteInfo := range voteInfos {
123 if s.rewardCfg.XPub == voteInfo.Vote {
124 xpubVoteNum = voteInfo.VoteNum
128 if xpubVoteNum == 0 {
129 return 0, errNotStandbyNode
132 amount := big.NewInt(0).SetUint64(standbyNodesRewardForConsensusCycle / standbyNodeNum)
133 rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
134 amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
135 return amount.Uint64(), nil
138 func (s *SettlementReward) getCoinbaseReward(height uint64) (uint64, error) {
139 block, err := s.node.GetBlockByHeight(height)
144 miningControl, err := common.GetControlProgramFromAddress(s.rewardCfg.MiningAddress)
149 for _, output := range block.Transactions[0].Outputs {
150 output, ok := output.TypedOutput.(*types.IntraChainOutput)
152 return 0, errors.New("Output type error")
155 if output.Amount == 0 {
159 if bytes.Equal(miningControl, output.ControlProgram) {
160 amount := big.NewInt(0).SetUint64(output.Amount)
161 rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
162 amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
164 return amount.Uint64(), nil
167 return 0, errNotFoundReward
170 func (s *SettlementReward) calcVoterRewards(voteResults []*voteResult, totalReward uint64) {
171 totalVoteNum := uint64(0)
172 for _, voteResult := range voteResults {
173 totalVoteNum += voteResult.VoteNum
176 for _, voteResult := range voteResults {
177 // voteNum / totalVoteNum * totalReward
178 voteNum := big.NewInt(0).SetUint64(voteResult.VoteNum)
179 total := big.NewInt(0).SetUint64(totalVoteNum)
180 reward := big.NewInt(0).SetUint64(totalReward)
182 amount := voteNum.Mul(voteNum, reward).Div(voteNum, total).Uint64()
185 s.rewards[voteResult.VoteAddress] += amount