1 package settlementvotereward
8 "github.com/jinzhu/gorm"
10 "github.com/vapor/consensus"
11 "github.com/vapor/errors"
12 "github.com/vapor/protocol/bc/types"
13 "github.com/vapor/toolbar/apinode"
14 "github.com/vapor/toolbar/common"
15 "github.com/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")
24 const standbyNodesRewardForConsensusCycle = 7610350076 // 400000000000000 / (365 * 24 * 60 / (500 * 1200 / 1000 / 60))
26 type voteResult struct {
31 type SettlementReward struct {
32 rewardCfg *config.RewardConfig
35 rewards map[string]uint64
41 StartHeight uint64 `json:"start_height"`
42 EndHeight uint64 `json:"end_height"`
43 NodePubkey string `json:"node_pubkey"`
44 RewardRatio uint64 `json:"reward_ratio"`
47 func NewSettlementReward(db *gorm.DB, cfg *config.Config, startHeight, endHeight uint64) *SettlementReward {
48 return &SettlementReward{
50 rewardCfg: cfg.RewardConf,
51 node: apinode.NewNode(cfg.NodeIP),
52 rewards: make(map[string]uint64),
53 startHeight: startHeight,
58 func (s *SettlementReward) getVoteResultFromDB(height uint64) (voteResults []*voteResult, err error) {
59 query := s.db.Table("utxos").Select("vote_address, sum(vote_num) as vote_num")
60 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)
61 query = query.Group("vote_address")
62 if err := query.Scan(&voteResults).Error; err != nil {
66 return voteResults, nil
69 func (s *SettlementReward) Settlement() error {
70 for height := s.startHeight + consensus.ActiveNetParams.RoundVoteBlockNums; height <= s.endHeight; height += consensus.ActiveNetParams.RoundVoteBlockNums {
71 totalReward, err := s.getCoinbaseReward(height + 1)
72 if err == errNotFoundReward {
73 totalReward, err = s.getStandbyNodeReward(height - consensus.ActiveNetParams.RoundVoteBlockNums)
76 if err == errNotStandbyNode {
81 return errors.Wrapf(err, "get total reward at height: %d", height)
84 voteResults, err := s.getVoteResultFromDB(height)
89 s.calcVoterRewards(voteResults, totalReward)
92 if len(s.rewards) == 0 {
96 data, err := json.Marshal(&memo{
97 StartHeight: s.startHeight,
98 EndHeight: s.endHeight,
99 NodePubkey: s.rewardCfg.XPub,
100 RewardRatio: s.rewardCfg.RewardRatio,
107 _, err = s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards, data)
111 func (s *SettlementReward) getStandbyNodeReward(height uint64) (uint64, error) {
112 voteInfos, err := s.node.GetVoteByHeight(height)
114 return 0, errors.Wrapf(err, "get alternative node reward")
117 voteInfos = common.CalcStandByNodes(voteInfos)
119 totalVoteNum, xpubVoteNum := uint64(0), uint64(0)
120 for _, voteInfo := range voteInfos {
121 totalVoteNum += voteInfo.VoteNum
122 if s.rewardCfg.XPub == voteInfo.Vote {
123 xpubVoteNum = voteInfo.VoteNum
127 if xpubVoteNum == 0 {
128 return 0, errNotStandbyNode
131 amount := big.NewInt(0).SetUint64(standbyNodesRewardForConsensusCycle)
132 rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
133 amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
134 total := big.NewInt(0).SetUint64(totalVoteNum)
135 voteNum := big.NewInt(0).SetUint64(xpubVoteNum)
136 return amount.Mul(amount, voteNum).Div(amount, total).Uint64(), nil
139 func (s *SettlementReward) getCoinbaseReward(height uint64) (uint64, error) {
140 block, err := s.node.GetBlockByHeight(height)
145 miningControl, err := common.GetControlProgramFromAddress(s.rewardCfg.MiningAddress)
150 for _, output := range block.Transactions[0].Outputs {
151 output, ok := output.TypedOutput.(*types.IntraChainOutput)
153 return 0, errors.New("Output type error")
156 if output.Amount == 0 {
160 if bytes.Equal(miningControl, output.ControlProgram) {
161 amount := big.NewInt(0).SetUint64(output.Amount)
162 rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
163 amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
165 return amount.Uint64(), nil
168 return 0, errNotFoundReward
171 func (s *SettlementReward) calcVoterRewards(voteResults []*voteResult, totalReward uint64) {
172 totalVoteNum := uint64(0)
173 for _, voteResult := range voteResults {
174 totalVoteNum += voteResult.VoteNum
177 for _, voteResult := range voteResults {
178 // voteNum / totalVoteNum * totalReward
179 voteNum := big.NewInt(0).SetUint64(voteResult.VoteNum)
180 total := big.NewInt(0).SetUint64(totalVoteNum)
181 reward := big.NewInt(0).SetUint64(totalReward)
183 amount := voteNum.Mul(voteNum, reward).Div(voteNum, total).Uint64()
186 s.rewards[voteResult.VoteAddress] += amount