9 log "github.com/sirupsen/logrus"
11 "github.com/bytom/bytom/account"
12 "github.com/bytom/bytom/blockchain/txbuilder"
13 "github.com/bytom/bytom/consensus"
14 "github.com/bytom/bytom/errors"
15 "github.com/bytom/bytom/protocol"
16 "github.com/bytom/bytom/protocol/bc"
17 "github.com/bytom/bytom/protocol/bc/types"
18 "github.com/bytom/bytom/protocol/state"
19 "github.com/bytom/bytom/protocol/validation"
20 "github.com/bytom/bytom/protocol/vm/vmutil"
24 logModule = "proposal"
33 // NewBlockTemplate returns a new block template that is ready to be solved
34 func NewBlockTemplate(chain *protocol.Chain, validator *state.Validator, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) (*types.Block, error) {
35 builder := newBlockBuilder(chain, validator, accountManager, timestamp, warnDuration, criticalDuration)
36 return builder.build()
39 type blockBuilder struct {
41 validator *state.Validator
42 accountManager *account.Manager
45 utxoView *state.UtxoViewpoint
47 warnTimeoutCh <-chan time.Time
48 criticalTimeoutCh <-chan time.Time
53 func newBlockBuilder(chain *protocol.Chain, validator *state.Validator, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) *blockBuilder {
54 preBlockHeader := chain.BestBlockHeader()
55 block := &types.Block{
56 BlockHeader: types.BlockHeader{
58 Height: preBlockHeader.Height + 1,
59 PreviousBlockHash: preBlockHeader.Hash(),
61 BlockCommitment: types.BlockCommitment{},
65 builder := &blockBuilder{
68 accountManager: accountManager,
70 utxoView: state.NewUtxoViewpoint(),
71 warnTimeoutCh: time.After(warnDuration),
72 criticalTimeoutCh: time.After(criticalDuration),
73 gasLeft: int64(consensus.MaxBlockGas),
74 timeoutStatus: timeoutOk,
79 func (b *blockBuilder) build() (*types.Block, error) {
80 b.block.Transactions = []*types.Tx{nil}
81 if err := b.applyTransactionFromPool(); err != nil {
85 if err := b.applyCoinbaseTransaction(); err != nil {
89 if err := b.calculateBlockCommitment(); err != nil {
93 blockHeader := &b.block.BlockHeader
94 b.chain.SignBlockHeader(blockHeader)
98 func (b *blockBuilder) applyCoinbaseTransaction() error {
99 coinbaseTx, err := b.createCoinbaseTx()
101 return errors.Wrap(err, "fail on create coinbase tx")
104 gasState, err := validation.ValidateTx(coinbaseTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Height: b.block.Height}, Transactions: []*bc.Tx{coinbaseTx.Tx}}, b.chain.ProgramConverter)
109 b.block.Transactions[0] = coinbaseTx
110 b.gasLeft -= gasState.GasUsed
114 func (b *blockBuilder) applyTransactionFromPool() error {
115 txDescList := b.chain.GetTxPool().GetTransactions()
116 sort.Sort(byTime(txDescList))
117 return b.applyTransactions(txDescList, timeoutWarn)
120 func (b *blockBuilder) calculateBlockCommitment() (err error) {
121 var txEntries []*bc.Tx
122 for _, tx := range b.block.Transactions {
123 txEntries = append(txEntries, tx.Tx)
126 b.block.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
134 // createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
135 // based on the passed block height to the provided address. When the address
136 // is nil, the coinbase transaction will instead be redeemable by anyone.
137 func (b *blockBuilder) createCoinbaseTx() (tx *types.Tx, err error) {
138 arbitrary := append([]byte{0x00}, []byte(strconv.FormatUint(b.block.Height, 10))...)
140 if b.accountManager == nil {
141 script, err = vmutil.DefaultCoinbaseProgram()
143 script, err = b.accountManager.GetCoinbaseControlProgram()
144 arbitrary = append(arbitrary, b.accountManager.GetCoinbaseArbitrary()...)
150 if len(arbitrary) > consensus.CoinbaseArbitrarySizeLimit {
151 return nil, validation.ErrCoinbaseArbitraryOversize
154 builder := txbuilder.NewBuilder(time.Now())
155 if err = builder.AddInput(types.NewCoinbaseInput(arbitrary), &txbuilder.SigningInstruction{}); err != nil {
159 checkpoint, err := b.getPrevCheckpoint()
164 if b.block.Height%state.BlocksOfEpoch == 1 && b.block.Height != 1 {
165 for controlProgram, amount := range checkpoint.Rewards {
166 controlProgramBytes, err := hex.DecodeString(controlProgram)
171 if err := builder.AddOutput(types.NewOriginalTxOutput(*consensus.BTMAssetID, amount, controlProgramBytes, [][]byte{})); err != nil {
176 if err = builder.AddOutput(types.NewOriginalTxOutput(*consensus.BTMAssetID, 0, script, [][]byte{})); err != nil {
181 _, txData, err := builder.Build()
186 byteData, err := txData.MarshalText()
191 txData.SerializedSize = uint64(len(byteData))
194 Tx: types.MapTx(txData),
199 func (b *blockBuilder) applyTransactions(txs []*protocol.TxDesc, timeoutStatus uint8) error {
200 batchTxs := []*protocol.TxDesc{}
201 for i := 0; i < len(txs); i++ {
202 if batchTxs = append(batchTxs, txs[i]); len(batchTxs) < batchApplyNum && i != len(txs)-1 {
206 results, gasLeft := b.preValidateTxs(batchTxs, b.chain, b.utxoView, b.gasLeft)
207 for _, result := range results {
208 if result.err != nil {
209 log.WithFields(log.Fields{"module": logModule, "error": result.err}).Error("propose block generation: skip tx due to")
210 b.chain.GetTxPool().RemoveTransaction(&result.tx.ID)
214 b.block.Transactions = append(b.block.Transactions, result.tx)
218 batchTxs = batchTxs[:0]
219 if b.getTimeoutStatus() >= timeoutStatus || len(b.block.Transactions) > softMaxTxNum {
226 type validateTxResult struct {
231 func (b *blockBuilder) preValidateTxs(txs []*protocol.TxDesc, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
232 var results []*validateTxResult
233 bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}}
234 bcTxs := make([]*bc.Tx, len(txs))
235 for i, tx := range txs {
239 validateResults := validation.ValidateTxs(bcTxs, bcBlock, b.chain.ProgramConverter)
240 for i := 0; i < len(validateResults) && gasLeft > 0; i++ {
242 gasStatus := validateResults[i].GetGasState()
243 if err := validateResults[i].GetError(); err != nil {
244 results = append(results, &validateTxResult{tx: tx, err: err})
248 if err := chain.GetTransactionsUtxo(view, []*bc.Tx{bcTxs[i]}); err != nil {
249 results = append(results, &validateTxResult{tx: tx, err: err})
253 if gasLeft-gasStatus.GasUsed < 0 {
257 if err := view.ApplyTransaction(bcBlock, bcTxs[i]); err != nil {
258 results = append(results, &validateTxResult{tx: tx, err: err})
262 results = append(results, &validateTxResult{tx: tx, err: validateResults[i].GetError()})
263 gasLeft -= gasStatus.GasUsed
265 return results, gasLeft
268 func (b *blockBuilder) getTimeoutStatus() uint8 {
269 if b.timeoutStatus == timeoutCritical {
270 return b.timeoutStatus
274 case <-b.criticalTimeoutCh:
275 b.timeoutStatus = timeoutCritical
276 case <-b.warnTimeoutCh:
277 b.timeoutStatus = timeoutWarn
281 return b.timeoutStatus
284 func (b *blockBuilder) prevBlockHash() *bc.Hash {
285 return &b.block.PreviousBlockHash
288 func (b *blockBuilder) getPrevCheckpoint() (*state.Checkpoint, error) {
289 return b.chain.PrevCheckpointByPrevHash(b.prevBlockHash())