"encoding/hex"
"sort"
+ "github.com/vapor/common/arithmetic"
+ "github.com/vapor/config"
"github.com/vapor/consensus"
"github.com/vapor/crypto/ed25519/chainkd"
"github.com/vapor/errors"
"github.com/vapor/protocol/bc/types"
)
-var errVotingOperationOverFlow = errors.New("voting operation result overflow")
-
+// ConsensusNode represents a consensus node
type ConsensusNode struct {
XPub chainkd.XPub
VoteNum uint64
}
func (c byVote) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
+// CalcVoteSeq calculate the vote sequence
// seq 0 is the genesis block
// seq 1 is the the block height 1, to block height RoundVoteBlockNums
// seq 2 is the block height RoundVoteBlockNums + 1 to block height 2 * RoundVoteBlockNums
return (blockHeight-1)/consensus.RoundVoteBlockNums + 1
}
-// VoteResult represents a snapshot of each round of DPOS voting
+// ConsensusResult represents a snapshot of each round of DPOS voting
// Seq indicates the sequence of current votes, which start from zero
// NumOfVote indicates the number of votes each consensus node receives, the key of map represent public key
-// Finalized indicates whether this vote is finalized
-type VoteResult struct {
- Seq uint64
- NumOfVote map[string]uint64
- BlockHash bc.Hash
- BlockHeight uint64
+// CoinbaseReward indicates the coinbase receiver and reward
+type ConsensusResult struct {
+ Seq uint64
+ NumOfVote map[string]uint64
+ CoinbaseReward map[string]uint64
+ BlockHash bc.Hash
+ BlockHeight uint64
+}
+
+// CoinbaseReward contains receiver and reward
+type CoinbaseReward struct {
+ Amount uint64
+ ControlProgram []byte
}
-func (v *VoteResult) ApplyBlock(block *types.Block) error {
- if v.BlockHash != block.PreviousBlockHash {
+// SortByAmount implements sort.Interface for CoinbaseReward slices
+type SortByAmount []CoinbaseReward
+
+func (a SortByAmount) Len() int { return len(a) }
+func (a SortByAmount) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a SortByAmount) Less(i, j int) bool { return a[i].Amount < a[j].Amount }
+
+// CalCoinbaseReward calculate the coinbase reward for block
+func CalCoinbaseReward(block *types.Block) (*CoinbaseReward, error) {
+ var coinbaseReceiver []byte
+ if len(block.Transactions) > 0 && len(block.Transactions[0].Outputs) > 0 {
+ coinbaseReceiver = block.Transactions[0].Outputs[0].ControlProgram()
+ }
+
+ if coinbaseReceiver == nil {
+ return nil, errors.New("not found coinbase receiver")
+ }
+
+ coinbaseAmount := consensus.BlockSubsidy(block.BlockHeader.Height)
+ for _, tx := range block.Transactions {
+ txFee, err := arithmetic.CalculateTxFee(tx)
+ if err != nil {
+ return nil, errors.Wrap(checked.ErrOverflow, "calculate transaction fee")
+ }
+ coinbaseAmount += txFee
+ }
+
+ return &CoinbaseReward{
+ Amount: coinbaseAmount,
+ ControlProgram: coinbaseReceiver,
+ }, nil
+}
+
+// ApplyBlock calculate the consensus result for new block
+func (c *ConsensusResult) ApplyBlock(block *types.Block) error {
+ if c.BlockHash != block.PreviousBlockHash {
return errors.New("block parent hash is not equals last block hash of vote result")
}
+ if err := c.AttachCoinbaseReward(block); err != nil {
+ return err
+ }
+
for _, tx := range block.Transactions {
for _, input := range tx.Inputs {
- unVoteInput, ok := input.TypedInput.(*types.UnvoteInput)
+ vetoInput, ok := input.TypedInput.(*types.VetoInput)
if !ok {
continue
}
- pubkey := hex.EncodeToString(unVoteInput.Vote)
- v.NumOfVote[pubkey], ok = checked.SubUint64(v.NumOfVote[pubkey], unVoteInput.Amount)
+ pubkey := hex.EncodeToString(vetoInput.Vote)
+ c.NumOfVote[pubkey], ok = checked.SubUint64(c.NumOfVote[pubkey], vetoInput.Amount)
if !ok {
- return errVotingOperationOverFlow
+ return checked.ErrOverflow
}
- if v.NumOfVote[pubkey] == 0 {
- delete(v.NumOfVote, pubkey)
+ if c.NumOfVote[pubkey] == 0 {
+ delete(c.NumOfVote, pubkey)
}
}
for _, output := range tx.Outputs {
- voteOutput, ok := output.TypedOutput.(*types.VoteTxOutput)
+ voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
if !ok {
continue
}
pubkey := hex.EncodeToString(voteOutput.Vote)
- if v.NumOfVote[pubkey], ok = checked.AddUint64(v.NumOfVote[pubkey], voteOutput.Amount); !ok {
- return errVotingOperationOverFlow
+ if c.NumOfVote[pubkey], ok = checked.AddUint64(c.NumOfVote[pubkey], voteOutput.Amount); !ok {
+ return checked.ErrOverflow
}
}
}
- v.BlockHash = block.Hash()
- v.BlockHeight = block.Height
- v.Seq = CalcVoteSeq(block.Height)
+ c.BlockHash = block.Hash()
+ c.BlockHeight = block.Height
+ c.Seq = CalcVoteSeq(block.Height)
return nil
}
-func (v *VoteResult) ConsensusNodes() (map[string]*ConsensusNode, error) {
+// ConsensusNodes returns all consensus nodes
+func (c *ConsensusResult) ConsensusNodes() (map[string]*ConsensusNode, error) {
var nodes []*ConsensusNode
- for pubkey, voteNum := range v.NumOfVote {
- if voteNum >= consensus.MinVoteNum {
+ for pubkey, voteNum := range c.NumOfVote {
+ if voteNum >= consensus.MinConsensusNodeVoteNum {
var xpub chainkd.XPub
if err := xpub.UnmarshalText([]byte(pubkey)); err != nil {
return nil, err
nodes[i].Order = uint64(i)
result[nodes[i].XPub.String()] = nodes[i]
}
- return result, nil
+
+ if len(result) != 0 {
+ return result, nil
+ }
+ return federationNodes(), nil
}
-func (v *VoteResult) DetachBlock(block *types.Block) error {
- if v.BlockHash != block.Hash() {
+func federationNodes() map[string]*ConsensusNode {
+ consensusResult := map[string]*ConsensusNode{}
+ for i, xpub := range config.CommonConfig.Federation.Xpubs {
+ consensusResult[xpub.String()] = &ConsensusNode{XPub: xpub, VoteNum: 0, Order: uint64(i)}
+ }
+ return consensusResult
+}
+
+// DetachBlock calculate the consensus result for detach block
+func (c *ConsensusResult) DetachBlock(block *types.Block) error {
+ if c.BlockHash != block.Hash() {
return errors.New("block hash is not equals last block hash of vote result")
}
+ if err := c.DetachCoinbaseReward(block); err != nil {
+ return err
+ }
+
for i := len(block.Transactions) - 1; i >= 0; i-- {
tx := block.Transactions[i]
for _, input := range tx.Inputs {
- unVoteInput, ok := input.TypedInput.(*types.UnvoteInput)
+ vetoInput, ok := input.TypedInput.(*types.VetoInput)
if !ok {
continue
}
- pubkey := hex.EncodeToString(unVoteInput.Vote)
- if v.NumOfVote[pubkey], ok = checked.AddUint64(v.NumOfVote[pubkey], unVoteInput.Amount); !ok {
- return errVotingOperationOverFlow
+ pubkey := hex.EncodeToString(vetoInput.Vote)
+ if c.NumOfVote[pubkey], ok = checked.AddUint64(c.NumOfVote[pubkey], vetoInput.Amount); !ok {
+ return checked.ErrOverflow
}
}
for _, output := range tx.Outputs {
- voteOutput, ok := output.TypedOutput.(*types.VoteTxOutput)
+ voteOutput, ok := output.TypedOutput.(*types.VoteOutput)
if !ok {
continue
}
pubkey := hex.EncodeToString(voteOutput.Vote)
- v.NumOfVote[pubkey], ok = checked.SubUint64(v.NumOfVote[pubkey], voteOutput.Amount)
+ c.NumOfVote[pubkey], ok = checked.SubUint64(c.NumOfVote[pubkey], voteOutput.Amount)
if !ok {
- return errVotingOperationOverFlow
+ return checked.ErrOverflow
}
- if v.NumOfVote[pubkey] == 0 {
- delete(v.NumOfVote, pubkey)
+ if c.NumOfVote[pubkey] == 0 {
+ delete(c.NumOfVote, pubkey)
}
}
}
- v.BlockHash = block.PreviousBlockHash
- v.BlockHeight = block.Height - 1
- v.Seq = CalcVoteSeq(block.Height - 1)
+ c.BlockHash = block.PreviousBlockHash
+ c.BlockHeight = block.Height - 1
+ c.Seq = CalcVoteSeq(block.Height - 1)
return nil
}
-func (v *VoteResult) Fork() *VoteResult {
- f := &VoteResult{
- Seq: v.Seq,
- NumOfVote: map[string]uint64{},
- BlockHash: v.BlockHash,
- BlockHeight: v.BlockHeight,
+func (c *ConsensusResult) Fork() *ConsensusResult {
+ f := &ConsensusResult{
+ Seq: c.Seq,
+ NumOfVote: map[string]uint64{},
+ CoinbaseReward: map[string]uint64{},
+ BlockHash: c.BlockHash,
+ BlockHeight: c.BlockHeight,
}
- for key, value := range v.NumOfVote {
+ for key, value := range c.NumOfVote {
f.NumOfVote[key] = value
}
+
+ for key, value := range c.CoinbaseReward {
+ f.CoinbaseReward[key] = value
+ }
return f
}
-func (v *VoteResult) IsFinalize() bool {
- return v.BlockHeight%consensus.RoundVoteBlockNums == 0
+func (c *ConsensusResult) IsFinalize() bool {
+ return c.BlockHeight%consensus.RoundVoteBlockNums == 0
+}
+
+// AttachCoinbaseReward attach coinbase reward
+func (c *ConsensusResult) AttachCoinbaseReward(block *types.Block) error {
+ reward, err := CalCoinbaseReward(block)
+ if err != nil {
+ return err
+ }
+
+ if block.Height%consensus.RoundVoteBlockNums == 1 {
+ c.CoinbaseReward = map[string]uint64{}
+ }
+
+ var ok bool
+ program := hex.EncodeToString(reward.ControlProgram)
+ c.CoinbaseReward[program], ok = checked.AddUint64(c.CoinbaseReward[program], reward.Amount)
+ if !ok {
+ return checked.ErrOverflow
+ }
+ return nil
+}
+
+// DetachCoinbaseReward detach coinbase reward
+func (c *ConsensusResult) DetachCoinbaseReward(block *types.Block) error {
+ if block.Height%consensus.RoundVoteBlockNums == 0 {
+ for i, output := range block.Transactions[0].Outputs {
+ if i == 0 {
+ continue
+ }
+ program := output.ControlProgram()
+ c.CoinbaseReward[hex.EncodeToString(program)] = output.AssetAmount().Amount
+ }
+ }
+
+ reward, err := CalCoinbaseReward(block)
+ if err != nil {
+ return err
+ }
+
+ var ok bool
+ program := hex.EncodeToString(reward.ControlProgram)
+ if c.CoinbaseReward[program], ok = checked.SubUint64(c.CoinbaseReward[program], reward.Amount); !ok {
+ return checked.ErrOverflow
+ }
+
+ if c.CoinbaseReward[program] == 0 {
+ delete(c.CoinbaseReward, program)
+ }
+ return nil
+}
+
+// GetCoinbaseRewards convert into CoinbaseReward array and sort it by amount
+func (c *ConsensusResult) GetCoinbaseRewards(blockHeight uint64) ([]CoinbaseReward, error) {
+ rewards := []CoinbaseReward{}
+ if blockHeight%consensus.RoundVoteBlockNums != 0 {
+ return rewards, nil
+ }
+
+ for p, amount := range c.CoinbaseReward {
+ coinbaseAmount := amount
+ program, err := hex.DecodeString(p)
+ if err != nil {
+ return nil, err
+ }
+
+ rewards = append(rewards, CoinbaseReward{
+ Amount: coinbaseAmount,
+ ControlProgram: program,
+ })
+ }
+ sort.Sort(SortByAmount(rewards))
+ return rewards, nil
}