8 log "github.com/sirupsen/logrus"
10 "github.com/bytom/bytom/account"
11 "github.com/bytom/bytom/blockchain/txbuilder"
12 "github.com/bytom/bytom/consensus"
13 "github.com/bytom/bytom/errors"
14 "github.com/bytom/bytom/protocol"
15 "github.com/bytom/bytom/protocol/bc"
16 "github.com/bytom/bytom/protocol/bc/types"
17 "github.com/bytom/bytom/protocol/state"
18 "github.com/bytom/bytom/protocol/validation"
19 "github.com/bytom/bytom/protocol/vm/vmutil"
23 logModule = "proposal"
32 // NewBlockTemplate returns a new block template that is ready to be solved
33 func NewBlockTemplate(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) (*types.Block, error) {
34 builder := newBlockBuilder(chain, accountManager, timestamp, warnDuration, criticalDuration)
35 return builder.build()
38 type blockBuilder struct {
40 accountManager *account.Manager
43 utxoView *state.UtxoViewpoint
45 warnTimeoutCh <-chan time.Time
46 criticalTimeoutCh <-chan time.Time
51 func newBlockBuilder(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) *blockBuilder {
52 preBlockHeader := chain.BestBlockHeader()
53 block := &types.Block{
54 BlockHeader: types.BlockHeader{
56 Height: preBlockHeader.Height + 1,
57 PreviousBlockHash: preBlockHeader.Hash(),
59 BlockCommitment: types.BlockCommitment{},
60 BlockWitness: make([]byte, protocol.SignatureLength),
64 builder := &blockBuilder{
66 accountManager: accountManager,
68 utxoView: state.NewUtxoViewpoint(),
69 warnTimeoutCh: time.After(warnDuration),
70 criticalTimeoutCh: time.After(criticalDuration),
71 gasLeft: int64(consensus.MaxBlockGas),
72 timeoutStatus: timeoutOk,
77 func (b *blockBuilder) build() (*types.Block, error) {
78 if err := b.applyCoinbaseTransaction(); err != nil {
82 if err := b.applyTransactionFromPool(); err != nil {
86 if err := b.calculateBlockCommitment(); err != nil {
90 b.chain.SignBlockHeader(&b.block.BlockHeader)
94 func (b *blockBuilder) applyCoinbaseTransaction() error {
95 coinbaseTx, err := b.createCoinbaseTx()
97 return errors.Wrap(err, "fail on create coinbase tx")
100 gasState, err := validation.ValidateTx(coinbaseTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Height: b.block.Height}, Transactions: []*bc.Tx{coinbaseTx.Tx}}, b.chain.ProgramConverter)
105 b.block.Transactions = append(b.block.Transactions, coinbaseTx)
106 b.gasLeft -= gasState.GasUsed
110 func (b *blockBuilder) applyTransactionFromPool() error {
111 txDescList := b.chain.GetTxPool().GetTransactions()
112 sort.Sort(byTime(txDescList))
114 poolTxs := make([]*types.Tx, len(txDescList))
115 for i, txDesc := range txDescList {
116 poolTxs[i] = txDesc.Tx
119 return b.applyTransactions(poolTxs, timeoutWarn)
122 func (b *blockBuilder) calculateBlockCommitment() (err error) {
123 var txEntries []*bc.Tx
124 for _, tx := range b.block.Transactions {
125 txEntries = append(txEntries, tx.Tx)
128 b.block.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
136 // createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
137 // based on the passed block height to the provided address. When the address
138 // is nil, the coinbase transaction will instead be redeemable by anyone.
139 func (b *blockBuilder) createCoinbaseTx() (tx *types.Tx, err error) {
140 arbitrary := append([]byte{0x00}, []byte(strconv.FormatUint(b.block.Height, 10))...)
142 if b.accountManager == nil {
143 script, err = vmutil.DefaultCoinbaseProgram()
145 script, err = b.accountManager.GetCoinbaseControlProgram()
146 arbitrary = append(arbitrary, b.accountManager.GetCoinbaseArbitrary()...)
152 if len(arbitrary) > consensus.CoinbaseArbitrarySizeLimit {
153 return nil, validation.ErrCoinbaseArbitraryOversize
156 builder := txbuilder.NewBuilder(time.Now())
157 if err = builder.AddInput(types.NewCoinbaseInput(arbitrary), &txbuilder.SigningInstruction{}); err != nil {
161 if err = builder.AddOutput(types.NewOriginalTxOutput(*consensus.BTMAssetID, 0, script, [][]byte{})); err != nil {
164 //TODO: calculate reward to proposer
166 _, txData, err := builder.Build()
171 byteData, err := txData.MarshalText()
176 txData.SerializedSize = uint64(len(byteData))
179 Tx: types.MapTx(txData),
184 func (b *blockBuilder) applyTransactions(txs []*types.Tx, timeoutStatus uint8) error {
185 batchTxs := []*types.Tx{}
186 for i := 0; i < len(txs); i++ {
187 if batchTxs = append(batchTxs, txs[i]); len(batchTxs) < batchApplyNum && i != len(txs)-1 {
191 results, gasLeft := b.preValidateTxs(batchTxs, b.chain, b.utxoView, b.gasLeft)
192 for _, result := range results {
193 if result.err != nil {
194 log.WithFields(log.Fields{"module": logModule, "error": result.err}).Error("propose block generation: skip tx due to")
195 b.chain.GetTxPool().RemoveTransaction(&result.tx.ID)
199 b.block.Transactions = append(b.block.Transactions, result.tx)
203 batchTxs = batchTxs[:0]
204 if b.getTimeoutStatus() >= timeoutStatus || len(b.block.Transactions) > softMaxTxNum {
211 type validateTxResult struct {
216 func (b *blockBuilder) preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
217 var results []*validateTxResult
218 bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}}
219 bcTxs := make([]*bc.Tx, len(txs))
220 for i, tx := range txs {
224 validateResults := validation.ValidateTxs(bcTxs, bcBlock, b.chain.ProgramConverter)
225 for i := 0; i < len(validateResults) && gasLeft > 0; i++ {
226 gasStatus := validateResults[i].GetGasState()
227 if err := validateResults[i].GetError(); err != nil {
228 results = append(results, &validateTxResult{tx: txs[i], err: err})
232 if err := chain.GetTransactionsUtxo(view, []*bc.Tx{bcTxs[i]}); err != nil {
233 results = append(results, &validateTxResult{tx: txs[i], err: err})
237 if gasLeft-gasStatus.GasUsed < 0 {
241 if err := view.ApplyTransaction(bcBlock, bcTxs[i]); err != nil {
242 results = append(results, &validateTxResult{tx: txs[i], err: err})
246 results = append(results, &validateTxResult{tx: txs[i], err: validateResults[i].GetError()})
247 gasLeft -= gasStatus.GasUsed
249 return results, gasLeft
252 func (b *blockBuilder) getTimeoutStatus() uint8 {
253 if b.timeoutStatus == timeoutCritical {
254 return b.timeoutStatus
258 case <-b.criticalTimeoutCh:
259 b.timeoutStatus = timeoutCritical
260 case <-b.warnTimeoutCh:
261 b.timeoutStatus = timeoutWarn
265 return b.timeoutStatus