7 log "github.com/sirupsen/logrus"
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"
19 const logModule = "state/consensus"
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},
30 // ConsensusNode represents a consensus node
31 type ConsensusNode struct {
37 type byVote []*ConsensusNode
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())
43 func (c byVote) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
45 // CoinbaseReward contains receiver and reward
46 type CoinbaseReward struct {
51 // SortByAmount implements sort.Interface for CoinbaseReward slices
52 type SortByAmount []CoinbaseReward
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))
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()
66 return nil, errors.New("not found coinbase receiver")
69 result.Amount = consensus.BlockSubsidy(block.BlockHeader.Height)
70 for _, tx := range block.Transactions {
71 txFee, err := arithmetic.CalculateTxFee(tx)
73 return nil, errors.Wrap(checked.ErrOverflow, "calculate transaction fee")
77 result.Amount, ok = checked.AddUint64(result.Amount, txFee)
79 return nil, checked.ErrOverflow
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 {
94 return (blockHeight-1)/consensus.ActiveNetParams.RoundVoteBlockNums + 1
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 {
103 NumOfVote map[string]uint64
104 CoinbaseReward map[string]uint64
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")
115 if err := c.AttachCoinbaseReward(block); err != nil {
119 for _, tx := range block.Transactions {
120 if err := c.ApplyTransaction(tx); err != nil {
125 c.BlockHash = block.Hash()
126 c.BlockHeight = block.Height
127 c.Seq = CalcVoteSeq(block.Height)
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)
139 pubkey := hex.EncodeToString(vetoInput.Vote)
140 c.NumOfVote[pubkey], ok = checked.SubUint64(c.NumOfVote[pubkey], vetoInput.Amount)
142 return checked.ErrOverflow
145 if c.NumOfVote[pubkey] == 0 {
146 delete(c.NumOfVote, pubkey)
150 for _, output := range tx.Outputs {
151 voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
156 pubkey := hex.EncodeToString(voteOutput.Vote)
157 if c.NumOfVote[pubkey], ok = checked.AddUint64(c.NumOfVote[pubkey], voteOutput.Amount); !ok {
158 return checked.ErrOverflow
164 // AttachCoinbaseReward attach coinbase reward
165 func (c *ConsensusResult) AttachCoinbaseReward(block *types.Block) error {
166 reward, err := CalCoinbaseReward(block)
171 if block.Height%consensus.ActiveNetParams.RoundVoteBlockNums == 1 {
172 c.CoinbaseReward = map[string]uint64{}
176 program := hex.EncodeToString(reward.ControlProgram)
177 c.CoinbaseReward[program], ok = checked.AddUint64(c.CoinbaseReward[program], reward.Amount)
179 return checked.ErrOverflow
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)
196 nodes = append(nodes, &ConsensusNode{XPub: xpub, VoteNum: voteNum})
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]
208 if len(result) != 0 {
211 return federationNodes(), nil
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")
220 if err := c.DetachCoinbaseReward(block); err != nil {
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)
232 pubkey := hex.EncodeToString(vetoInput.Vote)
233 if c.NumOfVote[pubkey], ok = checked.AddUint64(c.NumOfVote[pubkey], vetoInput.Amount); !ok {
234 return checked.ErrOverflow
238 for _, output := range tx.Outputs {
239 voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
244 pubkey := hex.EncodeToString(voteOutput.Vote)
245 c.NumOfVote[pubkey], ok = checked.SubUint64(c.NumOfVote[pubkey], voteOutput.Amount)
247 return checked.ErrOverflow
250 if c.NumOfVote[pubkey] == 0 {
251 delete(c.NumOfVote, pubkey)
256 c.BlockHash = block.PreviousBlockHash
257 c.BlockHeight = block.Height - 1
258 c.Seq = CalcVoteSeq(block.Height - 1)
262 // DetachCoinbaseReward detach coinbase reward
263 func (c *ConsensusResult) DetachCoinbaseReward(block *types.Block) error {
264 reward, err := CalCoinbaseReward(block)
270 program := hex.EncodeToString(reward.ControlProgram)
271 if c.CoinbaseReward[program], ok = checked.SubUint64(c.CoinbaseReward[program], reward.Amount); !ok {
272 return checked.ErrOverflow
275 if c.CoinbaseReward[program] == 0 {
276 delete(c.CoinbaseReward, program)
279 if block.Height%consensus.ActiveNetParams.RoundVoteBlockNums == 1 {
280 c.CoinbaseReward = map[string]uint64{}
281 for i, output := range block.Transactions[0].Outputs {
285 program := output.ControlProgram()
286 c.CoinbaseReward[hex.EncodeToString(program)] = output.AssetAmount().Amount
292 // Fork copy the ConsensusResult struct
293 func (c *ConsensusResult) Fork() *ConsensusResult {
294 f := &ConsensusResult{
296 NumOfVote: map[string]uint64{},
297 CoinbaseReward: map[string]uint64{},
298 BlockHash: c.BlockHash,
299 BlockHeight: c.BlockHeight,
302 for key, value := range c.NumOfVote {
303 f.NumOfVote[key] = value
306 for key, value := range c.CoinbaseReward {
307 f.CoinbaseReward[key] = value
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
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 {
324 for p, amount := range c.CoinbaseReward {
325 program, err := hex.DecodeString(p)
330 rewards = append(rewards, CoinbaseReward{
332 ControlProgram: program,
335 sort.Sort(SortByAmount(rewards))
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)}
345 return consensusResult