OSDN Git Service

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