github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/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  	"fmt"
    10  	"math"
    11  	"sync"
    12  	"time"
    13  
    14  	"go.uber.org/zap"
    15  
    16  	"github.com/ava-labs/avalanchego/ids"
    17  	"github.com/ava-labs/avalanchego/snow"
    18  	"github.com/ava-labs/avalanchego/snow/consensus/snowman"
    19  	"github.com/ava-labs/avalanchego/utils/set"
    20  	"github.com/ava-labs/avalanchego/utils/timer/mockable"
    21  	"github.com/ava-labs/avalanchego/utils/units"
    22  	"github.com/ava-labs/avalanchego/vms/components/gas"
    23  	"github.com/ava-labs/avalanchego/vms/platformvm/block"
    24  	"github.com/ava-labs/avalanchego/vms/platformvm/state"
    25  	"github.com/ava-labs/avalanchego/vms/platformvm/status"
    26  	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
    27  	"github.com/ava-labs/avalanchego/vms/platformvm/txs/fee"
    28  	"github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool"
    29  
    30  	blockexecutor "github.com/ava-labs/avalanchego/vms/platformvm/block/executor"
    31  	txexecutor "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor"
    32  )
    33  
    34  // targetBlockSize is maximum number of transaction bytes to place into a
    35  // StandardBlock
    36  const targetBlockSize = 128 * units.KiB
    37  
    38  var (
    39  	_ Builder = (*builder)(nil)
    40  
    41  	ErrEndOfTime                 = errors.New("program time is suspiciously far in the future")
    42  	ErrNoPendingBlocks           = errors.New("no pending blocks")
    43  	errMissingPreferredState     = errors.New("missing preferred block state")
    44  	errCalculatingNextStakerTime = errors.New("failed calculating next staker time")
    45  )
    46  
    47  type Builder interface {
    48  	mempool.Mempool
    49  
    50  	// StartBlockTimer starts to issue block creation requests to advance the
    51  	// chain timestamp.
    52  	StartBlockTimer()
    53  
    54  	// ResetBlockTimer forces the block timer to recalculate when it should
    55  	// advance the chain timestamp.
    56  	ResetBlockTimer()
    57  
    58  	// ShutdownBlockTimer stops block creation requests to advance the chain
    59  	// timestamp.
    60  	//
    61  	// Invariant: Assumes the context lock is held when calling.
    62  	ShutdownBlockTimer()
    63  
    64  	// BuildBlock can be called to attempt to create a new block
    65  	BuildBlock(context.Context) (snowman.Block, error)
    66  
    67  	// PackAllBlockTxs returns an array of all txs that could be packed into a
    68  	// valid block of infinite size. The returned txs are all verified against
    69  	// the preferred state.
    70  	//
    71  	// Note: This function does not call the consensus engine.
    72  	PackAllBlockTxs() ([]*txs.Tx, error)
    73  }
    74  
    75  // builder implements a simple builder to convert txs into valid blocks
    76  type builder struct {
    77  	mempool.Mempool
    78  
    79  	txExecutorBackend *txexecutor.Backend
    80  	blkManager        blockexecutor.Manager
    81  
    82  	// resetTimer is used to signal that the block builder timer should update
    83  	// when it will trigger building of a block.
    84  	resetTimer chan struct{}
    85  	closed     chan struct{}
    86  	closeOnce  sync.Once
    87  }
    88  
    89  func New(
    90  	mempool mempool.Mempool,
    91  	txExecutorBackend *txexecutor.Backend,
    92  	blkManager blockexecutor.Manager,
    93  ) Builder {
    94  	return &builder{
    95  		Mempool:           mempool,
    96  		txExecutorBackend: txExecutorBackend,
    97  		blkManager:        blkManager,
    98  		resetTimer:        make(chan struct{}, 1),
    99  		closed:            make(chan struct{}),
   100  	}
   101  }
   102  
   103  func (b *builder) StartBlockTimer() {
   104  	go func() {
   105  		timer := time.NewTimer(0)
   106  		defer timer.Stop()
   107  
   108  		for {
   109  			// Invariant: The [timer] is not stopped.
   110  			select {
   111  			case <-timer.C:
   112  			case <-b.resetTimer:
   113  				if !timer.Stop() {
   114  					<-timer.C
   115  				}
   116  			case <-b.closed:
   117  				return
   118  			}
   119  
   120  			// Note: Because the context lock is not held here, it is possible
   121  			// that [ShutdownBlockTimer] is called concurrently with this
   122  			// execution.
   123  			for {
   124  				duration, err := b.durationToSleep()
   125  				if err != nil {
   126  					b.txExecutorBackend.Ctx.Log.Error("block builder encountered a fatal error",
   127  						zap.Error(err),
   128  					)
   129  					return
   130  				}
   131  
   132  				if duration > 0 {
   133  					timer.Reset(duration)
   134  					break
   135  				}
   136  
   137  				// Block needs to be issued to advance time.
   138  				b.Mempool.RequestBuildBlock(true /*=emptyBlockPermitted*/)
   139  
   140  				// Invariant: ResetBlockTimer is guaranteed to be called after
   141  				// [durationToSleep] returns a value <= 0. This is because we
   142  				// are guaranteed to attempt to build block. After building a
   143  				// valid block, the chain will have its preference updated which
   144  				// may change the duration to sleep and trigger a timer reset.
   145  				select {
   146  				case <-b.resetTimer:
   147  				case <-b.closed:
   148  					return
   149  				}
   150  			}
   151  		}
   152  	}()
   153  }
   154  
   155  func (b *builder) durationToSleep() (time.Duration, error) {
   156  	// Grabbing the lock here enforces that this function is not called mid-way
   157  	// through modifying of the state.
   158  	b.txExecutorBackend.Ctx.Lock.Lock()
   159  	defer b.txExecutorBackend.Ctx.Lock.Unlock()
   160  
   161  	// If [ShutdownBlockTimer] was called, we want to exit the block timer
   162  	// goroutine. We check this with the context lock held because
   163  	// [ShutdownBlockTimer] is expected to only be called with the context lock
   164  	// held.
   165  	select {
   166  	case <-b.closed:
   167  		return 0, nil
   168  	default:
   169  	}
   170  
   171  	preferredID := b.blkManager.Preferred()
   172  	preferredState, ok := b.blkManager.GetState(preferredID)
   173  	if !ok {
   174  		return 0, fmt.Errorf("%w: %s", errMissingPreferredState, preferredID)
   175  	}
   176  
   177  	nextStakerChangeTime, err := state.GetNextStakerChangeTime(preferredState)
   178  	if err != nil {
   179  		return 0, fmt.Errorf("%w of %s: %w", errCalculatingNextStakerTime, preferredID, err)
   180  	}
   181  
   182  	now := b.txExecutorBackend.Clk.Time()
   183  	return nextStakerChangeTime.Sub(now), nil
   184  }
   185  
   186  func (b *builder) ResetBlockTimer() {
   187  	// Ensure that the timer will be reset at least once.
   188  	select {
   189  	case b.resetTimer <- struct{}{}:
   190  	default:
   191  	}
   192  }
   193  
   194  func (b *builder) ShutdownBlockTimer() {
   195  	b.closeOnce.Do(func() {
   196  		close(b.closed)
   197  	})
   198  }
   199  
   200  // BuildBlock builds a block to be added to consensus.
   201  // This method removes the transactions from the returned
   202  // blocks from the mempool.
   203  func (b *builder) BuildBlock(context.Context) (snowman.Block, error) {
   204  	// If there are still transactions in the mempool, then we need to
   205  	// re-trigger block building.
   206  	defer b.Mempool.RequestBuildBlock(false /*=emptyBlockPermitted*/)
   207  
   208  	b.txExecutorBackend.Ctx.Log.Debug("starting to attempt to build a block")
   209  
   210  	// Get the block to build on top of and retrieve the new block's context.
   211  	preferredID := b.blkManager.Preferred()
   212  	preferred, err := b.blkManager.GetBlock(preferredID)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	nextHeight := preferred.Height() + 1
   217  	preferredState, ok := b.blkManager.GetState(preferredID)
   218  	if !ok {
   219  		return nil, fmt.Errorf("%w: %s", state.ErrMissingParentState, preferredID)
   220  	}
   221  
   222  	timestamp, timeWasCapped, err := state.NextBlockTime(preferredState, b.txExecutorBackend.Clk)
   223  	if err != nil {
   224  		return nil, fmt.Errorf("could not calculate next staker change time: %w", err)
   225  	}
   226  
   227  	statelessBlk, err := buildBlock(
   228  		b,
   229  		preferredID,
   230  		nextHeight,
   231  		timestamp,
   232  		timeWasCapped,
   233  		preferredState,
   234  	)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	return b.blkManager.NewBlock(statelessBlk), nil
   240  }
   241  
   242  func (b *builder) PackAllBlockTxs() ([]*txs.Tx, error) {
   243  	preferredID := b.blkManager.Preferred()
   244  	preferredState, ok := b.blkManager.GetState(preferredID)
   245  	if !ok {
   246  		return nil, fmt.Errorf("%w: %s", errMissingPreferredState, preferredID)
   247  	}
   248  
   249  	timestamp, _, err := state.NextBlockTime(preferredState, b.txExecutorBackend.Clk)
   250  	if err != nil {
   251  		return nil, fmt.Errorf("could not calculate next staker change time: %w", err)
   252  	}
   253  
   254  	if !b.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) {
   255  		return packDurangoBlockTxs(
   256  			preferredID,
   257  			preferredState,
   258  			b.Mempool,
   259  			b.txExecutorBackend,
   260  			b.blkManager,
   261  			timestamp,
   262  			math.MaxInt,
   263  		)
   264  	}
   265  	return packEtnaBlockTxs(
   266  		preferredID,
   267  		preferredState,
   268  		b.Mempool,
   269  		b.txExecutorBackend,
   270  		b.blkManager,
   271  		timestamp,
   272  		math.MaxUint64,
   273  	)
   274  }
   275  
   276  // [timestamp] is min(max(now, parent timestamp), next staker change time)
   277  func buildBlock(
   278  	builder *builder,
   279  	parentID ids.ID,
   280  	height uint64,
   281  	timestamp time.Time,
   282  	forceAdvanceTime bool,
   283  	parentState state.Chain,
   284  ) (block.Block, error) {
   285  	var (
   286  		blockTxs []*txs.Tx
   287  		err      error
   288  	)
   289  	if builder.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) {
   290  		blockTxs, err = packEtnaBlockTxs(
   291  			parentID,
   292  			parentState,
   293  			builder.Mempool,
   294  			builder.txExecutorBackend,
   295  			builder.blkManager,
   296  			timestamp,
   297  			0, // minCapacity is 0 as we want to honor the capacity in state.
   298  		)
   299  	} else {
   300  		blockTxs, err = packDurangoBlockTxs(
   301  			parentID,
   302  			parentState,
   303  			builder.Mempool,
   304  			builder.txExecutorBackend,
   305  			builder.blkManager,
   306  			timestamp,
   307  			targetBlockSize,
   308  		)
   309  	}
   310  	if err != nil {
   311  		return nil, fmt.Errorf("failed to pack block txs: %w", err)
   312  	}
   313  
   314  	// Try rewarding stakers whose staking period ends at the new chain time.
   315  	// This is done first to prioritize advancing the timestamp as quickly as
   316  	// possible.
   317  	stakerTxID, shouldReward, err := getNextStakerToReward(timestamp, parentState)
   318  	if err != nil {
   319  		return nil, fmt.Errorf("could not find next staker to reward: %w", err)
   320  	}
   321  	if shouldReward {
   322  		rewardValidatorTx, err := NewRewardValidatorTx(builder.txExecutorBackend.Ctx, stakerTxID)
   323  		if err != nil {
   324  			return nil, fmt.Errorf("could not build tx to reward staker: %w", err)
   325  		}
   326  
   327  		return block.NewBanffProposalBlock(
   328  			timestamp,
   329  			parentID,
   330  			height,
   331  			rewardValidatorTx,
   332  			blockTxs,
   333  		)
   334  	}
   335  
   336  	// If there is no reason to build a block, don't.
   337  	if len(blockTxs) == 0 && !forceAdvanceTime {
   338  		builder.txExecutorBackend.Ctx.Log.Debug("no pending txs to issue into a block")
   339  		return nil, ErrNoPendingBlocks
   340  	}
   341  
   342  	// Issue a block with as many transactions as possible.
   343  	return block.NewBanffStandardBlock(
   344  		timestamp,
   345  		parentID,
   346  		height,
   347  		blockTxs,
   348  	)
   349  }
   350  
   351  func packDurangoBlockTxs(
   352  	parentID ids.ID,
   353  	parentState state.Chain,
   354  	mempool mempool.Mempool,
   355  	backend *txexecutor.Backend,
   356  	manager blockexecutor.Manager,
   357  	timestamp time.Time,
   358  	remainingSize int,
   359  ) ([]*txs.Tx, error) {
   360  	stateDiff, err := state.NewDiffOn(parentState)
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	if _, err := txexecutor.AdvanceTimeTo(backend, stateDiff, timestamp); err != nil {
   366  		return nil, err
   367  	}
   368  
   369  	var (
   370  		blockTxs      []*txs.Tx
   371  		inputs        set.Set[ids.ID]
   372  		feeCalculator = state.PickFeeCalculator(backend.Config, stateDiff)
   373  	)
   374  	for {
   375  		tx, exists := mempool.Peek()
   376  		if !exists {
   377  			break
   378  		}
   379  		txSize := len(tx.Bytes())
   380  		if txSize > remainingSize {
   381  			break
   382  		}
   383  
   384  		shouldAdd, err := executeTx(
   385  			parentID,
   386  			stateDiff,
   387  			mempool,
   388  			backend,
   389  			manager,
   390  			&inputs,
   391  			feeCalculator,
   392  			tx,
   393  		)
   394  		if err != nil {
   395  			return nil, err
   396  		}
   397  		if !shouldAdd {
   398  			continue
   399  		}
   400  
   401  		remainingSize -= txSize
   402  		blockTxs = append(blockTxs, tx)
   403  	}
   404  
   405  	return blockTxs, nil
   406  }
   407  
   408  func packEtnaBlockTxs(
   409  	parentID ids.ID,
   410  	parentState state.Chain,
   411  	mempool mempool.Mempool,
   412  	backend *txexecutor.Backend,
   413  	manager blockexecutor.Manager,
   414  	timestamp time.Time,
   415  	minCapacity gas.Gas,
   416  ) ([]*txs.Tx, error) {
   417  	stateDiff, err := state.NewDiffOn(parentState)
   418  	if err != nil {
   419  		return nil, err
   420  	}
   421  
   422  	if _, err := txexecutor.AdvanceTimeTo(backend, stateDiff, timestamp); err != nil {
   423  		return nil, err
   424  	}
   425  
   426  	feeState := stateDiff.GetFeeState()
   427  	capacity := max(feeState.Capacity, minCapacity)
   428  
   429  	var (
   430  		blockTxs        []*txs.Tx
   431  		inputs          set.Set[ids.ID]
   432  		blockComplexity gas.Dimensions
   433  		feeCalculator   = state.PickFeeCalculator(backend.Config, stateDiff)
   434  	)
   435  	for {
   436  		tx, exists := mempool.Peek()
   437  		if !exists {
   438  			break
   439  		}
   440  
   441  		txComplexity, err := fee.TxComplexity(tx.Unsigned)
   442  		if err != nil {
   443  			return nil, err
   444  		}
   445  		newBlockComplexity, err := blockComplexity.Add(&txComplexity)
   446  		if err != nil {
   447  			return nil, err
   448  		}
   449  		newBlockGas, err := newBlockComplexity.ToGas(backend.Config.DynamicFeeConfig.Weights)
   450  		if err != nil {
   451  			return nil, err
   452  		}
   453  		if newBlockGas > capacity {
   454  			break
   455  		}
   456  
   457  		shouldAdd, err := executeTx(
   458  			parentID,
   459  			stateDiff,
   460  			mempool,
   461  			backend,
   462  			manager,
   463  			&inputs,
   464  			feeCalculator,
   465  			tx,
   466  		)
   467  		if err != nil {
   468  			return nil, err
   469  		}
   470  		if !shouldAdd {
   471  			continue
   472  		}
   473  
   474  		blockComplexity = newBlockComplexity
   475  		blockTxs = append(blockTxs, tx)
   476  	}
   477  
   478  	return blockTxs, nil
   479  }
   480  
   481  func executeTx(
   482  	parentID ids.ID,
   483  	stateDiff state.Diff,
   484  	mempool mempool.Mempool,
   485  	backend *txexecutor.Backend,
   486  	manager blockexecutor.Manager,
   487  	inputs *set.Set[ids.ID],
   488  	feeCalculator fee.Calculator,
   489  	tx *txs.Tx,
   490  ) (bool, error) {
   491  	mempool.Remove(tx)
   492  
   493  	// Invariant: [tx] has already been syntactically verified.
   494  
   495  	txDiff, err := state.NewDiffOn(stateDiff)
   496  	if err != nil {
   497  		return false, err
   498  	}
   499  
   500  	executor := &txexecutor.StandardTxExecutor{
   501  		Backend:       backend,
   502  		State:         txDiff,
   503  		FeeCalculator: feeCalculator,
   504  		Tx:            tx,
   505  	}
   506  
   507  	err = tx.Unsigned.Visit(executor)
   508  	if err != nil {
   509  		txID := tx.ID()
   510  		mempool.MarkDropped(txID, err)
   511  		return false, nil
   512  	}
   513  
   514  	if inputs.Overlaps(executor.Inputs) {
   515  		txID := tx.ID()
   516  		mempool.MarkDropped(txID, blockexecutor.ErrConflictingBlockTxs)
   517  		return false, nil
   518  	}
   519  	err = manager.VerifyUniqueInputs(parentID, executor.Inputs)
   520  	if err != nil {
   521  		txID := tx.ID()
   522  		mempool.MarkDropped(txID, err)
   523  		return false, nil
   524  	}
   525  	inputs.Union(executor.Inputs)
   526  
   527  	txDiff.AddTx(tx, status.Committed)
   528  	return true, txDiff.Apply(stateDiff)
   529  }
   530  
   531  // getNextStakerToReward returns the next staker txID to remove from the staking
   532  // set with a RewardValidatorTx rather than an AdvanceTimeTx. [chainTimestamp]
   533  // is the timestamp of the chain at the time this validator would be getting
   534  // removed and is used to calculate [shouldReward].
   535  // Returns:
   536  // - [txID] of the next staker to reward
   537  // - [shouldReward] if the txID exists and is ready to be rewarded
   538  // - [err] if something bad happened
   539  func getNextStakerToReward(
   540  	chainTimestamp time.Time,
   541  	preferredState state.Chain,
   542  ) (ids.ID, bool, error) {
   543  	if !chainTimestamp.Before(mockable.MaxTime) {
   544  		return ids.Empty, false, ErrEndOfTime
   545  	}
   546  
   547  	currentStakerIterator, err := preferredState.GetCurrentStakerIterator()
   548  	if err != nil {
   549  		return ids.Empty, false, err
   550  	}
   551  	defer currentStakerIterator.Release()
   552  
   553  	for currentStakerIterator.Next() {
   554  		currentStaker := currentStakerIterator.Value()
   555  		priority := currentStaker.Priority
   556  		// If the staker is a permissionless staker (not a permissioned subnet
   557  		// validator), it's the next staker we will want to remove with a
   558  		// RewardValidatorTx rather than an AdvanceTimeTx.
   559  		if priority != txs.SubnetPermissionedValidatorCurrentPriority {
   560  			return currentStaker.TxID, chainTimestamp.Equal(currentStaker.EndTime), nil
   561  		}
   562  	}
   563  	return ids.Empty, false, nil
   564  }
   565  
   566  func NewRewardValidatorTx(ctx *snow.Context, txID ids.ID) (*txs.Tx, error) {
   567  	utx := &txs.RewardValidatorTx{TxID: txID}
   568  	tx, err := txs.NewSigned(utx, txs.Codec, nil)
   569  	if err != nil {
   570  		return nil, err
   571  	}
   572  	return tx, tx.SyntacticVerify(ctx)
   573  }