OSDN Git Service

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