OSDN Git Service

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