OSDN Git Service

fix ci
[bytom/vapor.git] / proposal / proposal.go
index b620465..af98f2c 100644 (file)
@@ -20,184 +20,274 @@ import (
 )
 
 const (
-       logModule = "mining"
+       logModule     = "mining"
+       batchApplyNum = 64
+
+       timeoutOk = iota + 1
+       timeoutWarn
+       timeoutCritical
 )
 
-// createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
-// based on the passed block height to the provided address.  When the address
-// is nil, the coinbase transaction will instead be redeemable by anyone.
-func createCoinbaseTx(accountManager *account.Manager, chain *protocol.Chain, preBlockHeader *types.BlockHeader) (tx *types.Tx, err error) {
-       preBlockHash := preBlockHeader.Hash()
-       consensusResult, err := chain.GetConsensusResultByHash(&preBlockHash)
-       if err != nil {
-               return nil, err
-       }
+// NewBlockTemplate returns a new block template that is ready to be solved
+func NewBlockTemplate(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) (*types.Block, error) {
+       builder := newBlockBuilder(chain, accountManager, timestamp, warnDuration, criticalDuration)
+       return builder.build()
+}
 
-       rewards, err := consensusResult.GetCoinbaseRewards(preBlockHeader.Height)
-       if err != nil {
-               return nil, err
-       }
+type blockBuilder struct {
+       chain          *protocol.Chain
+       accountManager *account.Manager
+
+       block    *types.Block
+       txStatus *bc.TransactionStatus
+       utxoView *state.UtxoViewpoint
 
-       return createCoinbaseTxByReward(accountManager, preBlockHeader.Height + 1, rewards)
+       warnTimeoutCh     <-chan time.Time
+       criticalTimeoutCh <-chan time.Time
+       timeoutStatus     uint8
+       gasLeft           int64
 }
 
-func createCoinbaseTxByReward(accountManager *account.Manager, blockHeight uint64, rewards []state.CoinbaseReward) (tx *types.Tx, err error) {
-       arbitrary := append([]byte{0x00}, []byte(strconv.FormatUint(blockHeight, 10))...)
-       var script []byte
-       if accountManager == nil {
-               script, err = vmutil.DefaultCoinbaseProgram()
-       } else {
-               script, err = accountManager.GetCoinbaseControlProgram()
-               arbitrary = append(arbitrary, accountManager.GetCoinbaseArbitrary()...)
+func newBlockBuilder(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) *blockBuilder {
+       preBlockHeader := chain.BestBlockHeader()
+       block := &types.Block{
+               BlockHeader: types.BlockHeader{
+                       Version:           1,
+                       Height:            preBlockHeader.Height + 1,
+                       PreviousBlockHash: preBlockHeader.Hash(),
+                       Timestamp:         timestamp,
+                       BlockCommitment:   types.BlockCommitment{},
+                       BlockWitness:      types.BlockWitness{Witness: make([][]byte, consensus.ActiveNetParams.NumOfConsensusNode)},
+               },
        }
-       if err != nil {
-               return nil, err
+
+       builder := &blockBuilder{
+               chain:             chain,
+               accountManager:    accountManager,
+               block:             block,
+               txStatus:          bc.NewTransactionStatus(),
+               utxoView:          state.NewUtxoViewpoint(),
+               warnTimeoutCh:     time.After(warnDuration),
+               criticalTimeoutCh: time.After(criticalDuration),
+               gasLeft:           int64(consensus.ActiveNetParams.MaxBlockGas),
+               timeoutStatus:     timeoutOk,
        }
+       return builder
+}
 
-       if len(arbitrary) > consensus.ActiveNetParams.CoinbaseArbitrarySizeLimit {
-               return nil, validation.ErrCoinbaseArbitraryOversize
+func (b *blockBuilder) applyCoinbaseTransaction() error {
+       coinbaseTx, err := b.createCoinbaseTx()
+       if err != nil {
+               return errors.Wrap(err, "fail on create coinbase tx")
        }
 
-       builder := txbuilder.NewBuilder(time.Now())
-       if err = builder.AddInput(types.NewCoinbaseInput(arbitrary), &txbuilder.SigningInstruction{}); err != nil {
-               return nil, err
+       gasState, err := validation.ValidateTx(coinbaseTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Height: b.block.Height}, Transactions: []*bc.Tx{coinbaseTx.Tx}})
+       if err != nil {
+               return err
        }
-       if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, 0, script)); err != nil {
-               return nil, err
+
+       b.block.Transactions = append(b.block.Transactions, coinbaseTx)
+       if err := b.txStatus.SetStatus(0, false); err != nil {
+               return err
        }
 
-       for _, r := range rewards {
-               if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, r.Amount, r.ControlProgram)); err != nil {
-                       return nil, err
+       b.gasLeft -= gasState.GasUsed
+       return nil
+}
+func (b *blockBuilder) applyTransactions(txs []*types.Tx) error {
+       tempTxs := []*types.Tx{}
+       for i := 0; i < len(txs); i++ {
+               if tempTxs = append(tempTxs, txs[i]); len(tempTxs) < batchApplyNum && i != len(txs)-1 {
+                       continue
+               }
+
+               results, gasLeft := preValidateTxs(tempTxs, b.chain, b.utxoView, b.gasLeft)
+               for _, result := range results {
+                       if result.err != nil && !result.gasOnly {
+                               log.WithFields(log.Fields{"module": logModule, "error": result.err}).Error("mining block generation: skip tx due to")
+                               b.chain.GetTxPool().RemoveTransaction(&result.tx.ID)
+                               continue
+                       }
+
+                       if err := b.txStatus.SetStatus(len(b.block.Transactions), result.gasOnly); err != nil {
+                               return err
+                       }
+
+                       b.block.Transactions = append(b.block.Transactions, result.tx)
+               }
+
+               b.gasLeft = gasLeft
+               tempTxs = []*types.Tx{}
+               if b.getTimeoutStatus() == timeoutCritical {
+                       break
                }
        }
+       return nil
+}
 
-       _, txData, err := builder.Build()
-       if err != nil {
-               return nil, err
+func (b *blockBuilder) applyTransactionFromPool() error {
+       txDescList := b.chain.GetTxPool().GetTransactions()
+       sort.Sort(byTime(txDescList))
+
+       poolTxs := make([]*types.Tx, len(txDescList))
+       for i, txDesc := range txDescList {
+               poolTxs[i] = txDesc.Tx
        }
 
-       byteData, err := txData.MarshalText()
+       return b.applyTransactions(poolTxs)
+}
+
+func (b *blockBuilder) applyTransactionFromSubProtocol() error {
+       cp, err := b.accountManager.GetCoinbaseControlProgram()
        if err != nil {
-               return nil, err
+               return err
        }
 
-       txData.SerializedSize = uint64(len(byteData))
-       tx = &types.Tx{
-               TxData: *txData,
-               Tx:     types.MapTx(txData),
+       isTimeout := func() bool {
+               return b.getTimeoutStatus() > timeoutOk
        }
-       return tx, nil
-}
 
-// NewBlockTemplate returns a new block template that is ready to be solved
-func NewBlockTemplate(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64) (*types.Block, error) {
-       block := createBasicBlock(chain, timestamp)
+       for i, p := range b.chain.SubProtocols() {
+               if b.gasLeft <= 0 || isTimeout() {
+                       break
+               }
 
-       view := state.NewUtxoViewpoint()
-       txStatus := bc.NewTransactionStatus()
+               subTxs, err := p.BeforeProposalBlock(b.block.Transactions, cp, b.block.Height, b.gasLeft, isTimeout)
+               if err != nil {
+                       log.WithFields(log.Fields{"module": logModule, "index": i, "error": err}).Error("failed on sub protocol txs package")
+                       continue
+               }
 
-       gasLeft, err := applyCoinbaseTransaction(chain, block, txStatus, accountManager, int64(consensus.ActiveNetParams.MaxBlockGas))
-       if err != nil {
-               return nil, err
+               if err := b.applyTransactions(subTxs); err != nil {
+                       return err
+               }
        }
+       return nil
+}
 
-       gasLeft, err = applyTransactionFromPool(chain, view, block, txStatus, gasLeft)
-       if err != nil {
+func (b *blockBuilder) build() (*types.Block, error) {
+       if err := b.applyCoinbaseTransaction(); err != nil {
                return nil, err
        }
-       
-       if err := applyTransactionFromSubProtocol(chain, view, block, txStatus, accountManager, gasLeft); err != nil {
+
+       if err := b.applyTransactionFromPool(); err != nil {
                return nil, err
        }
 
-       var txEntries []*bc.Tx
-       for _, tx := range block.Transactions {
-               txEntries = append(txEntries, tx.Tx)
+       if err := b.applyTransactionFromSubProtocol(); err != nil {
+               return nil, err
        }
 
-       block.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
-       if err != nil {
+       if err := b.calcBlockCommitment(); err != nil {
                return nil, err
        }
 
-       block.BlockHeader.BlockCommitment.TransactionStatusHash, err = types.TxStatusMerkleRoot(txStatus.VerifyStatus)
-
-       _, err = chain.SignBlock(block)
-       return block, err
+       _, err := b.chain.SignBlock(b.block)
+       return b.block, err
 }
 
-func createBasicBlock(chain *protocol.Chain, timestamp uint64) *types.Block {
-       preBlockHeader := chain.BestBlockHeader()
-       return &types.Block{
-               BlockHeader: types.BlockHeader{
-                       Version:           1,
-                       Height:            preBlockHeader.Height + 1,
-                       PreviousBlockHash: preBlockHeader.Hash(),
-                       Timestamp:         timestamp,
-                       BlockCommitment:   types.BlockCommitment{},
-                       BlockWitness:      types.BlockWitness{Witness: make([][]byte, consensus.ActiveNetParams.NumOfConsensusNode)},
-               },
+func (b *blockBuilder) calcBlockCommitment() (err error) {
+       var txEntries []*bc.Tx
+       for _, tx := range b.block.Transactions {
+               txEntries = append(txEntries, tx.Tx)
        }
-}
 
-func applyCoinbaseTransaction(chain *protocol.Chain, block *types.Block, txStatus *bc.TransactionStatus, accountManager *account.Manager, gasLeft int64) (int64, error) {
-       coinbaseTx, err := createCoinbaseTx(accountManager, chain, chain.BestBlockHeader())
+       b.block.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
        if err != nil {
-               return 0, errors.Wrap(err, "fail on create coinbase tx")
+               return err
        }
 
-       gasState, err := validation.ValidateTx(coinbaseTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}, Transactions: []*bc.Tx{coinbaseTx.Tx}})
+       b.block.BlockHeader.BlockCommitment.TransactionStatusHash, err = types.TxStatusMerkleRoot(b.txStatus.VerifyStatus)
+       return err
+}
+
+// createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
+// based on the passed block height to the provided address.  When the address
+// is nil, the coinbase transaction will instead be redeemable by anyone.
+func (b *blockBuilder) createCoinbaseTx() (*types.Tx, error) {
+       consensusResult, err := b.chain.GetConsensusResultByHash(&b.block.PreviousBlockHash)
        if err != nil {
-               return 0, err
+               return nil, err
        }
 
-       block.Transactions = append(block.Transactions, coinbaseTx)
-       if err := txStatus.SetStatus(0, false); err != nil {
-               return 0, err
+       rewards, err := consensusResult.GetCoinbaseRewards(b.block.Height - 1)
+       if err != nil {
+               return nil, err
        }
 
-       return gasLeft - gasState.GasUsed, nil
+       return createCoinbaseTxByReward(b.accountManager, b.block.Height, rewards)
 }
 
+func (b *blockBuilder) getTimeoutStatus() uint8 {
+       if b.timeoutStatus == timeoutCritical {
+               return b.timeoutStatus
+       }
 
-func applyTransactionFromPool(chain *protocol.Chain, view *state.UtxoViewpoint, block *types.Block, txStatus *bc.TransactionStatus, gasLeft int64) (int64, error) {
-       poolTxs := getAllTxsFromPool(chain.GetTxPool())
-       results, gasLeft := preValidateTxs(poolTxs, chain, view, gasLeft)
-       for _, result := range results {
-               if result.err != nil && !result.gasOnly {
-                       blkGenSkipTxForErr(chain.GetTxPool(), &result.tx.ID, result.err)
-                       continue
-               }
+       select {
+       case <-b.criticalTimeoutCh:
+               b.timeoutStatus = timeoutCritical
+       default:
+       }
 
-               if err := txStatus.SetStatus(len(block.Transactions), result.gasOnly); err != nil {
-                       return 0, err
-               }
+       if b.timeoutStatus > timeoutOk {
+               return b.timeoutStatus
+       }
 
-               block.Transactions = append(block.Transactions, result.tx)
+       select {
+       case <-b.warnTimeoutCh:
+               b.timeoutStatus = timeoutWarn
+       default:
        }
-       return gasLeft, nil
+       return b.timeoutStatus
 }
 
-func applyTransactionFromSubProtocol(chain *protocol.Chain, view *state.UtxoViewpoint, block *types.Block, txStatus *bc.TransactionStatus, accountManager *account.Manager, gasLeft int64) error {
-       txs, err := getTxsFromSubProtocols(chain, accountManager, block.Transactions, gasLeft)
+func createCoinbaseTxByReward(accountManager *account.Manager, blockHeight uint64, rewards []state.CoinbaseReward) (tx *types.Tx, err error) {
+       arbitrary := append([]byte{0x00}, []byte(strconv.FormatUint(blockHeight, 10))...)
+       var script []byte
+       if accountManager == nil {
+               script, err = vmutil.DefaultCoinbaseProgram()
+       } else {
+               script, err = accountManager.GetCoinbaseControlProgram()
+               arbitrary = append(arbitrary, accountManager.GetCoinbaseArbitrary()...)
+       }
        if err != nil {
-               return err
+               return nil, err
        }
 
-       results, gasLeft := preValidateTxs(txs, chain, view, gasLeft)
-       for _, result := range results {
-               if result.err != nil {
-                       return err
-               }
+       if len(arbitrary) > consensus.ActiveNetParams.CoinbaseArbitrarySizeLimit {
+               return nil, validation.ErrCoinbaseArbitraryOversize
+       }
 
-               if err := txStatus.SetStatus(len(block.Transactions), result.gasOnly); err != nil {
-                       return err
+       builder := txbuilder.NewBuilder(time.Now())
+       if err = builder.AddInput(types.NewCoinbaseInput(arbitrary), &txbuilder.SigningInstruction{}); err != nil {
+               return nil, err
+       }
+       if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, 0, script)); err != nil {
+               return nil, err
+       }
+
+       for _, r := range rewards {
+               if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, r.Amount, r.ControlProgram)); err != nil {
+                       return nil, err
                }
+       }
 
-               block.Transactions = append(block.Transactions, result.tx)
+       _, txData, err := builder.Build()
+       if err != nil {
+               return nil, err
        }
-       return nil
+
+       byteData, err := txData.MarshalText()
+       if err != nil {
+               return nil, err
+       }
+
+       txData.SerializedSize = uint64(len(byteData))
+       tx = &types.Tx{
+               TxData: *txData,
+               Tx:     types.MapTx(txData),
+       }
+       return tx, nil
 }
 
 type validateTxResult struct {
@@ -261,43 +351,3 @@ func validateBySubProtocols(tx *types.Tx, statusFail bool, subProtocols []protoc
        }
        return nil
 }
-
-func getAllTxsFromPool(txPool *protocol.TxPool) []*types.Tx {
-       txDescList := txPool.GetTransactions()
-       sort.Sort(byTime(txDescList))
-
-       poolTxs := make([]*types.Tx, len(txDescList))
-       for i, txDesc := range txDescList {
-               poolTxs[i] = txDesc.Tx
-       }
-       return poolTxs
-}
-
-func getTxsFromSubProtocols(chain *protocol.Chain, accountManager *account.Manager, poolTxs []*types.Tx, gasLeft int64) ([]*types.Tx, error) {
-       cp, err := accountManager.GetCoinbaseControlProgram()
-       if err != nil {
-               return nil, err
-       }
-
-       var result []*types.Tx
-       var subTxs []*types.Tx
-       for i, p := range chain.SubProtocols() {
-               if gasLeft <= 0 {
-                       break
-               }
-
-               subTxs, gasLeft, err = p.BeforeProposalBlock(poolTxs, cp, chain.BestBlockHeight() + 1, gasLeft)
-               if err != nil {
-                       log.WithFields(log.Fields{"module": logModule, "index": i, "error": err}).Error("failed on sub protocol txs package")
-                       continue
-               }
-
-               result = append(result, subTxs...)
-       }
-       return result, nil
-}
-
-func blkGenSkipTxForErr(txPool *protocol.TxPool, txHash *bc.Hash, err error) {
-       log.WithFields(log.Fields{"module": logModule, "error": err}).Error("mining block generation: skip tx due to")
-       txPool.RemoveTransaction(txHash)
-}