OSDN Git Service

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