1 package settlementvotereward
7 "github.com/jinzhu/gorm"
9 "github.com/vapor/consensus"
10 "github.com/vapor/errors"
11 "github.com/vapor/protocol/bc/types"
12 "github.com/vapor/toolbar/apinode"
13 "github.com/vapor/toolbar/common"
14 "github.com/vapor/toolbar/vote_reward/config"
18 errNotFoundReward = errors.New("No reward found")
19 errNotStandbyNode = errors.New("No Standby Node")
20 errNotRewardTx = errors.New("No reward transaction")
23 const standbyNodesRewardForConsensusCycle = 7610350076 // 400000000000000 / (365 * 24 * 60 / (500 * 1200 / 1000 / 60))
25 type voteResult struct {
30 type SettlementReward struct {
31 rewardCfg *config.RewardConfig
34 rewards map[string]uint64
39 func NewSettlementReward(db *gorm.DB, cfg *config.Config, startHeight, endHeight uint64) *SettlementReward {
40 return &SettlementReward{
42 rewardCfg: cfg.RewardConf,
43 node: apinode.NewNode(cfg.NodeIP),
44 rewards: make(map[string]uint64),
45 startHeight: startHeight,
50 func (s *SettlementReward) getVoteResultFromDB(height uint64) (voteResults []*voteResult, err error) {
51 query := s.db.Table("utxos").Select("vote_address, sum(vote_num) as vote_num")
52 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)
53 query = query.Group("vote_address")
54 if err := query.Scan(&voteResults).Error; err != nil {
58 return voteResults, nil
61 func (s *SettlementReward) Settlement() error {
62 for height := s.startHeight + consensus.ActiveNetParams.RoundVoteBlockNums; height <= s.endHeight; height += consensus.ActiveNetParams.RoundVoteBlockNums {
63 totalReward, err := s.getCoinbaseReward(height + 1)
64 if err == errNotFoundReward {
65 totalReward, err = s.getStandbyNodeReward(height - consensus.ActiveNetParams.RoundVoteBlockNums)
68 if err == errNotStandbyNode {
73 return errors.Wrapf(err, "get total reward at height: %d", height)
76 voteResults, err := s.getVoteResultFromDB(height)
81 s.calcVoterRewards(voteResults, totalReward)
84 if len(s.rewards) == 0 {
89 _, err := s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards)
93 func (s *SettlementReward) getStandbyNodeReward(height uint64) (uint64, error) {
94 voteInfos, err := s.node.GetVoteByHeight(height)
96 return 0, errors.Wrapf(err, "get alternative node reward")
99 voteInfos = common.CalcStandByNodes(voteInfos)
101 totalVoteNum, xpubVoteNum := uint64(0), uint64(0)
102 for _, voteInfo := range voteInfos {
103 totalVoteNum += voteInfo.VoteNum
104 if s.rewardCfg.XPub == voteInfo.Vote {
105 xpubVoteNum = voteInfo.VoteNum
109 if xpubVoteNum == 0 {
110 return 0, errNotStandbyNode
113 amount := big.NewInt(0).SetUint64(standbyNodesRewardForConsensusCycle)
114 rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
115 amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
116 total := big.NewInt(0).SetUint64(totalVoteNum)
117 voteNum := big.NewInt(0).SetUint64(xpubVoteNum)
118 return amount.Mul(amount, voteNum).Div(amount, total).Uint64(), nil
121 func (s *SettlementReward) getCoinbaseReward(height uint64) (uint64, error) {
122 block, err := s.node.GetBlockByHeight(height)
127 miningControl, err := common.GetControlProgramFromAddress(s.rewardCfg.MiningAddress)
132 for _, output := range block.Transactions[0].Outputs {
133 output, ok := output.TypedOutput.(*types.IntraChainOutput)
135 return 0, errors.New("Output type error")
138 if output.Amount == 0 {
142 if bytes.Equal(miningControl, output.ControlProgram) {
143 amount := big.NewInt(0).SetUint64(output.Amount)
144 rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
145 amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
147 return amount.Uint64(), nil
150 return 0, errNotFoundReward
153 func (s *SettlementReward) calcVoterRewards(voteResults []*voteResult, totalReward uint64) {
154 totalVoteNum := uint64(0)
155 for _, voteResult := range voteResults {
156 totalVoteNum += voteResult.VoteNum
159 for _, voteResult := range voteResults {
160 // voteNum / totalVoteNum * totalReward
161 voteNum := big.NewInt(0).SetUint64(voteResult.VoteNum)
162 total := big.NewInt(0).SetUint64(totalVoteNum)
163 reward := big.NewInt(0).SetUint64(totalReward)
165 amount := voteNum.Mul(voteNum, reward).Div(voteNum, total).Uint64()
168 s.rewards[voteResult.VoteAddress] += amount