}
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
-// consensus node of the current round is the final result of previous round
-func CalcVoteSeq(blockHeight uint64) uint64 {
- if blockHeight == 0 {
- return 0
- }
- return (blockHeight-1)/consensus.RoundVoteBlockNums + 1
-}
-
-// 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
-// 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
// CalCoinbaseReward calculate the coinbase reward for block
func CalCoinbaseReward(block *types.Block) (*CoinbaseReward, error) {
- var coinbaseReceiver []byte
+ result := &CoinbaseReward{}
if len(block.Transactions) > 0 && len(block.Transactions[0].Outputs) > 0 {
- coinbaseReceiver = block.Transactions[0].Outputs[0].ControlProgram()
- }
-
- if coinbaseReceiver == nil {
+ result.ControlProgram = block.Transactions[0].Outputs[0].ControlProgram()
+ } else {
return nil, errors.New("not found coinbase receiver")
}
- coinbaseAmount := consensus.BlockSubsidy(block.BlockHeader.Height)
+ result.Amount = 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
+
+ result.Amount += txFee
+ }
+ return result, nil
+}
+
+// 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
+// consensus node of the current round is the final result of previous round
+func CalcVoteSeq(blockHeight uint64) uint64 {
+ if blockHeight == 0 {
+ return 0
}
+ return (blockHeight-1)/consensus.RoundVoteBlockNums + 1
+}
- return &CoinbaseReward{
- Amount: coinbaseAmount,
- ControlProgram: coinbaseReceiver,
- }, nil
+// 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
+// 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
}
// ApplyBlock calculate the consensus result for new block
return nil
}
+// 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
+}
+
// ConsensusNodes returns all consensus nodes
func (c *ConsensusResult) ConsensusNodes() (map[string]*ConsensusNode, error) {
var nodes []*ConsensusNode
return federationNodes(), nil
}
-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 nil
}
-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 c.NumOfVote {
- f.NumOfVote[key] = value
- }
-
- for key, value := range c.CoinbaseReward {
- f.CoinbaseReward[key] = value
- }
- return f
-}
-
-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 {
return nil
}
+// Fork copy the ConsensusResult struct
+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 c.NumOfVote {
+ f.NumOfVote[key] = value
+ }
+
+ for key, value := range c.CoinbaseReward {
+ f.CoinbaseReward[key] = value
+ }
+ return f
+}
+
+// IsFinalize check if the result is end of consensus round
+func (c *ConsensusResult) IsFinalize() bool {
+ return c.BlockHeight%consensus.RoundVoteBlockNums == 0
+}
+
// GetCoinbaseRewards convert into CoinbaseReward array and sort it by amount
func (c *ConsensusResult) GetCoinbaseRewards(blockHeight uint64) ([]CoinbaseReward, error) {
rewards := []CoinbaseReward{}
}
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,
+ Amount: amount,
ControlProgram: program,
})
}
sort.Sort(SortByAmount(rewards))
return rewards, nil
}
+
+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
+}
}
func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx, statusFail bool) error {
- for _, prevout := range tx.MainchainOutputIDs {
- entry, ok := view.Entries[prevout]
- if !ok {
- return errors.New("fail to find mainchain output entry")
- }
+ if err := view.applyCrossChainUtxo(block, tx); err != nil {
+ return err
+ }
- if entry.Type != storage.CrosschainUTXOType {
- return errors.New("look up mainchainOutputID but find utxo not from mainchain")
+ if err := view.applySpendUtxo(block, tx, statusFail); err != nil {
+ return err
+ }
+
+ return view.applyOutputUtxo(block, tx, statusFail)
+}
+
+func (view *UtxoViewpoint) ApplyBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
+ for i, tx := range block.Transactions {
+ statusFail, err := txStatus.GetStatus(i)
+ if err != nil {
+ return err
}
- if entry.Spent {
- return errors.New("mainchain output has been spent")
+ if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
+ return err
}
+ }
+ return nil
+}
- entry.BlockHeight = block.Height
- entry.SpendOutput()
+func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
+ entry := view.Entries[*hash]
+ return entry != nil && !entry.Spent
+}
+
+func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx, statusFail bool) error {
+ if err := view.detachCrossChainUtxo(tx); err != nil {
+ return err
}
- for _, prevout := range tx.SpentOutputIDs {
- assetID := bc.AssetID{}
- entryOutput, err := tx.Entry(prevout)
+ if err := view.detachSpendUtxo(tx, statusFail); err != nil {
+ return err
+ }
+
+ return view.detachOutputUtxo(tx, statusFail)
+}
+
+func (view *UtxoViewpoint) DetachBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
+ for i := len(block.Transactions) - 1; i >= 0; i-- {
+ statusFail, err := txStatus.GetStatus(i)
if err != nil {
return err
}
- switch output := entryOutput.(type) {
- case *bc.IntraChainOutput:
- assetID = *output.Source.Value.AssetId
- case *bc.VoteOutput:
- assetID = *output.Source.Value.AssetId
- default:
- return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
+ if err := view.DetachTransaction(block.Transactions[i], statusFail); err != nil {
+ return err
}
+ }
+ return nil
+}
- if statusFail && assetID != *consensus.BTMAssetID {
- continue
- }
+func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
+ _, ok := view.Entries[*hash]
+ return ok
+}
+func (view *UtxoViewpoint) applyCrossChainUtxo(block *bc.Block, tx *bc.Tx) error {
+ for _, prevout := range tx.MainchainOutputIDs {
entry, ok := view.Entries[prevout]
if !ok {
- return errors.New("fail to find utxo entry")
+ return errors.New("fail to find mainchain output entry")
}
if entry.Spent {
- return errors.New("utxo has been spent")
- }
-
- switch entry.Type {
- case storage.CrosschainUTXOType:
- return errors.New("look up spentOutputID but find utxo from mainchain")
-
- case storage.CoinbaseUTXOType:
- if (entry.BlockHeight + consensus.CoinbasePendingBlockNumber) > block.Height {
- return errors.New("coinbase utxo is not ready for use")
- }
-
- case storage.VoteUTXOType:
- if (entry.BlockHeight + consensus.VotePendingBlockNumber) > block.Height {
- return errors.New("Coin is within the voting lock time")
- }
+ return errors.New("mainchain output has been spent")
}
+ entry.BlockHeight = block.Height
entry.SpendOutput()
}
+ return nil
+}
+func (view *UtxoViewpoint) applyOutputUtxo(block *bc.Block, tx *bc.Tx, statusFail bool) error {
for _, id := range tx.TxHeader.ResultIds {
- assetID := bc.AssetID{}
entryOutput, err := tx.Entry(*id)
if err != nil {
- continue
+ return err
}
+ var assetID bc.AssetID
utxoType := storage.NormalUTXOType
-
switch output := entryOutput.(type) {
case *bc.IntraChainOutput:
if output.Source.Value.Amount == uint64(0) {
return nil
}
-func (view *UtxoViewpoint) ApplyBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
- for i, tx := range block.Transactions {
- statusFail, err := txStatus.GetStatus(i)
- if err != nil {
- return err
- }
- if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
- entry := view.Entries[*hash]
- return entry != nil && !entry.Spent
-}
-
-func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx, statusFail bool) error {
- for _, prevout := range tx.MainchainOutputIDs {
- // don't simply delete(view.Entries, prevout), because we need to delete from db in saveUtxoView()
- entry, ok := view.Entries[prevout]
- if ok && (entry.Type != storage.CrosschainUTXOType) {
- return errors.New("look up mainchainOutputID but find utxo not from mainchain")
- }
-
- if ok && !entry.Spent {
- return errors.New("try to revert an unspent utxo")
- }
-
- if !ok {
- view.Entries[prevout] = storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, false)
- continue
- }
- entry.UnspendOutput()
- }
-
+func (view *UtxoViewpoint) applySpendUtxo(block *bc.Block, tx *bc.Tx, statusFail bool) error {
for _, prevout := range tx.SpentOutputIDs {
- assetID := bc.AssetID{}
entryOutput, err := tx.Entry(prevout)
if err != nil {
return err
}
- utxoType := storage.NormalUTXOType
+ var assetID bc.AssetID
switch output := entryOutput.(type) {
case *bc.IntraChainOutput:
assetID = *output.Source.Value.AssetId
case *bc.VoteOutput:
assetID = *output.Source.Value.AssetId
- utxoType = storage.VoteUTXOType
default:
return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
}
}
entry, ok := view.Entries[prevout]
- if ok && (entry.Type == storage.CrosschainUTXOType) {
- return errors.New("look up SpentOutputIDs but find mainchain utxo")
+ if !ok {
+ return errors.New("fail to find utxo entry")
}
- if ok && !entry.Spent {
- return errors.New("try to revert an unspent utxo")
+ if entry.Spent {
+ return errors.New("utxo has been spent")
}
+ switch entry.Type {
+ case storage.CoinbaseUTXOType:
+ if (entry.BlockHeight + consensus.CoinbasePendingBlockNumber) > block.Height {
+ return errors.New("coinbase utxo is not ready for use")
+ }
+
+ case storage.VoteUTXOType:
+ if (entry.BlockHeight + consensus.VotePendingBlockNumber) > block.Height {
+ return errors.New("Coin is within the voting lock time")
+ }
+ }
+
+ entry.SpendOutput()
+ }
+ return nil
+}
+
+func (view *UtxoViewpoint) detachCrossChainUtxo(tx *bc.Tx) error {
+ for _, prevout := range tx.MainchainOutputIDs {
+ entry, ok := view.Entries[prevout]
if !ok {
- view.Entries[prevout] = storage.NewUtxoEntry(utxoType, 0, false)
- continue
+ return errors.New("fail to find mainchain output entry")
}
+
+ if !entry.Spent {
+ return errors.New("mainchain output is unspent")
+ }
+
entry.UnspendOutput()
}
+ return nil
+}
+func (view *UtxoViewpoint) detachOutputUtxo(tx *bc.Tx, statusFail bool) error {
for _, id := range tx.TxHeader.ResultIds {
- assetID := bc.AssetID{}
entryOutput, err := tx.Entry(*id)
if err != nil {
- continue
+ return err
}
+ var assetID bc.AssetID
utxoType := storage.NormalUTXOType
switch output := entryOutput.(type) {
case *bc.IntraChainOutput:
return nil
}
-func (view *UtxoViewpoint) DetachBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
- for i := len(block.Transactions) - 1; i >= 0; i-- {
- statusFail, err := txStatus.GetStatus(i)
+func (view *UtxoViewpoint) detachSpendUtxo(tx *bc.Tx, statusFail bool) error {
+ for _, prevout := range tx.SpentOutputIDs {
+ entryOutput, err := tx.Entry(prevout)
if err != nil {
return err
}
- if err := view.DetachTransaction(block.Transactions[i], statusFail); err != nil {
- return err
+
+ var assetID bc.AssetID
+ utxoType := storage.NormalUTXOType
+ switch output := entryOutput.(type) {
+ case *bc.IntraChainOutput:
+ assetID = *output.Source.Value.AssetId
+ case *bc.VoteOutput:
+ assetID = *output.Source.Value.AssetId
+ utxoType = storage.VoteUTXOType
+ default:
+ return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
+ }
+
+ if statusFail && assetID != *consensus.BTMAssetID {
+ continue
+ }
+
+ entry, ok := view.Entries[prevout]
+ if ok && !entry.Spent {
+ return errors.New("try to revert an unspent utxo")
}
+
+ if !ok {
+ view.Entries[prevout] = storage.NewUtxoEntry(utxoType, 0, false)
+ continue
+ }
+ entry.UnspendOutput()
}
return nil
}
-
-func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
- _, ok := view.Entries[*hash]
- return ok
-}