OSDN Git Service

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