OSDN Git Service

versoin1.1.9 (#594)
[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/bytom/vapor/consensus"
11         "github.com/bytom/vapor/errors"
12         "github.com/bytom/vapor/protocol/bc/types"
13         "github.com/bytom/vapor/toolbar/apinode"
14         "github.com/bytom/vapor/toolbar/common"
15         "github.com/bytom/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 (
25         standbyNodesRewardForConsensusCycle = 7610350076 // 400000000000000 / (365 * 24 * 60 / (500 * 1200 / 1000 / 60))
26         standbyNodeNum                      = 32
27 )
28
29 type voteResult struct {
30         VoteAddress string
31         VoteNum     uint64
32 }
33
34 type SettlementReward struct {
35         rewardCfg   *config.RewardConfig
36         node        *apinode.Node
37         db          *gorm.DB
38         rewards     map[string]uint64
39         startHeight uint64
40         endHeight   uint64
41 }
42
43 type memo struct {
44         StartHeight uint64 `json:"start_height"`
45         EndHeight   uint64 `json:"end_height"`
46         NodePubkey  string `json:"node_pubkey"`
47         RewardRatio uint64 `json:"reward_ratio"`
48 }
49
50 func NewSettlementReward(db *gorm.DB, cfg *config.Config, startHeight, endHeight uint64) *SettlementReward {
51         return &SettlementReward{
52                 db:          db,
53                 rewardCfg:   cfg.RewardConf,
54                 node:        apinode.NewNode(cfg.NodeIP),
55                 rewards:     make(map[string]uint64),
56                 startHeight: startHeight,
57                 endHeight:   endHeight,
58         }
59 }
60
61 func (s *SettlementReward) getVoteResultFromDB(height uint64) (voteResults []*voteResult, err error) {
62         query := s.db.Table("utxos").Select("vote_address, sum(vote_num) as vote_num")
63         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)
64         query = query.Group("vote_address")
65         if err := query.Scan(&voteResults).Error; err != nil {
66                 return nil, err
67         }
68
69         return voteResults, nil
70 }
71
72 func (s *SettlementReward) Settlement() error {
73         for height := s.startHeight + consensus.ActiveNetParams.RoundVoteBlockNums; height <= s.endHeight; height += consensus.ActiveNetParams.RoundVoteBlockNums {
74                 totalReward, err := s.getCoinbaseReward(height + 1)
75                 if err == errNotFoundReward {
76                         totalReward, err = s.getStandbyNodeReward(height - consensus.ActiveNetParams.RoundVoteBlockNums)
77                 }
78
79                 if err == errNotStandbyNode {
80                         continue
81                 }
82
83                 if err != nil {
84                         return errors.Wrapf(err, "get total reward at height: %d", height)
85                 }
86
87                 voteResults, err := s.getVoteResultFromDB(height)
88                 if err != nil {
89                         return err
90                 }
91
92                 s.calcVoterRewards(voteResults, totalReward)
93         }
94
95         if len(s.rewards) == 0 {
96                 return errNotRewardTx
97         }
98
99         data, err := json.Marshal(&memo{
100                 StartHeight: s.startHeight,
101                 EndHeight:   s.endHeight,
102                 NodePubkey:  s.rewardCfg.XPub,
103                 RewardRatio: s.rewardCfg.RewardRatio,
104         })
105         if err != nil {
106                 return err
107         }
108
109         // send transactions
110         _, err = s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards, data)
111         return err
112 }
113
114 func (s *SettlementReward) getStandbyNodeReward(height uint64) (uint64, error) {
115         voteInfos, err := s.node.GetVoteByHeight(height)
116         if err != nil {
117                 return 0, errors.Wrapf(err, "get alternative node reward")
118         }
119
120         voteInfos = common.CalcStandByNodes(voteInfos)
121         xpubVoteNum := uint64(0)
122         for _, voteInfo := range voteInfos {
123                 if s.rewardCfg.XPub == voteInfo.Vote {
124                         xpubVoteNum = voteInfo.VoteNum
125                 }
126         }
127
128         if xpubVoteNum == 0 {
129                 return 0, errNotStandbyNode
130         }
131
132         amount := big.NewInt(0).SetUint64(standbyNodesRewardForConsensusCycle / standbyNodeNum)
133         rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
134         amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
135         return amount.Uint64(), nil
136 }
137
138 func (s *SettlementReward) getCoinbaseReward(height uint64) (uint64, error) {
139         block, err := s.node.GetBlockByHeight(height)
140         if err != nil {
141                 return 0, err
142         }
143
144         miningControl, err := common.GetControlProgramFromAddress(s.rewardCfg.MiningAddress)
145         if err != nil {
146                 return 0, err
147         }
148
149         for _, output := range block.Transactions[0].Outputs {
150                 output, ok := output.TypedOutput.(*types.IntraChainOutput)
151                 if !ok {
152                         return 0, errors.New("Output type error")
153                 }
154
155                 if output.Amount == 0 {
156                         continue
157                 }
158
159                 if bytes.Equal(miningControl, output.ControlProgram) {
160                         amount := big.NewInt(0).SetUint64(output.Amount)
161                         rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
162                         amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
163
164                         return amount.Uint64(), nil
165                 }
166         }
167         return 0, errNotFoundReward
168 }
169
170 func (s *SettlementReward) calcVoterRewards(voteResults []*voteResult, totalReward uint64) {
171         totalVoteNum := uint64(0)
172         for _, voteResult := range voteResults {
173                 totalVoteNum += voteResult.VoteNum
174         }
175
176         for _, voteResult := range voteResults {
177                 // voteNum / totalVoteNum  * totalReward
178                 voteNum := big.NewInt(0).SetUint64(voteResult.VoteNum)
179                 total := big.NewInt(0).SetUint64(totalVoteNum)
180                 reward := big.NewInt(0).SetUint64(totalReward)
181
182                 amount := voteNum.Mul(voteNum, reward).Div(voteNum, total).Uint64()
183
184                 if amount != 0 {
185                         s.rewards[voteResult.VoteAddress] += amount
186                 }
187         }
188 }