OSDN Git Service

e1368485b60fafaa0a210efc574abb03031cb87f
[bytom/vapor.git] / protocol / state / consensus_result.go
1 package state
2
3 import (
4         "encoding/hex"
5         "sort"
6
7         "github.com/vapor/common/arithmetic"
8         "github.com/vapor/config"
9         "github.com/vapor/consensus"
10         "github.com/vapor/crypto/ed25519/chainkd"
11         "github.com/vapor/errors"
12         "github.com/vapor/math/checked"
13         "github.com/vapor/protocol/bc"
14         "github.com/vapor/protocol/bc/types"
15 )
16
17 // ConsensusNode represents a consensus node
18 type ConsensusNode struct {
19         XPub    chainkd.XPub
20         VoteNum uint64
21         Order   uint64
22 }
23
24 type byVote []*ConsensusNode
25
26 func (c byVote) Len() int { return len(c) }
27 func (c byVote) Less(i, j int) bool {
28         return c[i].VoteNum > c[j].VoteNum || (c[i].VoteNum == c[j].VoteNum && c[i].XPub.String() > c[j].XPub.String())
29 }
30 func (c byVote) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
31
32 // CalcVoteSeq calculate the vote sequence
33 // seq 0 is the genesis block
34 // seq 1 is the the block height 1, to block height RoundVoteBlockNums
35 // seq 2 is the block height RoundVoteBlockNums + 1 to block height 2 * RoundVoteBlockNums
36 // consensus node of the current round is the final result of previous round
37 func CalcVoteSeq(blockHeight uint64) uint64 {
38         if blockHeight == 0 {
39                 return 0
40         }
41         return (blockHeight-1)/consensus.RoundVoteBlockNums + 1
42 }
43
44 // ConsensusResult represents a snapshot of each round of DPOS voting
45 // Seq indicates the sequence of current votes, which start from zero
46 // NumOfVote indicates the number of votes each consensus node receives, the key of map represent public key
47 // CoinbaseReward indicates the coinbase receiver and reward
48 type ConsensusResult struct {
49         Seq            uint64
50         NumOfVote      map[string]uint64
51         CoinbaseReward map[string]uint64
52         BlockHash      bc.Hash
53         BlockHeight    uint64
54 }
55
56 // CoinbaseReward contains receiver and reward
57 type CoinbaseReward struct {
58         Amount         uint64
59         ControlProgram []byte
60 }
61
62 // SortByAmount implements sort.Interface for CoinbaseReward slices
63 type SortByAmount []CoinbaseReward
64
65 func (a SortByAmount) Len() int           { return len(a) }
66 func (a SortByAmount) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
67 func (a SortByAmount) Less(i, j int) bool { return a[i].Amount < a[j].Amount }
68
69 // CalCoinbaseReward calculate the coinbase reward for block
70 func CalCoinbaseReward(block *types.Block) (*CoinbaseReward, error) {
71         var coinbaseReceiver []byte
72         if len(block.Transactions) > 0 && len(block.Transactions[0].Outputs) > 0 {
73                 coinbaseReceiver = block.Transactions[0].Outputs[0].ControlProgram()
74         }
75
76         if coinbaseReceiver == nil {
77                 return nil, errors.New("not found coinbase receiver")
78         }
79
80         coinbaseAmount := consensus.BlockSubsidy(block.BlockHeader.Height)
81         for _, tx := range block.Transactions {
82                 txFee, err := arithmetic.CalculateTxFee(tx)
83                 if err != nil {
84                         return nil, errors.Wrap(checked.ErrOverflow, "calculate transaction fee")
85                 }
86                 coinbaseAmount += txFee
87         }
88
89         return &CoinbaseReward{
90                 Amount:         coinbaseAmount,
91                 ControlProgram: coinbaseReceiver,
92         }, nil
93 }
94
95 // ApplyBlock calculate the consensus result for new block
96 func (c *ConsensusResult) ApplyBlock(block *types.Block) error {
97         if c.BlockHash != block.PreviousBlockHash {
98                 return errors.New("block parent hash is not equals last block hash of vote result")
99         }
100
101         if err := c.AttachCoinbaseReward(block); err != nil {
102                 return err
103         }
104
105         for _, tx := range block.Transactions {
106                 for _, input := range tx.Inputs {
107                         vetoInput, ok := input.TypedInput.(*types.VetoInput)
108                         if !ok {
109                                 continue
110                         }
111
112                         pubkey := hex.EncodeToString(vetoInput.Vote)
113                         c.NumOfVote[pubkey], ok = checked.SubUint64(c.NumOfVote[pubkey], vetoInput.Amount)
114                         if !ok {
115                                 return checked.ErrOverflow
116                         }
117
118                         if c.NumOfVote[pubkey] == 0 {
119                                 delete(c.NumOfVote, pubkey)
120                         }
121                 }
122
123                 for _, output := range tx.Outputs {
124                         voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
125                         if !ok {
126                                 continue
127                         }
128
129                         pubkey := hex.EncodeToString(voteOutput.Vote)
130                         if c.NumOfVote[pubkey], ok = checked.AddUint64(c.NumOfVote[pubkey], voteOutput.Amount); !ok {
131                                 return checked.ErrOverflow
132                         }
133                 }
134         }
135
136         c.BlockHash = block.Hash()
137         c.BlockHeight = block.Height
138         c.Seq = CalcVoteSeq(block.Height)
139         return nil
140 }
141
142 // ConsensusNodes returns all consensus nodes
143 func (c *ConsensusResult) ConsensusNodes() (map[string]*ConsensusNode, error) {
144         var nodes []*ConsensusNode
145         for pubkey, voteNum := range c.NumOfVote {
146                 if voteNum >= consensus.MinConsensusNodeVoteNum {
147                         var xpub chainkd.XPub
148                         if err := xpub.UnmarshalText([]byte(pubkey)); err != nil {
149                                 return nil, err
150                         }
151
152                         nodes = append(nodes, &ConsensusNode{XPub: xpub, VoteNum: voteNum})
153                 }
154         }
155         // In principle, there is no need to sort all voting nodes.
156         // if there is a performance problem, consider the optimization later.
157         sort.Sort(byVote(nodes))
158         result := make(map[string]*ConsensusNode)
159         for i := 0; i < len(nodes) && i < consensus.NumOfConsensusNode; i++ {
160                 nodes[i].Order = uint64(i)
161                 result[nodes[i].XPub.String()] = nodes[i]
162         }
163
164         if len(result) != 0 {
165                 return result, nil
166         }
167         return federationNodes(), nil
168 }
169
170 func federationNodes() map[string]*ConsensusNode {
171         consensusResult := map[string]*ConsensusNode{}
172         for i, xpub := range config.CommonConfig.Federation.Xpubs {
173                 consensusResult[xpub.String()] = &ConsensusNode{XPub: xpub, VoteNum: 0, Order: uint64(i)}
174         }
175         return consensusResult
176 }
177
178 // DetachBlock calculate the consensus result for detach block
179 func (c *ConsensusResult) DetachBlock(block *types.Block) error {
180         if c.BlockHash != block.Hash() {
181                 return errors.New("block hash is not equals last block hash of vote result")
182         }
183
184         if err := c.DetachCoinbaseReward(block); err != nil {
185                 return err
186         }
187
188         for i := len(block.Transactions) - 1; i >= 0; i-- {
189                 tx := block.Transactions[i]
190                 for _, input := range tx.Inputs {
191                         vetoInput, ok := input.TypedInput.(*types.VetoInput)
192                         if !ok {
193                                 continue
194                         }
195
196                         pubkey := hex.EncodeToString(vetoInput.Vote)
197                         if c.NumOfVote[pubkey], ok = checked.AddUint64(c.NumOfVote[pubkey], vetoInput.Amount); !ok {
198                                 return checked.ErrOverflow
199                         }
200                 }
201
202                 for _, output := range tx.Outputs {
203                         voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
204                         if !ok {
205                                 continue
206                         }
207
208                         pubkey := hex.EncodeToString(voteOutput.Vote)
209                         c.NumOfVote[pubkey], ok = checked.SubUint64(c.NumOfVote[pubkey], voteOutput.Amount)
210                         if !ok {
211                                 return checked.ErrOverflow
212                         }
213
214                         if c.NumOfVote[pubkey] == 0 {
215                                 delete(c.NumOfVote, pubkey)
216                         }
217                 }
218         }
219
220         c.BlockHash = block.PreviousBlockHash
221         c.BlockHeight = block.Height - 1
222         c.Seq = CalcVoteSeq(block.Height - 1)
223         return nil
224 }
225
226 func (c *ConsensusResult) Fork() *ConsensusResult {
227         f := &ConsensusResult{
228                 Seq:            c.Seq,
229                 NumOfVote:      map[string]uint64{},
230                 CoinbaseReward: map[string]uint64{},
231                 BlockHash:      c.BlockHash,
232                 BlockHeight:    c.BlockHeight,
233         }
234
235         for key, value := range c.NumOfVote {
236                 f.NumOfVote[key] = value
237         }
238
239         for key, value := range c.CoinbaseReward {
240                 f.CoinbaseReward[key] = value
241         }
242         return f
243 }
244
245 func (c *ConsensusResult) IsFinalize() bool {
246         return c.BlockHeight%consensus.RoundVoteBlockNums == 0
247 }
248
249 // AttachCoinbaseReward attach coinbase reward
250 func (c *ConsensusResult) AttachCoinbaseReward(block *types.Block) error {
251         reward, err := CalCoinbaseReward(block)
252         if err != nil {
253                 return err
254         }
255
256         if block.Height%consensus.RoundVoteBlockNums == 1 {
257                 c.CoinbaseReward = map[string]uint64{}
258         }
259
260         var ok bool
261         program := hex.EncodeToString(reward.ControlProgram)
262         c.CoinbaseReward[program], ok = checked.AddUint64(c.CoinbaseReward[program], reward.Amount)
263         if !ok {
264                 return checked.ErrOverflow
265         }
266         return nil
267 }
268
269 // DetachCoinbaseReward detach coinbase reward
270 func (c *ConsensusResult) DetachCoinbaseReward(block *types.Block) error {
271         if block.Height%consensus.RoundVoteBlockNums == 0 {
272                 for i, output := range block.Transactions[0].Outputs {
273                         if i == 0 {
274                                 continue
275                         }
276                         program := output.ControlProgram()
277                         c.CoinbaseReward[hex.EncodeToString(program)] = output.AssetAmount().Amount
278                 }
279         }
280
281         reward, err := CalCoinbaseReward(block)
282         if err != nil {
283                 return err
284         }
285
286         var ok bool
287         program := hex.EncodeToString(reward.ControlProgram)
288         if c.CoinbaseReward[program], ok = checked.SubUint64(c.CoinbaseReward[program], reward.Amount); !ok {
289                 return checked.ErrOverflow
290         }
291
292         if c.CoinbaseReward[program] == 0 {
293                 delete(c.CoinbaseReward, program)
294         }
295         return nil
296 }
297
298 // GetCoinbaseRewards convert into CoinbaseReward array and sort it by amount
299 func (c *ConsensusResult) GetCoinbaseRewards(blockHeight uint64) ([]CoinbaseReward, error) {
300         rewards := []CoinbaseReward{}
301         if blockHeight%consensus.RoundVoteBlockNums != 0 {
302                 return rewards, nil
303         }
304
305         for p, amount := range c.CoinbaseReward {
306                 coinbaseAmount := amount
307                 program, err := hex.DecodeString(p)
308                 if err != nil {
309                         return nil, err
310                 }
311
312                 rewards = append(rewards, CoinbaseReward{
313                         Amount:         coinbaseAmount,
314                         ControlProgram: program,
315                 })
316         }
317         sort.Sort(SortByAmount(rewards))
318         return rewards, nil
319 }