log "github.com/sirupsen/logrus"
- "github.com/vapor/account"
- "github.com/vapor/blockchain/txbuilder"
- "github.com/vapor/consensus"
- "github.com/vapor/errors"
- "github.com/vapor/protocol"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/state"
- "github.com/vapor/protocol/validation"
- "github.com/vapor/protocol/vm/vmutil"
+ "github.com/bytom/vapor/account"
+ "github.com/bytom/vapor/blockchain/txbuilder"
+ "github.com/bytom/vapor/consensus"
+ "github.com/bytom/vapor/errors"
+ "github.com/bytom/vapor/protocol"
+ "github.com/bytom/vapor/protocol/bc"
+ "github.com/bytom/vapor/protocol/bc/types"
+ "github.com/bytom/vapor/protocol/state"
+ "github.com/bytom/vapor/protocol/validation"
+ "github.com/bytom/vapor/protocol/vm/vmutil"
)
-const logModule = "mining"
+const (
+ logModule = "mining"
+ batchApplyNum = 16
+ softMaxTxNum = 128
+
+ timeoutOk = iota + 1
+ timeoutWarn
+ timeoutCritical
+)
+
+// 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()
+}
+
+type blockBuilder struct {
+ chain *protocol.Chain
+ accountManager *account.Manager
+
+ block *types.Block
+ txStatus *bc.TransactionStatus
+ utxoView *state.UtxoViewpoint
+
+ warnTimeoutCh <-chan time.Time
+ criticalTimeoutCh <-chan time.Time
+ timeoutStatus uint8
+ gasLeft int64
+}
+
+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)},
+ },
+ }
+
+ 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
+}
+
+func (b *blockBuilder) applyCoinbaseTransaction() error {
+ coinbaseTx, err := b.createCoinbaseTx()
+ if err != nil {
+ return errors.Wrap(err, "fail on create coinbase tx")
+ }
+
+ 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
+ }
+
+ b.block.Transactions = append(b.block.Transactions, coinbaseTx)
+ if err := b.txStatus.SetStatus(0, false); err != nil {
+ return err
+ }
+
+ b.gasLeft -= gasState.GasUsed
+ return nil
+}
+
+func (b *blockBuilder) applyTransactions(txs []*types.Tx, timeoutStatus uint8) 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 := b.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() >= timeoutStatus || len(b.block.Transactions) > softMaxTxNum {
+ break
+ }
+ }
+ return nil
+}
+
+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
+ }
+
+ return b.applyTransactions(poolTxs, timeoutWarn)
+}
+
+func (b *blockBuilder) applyTransactionFromSubProtocol() error {
+ isTimeout := func() bool {
+ return b.getTimeoutStatus() > timeoutOk
+ }
+
+ for i, p := range b.chain.SubProtocols() {
+ if b.gasLeft <= 0 || isTimeout() {
+ break
+ }
+
+ subTxs, err := p.BeforeProposalBlock(b.block, b.gasLeft, isTimeout)
+ if err != nil {
+ log.WithFields(log.Fields{"module": logModule, "index": i, "error": err}).Error("failed on sub protocol txs package")
+ continue
+ }
+
+ if err := b.applyTransactions(subTxs, timeoutCritical); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (b *blockBuilder) build() (*types.Block, error) {
+ if err := b.applyCoinbaseTransaction(); err != nil {
+ return nil, err
+ }
+
+ if err := b.applyTransactionFromPool(); err != nil {
+ return nil, err
+ }
+
+ if err := b.applyTransactionFromSubProtocol(); err != nil {
+ return nil, err
+ }
+
+ if err := b.calcBlockCommitment(); err != nil {
+ return nil, err
+ }
+
+ if err := b.chain.SignBlockHeader(&b.block.BlockHeader); err != nil {
+ return nil, err
+ }
+
+ return b.block, nil
+}
+
+func (b *blockBuilder) calcBlockCommitment() (err error) {
+ var txEntries []*bc.Tx
+ for _, tx := range b.block.Transactions {
+ txEntries = append(txEntries, tx.Tx)
+ }
+
+ b.block.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
+ if err != nil {
+ return err
+ }
+
+ 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 createCoinbaseTx(accountManager *account.Manager, blockHeight uint64) (tx *types.Tx, err error) {
+func (b *blockBuilder) createCoinbaseTx() (*types.Tx, error) {
+ consensusResult, err := b.chain.GetConsensusResultByHash(&b.block.PreviousBlockHash)
+ if err != nil {
+ return nil, err
+ }
+
+ rewards, err := consensusResult.GetCoinbaseRewards(b.block.Height - 1)
+ if err != nil {
+ return nil, err
+ }
+
+ return createCoinbaseTxByReward(b.accountManager, b.block.Height, rewards)
+}
+
+func (b *blockBuilder) getTimeoutStatus() uint8 {
+ if b.timeoutStatus == timeoutCritical {
+ return b.timeoutStatus
+ }
+
+ select {
+ case <-b.criticalTimeoutCh:
+ b.timeoutStatus = timeoutCritical
+ case <-b.warnTimeoutCh:
+ b.timeoutStatus = timeoutWarn
+ default:
+ }
+
+ return b.timeoutStatus
+}
+
+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 {
return nil, err
}
- if len(arbitrary) > consensus.CoinbaseArbitrarySizeLimit {
+ if len(arbitrary) > consensus.ActiveNetParams.CoinbaseArbitrarySizeLimit {
return nil, validation.ErrCoinbaseArbitraryOversize
}
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
+ }
+ }
+
_, txData, err := builder.Build()
if err != nil {
return nil, err
}
+ 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
}
-// restructCoinbaseTx build coinbase transaction with aggregate outputs when it achieved the specified block height
-func restructCoinbaseTx(tx *types.Tx, rewards []state.CoinbaseReward) error {
- for _, r := range rewards {
- tx.Outputs = append(tx.Outputs, types.NewIntraChainOutput(*consensus.BTMAssetID, r.Amount, r.ControlProgram))
- }
-
- byteData, err := tx.TxData.MarshalText()
- if err != nil {
- return err
- }
-
- tx.TxData.SerializedSize = uint64(len(byteData))
- tx.Tx = types.MapTx(&tx.TxData)
- return nil
+type validateTxResult struct {
+ tx *types.Tx
+ gasOnly bool
+ err error
}
-// NewBlockTemplate returns a new block template that is ready to be solved
-func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager *account.Manager, timestamp uint64) (b *types.Block, err error) {
- view := state.NewUtxoViewpoint()
- txStatus := bc.NewTransactionStatus()
- if err := txStatus.SetStatus(0, false); err != nil {
- return nil, err
- }
- txEntries := []*bc.Tx{nil}
- gasUsed := uint64(0)
-
- // get preblock info for generate next block
- preBlockHeader := c.BestBlockHeader()
- preBlockHash := preBlockHeader.Hash()
- nextBlockHeight := preBlockHeader.Height + 1
-
- b = &types.Block{
- BlockHeader: types.BlockHeader{
- Version: 1,
- Height: nextBlockHeight,
- PreviousBlockHash: preBlockHash,
- Timestamp: timestamp,
- BlockCommitment: types.BlockCommitment{},
- BlockWitness: types.BlockWitness{Witness: make([][]byte, consensus.NumOfConsensusNode)},
- },
- }
- bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: nextBlockHeight}}
- b.Transactions = []*types.Tx{nil}
-
- txs := txPool.GetTransactions()
- sort.Sort(byTime(txs))
-
- entriesTxs := []*bc.Tx{}
- for _, txDesc := range txs {
- entriesTxs = append(entriesTxs, txDesc.Tx.Tx)
+func (b *blockBuilder) preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
+ var results []*validateTxResult
+ bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}}
+ bcTxs := make([]*bc.Tx, len(txs))
+ for i, tx := range txs {
+ bcTxs[i] = tx.Tx
}
- validateResults := validation.ValidateTxs(entriesTxs, bcBlock)
- for i, validateResult := range validateResults {
- txDesc := txs[i]
- tx := txDesc.Tx.Tx
+ validateResults := validation.ValidateTxs(bcTxs, bcBlock)
+ for i := 0; i < len(validateResults) && gasLeft > 0; i++ {
gasOnlyTx := false
-
- gasStatus := validateResult.GetGasState()
- if validateResult.GetError() != nil {
+ gasStatus := validateResults[i].GetGasState()
+ if err := validateResults[i].GetError(); err != nil {
if !gasStatus.GasValid {
- blkGenSkipTxForErr(txPool, &tx.ID, err)
+ results = append(results, &validateTxResult{tx: txs[i], err: err})
continue
}
gasOnlyTx = true
}
- if err := c.GetTransactionsUtxo(view, []*bc.Tx{tx}); err != nil {
- blkGenSkipTxForErr(txPool, &tx.ID, err)
+ if err := chain.GetTransactionsUtxo(view, []*bc.Tx{bcTxs[i]}); err != nil {
+ results = append(results, &validateTxResult{tx: txs[i], err: err})
continue
}
- if gasUsed+uint64(gasStatus.GasUsed) > consensus.MaxBlockGas {
+ if gasLeft-gasStatus.GasUsed < 0 {
break
}
- if err := view.ApplyTransaction(bcBlock, tx, gasOnlyTx); err != nil {
- blkGenSkipTxForErr(txPool, &tx.ID, err)
+ if err := view.ApplyTransaction(bcBlock, bcTxs[i], gasOnlyTx); err != nil {
+ results = append(results, &validateTxResult{tx: txs[i], err: err})
continue
}
- if err := txStatus.SetStatus(len(b.Transactions), gasOnlyTx); err != nil {
- return nil, err
- }
-
- b.Transactions = append(b.Transactions, txDesc.Tx)
- txEntries = append(txEntries, tx)
- gasUsed += uint64(gasStatus.GasUsed)
- if gasUsed == consensus.MaxBlockGas {
- break
+ if err := b.validateBySubProtocols(txs[i], validateResults[i].GetError() != nil, chain.SubProtocols()); err != nil {
+ results = append(results, &validateTxResult{tx: txs[i], err: err})
+ continue
}
+ results = append(results, &validateTxResult{tx: txs[i], gasOnly: gasOnlyTx, err: validateResults[i].GetError()})
+ gasLeft -= gasStatus.GasUsed
}
-
- // create coinbase transaction
- b.Transactions[0], err = createCoinbaseTx(accountManager, nextBlockHeight)
- if err != nil {
- return nil, errors.Wrap(err, "fail on createCoinbaseTx")
- }
-
- consensusResult, err := c.GetConsensusResultByHash(&preBlockHash)
- if err != nil {
- return nil, err
- }
-
- if err := consensusResult.AttachCoinbaseReward(b); err != nil {
- return nil, err
- }
-
- rewards, err := consensusResult.GetCoinbaseRewards(nextBlockHeight)
- if err != nil {
- return nil, err
- }
-
- // restruct coinbase transaction
- if err = restructCoinbaseTx(b.Transactions[0], rewards); err != nil {
- return nil, errors.Wrap(err, "fail on createCoinbaseTx")
- }
-
- txEntries[0] = b.Transactions[0].Tx
- b.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
- if err != nil {
- return nil, err
- }
-
- b.BlockHeader.BlockCommitment.TransactionStatusHash, err = types.TxStatusMerkleRoot(txStatus.VerifyStatus)
-
- _, err = c.SignBlock(b)
- return b, err
+ return results, gasLeft
}
-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)
+func (b *blockBuilder) validateBySubProtocols(tx *types.Tx, statusFail bool, subProtocols []protocol.SubProtocol) error {
+ for _, subProtocol := range subProtocols {
+ verifyResult := &bc.TxVerifyResult{StatusFail: statusFail}
+ if err := subProtocol.ValidateTx(tx, verifyResult, b.block.Height); err != nil {
+ return err
+ }
+ }
+ return nil
}