github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/block/executor/verifier.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package executor
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  
    10  	"github.com/MetalBlockchain/metalgo/chains/atomic"
    11  	"github.com/MetalBlockchain/metalgo/ids"
    12  	"github.com/MetalBlockchain/metalgo/utils/set"
    13  	"github.com/MetalBlockchain/metalgo/vms/platformvm/block"
    14  	"github.com/MetalBlockchain/metalgo/vms/platformvm/state"
    15  	"github.com/MetalBlockchain/metalgo/vms/platformvm/status"
    16  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs"
    17  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs/executor"
    18  )
    19  
    20  var (
    21  	_ block.Visitor = (*verifier)(nil)
    22  
    23  	ErrConflictingBlockTxs = errors.New("block contains conflicting transactions")
    24  
    25  	errApricotBlockIssuedAfterFork           = errors.New("apricot block issued after fork")
    26  	errBanffStandardBlockWithoutChanges      = errors.New("BanffStandardBlock performs no state changes")
    27  	errIncorrectBlockHeight                  = errors.New("incorrect block height")
    28  	errChildBlockEarlierThanParent           = errors.New("proposed timestamp before current chain time")
    29  	errOptionBlockTimestampNotMatchingParent = errors.New("option block proposed timestamp not matching parent block one")
    30  )
    31  
    32  // verifier handles the logic for verifying a block.
    33  type verifier struct {
    34  	*backend
    35  	txExecutorBackend *executor.Backend
    36  }
    37  
    38  func (v *verifier) BanffAbortBlock(b *block.BanffAbortBlock) error {
    39  	if err := v.banffOptionBlock(b); err != nil {
    40  		return err
    41  	}
    42  	return v.abortBlock(b)
    43  }
    44  
    45  func (v *verifier) BanffCommitBlock(b *block.BanffCommitBlock) error {
    46  	if err := v.banffOptionBlock(b); err != nil {
    47  		return err
    48  	}
    49  	return v.commitBlock(b)
    50  }
    51  
    52  func (v *verifier) BanffProposalBlock(b *block.BanffProposalBlock) error {
    53  	if err := v.banffNonOptionBlock(b); err != nil {
    54  		return err
    55  	}
    56  
    57  	parentID := b.Parent()
    58  	onDecisionState, err := state.NewDiff(parentID, v.backend)
    59  	if err != nil {
    60  		return err
    61  	}
    62  
    63  	// Advance the time to [nextChainTime].
    64  	nextChainTime := b.Timestamp()
    65  	if _, err := executor.AdvanceTimeTo(v.txExecutorBackend, onDecisionState, nextChainTime); err != nil {
    66  		return err
    67  	}
    68  
    69  	inputs, atomicRequests, onAcceptFunc, err := v.processStandardTxs(b.Transactions, onDecisionState, b.Parent())
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	onCommitState, err := state.NewDiffOn(onDecisionState)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	onAbortState, err := state.NewDiffOn(onDecisionState)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	return v.proposalBlock(
    85  		&b.ApricotProposalBlock,
    86  		onDecisionState,
    87  		onCommitState,
    88  		onAbortState,
    89  		inputs,
    90  		atomicRequests,
    91  		onAcceptFunc,
    92  	)
    93  }
    94  
    95  func (v *verifier) BanffStandardBlock(b *block.BanffStandardBlock) error {
    96  	if err := v.banffNonOptionBlock(b); err != nil {
    97  		return err
    98  	}
    99  
   100  	parentID := b.Parent()
   101  	onAcceptState, err := state.NewDiff(parentID, v.backend)
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	// Advance the time to [b.Timestamp()].
   107  	changed, err := executor.AdvanceTimeTo(
   108  		v.txExecutorBackend,
   109  		onAcceptState,
   110  		b.Timestamp(),
   111  	)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	// If this block doesn't perform any changes, then it should never have been
   117  	// issued.
   118  	if !changed && len(b.Transactions) == 0 {
   119  		return errBanffStandardBlockWithoutChanges
   120  	}
   121  
   122  	return v.standardBlock(&b.ApricotStandardBlock, onAcceptState)
   123  }
   124  
   125  func (v *verifier) ApricotAbortBlock(b *block.ApricotAbortBlock) error {
   126  	if err := v.apricotCommonBlock(b); err != nil {
   127  		return err
   128  	}
   129  	return v.abortBlock(b)
   130  }
   131  
   132  func (v *verifier) ApricotCommitBlock(b *block.ApricotCommitBlock) error {
   133  	if err := v.apricotCommonBlock(b); err != nil {
   134  		return err
   135  	}
   136  	return v.commitBlock(b)
   137  }
   138  
   139  func (v *verifier) ApricotProposalBlock(b *block.ApricotProposalBlock) error {
   140  	if err := v.apricotCommonBlock(b); err != nil {
   141  		return err
   142  	}
   143  
   144  	parentID := b.Parent()
   145  	onCommitState, err := state.NewDiff(parentID, v.backend)
   146  	if err != nil {
   147  		return err
   148  	}
   149  	onAbortState, err := state.NewDiff(parentID, v.backend)
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	return v.proposalBlock(b, nil, onCommitState, onAbortState, nil, nil, nil)
   155  }
   156  
   157  func (v *verifier) ApricotStandardBlock(b *block.ApricotStandardBlock) error {
   158  	if err := v.apricotCommonBlock(b); err != nil {
   159  		return err
   160  	}
   161  
   162  	parentID := b.Parent()
   163  	onAcceptState, err := state.NewDiff(parentID, v)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	return v.standardBlock(b, onAcceptState)
   169  }
   170  
   171  func (v *verifier) ApricotAtomicBlock(b *block.ApricotAtomicBlock) error {
   172  	// We call [commonBlock] here rather than [apricotCommonBlock] because below
   173  	// this check we perform the more strict check that ApricotPhase5 isn't
   174  	// activated.
   175  	if err := v.commonBlock(b); err != nil {
   176  		return err
   177  	}
   178  
   179  	parentID := b.Parent()
   180  	currentTimestamp := v.getTimestamp(parentID)
   181  	cfg := v.txExecutorBackend.Config
   182  	if cfg.UpgradeConfig.IsApricotPhase5Activated(currentTimestamp) {
   183  		return fmt.Errorf(
   184  			"the chain timestamp (%d) is after the apricot phase 5 time (%d), hence atomic transactions should go through the standard block",
   185  			currentTimestamp.Unix(),
   186  			cfg.UpgradeConfig.ApricotPhase5Time.Unix(),
   187  		)
   188  	}
   189  
   190  	atomicExecutor := executor.AtomicTxExecutor{
   191  		Backend:       v.txExecutorBackend,
   192  		ParentID:      parentID,
   193  		StateVersions: v,
   194  		Tx:            b.Tx,
   195  	}
   196  
   197  	if err := b.Tx.Unsigned.Visit(&atomicExecutor); err != nil {
   198  		txID := b.Tx.ID()
   199  		v.MarkDropped(txID, err) // cache tx as dropped
   200  		return fmt.Errorf("tx %s failed semantic verification: %w", txID, err)
   201  	}
   202  
   203  	atomicExecutor.OnAccept.AddTx(b.Tx, status.Committed)
   204  
   205  	if err := v.verifyUniqueInputs(parentID, atomicExecutor.Inputs); err != nil {
   206  		return err
   207  	}
   208  
   209  	v.Mempool.Remove(b.Tx)
   210  
   211  	blkID := b.ID()
   212  	v.blkIDToState[blkID] = &blockState{
   213  		statelessBlock: b,
   214  
   215  		onAcceptState: atomicExecutor.OnAccept,
   216  
   217  		inputs:         atomicExecutor.Inputs,
   218  		timestamp:      atomicExecutor.OnAccept.GetTimestamp(),
   219  		atomicRequests: atomicExecutor.AtomicRequests,
   220  	}
   221  	return nil
   222  }
   223  
   224  func (v *verifier) banffOptionBlock(b block.BanffBlock) error {
   225  	if err := v.commonBlock(b); err != nil {
   226  		return err
   227  	}
   228  
   229  	// Banff option blocks must be uniquely generated from the
   230  	// BanffProposalBlock. This means that the timestamp must be
   231  	// standardized to a specific value. Therefore, we require the timestamp to
   232  	// be equal to the parents timestamp.
   233  	parentID := b.Parent()
   234  	parentBlkTime := v.getTimestamp(parentID)
   235  	blkTime := b.Timestamp()
   236  	if !blkTime.Equal(parentBlkTime) {
   237  		return fmt.Errorf(
   238  			"%w parent block timestamp (%s) option block timestamp (%s)",
   239  			errOptionBlockTimestampNotMatchingParent,
   240  			parentBlkTime,
   241  			blkTime,
   242  		)
   243  	}
   244  	return nil
   245  }
   246  
   247  func (v *verifier) banffNonOptionBlock(b block.BanffBlock) error {
   248  	if err := v.commonBlock(b); err != nil {
   249  		return err
   250  	}
   251  
   252  	parentID := b.Parent()
   253  	parentState, ok := v.GetState(parentID)
   254  	if !ok {
   255  		return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID)
   256  	}
   257  
   258  	newChainTime := b.Timestamp()
   259  	parentChainTime := parentState.GetTimestamp()
   260  	if newChainTime.Before(parentChainTime) {
   261  		return fmt.Errorf(
   262  			"%w: proposed timestamp (%s), chain time (%s)",
   263  			errChildBlockEarlierThanParent,
   264  			newChainTime,
   265  			parentChainTime,
   266  		)
   267  	}
   268  
   269  	nextStakerChangeTime, err := state.GetNextStakerChangeTime(parentState)
   270  	if err != nil {
   271  		return fmt.Errorf("could not verify block timestamp: %w", err)
   272  	}
   273  
   274  	now := v.txExecutorBackend.Clk.Time()
   275  	return executor.VerifyNewChainTime(
   276  		newChainTime,
   277  		nextStakerChangeTime,
   278  		now,
   279  	)
   280  }
   281  
   282  func (v *verifier) apricotCommonBlock(b block.Block) error {
   283  	// We can use the parent timestamp here, because we are guaranteed that the
   284  	// parent was verified. Apricot blocks only update the timestamp with
   285  	// AdvanceTimeTxs. This means that this block's timestamp will be equal to
   286  	// the parent block's timestamp; unless this is a CommitBlock. In order for
   287  	// the timestamp of the CommitBlock to be after the Banff activation,
   288  	// the parent ApricotProposalBlock must include an AdvanceTimeTx with a
   289  	// timestamp after the Banff timestamp. This is verified not to occur
   290  	// during the verification of the ProposalBlock.
   291  	parentID := b.Parent()
   292  	timestamp := v.getTimestamp(parentID)
   293  	if v.txExecutorBackend.Config.UpgradeConfig.IsBanffActivated(timestamp) {
   294  		return fmt.Errorf("%w: timestamp = %s", errApricotBlockIssuedAfterFork, timestamp)
   295  	}
   296  	return v.commonBlock(b)
   297  }
   298  
   299  func (v *verifier) commonBlock(b block.Block) error {
   300  	parentID := b.Parent()
   301  	parent, err := v.GetBlock(parentID)
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	expectedHeight := parent.Height() + 1
   307  	height := b.Height()
   308  	if expectedHeight != height {
   309  		return fmt.Errorf(
   310  			"%w expected %d, but found %d",
   311  			errIncorrectBlockHeight,
   312  			expectedHeight,
   313  			height,
   314  		)
   315  	}
   316  	return nil
   317  }
   318  
   319  // abortBlock populates the state of this block if [nil] is returned
   320  func (v *verifier) abortBlock(b block.Block) error {
   321  	parentID := b.Parent()
   322  	onAbortState, ok := v.getOnAbortState(parentID)
   323  	if !ok {
   324  		return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID)
   325  	}
   326  
   327  	blkID := b.ID()
   328  	v.blkIDToState[blkID] = &blockState{
   329  		statelessBlock: b,
   330  		onAcceptState:  onAbortState,
   331  		timestamp:      onAbortState.GetTimestamp(),
   332  	}
   333  	return nil
   334  }
   335  
   336  // commitBlock populates the state of this block if [nil] is returned
   337  func (v *verifier) commitBlock(b block.Block) error {
   338  	parentID := b.Parent()
   339  	onCommitState, ok := v.getOnCommitState(parentID)
   340  	if !ok {
   341  		return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID)
   342  	}
   343  
   344  	blkID := b.ID()
   345  	v.blkIDToState[blkID] = &blockState{
   346  		statelessBlock: b,
   347  		onAcceptState:  onCommitState,
   348  		timestamp:      onCommitState.GetTimestamp(),
   349  	}
   350  	return nil
   351  }
   352  
   353  // proposalBlock populates the state of this block if [nil] is returned
   354  func (v *verifier) proposalBlock(
   355  	b *block.ApricotProposalBlock,
   356  	onDecisionState state.Diff,
   357  	onCommitState state.Diff,
   358  	onAbortState state.Diff,
   359  	inputs set.Set[ids.ID],
   360  	atomicRequests map[ids.ID]*atomic.Requests,
   361  	onAcceptFunc func(),
   362  ) error {
   363  	txExecutor := executor.ProposalTxExecutor{
   364  		OnCommitState: onCommitState,
   365  		OnAbortState:  onAbortState,
   366  		Backend:       v.txExecutorBackend,
   367  		Tx:            b.Tx,
   368  	}
   369  
   370  	if err := b.Tx.Unsigned.Visit(&txExecutor); err != nil {
   371  		txID := b.Tx.ID()
   372  		v.MarkDropped(txID, err) // cache tx as dropped
   373  		return err
   374  	}
   375  
   376  	onCommitState.AddTx(b.Tx, status.Committed)
   377  	onAbortState.AddTx(b.Tx, status.Aborted)
   378  
   379  	v.Mempool.Remove(b.Tx)
   380  
   381  	blkID := b.ID()
   382  	v.blkIDToState[blkID] = &blockState{
   383  		proposalBlockState: proposalBlockState{
   384  			onDecisionState: onDecisionState,
   385  			onCommitState:   onCommitState,
   386  			onAbortState:    onAbortState,
   387  		},
   388  
   389  		statelessBlock: b,
   390  
   391  		onAcceptFunc: onAcceptFunc,
   392  
   393  		inputs: inputs,
   394  		// It is safe to use [b.onAbortState] here because the timestamp will
   395  		// never be modified by an Apricot Abort block and the timestamp will
   396  		// always be the same as the Banff Proposal Block.
   397  		timestamp:      onAbortState.GetTimestamp(),
   398  		atomicRequests: atomicRequests,
   399  	}
   400  	return nil
   401  }
   402  
   403  // standardBlock populates the state of this block if [nil] is returned
   404  func (v *verifier) standardBlock(
   405  	b *block.ApricotStandardBlock,
   406  	onAcceptState state.Diff,
   407  ) error {
   408  	inputs, atomicRequests, onAcceptFunc, err := v.processStandardTxs(b.Transactions, onAcceptState, b.Parent())
   409  	if err != nil {
   410  		return err
   411  	}
   412  
   413  	v.Mempool.Remove(b.Transactions...)
   414  
   415  	blkID := b.ID()
   416  	v.blkIDToState[blkID] = &blockState{
   417  		statelessBlock: b,
   418  
   419  		onAcceptState: onAcceptState,
   420  		onAcceptFunc:  onAcceptFunc,
   421  
   422  		timestamp:      onAcceptState.GetTimestamp(),
   423  		inputs:         inputs,
   424  		atomicRequests: atomicRequests,
   425  	}
   426  	return nil
   427  }
   428  
   429  func (v *verifier) processStandardTxs(txs []*txs.Tx, state state.Diff, parentID ids.ID) (
   430  	set.Set[ids.ID],
   431  	map[ids.ID]*atomic.Requests,
   432  	func(),
   433  	error,
   434  ) {
   435  	var (
   436  		onAcceptFunc   func()
   437  		inputs         set.Set[ids.ID]
   438  		funcs          = make([]func(), 0, len(txs))
   439  		atomicRequests = make(map[ids.ID]*atomic.Requests)
   440  	)
   441  	for _, tx := range txs {
   442  		txExecutor := executor.StandardTxExecutor{
   443  			Backend: v.txExecutorBackend,
   444  			State:   state,
   445  			Tx:      tx,
   446  		}
   447  		if err := tx.Unsigned.Visit(&txExecutor); err != nil {
   448  			txID := tx.ID()
   449  			v.MarkDropped(txID, err) // cache tx as dropped
   450  			return nil, nil, nil, err
   451  		}
   452  		// ensure it doesn't overlap with current input batch
   453  		if inputs.Overlaps(txExecutor.Inputs) {
   454  			return nil, nil, nil, ErrConflictingBlockTxs
   455  		}
   456  		// Add UTXOs to batch
   457  		inputs.Union(txExecutor.Inputs)
   458  
   459  		state.AddTx(tx, status.Committed)
   460  		if txExecutor.OnAccept != nil {
   461  			funcs = append(funcs, txExecutor.OnAccept)
   462  		}
   463  
   464  		for chainID, txRequests := range txExecutor.AtomicRequests {
   465  			// Add/merge in the atomic requests represented by [tx]
   466  			chainRequests, exists := atomicRequests[chainID]
   467  			if !exists {
   468  				atomicRequests[chainID] = txRequests
   469  				continue
   470  			}
   471  
   472  			chainRequests.PutRequests = append(chainRequests.PutRequests, txRequests.PutRequests...)
   473  			chainRequests.RemoveRequests = append(chainRequests.RemoveRequests, txRequests.RemoveRequests...)
   474  		}
   475  	}
   476  
   477  	if err := v.verifyUniqueInputs(parentID, inputs); err != nil {
   478  		return nil, nil, nil, err
   479  	}
   480  
   481  	if numFuncs := len(funcs); numFuncs == 1 {
   482  		onAcceptFunc = funcs[0]
   483  	} else if numFuncs > 1 {
   484  		onAcceptFunc = func() {
   485  			for _, f := range funcs {
   486  				f()
   487  			}
   488  		}
   489  	}
   490  
   491  	return inputs, atomicRequests, onAcceptFunc, nil
   492  }