OSDN Git Service

f93d78880be7f10fca9abf33363849ff216bd5d5
[bytom/vapor.git] / toolbar / reward / reward / vote_reward.go
1 package reward
2
3 import (
4         "fmt"
5         "math/big"
6
7         log "github.com/sirupsen/logrus"
8
9         "github.com/vapor/errors"
10         "github.com/vapor/protocol/bc/types"
11         "github.com/vapor/toolbar/common"
12         "github.com/vapor/toolbar/reward/config"
13 )
14
15 type voterReward struct {
16         rewards map[string]*big.Int
17 }
18
19 type voteResult struct {
20         Votes     map[string]*big.Int
21         VoteTotal *big.Int
22 }
23
24 type coinBaseReward struct {
25         totalReward     uint64
26         voteTotalReward *big.Int
27 }
28
29 type Vote struct {
30         nodes          []config.VoteRewardConfig
31         ch             chan VoteInfo
32         overReadCH     chan struct{}
33         quit           chan struct{}
34         voteResults    map[string]*voteResult
35         voterRewards   map[string]*voterReward
36         coinBaseReward map[string]*coinBaseReward
37         period         uint64
38 }
39
40 func NewVote(nodes []config.VoteRewardConfig, ch chan VoteInfo, overReadCH, quit chan struct{}, period uint64) *Vote {
41         return &Vote{
42                 nodes:          nodes,
43                 ch:             ch,
44                 overReadCH:     overReadCH,
45                 quit:           quit,
46                 voteResults:    make(map[string]*voteResult),
47                 voterRewards:   make(map[string]*voterReward),
48                 coinBaseReward: make(map[string]*coinBaseReward),
49                 period:         period,
50         }
51 }
52
53 func (v *Vote) Start() {
54         // get coinbase reward
55         if err := v.getCoinbaseReward(); err != nil {
56                 panic(errors.Wrap(err, "get coinbase reward"))
57         }
58
59         v.countVotes()
60         v.countReward()
61
62         // send transactions
63         if err := v.sendRewardTransaction(); err != nil {
64                 panic(err)
65         }
66 }
67
68 func (v *Vote) getCoinbaseReward() error {
69         if len(v.nodes) > 0 {
70                 tx := Transaction{
71                         ip: fmt.Sprintf("http://%s:%d", v.nodes[0].Host, v.nodes[0].Port),
72                 }
73                 for {
74                         h, err := tx.GetCurrentHeight()
75                         if err != nil {
76                                 close(v.quit)
77                                 return errors.Wrap(err, "get block height")
78                         }
79                         if h >= 1200*v.period {
80                                 break
81                         }
82                 }
83
84                 coinbaseTx, err := tx.GetCoinbaseTx(1200 * v.period)
85                 if err != nil {
86                         close(v.quit)
87                         return err
88                 }
89                 for _, output := range coinbaseTx.Outputs {
90                         output, ok := output.TypedOutput.(*types.IntraChainOutput)
91                         if !ok {
92                                 close(v.quit)
93                                 return errors.New("Output type error")
94                         }
95                         address := common.GetAddressFromControlProgram(output.ControlProgram)
96                         for _, node := range v.nodes {
97                                 if address == node.MiningAddress {
98                                         reward := &coinBaseReward{
99                                                 totalReward: output.Amount,
100                                         }
101                                         ratioNumerator := big.NewInt(int64(node.RewardRatio))
102                                         ratioDenominator := big.NewInt(100)
103                                         coinBaseReward := big.NewInt(0).SetUint64(output.Amount)
104                                         reward.voteTotalReward = coinBaseReward.Mul(coinBaseReward, ratioNumerator).Div(coinBaseReward, ratioDenominator)
105                                         v.coinBaseReward[node.XPub] = reward
106                                 }
107                         }
108                 }
109         }
110         return nil
111 }
112
113 func (v *Vote) countVotes() {
114 out:
115         for {
116                 select {
117                 case voteInfo := <-v.ch:
118                         bigBlockNum := big.NewInt(0).SetUint64(voteInfo.VoteBlockNum)
119                         bigVoteNum := big.NewInt(0).SetUint64(voteInfo.VoteNum)
120                         bigVoteNum.Mul(bigVoteNum, bigBlockNum)
121
122                         if value, ok := v.voteResults[voteInfo.XPub]; ok {
123                                 if vote, ok := value.Votes[voteInfo.Address]; ok {
124                                         vote.Add(vote, bigVoteNum)
125                                 } else {
126                                         value.Votes[voteInfo.Address] = bigVoteNum
127                                 }
128                         } else {
129                                 voteResult := &voteResult{
130                                         Votes:     make(map[string]*big.Int),
131                                         VoteTotal: big.NewInt(0),
132                                 }
133
134                                 voteResult.Votes[voteInfo.Address] = bigVoteNum
135                                 v.voteResults[voteInfo.XPub] = voteResult
136                         }
137                         voteTotal := v.voteResults[voteInfo.XPub].VoteTotal
138                         voteTotal.Add(voteTotal, bigVoteNum)
139                         v.voteResults[voteInfo.XPub].VoteTotal = voteTotal
140                 case <-v.overReadCH:
141                         break out
142                 }
143         }
144 }
145
146 func (v *Vote) countReward() {
147         for xpub, votes := range v.voteResults {
148                 coinBaseReward, ok := v.coinBaseReward[xpub]
149                 if !ok {
150                         log.Errorf("%s doesn't have a coinbase reward \n", xpub)
151                         continue
152                 }
153
154                 for address, vote := range votes.Votes {
155                         if value, ok := v.voterRewards[xpub]; ok {
156                                 mul := vote.Mul(vote, coinBaseReward.voteTotalReward)
157                                 amount := big.NewInt(0)
158                                 amount.Div(mul, votes.VoteTotal)
159
160                                 value.rewards[address] = amount
161                         } else {
162                                 reward := &voterReward{
163                                         rewards: make(map[string]*big.Int),
164                                 }
165
166                                 mul := vote.Mul(vote, coinBaseReward.voteTotalReward)
167                                 amount := big.NewInt(0)
168                                 amount.Div(mul, votes.VoteTotal)
169                                 if amount.Uint64() > 0 {
170                                         reward.rewards[address] = amount
171                                         v.voterRewards[xpub] = reward
172                                 }
173                         }
174                 }
175
176         }
177 }
178
179 func (v *Vote) sendRewardTransaction() error {
180         for _, node := range v.nodes {
181                 coinbaseReward, ok := v.coinBaseReward[node.XPub]
182                 if !ok {
183                         log.Errorf("%s doesn't have a coinbase reward \n", node.XPub)
184                         continue
185                 }
186
187                 if voterRewards, ok := v.voterRewards[node.XPub]; ok {
188                         txID, err := v.sendReward(coinbaseReward.totalReward, node, voterRewards)
189                         if err != nil {
190                                 return err
191                         }
192                         log.Info("tx_id: ", txID)
193                 }
194         }
195         close(v.quit)
196         return nil
197 }
198
199 func (v *Vote) sendReward(coinbaseReward uint64, node config.VoteRewardConfig, voterReward *voterReward) (string, error) {
200         var outputAction string
201
202         inputAction := fmt.Sprintf(inputActionFmt, coinbaseReward, node.AccountID)
203
204         index := 0
205         for address, amount := range voterReward.rewards {
206                 index++
207                 outputAction += fmt.Sprintf(outputActionFmt, amount.Uint64(), address)
208                 if index != len(voterReward.rewards) {
209                         outputAction += ","
210                 }
211         }
212         tx := Transaction{
213                 ip: fmt.Sprintf("http://%s:%d", node.Host, node.Port),
214         }
215
216         tmpl, err := tx.buildTx(inputAction, outputAction)
217         if err != nil {
218                 return "", err
219         }
220
221         tmpl, err = tx.signTx(node.Passwd, *tmpl)
222         if err != nil {
223                 return "", err
224         }
225
226         return tx.SubmitTx(tmpl.Transaction)
227 }