github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/toolbar/vote_reward/settlementvotereward/settlementreward.go (about) 1 package settlementvotereward 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "math/big" 7 8 "github.com/jinzhu/gorm" 9 10 "github.com/bytom/bytom/consensus" 11 "github.com/bytom/bytom/errors" 12 "github.com/bytom/bytom/toolbar/apinode" 13 "github.com/bytom/bytom/toolbar/common" 14 "github.com/bytom/bytom/toolbar/vote_reward/config" 15 ) 16 17 var ( 18 errNotFoundReward = errors.New("No reward found") 19 errNotRewardTx = errors.New("No reward transaction") 20 ) 21 22 type voteResult struct { 23 VoteAddress string 24 VoteNum uint64 25 } 26 27 type SettlementReward struct { 28 rewardCfg *config.RewardConfig 29 node *apinode.Node 30 db *gorm.DB 31 rewards map[string]uint64 32 startHeight uint64 33 endHeight uint64 34 } 35 36 type memo struct { 37 StartHeight uint64 `json:"start_height"` 38 EndHeight uint64 `json:"end_height"` 39 NodePubkey string `json:"node_pubkey"` 40 RewardRatio uint64 `json:"reward_ratio"` 41 } 42 43 func NewSettlementReward(db *gorm.DB, cfg *config.Config, startHeight, endHeight uint64) *SettlementReward { 44 return &SettlementReward{ 45 db: db, 46 rewardCfg: cfg.RewardConf, 47 node: apinode.NewNode(cfg.NodeIP), 48 rewards: make(map[string]uint64), 49 startHeight: startHeight, 50 endHeight: endHeight, 51 } 52 } 53 54 func (s *SettlementReward) getVoteResultFromDB(height uint64) (voteResults []*voteResult, err error) { 55 query := s.db.Table("utxos").Select("vote_address, sum(vote_num) as vote_num") 56 query = query.Where("(veto_height >= ? or veto_height = 0) and vote_height <= ? and xpub = ?", height-consensus.ActiveNetParams.BlocksOfEpoch+1, height-consensus.ActiveNetParams.BlocksOfEpoch, s.rewardCfg.XPub) 57 query = query.Group("vote_address") 58 if err := query.Scan(&voteResults).Error; err != nil { 59 return nil, err 60 } 61 62 return voteResults, nil 63 } 64 65 func (s *SettlementReward) Settlement() error { 66 for height := s.startHeight + consensus.ActiveNetParams.BlocksOfEpoch; height <= s.endHeight; height += consensus.ActiveNetParams.BlocksOfEpoch { 67 totalReward, err := s.getCoinbaseReward(height + 1) 68 if err == errNotFoundReward { 69 continue 70 } 71 72 if err != nil { 73 return errors.Wrapf(err, "get total reward at height: %d", height) 74 } 75 76 voteResults, err := s.getVoteResultFromDB(height) 77 if err != nil { 78 return err 79 } 80 81 s.calcVoterRewards(voteResults, totalReward) 82 } 83 84 if len(s.rewards) == 0 { 85 return errNotRewardTx 86 } 87 88 data, err := json.Marshal(&memo{ 89 StartHeight: s.startHeight, 90 EndHeight: s.endHeight, 91 NodePubkey: s.rewardCfg.XPub, 92 RewardRatio: s.rewardCfg.RewardRatio, 93 }) 94 if err != nil { 95 return err 96 } 97 98 // send transactions 99 return s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards, data) 100 } 101 102 func (s *SettlementReward) getCoinbaseReward(height uint64) (uint64, error) { 103 block, err := s.node.GetBlockByHeight(height) 104 if err != nil { 105 return 0, err 106 } 107 108 miningControl, err := common.GetControlProgramFromAddress(s.rewardCfg.MiningAddress) 109 if err != nil { 110 return 0, err 111 } 112 113 for _, output := range block.Transactions[0].Outputs { 114 if output.Amount == 0 { 115 continue 116 } 117 118 if bytes.Equal(miningControl, output.ControlProgram) { 119 amount := big.NewInt(0).SetUint64(output.Amount) 120 rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio) 121 amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100)) 122 123 return amount.Uint64(), nil 124 } 125 } 126 return 0, errNotFoundReward 127 } 128 129 func (s *SettlementReward) calcVoterRewards(voteResults []*voteResult, totalReward uint64) { 130 totalVoteNum := uint64(0) 131 for _, voteResult := range voteResults { 132 totalVoteNum += voteResult.VoteNum 133 } 134 135 for _, voteResult := range voteResults { 136 // voteNum / totalVoteNum * totalReward 137 voteNum := big.NewInt(0).SetUint64(voteResult.VoteNum) 138 total := big.NewInt(0).SetUint64(totalVoteNum) 139 reward := big.NewInt(0).SetUint64(totalReward) 140 141 amount := voteNum.Mul(voteNum, reward).Div(voteNum, total).Uint64() 142 143 if amount != 0 { 144 s.rewards[voteResult.VoteAddress] += amount 145 } 146 } 147 }