OSDN Git Service

Add vote reward memo (#381)
[bytom/vapor.git] / toolbar / vote_reward / settlementvotereward / settlementreward.go
1 package settlementvotereward
2
3 import (
4         "bytes"
5         "encoding/json"
6         "math/big"
7
8         "github.com/jinzhu/gorm"
9
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"
16 )
17
18 var (
19         errNotFoundReward = errors.New("No reward found")
20         errNotStandbyNode = errors.New("No Standby Node")
21         errNotRewardTx    = errors.New("No reward transaction")
22 )
23
24 const standbyNodesRewardForConsensusCycle = 7610350076 // 400000000000000 / (365 * 24 * 60 / (500 * 1200 / 1000 / 60))
25
26 type voteResult struct {
27         VoteAddress string
28         VoteNum     uint64
29 }
30
31 type SettlementReward struct {
32         rewardCfg   *config.RewardConfig
33         node        *apinode.Node
34         db          *gorm.DB
35         rewards     map[string]uint64
36         startHeight uint64
37         endHeight   uint64
38 }
39
40 type memo struct {
41         StartHeight uint64 `json:"start_height"`
42         EndHeight   uint64 `json:"end_height"`
43         NodePubkey  string `json:"node_pubkey"`
44         RewardRatio uint64 `json:"reward_ratio"`
45 }
46
47 func NewSettlementReward(db *gorm.DB, cfg *config.Config, startHeight, endHeight uint64) *SettlementReward {
48         return &SettlementReward{
49                 db:          db,
50                 rewardCfg:   cfg.RewardConf,
51                 node:        apinode.NewNode(cfg.NodeIP),
52                 rewards:     make(map[string]uint64),
53                 startHeight: startHeight,
54                 endHeight:   endHeight,
55         }
56 }
57
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 {
63                 return nil, err
64         }
65
66         return voteResults, nil
67 }
68
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)
74                 }
75
76                 if err == errNotStandbyNode {
77                         continue
78                 }
79
80                 if err != nil {
81                         return errors.Wrapf(err, "get total reward at height: %d", height)
82                 }
83
84                 voteResults, err := s.getVoteResultFromDB(height)
85                 if err != nil {
86                         return err
87                 }
88
89                 s.calcVoterRewards(voteResults, totalReward)
90         }
91
92         if len(s.rewards) == 0 {
93                 return errNotRewardTx
94         }
95
96         data, err := json.Marshal(&memo{
97                 StartHeight: s.startHeight,
98                 EndHeight:   s.endHeight,
99                 NodePubkey:  s.rewardCfg.XPub,
100                 RewardRatio: s.rewardCfg.RewardRatio,
101         })
102         if err != nil {
103                 return err
104         }
105
106         // send transactions
107         _, err = s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards, data)
108         return err
109 }
110
111 func (s *SettlementReward) getStandbyNodeReward(height uint64) (uint64, error) {
112         voteInfos, err := s.node.GetVoteByHeight(height)
113         if err != nil {
114                 return 0, errors.Wrapf(err, "get alternative node reward")
115         }
116
117         voteInfos = common.CalcStandByNodes(voteInfos)
118
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
124                 }
125         }
126
127         if xpubVoteNum == 0 {
128                 return 0, errNotStandbyNode
129         }
130
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
137 }
138
139 func (s *SettlementReward) getCoinbaseReward(height uint64) (uint64, error) {
140         block, err := s.node.GetBlockByHeight(height)
141         if err != nil {
142                 return 0, err
143         }
144
145         miningControl, err := common.GetControlProgramFromAddress(s.rewardCfg.MiningAddress)
146         if err != nil {
147                 return 0, err
148         }
149
150         for _, output := range block.Transactions[0].Outputs {
151                 output, ok := output.TypedOutput.(*types.IntraChainOutput)
152                 if !ok {
153                         return 0, errors.New("Output type error")
154                 }
155
156                 if output.Amount == 0 {
157                         continue
158                 }
159
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))
164
165                         return amount.Uint64(), nil
166                 }
167         }
168         return 0, errNotFoundReward
169 }
170
171 func (s *SettlementReward) calcVoterRewards(voteResults []*voteResult, totalReward uint64) {
172         totalVoteNum := uint64(0)
173         for _, voteResult := range voteResults {
174                 totalVoteNum += voteResult.VoteNum
175         }
176
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)
182
183                 amount := voteNum.Mul(voteNum, reward).Div(voteNum, total).Uint64()
184
185                 if amount != 0 {
186                         s.rewards[voteResult.VoteAddress] += amount
187                 }
188         }
189 }