OSDN Git Service

c367daebf48e4d13cee3fc7ee45f7f196b1be26d
[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 type voteResult struct {
18         VoteAddress string
19         VoteNum     uint64
20 }
21
22 type SettlementReward struct {
23         rewardCfg   *config.RewardConfig
24         node        *apinode.Node
25         db          *gorm.DB
26         rewards     map[string]uint64
27         startHeight uint64
28         endHeight   uint64
29 }
30
31 func NewSettlementReward(db *gorm.DB, cfg *config.Config, startHeight, endHeight uint64) *SettlementReward {
32         return &SettlementReward{
33                 db:          db,
34                 rewardCfg:   cfg.RewardConf,
35                 node:        apinode.NewNode(cfg.NodeIP),
36                 rewards:     make(map[string]uint64),
37                 startHeight: startHeight,
38                 endHeight:   endHeight,
39         }
40 }
41
42 func (s *SettlementReward) getVoteResultFromDB(height uint64) (voteResults []*voteResult, err error) {
43         query := s.db.Table("utxos").Select("vote_address, sum(vote_num) as vote_num")
44         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)
45         query = query.Group("vote_address")
46         if err := query.Scan(&voteResults).Error; err != nil {
47                 return nil, err
48         }
49
50         return voteResults, nil
51 }
52
53 func (s *SettlementReward) Settlement() error {
54         for height := s.startHeight + consensus.ActiveNetParams.RoundVoteBlockNums; height <= s.endHeight; height += consensus.ActiveNetParams.RoundVoteBlockNums {
55                 coinbaseHeight := height + 1
56                 totalReward, err := s.getCoinbaseReward(coinbaseHeight)
57                 if err != nil {
58                         return errors.Wrapf(err, "get total reward at coinbase_height: %d", coinbaseHeight)
59                 }
60
61                 voteResults, err := s.getVoteResultFromDB(height)
62                 if err != nil {
63                         return err
64                 }
65
66                 s.calcVoterRewards(voteResults, totalReward)
67         }
68
69         // send transactions
70         return s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards)
71 }
72
73 func (s *SettlementReward) getCoinbaseReward(height uint64) (uint64, error) {
74         block, err := s.node.GetBlockByHeight(height)
75         if err != nil {
76                 return 0, err
77         }
78
79         miningControl, err := common.GetControlProgramFromAddress(s.rewardCfg.MiningAddress)
80         if err != nil {
81                 return 0, err
82         }
83
84         for _, output := range block.Transactions[0].Outputs {
85                 output, ok := output.TypedOutput.(*types.IntraChainOutput)
86                 if !ok {
87                         return 0, errors.New("Output type error")
88                 }
89
90                 if output.Amount == 0 {
91                         continue
92                 }
93
94                 if bytes.Equal(miningControl, output.ControlProgram) {
95                         amount := big.NewInt(0).SetUint64(output.Amount)
96                         rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
97                         amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
98
99                         return amount.Uint64(), nil
100                 }
101         }
102         return 0, errors.New("No reward found")
103 }
104
105 func (s *SettlementReward) calcVoterRewards(voteResults []*voteResult, totalReward uint64) {
106         totalVoteNum := uint64(0)
107         for _, voteResult := range voteResults {
108                 totalVoteNum += voteResult.VoteNum
109         }
110
111         for _, voteResult := range voteResults {
112                 // voteNum / totalVoteNum  * totalReward
113                 voteNum := big.NewInt(0).SetUint64(voteResult.VoteNum)
114                 total := big.NewInt(0).SetUint64(totalVoteNum)
115                 reward := big.NewInt(0).SetUint64(totalReward)
116
117                 amount := voteNum.Mul(voteNum, reward).Div(voteNum, total).Uint64()
118
119                 if amount != 0 {
120                         s.rewards[voteResult.VoteAddress] += amount
121                 }
122         }
123 }