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"
17 // ConsensusNode represents a consensus node
18 type ConsensusNode struct {
24 type byVote []*ConsensusNode
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())
30 func (c byVote) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
32 // CalcVoteSeq calculate the vote sequence
33 // seq 0 is the genesis block
34 // seq 1 is the the block height 1, to block height RoundVoteBlockNums
35 // seq 2 is the block height RoundVoteBlockNums + 1 to block height 2 * RoundVoteBlockNums
36 // consensus node of the current round is the final result of previous round
37 func CalcVoteSeq(blockHeight uint64) uint64 {
41 return (blockHeight-1)/consensus.RoundVoteBlockNums + 1
44 // ConsensusResult represents a snapshot of each round of DPOS voting
45 // Seq indicates the sequence of current votes, which start from zero
46 // NumOfVote indicates the number of votes each consensus node receives, the key of map represent public key
47 // CoinbaseReward indicates the coinbase receiver and reward
48 type ConsensusResult struct {
50 NumOfVote map[string]uint64
51 CoinbaseReward map[string]uint64
56 // CoinbaseReward contains receiver and reward
57 type CoinbaseReward struct {
62 // SortByAmount implements sort.Interface for CoinbaseReward slices
63 type SortByAmount []CoinbaseReward
65 func (a SortByAmount) Len() int { return len(a) }
66 func (a SortByAmount) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
67 func (a SortByAmount) Less(i, j int) bool { return a[i].Amount < a[j].Amount }
69 // CalCoinbaseReward calculate the coinbase reward for block
70 func CalCoinbaseReward(block *types.Block) (*CoinbaseReward, error) {
71 var coinbaseReceiver []byte
72 if len(block.Transactions) > 0 && len(block.Transactions[0].Outputs) > 0 {
73 coinbaseReceiver = block.Transactions[0].Outputs[0].ControlProgram()
76 if coinbaseReceiver == nil {
77 return nil, errors.New("not found coinbase receiver")
80 coinbaseAmount := consensus.BlockSubsidy(block.BlockHeader.Height)
81 for _, tx := range block.Transactions {
82 txFee, err := arithmetic.CalculateTxFee(tx)
84 return nil, errors.Wrap(checked.ErrOverflow, "calculate transaction fee")
86 coinbaseAmount += txFee
89 return &CoinbaseReward{
90 Amount: coinbaseAmount,
91 ControlProgram: coinbaseReceiver,
95 // ApplyBlock calculate the consensus result for new block
96 func (c *ConsensusResult) ApplyBlock(block *types.Block) error {
97 if c.BlockHash != block.PreviousBlockHash {
98 return errors.New("block parent hash is not equals last block hash of vote result")
101 if err := c.AttachCoinbaseReward(block); err != nil {
105 for _, tx := range block.Transactions {
106 for _, input := range tx.Inputs {
107 vetoInput, ok := input.TypedInput.(*types.VetoInput)
112 pubkey := hex.EncodeToString(vetoInput.Vote)
113 c.NumOfVote[pubkey], ok = checked.SubUint64(c.NumOfVote[pubkey], vetoInput.Amount)
115 return checked.ErrOverflow
118 if c.NumOfVote[pubkey] == 0 {
119 delete(c.NumOfVote, pubkey)
123 for _, output := range tx.Outputs {
124 voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
129 pubkey := hex.EncodeToString(voteOutput.Vote)
130 if c.NumOfVote[pubkey], ok = checked.AddUint64(c.NumOfVote[pubkey], voteOutput.Amount); !ok {
131 return checked.ErrOverflow
136 c.BlockHash = block.Hash()
137 c.BlockHeight = block.Height
138 c.Seq = CalcVoteSeq(block.Height)
142 // ConsensusNodes returns all consensus nodes
143 func (c *ConsensusResult) ConsensusNodes() (map[string]*ConsensusNode, error) {
144 var nodes []*ConsensusNode
145 for pubkey, voteNum := range c.NumOfVote {
146 if voteNum >= consensus.MinConsensusNodeVoteNum {
147 var xpub chainkd.XPub
148 if err := xpub.UnmarshalText([]byte(pubkey)); err != nil {
152 nodes = append(nodes, &ConsensusNode{XPub: xpub, VoteNum: voteNum})
155 // In principle, there is no need to sort all voting nodes.
156 // if there is a performance problem, consider the optimization later.
157 sort.Sort(byVote(nodes))
158 result := make(map[string]*ConsensusNode)
159 for i := 0; i < len(nodes) && i < consensus.NumOfConsensusNode; i++ {
160 nodes[i].Order = uint64(i)
161 result[nodes[i].XPub.String()] = nodes[i]
164 if len(result) != 0 {
167 return federationNodes(), nil
170 func federationNodes() map[string]*ConsensusNode {
171 consensusResult := map[string]*ConsensusNode{}
172 for i, xpub := range config.CommonConfig.Federation.Xpubs {
173 consensusResult[xpub.String()] = &ConsensusNode{XPub: xpub, VoteNum: 0, Order: uint64(i)}
175 return consensusResult
178 // DetachBlock calculate the consensus result for detach block
179 func (c *ConsensusResult) DetachBlock(block *types.Block) error {
180 if c.BlockHash != block.Hash() {
181 return errors.New("block hash is not equals last block hash of vote result")
184 if err := c.DetachCoinbaseReward(block); err != nil {
188 for i := len(block.Transactions) - 1; i >= 0; i-- {
189 tx := block.Transactions[i]
190 for _, input := range tx.Inputs {
191 vetoInput, ok := input.TypedInput.(*types.VetoInput)
196 pubkey := hex.EncodeToString(vetoInput.Vote)
197 if c.NumOfVote[pubkey], ok = checked.AddUint64(c.NumOfVote[pubkey], vetoInput.Amount); !ok {
198 return checked.ErrOverflow
202 for _, output := range tx.Outputs {
203 voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
208 pubkey := hex.EncodeToString(voteOutput.Vote)
209 c.NumOfVote[pubkey], ok = checked.SubUint64(c.NumOfVote[pubkey], voteOutput.Amount)
211 return checked.ErrOverflow
214 if c.NumOfVote[pubkey] == 0 {
215 delete(c.NumOfVote, pubkey)
220 c.BlockHash = block.PreviousBlockHash
221 c.BlockHeight = block.Height - 1
222 c.Seq = CalcVoteSeq(block.Height - 1)
226 func (c *ConsensusResult) Fork() *ConsensusResult {
227 f := &ConsensusResult{
229 NumOfVote: map[string]uint64{},
230 CoinbaseReward: map[string]uint64{},
231 BlockHash: c.BlockHash,
232 BlockHeight: c.BlockHeight,
235 for key, value := range c.NumOfVote {
236 f.NumOfVote[key] = value
239 for key, value := range c.CoinbaseReward {
240 f.CoinbaseReward[key] = value
245 func (c *ConsensusResult) IsFinalize() bool {
246 return c.BlockHeight%consensus.RoundVoteBlockNums == 0
249 // AttachCoinbaseReward attach coinbase reward
250 func (c *ConsensusResult) AttachCoinbaseReward(block *types.Block) error {
251 reward, err := CalCoinbaseReward(block)
256 if block.Height%consensus.RoundVoteBlockNums == 1 {
257 c.CoinbaseReward = map[string]uint64{}
261 program := hex.EncodeToString(reward.ControlProgram)
262 c.CoinbaseReward[program], ok = checked.AddUint64(c.CoinbaseReward[program], reward.Amount)
264 return checked.ErrOverflow
269 // DetachCoinbaseReward detach coinbase reward
270 func (c *ConsensusResult) DetachCoinbaseReward(block *types.Block) error {
271 if block.Height%consensus.RoundVoteBlockNums == 0 {
272 for i, output := range block.Transactions[0].Outputs {
276 program := output.ControlProgram()
277 c.CoinbaseReward[hex.EncodeToString(program)] = output.AssetAmount().Amount
281 reward, err := CalCoinbaseReward(block)
287 program := hex.EncodeToString(reward.ControlProgram)
288 if c.CoinbaseReward[program], ok = checked.SubUint64(c.CoinbaseReward[program], reward.Amount); !ok {
289 return checked.ErrOverflow
292 if c.CoinbaseReward[program] == 0 {
293 delete(c.CoinbaseReward, program)
298 // GetCoinbaseRewards convert into CoinbaseReward array and sort it by amount
299 func (c *ConsensusResult) GetCoinbaseRewards(blockHeight uint64) ([]CoinbaseReward, error) {
300 rewards := []CoinbaseReward{}
301 if blockHeight%consensus.RoundVoteBlockNums != 0 {
305 for p, amount := range c.CoinbaseReward {
306 coinbaseAmount := amount
307 program, err := hex.DecodeString(p)
312 rewards = append(rewards, CoinbaseReward{
313 Amount: coinbaseAmount,
314 ControlProgram: program,
317 sort.Sort(SortByAmount(rewards))