github.com/MetalBlockchain/metalgo@v1.11.9/vms/avm/block/builder/builder.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package builder
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  
    10  	"github.com/MetalBlockchain/metalgo/ids"
    11  	"github.com/MetalBlockchain/metalgo/snow/consensus/snowman"
    12  	"github.com/MetalBlockchain/metalgo/utils/set"
    13  	"github.com/MetalBlockchain/metalgo/utils/timer/mockable"
    14  	"github.com/MetalBlockchain/metalgo/utils/units"
    15  	"github.com/MetalBlockchain/metalgo/vms/avm/block"
    16  	"github.com/MetalBlockchain/metalgo/vms/avm/state"
    17  	"github.com/MetalBlockchain/metalgo/vms/avm/txs"
    18  	"github.com/MetalBlockchain/metalgo/vms/avm/txs/mempool"
    19  
    20  	blockexecutor "github.com/MetalBlockchain/metalgo/vms/avm/block/executor"
    21  	txexecutor "github.com/MetalBlockchain/metalgo/vms/avm/txs/executor"
    22  )
    23  
    24  // targetBlockSize is the max block size we aim to produce
    25  const targetBlockSize = 128 * units.KiB
    26  
    27  var (
    28  	_ Builder = (*builder)(nil)
    29  
    30  	ErrNoTransactions = errors.New("no transactions")
    31  )
    32  
    33  type Builder interface {
    34  	// BuildBlock can be called to attempt to create a new block
    35  	BuildBlock(context.Context) (snowman.Block, error)
    36  }
    37  
    38  // builder implements a simple builder to convert txs into valid blocks
    39  type builder struct {
    40  	backend *txexecutor.Backend
    41  	manager blockexecutor.Manager
    42  	clk     *mockable.Clock
    43  
    44  	// Pool of all txs that may be able to be added
    45  	mempool mempool.Mempool
    46  }
    47  
    48  func New(
    49  	backend *txexecutor.Backend,
    50  	manager blockexecutor.Manager,
    51  	clk *mockable.Clock,
    52  	mempool mempool.Mempool,
    53  ) Builder {
    54  	return &builder{
    55  		backend: backend,
    56  		manager: manager,
    57  		clk:     clk,
    58  		mempool: mempool,
    59  	}
    60  }
    61  
    62  // BuildBlock builds a block to be added to consensus.
    63  func (b *builder) BuildBlock(context.Context) (snowman.Block, error) {
    64  	defer b.mempool.RequestBuildBlock()
    65  
    66  	ctx := b.backend.Ctx
    67  	ctx.Log.Debug("starting to attempt to build a block")
    68  
    69  	// Get the block to build on top of and retrieve the new block's context.
    70  	preferredID := b.manager.Preferred()
    71  	preferred, err := b.manager.GetStatelessBlock(preferredID)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	preferredHeight := preferred.Height()
    77  	preferredTimestamp := preferred.Timestamp()
    78  
    79  	nextHeight := preferredHeight + 1
    80  	nextTimestamp := b.clk.Time() // [timestamp] = max(now, parentTime)
    81  	if preferredTimestamp.After(nextTimestamp) {
    82  		nextTimestamp = preferredTimestamp
    83  	}
    84  
    85  	stateDiff, err := state.NewDiff(preferredID, b.manager)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	var (
    91  		blockTxs      []*txs.Tx
    92  		inputs        set.Set[ids.ID]
    93  		remainingSize = targetBlockSize
    94  	)
    95  	for {
    96  		tx, exists := b.mempool.Peek()
    97  		// Invariant: [mempool.MaxTxSize] < [targetBlockSize]. This guarantees
    98  		// that we will only stop building a block once there are no
    99  		// transactions in the mempool or the block is at least
   100  		// [targetBlockSize - mempool.MaxTxSize] bytes full.
   101  		if !exists || len(tx.Bytes()) > remainingSize {
   102  			break
   103  		}
   104  		b.mempool.Remove(tx)
   105  
   106  		// Invariant: [tx] has already been syntactically verified.
   107  
   108  		txDiff, err := state.NewDiffOn(stateDiff)
   109  		if err != nil {
   110  			return nil, err
   111  		}
   112  
   113  		err = tx.Unsigned.Visit(&txexecutor.SemanticVerifier{
   114  			Backend: b.backend,
   115  			State:   txDiff,
   116  			Tx:      tx,
   117  		})
   118  		if err != nil {
   119  			txID := tx.ID()
   120  			b.mempool.MarkDropped(txID, err)
   121  			continue
   122  		}
   123  
   124  		executor := &txexecutor.Executor{
   125  			Codec: b.backend.Codec,
   126  			State: txDiff,
   127  			Tx:    tx,
   128  		}
   129  		err = tx.Unsigned.Visit(executor)
   130  		if err != nil {
   131  			txID := tx.ID()
   132  			b.mempool.MarkDropped(txID, err)
   133  			continue
   134  		}
   135  
   136  		if inputs.Overlaps(executor.Inputs) {
   137  			txID := tx.ID()
   138  			b.mempool.MarkDropped(txID, blockexecutor.ErrConflictingBlockTxs)
   139  			continue
   140  		}
   141  		err = b.manager.VerifyUniqueInputs(preferredID, inputs)
   142  		if err != nil {
   143  			txID := tx.ID()
   144  			b.mempool.MarkDropped(txID, err)
   145  			continue
   146  		}
   147  		inputs.Union(executor.Inputs)
   148  
   149  		txDiff.AddTx(tx)
   150  		txDiff.Apply(stateDiff)
   151  
   152  		remainingSize -= len(tx.Bytes())
   153  		blockTxs = append(blockTxs, tx)
   154  	}
   155  
   156  	if len(blockTxs) == 0 {
   157  		return nil, ErrNoTransactions
   158  	}
   159  
   160  	statelessBlk, err := block.NewStandardBlock(
   161  		preferredID,
   162  		nextHeight,
   163  		nextTimestamp,
   164  		blockTxs,
   165  		b.backend.Codec,
   166  	)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	return b.manager.NewBlock(statelessBlk), nil
   172  }