OSDN Git Service

fix review
[bytom/vapor.git] / toolbar / vote_reward / settlementvotereward / settlementreward.go
1 package settlementvotereward
2
3 import (
4         "bytes"
5         "math/big"
6
7         "github.com/jinzhu/gorm"
8
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"
15 )
16
17 var (
18         errFoundReward   = errors.New("No reward found")
19         errNoStandbyNode = errors.New("No Standby Node")
20         errNoRewardTx    = errors.New("No reward transaction")
21 )
22
23 const standbyNodesRewardForConsensusCycle = 7610350076 // 400000000000000 / (365 * 24 * 60 / (500 * 1200 / 1000 / 60))
24
25 type voteResult struct {
26         VoteAddress string
27         VoteNum     uint64
28 }
29
30 type SettlementReward struct {
31         rewardCfg   *config.RewardConfig
32         node        *apinode.Node
33         db          *gorm.DB
34         rewards     map[string]uint64
35         startHeight uint64
36         endHeight   uint64
37 }
38
39 func NewSettlementReward(db *gorm.DB, cfg *config.Config, startHeight, endHeight uint64) *SettlementReward {
40         return &SettlementReward{
41                 db:          db,
42                 rewardCfg:   cfg.RewardConf,
43                 node:        apinode.NewNode(cfg.NodeIP),
44                 rewards:     make(map[string]uint64),
45                 startHeight: startHeight,
46                 endHeight:   endHeight,
47         }
48 }
49
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 {
55                 return nil, err
56         }
57
58         return voteResults, nil
59 }
60
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 == errFoundReward {
65                         totalReward, err = s.getStandbyNodeReward(height - consensus.ActiveNetParams.RoundVoteBlockNums)
66                 }
67
68                 if err == errNoStandbyNode {
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 errNoRewardTx
86         }
87
88         // send transactions
89         return s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards)
90 }
91
92 func (s *SettlementReward) getStandbyNodeReward(height uint64) (uint64, error) {
93         voteInfos, err := s.node.GetVoteByHeight(height)
94         if err != nil {
95                 return 0, errors.Wrapf(err, "get alternative node reward")
96         }
97
98         voteInfos = common.CalcStandByNodes(voteInfos)
99
100         err = errNoStandbyNode
101         totalVoteNum := uint64(0)
102         xpubVoteNum := uint64(0)
103         for _, voteInfo := range voteInfos {
104                 totalVoteNum += voteInfo.VoteNum
105                 if s.rewardCfg.XPub == voteInfo.Vote {
106                         xpubVoteNum = voteInfo.VoteNum
107                         err = nil
108                 }
109         }
110
111         if err != nil {
112                 return 0, err
113         }
114
115         amount := big.NewInt(0).SetUint64(standbyNodesRewardForConsensusCycle)
116         rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
117         amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
118
119         total := big.NewInt(0).SetUint64(totalVoteNum)
120         voteNum := big.NewInt(0).SetUint64(xpubVoteNum)
121
122         return amount.Mul(amount, voteNum).Div(amount, total).Uint64(), nil
123 }
124
125 func (s *SettlementReward) getCoinbaseReward(height uint64) (uint64, error) {
126         block, err := s.node.GetBlockByHeight(height)
127         if err != nil {
128                 return 0, err
129         }
130
131         miningControl, err := common.GetControlProgramFromAddress(s.rewardCfg.MiningAddress)
132         if err != nil {
133                 return 0, err
134         }
135
136         for _, output := range block.Transactions[0].Outputs {
137                 output, ok := output.TypedOutput.(*types.IntraChainOutput)
138                 if !ok {
139                         return 0, errors.New("Output type error")
140                 }
141
142                 if output.Amount == 0 {
143                         continue
144                 }
145
146                 if bytes.Equal(miningControl, output.ControlProgram) {
147                         amount := big.NewInt(0).SetUint64(output.Amount)
148                         rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
149                         amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
150
151                         return amount.Uint64(), nil
152                 }
153         }
154         return 0, errFoundReward
155 }
156
157 func (s *SettlementReward) calcVoterRewards(voteResults []*voteResult, totalReward uint64) {
158         totalVoteNum := uint64(0)
159         for _, voteResult := range voteResults {
160                 totalVoteNum += voteResult.VoteNum
161         }
162
163         for _, voteResult := range voteResults {
164                 // voteNum / totalVoteNum  * totalReward
165                 voteNum := big.NewInt(0).SetUint64(voteResult.VoteNum)
166                 total := big.NewInt(0).SetUint64(totalVoteNum)
167                 reward := big.NewInt(0).SetUint64(totalReward)
168
169                 amount := voteNum.Mul(voteNum, reward).Div(voteNum, total).Uint64()
170
171                 if amount != 0 {
172                         s.rewards[voteResult.VoteAddress] += amount
173                 }
174         }
175 }