github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/block/executor/acceptor.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  	"go.uber.org/zap"
    11  
    12  	"github.com/ava-labs/avalanchego/utils"
    13  	"github.com/ava-labs/avalanchego/vms/platformvm/block"
    14  	"github.com/ava-labs/avalanchego/vms/platformvm/metrics"
    15  	"github.com/ava-labs/avalanchego/vms/platformvm/state"
    16  	"github.com/ava-labs/avalanchego/vms/platformvm/validators"
    17  )
    18  
    19  var (
    20  	_ block.Visitor = (*acceptor)(nil)
    21  
    22  	errMissingBlockState = errors.New("missing state of block")
    23  )
    24  
    25  // acceptor handles the logic for accepting a block.
    26  // All errors returned by this struct are fatal and should result in the chain
    27  // being shutdown.
    28  type acceptor struct {
    29  	*backend
    30  	metrics      metrics.Metrics
    31  	validators   validators.Manager
    32  	bootstrapped *utils.Atomic[bool]
    33  }
    34  
    35  func (a *acceptor) BanffAbortBlock(b *block.BanffAbortBlock) error {
    36  	return a.optionBlock(b, "banff abort")
    37  }
    38  
    39  func (a *acceptor) BanffCommitBlock(b *block.BanffCommitBlock) error {
    40  	return a.optionBlock(b, "banff commit")
    41  }
    42  
    43  func (a *acceptor) BanffProposalBlock(b *block.BanffProposalBlock) error {
    44  	a.proposalBlock(b, "banff proposal")
    45  	return nil
    46  }
    47  
    48  func (a *acceptor) BanffStandardBlock(b *block.BanffStandardBlock) error {
    49  	return a.standardBlock(b, "banff standard")
    50  }
    51  
    52  func (a *acceptor) ApricotAbortBlock(b *block.ApricotAbortBlock) error {
    53  	return a.optionBlock(b, "apricot abort")
    54  }
    55  
    56  func (a *acceptor) ApricotCommitBlock(b *block.ApricotCommitBlock) error {
    57  	return a.optionBlock(b, "apricot commit")
    58  }
    59  
    60  func (a *acceptor) ApricotProposalBlock(b *block.ApricotProposalBlock) error {
    61  	a.proposalBlock(b, "apricot proposal")
    62  	return nil
    63  }
    64  
    65  func (a *acceptor) ApricotStandardBlock(b *block.ApricotStandardBlock) error {
    66  	return a.standardBlock(b, "apricot standard")
    67  }
    68  
    69  func (a *acceptor) ApricotAtomicBlock(b *block.ApricotAtomicBlock) error {
    70  	blkID := b.ID()
    71  	defer a.free(blkID)
    72  
    73  	if err := a.commonAccept(b); err != nil {
    74  		return err
    75  	}
    76  
    77  	blkState, ok := a.blkIDToState[blkID]
    78  	if !ok {
    79  		return fmt.Errorf("%w %s", errMissingBlockState, blkID)
    80  	}
    81  
    82  	// Update the state to reflect the changes made in [onAcceptState].
    83  	if err := blkState.onAcceptState.Apply(a.state); err != nil {
    84  		return err
    85  	}
    86  
    87  	defer a.state.Abort()
    88  	batch, err := a.state.CommitBatch()
    89  	if err != nil {
    90  		return fmt.Errorf(
    91  			"failed to commit VM's database for block %s: %w",
    92  			blkID,
    93  			err,
    94  		)
    95  	}
    96  
    97  	// Note that this method writes [batch] to the database.
    98  	if err := a.ctx.SharedMemory.Apply(blkState.atomicRequests, batch); err != nil {
    99  		return fmt.Errorf(
   100  			"failed to atomically accept tx %s in block %s: %w",
   101  			b.Tx.ID(),
   102  			blkID,
   103  			err,
   104  		)
   105  	}
   106  
   107  	a.ctx.Log.Trace(
   108  		"accepted block",
   109  		zap.String("blockType", "apricot atomic"),
   110  		zap.Stringer("blkID", blkID),
   111  		zap.Uint64("height", b.Height()),
   112  		zap.Stringer("parentID", b.Parent()),
   113  		zap.Stringer("utxoChecksum", a.state.Checksum()),
   114  	)
   115  
   116  	return nil
   117  }
   118  
   119  func (a *acceptor) optionBlock(b block.Block, blockType string) error {
   120  	parentID := b.Parent()
   121  	parentState, ok := a.blkIDToState[parentID]
   122  	if !ok {
   123  		return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID)
   124  	}
   125  
   126  	blkID := b.ID()
   127  	defer func() {
   128  		// Note: we assume this block's sibling doesn't
   129  		// need the parent's state when it's rejected.
   130  		a.free(parentID)
   131  		a.free(blkID)
   132  	}()
   133  
   134  	// Note that the parent must be accepted first.
   135  	if err := a.commonAccept(parentState.statelessBlock); err != nil {
   136  		return err
   137  	}
   138  
   139  	if err := a.commonAccept(b); err != nil {
   140  		return err
   141  	}
   142  
   143  	if parentState.onDecisionState != nil {
   144  		if err := parentState.onDecisionState.Apply(a.state); err != nil {
   145  			return err
   146  		}
   147  	}
   148  
   149  	blkState, ok := a.blkIDToState[blkID]
   150  	if !ok {
   151  		return fmt.Errorf("%w %s", errMissingBlockState, blkID)
   152  	}
   153  	if err := blkState.onAcceptState.Apply(a.state); err != nil {
   154  		return err
   155  	}
   156  
   157  	defer a.state.Abort()
   158  	batch, err := a.state.CommitBatch()
   159  	if err != nil {
   160  		return fmt.Errorf(
   161  			"failed to commit VM's database for block %s: %w",
   162  			blkID,
   163  			err,
   164  		)
   165  	}
   166  
   167  	// Note that this method writes [batch] to the database.
   168  	if err := a.ctx.SharedMemory.Apply(parentState.atomicRequests, batch); err != nil {
   169  		return fmt.Errorf("failed to apply vm's state to shared memory: %w", err)
   170  	}
   171  
   172  	if onAcceptFunc := parentState.onAcceptFunc; onAcceptFunc != nil {
   173  		onAcceptFunc()
   174  	}
   175  
   176  	a.ctx.Log.Trace(
   177  		"accepted block",
   178  		zap.String("blockType", blockType),
   179  		zap.Stringer("blkID", blkID),
   180  		zap.Uint64("height", b.Height()),
   181  		zap.Stringer("parentID", parentID),
   182  		zap.Stringer("utxoChecksum", a.state.Checksum()),
   183  	)
   184  
   185  	return nil
   186  }
   187  
   188  func (a *acceptor) proposalBlock(b block.Block, blockType string) {
   189  	// Note that:
   190  	//
   191  	// * We don't free the proposal block in this method.
   192  	//   It is freed when its child is accepted.
   193  	//   We need to keep this block's state in memory for its child to use.
   194  	//
   195  	// * We only update the metrics to reflect this block's
   196  	//   acceptance when its child is accepted.
   197  	//
   198  	// * We don't write this block to state here.
   199  	//   That is done when this block's child (a CommitBlock or AbortBlock) is accepted.
   200  	//   We do this so that in the event that the node shuts down, the proposal block
   201  	//   is not written to disk unless its child is.
   202  	//   (The VM's Shutdown method commits the database.)
   203  	//   The snowman.Engine requires that the last committed block is a decision block
   204  
   205  	blkID := b.ID()
   206  	a.backend.lastAccepted = blkID
   207  
   208  	a.ctx.Log.Trace(
   209  		"accepted block",
   210  		zap.String("blockType", blockType),
   211  		zap.Stringer("blkID", blkID),
   212  		zap.Uint64("height", b.Height()),
   213  		zap.Stringer("parentID", b.Parent()),
   214  		zap.Stringer("utxoChecksum", a.state.Checksum()),
   215  	)
   216  }
   217  
   218  func (a *acceptor) standardBlock(b block.Block, blockType string) error {
   219  	blkID := b.ID()
   220  	defer a.free(blkID)
   221  
   222  	if err := a.commonAccept(b); err != nil {
   223  		return err
   224  	}
   225  
   226  	blkState, ok := a.blkIDToState[blkID]
   227  	if !ok {
   228  		return fmt.Errorf("%w %s", errMissingBlockState, blkID)
   229  	}
   230  
   231  	// Update the state to reflect the changes made in [onAcceptState].
   232  	if err := blkState.onAcceptState.Apply(a.state); err != nil {
   233  		return err
   234  	}
   235  
   236  	defer a.state.Abort()
   237  	batch, err := a.state.CommitBatch()
   238  	if err != nil {
   239  		return fmt.Errorf(
   240  			"failed to commit VM's database for block %s: %w",
   241  			blkID,
   242  			err,
   243  		)
   244  	}
   245  
   246  	// Note that this method writes [batch] to the database.
   247  	if err := a.ctx.SharedMemory.Apply(blkState.atomicRequests, batch); err != nil {
   248  		return fmt.Errorf("failed to apply vm's state to shared memory: %w", err)
   249  	}
   250  
   251  	if onAcceptFunc := blkState.onAcceptFunc; onAcceptFunc != nil {
   252  		onAcceptFunc()
   253  	}
   254  
   255  	a.ctx.Log.Trace(
   256  		"accepted block",
   257  		zap.String("blockType", blockType),
   258  		zap.Stringer("blkID", blkID),
   259  		zap.Uint64("height", b.Height()),
   260  		zap.Stringer("parentID", b.Parent()),
   261  		zap.Stringer("utxoChecksum", a.state.Checksum()),
   262  	)
   263  
   264  	return nil
   265  }
   266  
   267  func (a *acceptor) commonAccept(b block.Block) error {
   268  	blkID := b.ID()
   269  
   270  	if err := a.metrics.MarkAccepted(b); err != nil {
   271  		return fmt.Errorf("failed to accept block %s: %w", blkID, err)
   272  	}
   273  
   274  	a.backend.lastAccepted = blkID
   275  	a.state.SetLastAccepted(blkID)
   276  	a.state.SetHeight(b.Height())
   277  	a.state.AddStatelessBlock(b)
   278  	a.validators.OnAcceptedBlockID(blkID)
   279  	return nil
   280  }