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"
17 var errMathOperationOverFlow = errors.New("arithmetic operation result overflow")
19 // ConsensusNode represents a consensus node
20 type ConsensusNode struct {
26 type byVote []*ConsensusNode
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())
32 func (c byVote) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
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 {
43 return (blockHeight-1)/consensus.RoundVoteBlockNums + 1
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 {
52 NumOfVote map[string]uint64
53 RewardOfCoinbase map[string]uint64
58 // ApplyBlock calculate the consensus result for new block
59 func (c *ConsensusResult) ApplyBlock(block *types.Block) error {
61 if c.BlockHash != block.PreviousBlockHash {
62 return errors.New("block parent hash is not equals last block hash of vote result")
65 reward, err := CalCoinbaseReward(block)
71 c.RewardOfCoinbase = map[string]uint64{}
74 program := hex.EncodeToString(reward.ControlProgram)
75 c.RewardOfCoinbase[program], ok = checked.AddUint64(c.RewardOfCoinbase[program], reward.Amount)
77 return errMathOperationOverFlow
80 for _, tx := range block.Transactions {
81 for _, input := range tx.Inputs {
82 vetoInput, ok := input.TypedInput.(*types.VetoInput)
87 pubkey := hex.EncodeToString(vetoInput.Vote)
88 c.NumOfVote[pubkey], ok = checked.SubUint64(c.NumOfVote[pubkey], vetoInput.Amount)
90 return errMathOperationOverFlow
93 if c.NumOfVote[pubkey] == 0 {
94 delete(c.NumOfVote, pubkey)
98 for _, output := range tx.Outputs {
99 voteOutput, ok := output.TypedOutput.(*types.VoteTxOutput)
104 pubkey := hex.EncodeToString(voteOutput.Vote)
105 if c.NumOfVote[pubkey], ok = checked.AddUint64(c.NumOfVote[pubkey], voteOutput.Amount); !ok {
106 return errMathOperationOverFlow
111 c.BlockHash = block.Hash()
112 c.BlockHeight = block.Height
113 c.Seq = CalcVoteSeq(block.Height)
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 {
127 nodes = append(nodes, &ConsensusNode{XPub: xpub, VoteNum: voteNum})
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]
139 if len(result) != 0 {
142 return federationNodes(), nil
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)}
150 return consensusResult
153 // DetachBlock calculate the consensus result for detach block
154 func (c *ConsensusResult) DetachBlock(block *types.Block) error {
156 if c.BlockHash != block.Hash() {
157 return errors.New("block hash is not equals last block hash of vote result")
160 reward, err := CalCoinbaseReward(block)
165 program := hex.EncodeToString(reward.ControlProgram)
166 if c.RewardOfCoinbase[program], ok = checked.SubUint64(c.RewardOfCoinbase[program], reward.Amount); !ok {
167 return errMathOperationOverFlow
170 if c.RewardOfCoinbase[program] == 0 {
171 delete(c.RewardOfCoinbase, program)
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)
182 pubkey := hex.EncodeToString(vetoInput.Vote)
183 if c.NumOfVote[pubkey], ok = checked.AddUint64(c.NumOfVote[pubkey], vetoInput.Amount); !ok {
184 return errMathOperationOverFlow
188 for _, output := range tx.Outputs {
189 voteOutput, ok := output.TypedOutput.(*types.VoteTxOutput)
194 pubkey := hex.EncodeToString(voteOutput.Vote)
195 c.NumOfVote[pubkey], ok = checked.SubUint64(c.NumOfVote[pubkey], voteOutput.Amount)
197 return errMathOperationOverFlow
200 if c.NumOfVote[pubkey] == 0 {
201 delete(c.NumOfVote, pubkey)
206 c.BlockHash = block.PreviousBlockHash
207 c.BlockHeight = block.Height - 1
208 c.Seq = CalcVoteSeq(block.Height - 1)
212 func (c *ConsensusResult) Fork() *ConsensusResult {
213 f := &ConsensusResult{
215 NumOfVote: map[string]uint64{},
216 RewardOfCoinbase: map[string]uint64{},
217 BlockHash: c.BlockHash,
218 BlockHeight: c.BlockHeight,
221 for key, value := range c.NumOfVote {
222 f.NumOfVote[key] = value
225 for key, value := range c.RewardOfCoinbase {
226 f.RewardOfCoinbase[key] = value
231 func (c *ConsensusResult) IsFinalize() bool {
232 return c.BlockHeight%consensus.RoundVoteBlockNums == 0
235 // CoinbaseReward contains receiver and reward
236 type CoinbaseReward struct {
238 ControlProgram []byte
241 // SortByAmount implements sort.Interface for CoinbaseReward slices
242 type SortByAmount []CoinbaseReward
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 }
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()
255 if coinbaseReceiver == nil {
256 return nil, errors.New("invalid block coinbase transaction with receiver address is empty")
259 coinbaseAmount := consensus.BlockSubsidy(block.BlockHeader.Height)
260 for _, tx := range block.Transactions {
261 txFee, err := calTxFee(tx)
265 coinbaseAmount += txFee
268 return &CoinbaseReward{
269 Amount: coinbaseAmount,
270 ControlProgram: coinbaseReceiver,
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 {
280 if input.AssetID() == *consensus.BTMAssetID {
281 totalInputBTM += input.Amount()
285 for _, output := range tx.Outputs {
286 if *output.AssetAmount().AssetId == *consensus.BTMAssetID {
287 totalOutputBTM += output.AssetAmount().Amount
291 txFee, ok := checked.SubUint64(totalInputBTM, totalOutputBTM)
293 return 0, errMathOperationOverFlow
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
305 aggregateFlag := false
306 for p, amount := range consensusResult.RewardOfCoinbase {
307 coinbaseAmount := amount
308 program, err := hex.DecodeString(p)
313 if res := bytes.Compare(program, reward.ControlProgram); res == 0 {
315 if coinbaseAmount, ok = checked.AddUint64(coinbaseAmount, reward.Amount); !ok {
316 return nil, errMathOperationOverFlow
321 rewards = append(rewards, CoinbaseReward{
322 Amount: coinbaseAmount,
323 ControlProgram: program,
328 rewards = append(rewards, *reward)
330 sort.Sort(SortByAmount(rewards))