OSDN Git Service

add vote reward
[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 Vote struct {
25         nodes          []config.VoteRewardConfig
26         ch             chan VoteInfo
27         overReadCH     chan struct{}
28         voteResults    map[string]*VoteResult
29         voterRewards   map[string]*VoterReward
30         coinBaseReward map[string]uint64
31         period         uint64
32 }
33
34 func NewVote(nodes []config.VoteRewardConfig, ch chan VoteInfo, overReadCH chan struct{}, period uint64) *Vote {
35         return &Vote{
36                 nodes:          nodes,
37                 ch:             ch,
38                 overReadCH:     overReadCH,
39                 voteResults:    make(map[string]*VoteResult),
40                 voterRewards:   make(map[string]*VoterReward),
41                 coinBaseReward: make(map[string]uint64),
42                 period:         period,
43         }
44 }
45
46 func (v *Vote) Start() {
47         // get coinbase reward
48         if err := v.getCoinbaseReward(); err != nil {
49                 panic(errors.Wrap(err, "get coinbase reward"))
50         }
51
52         v.countVotes()
53         v.countReward()
54
55         // send transactions
56         v.sendRewardTransaction()
57 }
58
59 func (v *Vote) getCoinbaseReward() error {
60         if len(v.nodes) > 0 {
61                 tx := Transaction{
62                         ip: fmt.Sprintf("http://%s:%d", v.nodes[0].Host, v.nodes[0].Port),
63                 }
64                 coinbaseTx, err := tx.GetCoinbaseTx(1200 * v.period)
65                 if err != nil {
66                         return err
67                 }
68                 for _, output := range coinbaseTx.Outputs {
69                         voteOutput, ok := output.TypedOutput.(*types.IntraChainOutput)
70                         if !ok {
71                                 return errors.New("Output type error")
72                         }
73                         address := common.GetAddressFromControlProgram(voteOutput.ControlProgram)
74                         for _, node := range v.nodes {
75                                 if address == node.MiningAddress {
76                                         v.coinBaseReward[node.XPub] = voteOutput.Amount
77                                 }
78                         }
79                 }
80         }
81         return nil
82 }
83
84 func (v *Vote) countVotes() {
85 out:
86         for {
87                 select {
88                 case voteInfo := <-v.ch:
89                         bigBlockNum := big.NewInt(0).SetUint64(voteInfo.VoteBlockNum)
90                         bigVoteNum := big.NewInt(0).SetUint64(voteInfo.VoteNum)
91                         bigVoteNum = bigBlockNum.Mul(bigBlockNum, bigVoteNum)
92
93                         if value, ok := v.voteResults[voteInfo.XPub]; ok {
94                                 value.Votes[voteInfo.Address] = bigVoteNum.Add(bigVoteNum, value.Votes[voteInfo.Address])
95                         } else {
96                                 voteResult := &VoteResult{
97                                         Votes:     make(map[string]*big.Int),
98                                         VoteTotal: big.NewInt(0),
99                                 }
100
101                                 voteResult.Votes[voteInfo.Address] = bigVoteNum
102                                 v.voteResults[voteInfo.XPub] = voteResult
103                         }
104
105                         v.voteResults[voteInfo.XPub].VoteTotal = bigVoteNum.Add(bigVoteNum, v.voteResults[voteInfo.XPub].VoteTotal)
106                 case <-v.overReadCH:
107                         break out
108                 }
109         }
110 }
111
112 func (v *Vote) countReward() {
113         for xpub, votes := range v.voteResults {
114                 coinbaseReward, ok := v.coinBaseReward[xpub]
115                 if !ok {
116                         log.Errorf("%s doesn't have a coinbase reward \n", xpub)
117                         continue
118                 }
119                 for address, vote := range votes.Votes {
120                         if value, ok := v.voterRewards[xpub]; ok {
121                                 coinBaseReward := big.NewInt(0).SetUint64(coinbaseReward)
122                                 value.rewards[address] = vote.Mul(vote, coinBaseReward).Div(vote, votes.VoteTotal)
123                         } else {
124                                 reward := &VoterReward{
125                                         rewards: make(map[string]*big.Int),
126                                 }
127                                 coinBaseReward := big.NewInt(0).SetUint64(coinbaseReward)
128                                 reward.rewards[address] = vote.Mul(vote, coinBaseReward).Div(vote, votes.VoteTotal)
129                                 v.voterRewards[xpub] = reward
130                         }
131                 }
132
133         }
134 }
135
136 func (v *Vote) sendRewardTransaction() error {
137         for _, node := range v.nodes {
138                 coinbaseReward, ok := v.coinBaseReward[node.XPub]
139                 if !ok {
140                         log.Errorf("%s doesn't have a coinbase reward \n", node.XPub)
141                         continue
142                 }
143
144                 if voterRewards, ok := v.voterRewards[node.XPub]; ok {
145                         txID, err := v.sendReward(coinbaseReward, node, voterRewards)
146                         if err != nil {
147                                 return err
148                         }
149                         log.Info("tx_id: ", txID)
150                 }
151         }
152
153         return nil
154 }
155
156 func (v *Vote) sendReward(coinbaseReward uint64, node config.VoteRewardConfig, voterReward *VoterReward) (string, error) {
157         var outputAction string
158
159         inputAction := fmt.Sprintf(inputActionFmt, coinbaseReward, node.AccountID)
160
161         index := 0
162         for address, amount := range voterReward.rewards {
163                 outputAction += fmt.Sprintf(outputActionFmt, amount.Uint64(), address)
164                 index++
165                 if index != len(voterReward.rewards) {
166                         outputAction += ","
167                 }
168         }
169         tx := Transaction{
170                 ip: fmt.Sprintf("http://%s:%d", node.Host, node.Port),
171         }
172
173         tmpl, err := tx.buildTx(inputAction, outputAction)
174         if err != nil {
175                 return "", err
176         }
177
178         tmpl, err = tx.signTx(node.Passwd, *tmpl)
179         if err != nil {
180                 return "", err
181         }
182
183         return tx.SubmitTx(tmpl.Transaction)
184 }