github.com/MetalBlockchain/metalgo@v1.11.9/vms/components/chain/state.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package chain
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/prometheus/client_golang/prometheus"
    12  
    13  	"github.com/MetalBlockchain/metalgo/cache"
    14  	"github.com/MetalBlockchain/metalgo/cache/metercacher"
    15  	"github.com/MetalBlockchain/metalgo/database"
    16  	"github.com/MetalBlockchain/metalgo/ids"
    17  	"github.com/MetalBlockchain/metalgo/snow/choices"
    18  	"github.com/MetalBlockchain/metalgo/snow/consensus/snowman"
    19  	"github.com/MetalBlockchain/metalgo/snow/engine/snowman/block"
    20  	"github.com/MetalBlockchain/metalgo/utils/constants"
    21  )
    22  
    23  func cachedBlockSize(_ ids.ID, bw *BlockWrapper) int {
    24  	return ids.IDLen + len(bw.Bytes()) + 2*constants.PointerOverhead
    25  }
    26  
    27  func cachedBlockBytesSize(blockBytes string, _ ids.ID) int {
    28  	return len(blockBytes) + ids.IDLen
    29  }
    30  
    31  // State implements an efficient caching layer used to wrap a VM
    32  // implementation.
    33  type State struct {
    34  	// getBlock retrieves a block from the VM's storage. If getBlock returns
    35  	// a nil error, then the returned block must not have the status Unknown
    36  	getBlock func(context.Context, ids.ID) (snowman.Block, error)
    37  	// unmarshals [b] into a block
    38  	unmarshalBlock        func(context.Context, []byte) (snowman.Block, error)
    39  	batchedUnmarshalBlock func(context.Context, [][]byte) ([]snowman.Block, error)
    40  	// buildBlock attempts to build a block on top of the currently preferred block
    41  	// buildBlock should always return a block with status Processing since it should never
    42  	// create an unknown block, and building on top of the preferred block should never yield
    43  	// a block that has already been decided.
    44  	buildBlock func(context.Context) (snowman.Block, error)
    45  
    46  	// If nil, [BuildBlockWithContext] returns [BuildBlock].
    47  	buildBlockWithContext func(context.Context, *block.Context) (snowman.Block, error)
    48  
    49  	// getStatus returns the status of the block
    50  	getStatus func(context.Context, snowman.Block) (choices.Status, error)
    51  
    52  	// verifiedBlocks is a map of blocks that have been verified and are
    53  	// therefore currently in consensus.
    54  	verifiedBlocks map[ids.ID]*BlockWrapper
    55  	// decidedBlocks is an LRU cache of decided blocks.
    56  	decidedBlocks cache.Cacher[ids.ID, *BlockWrapper]
    57  	// unverifiedBlocks is an LRU cache of blocks with status processing
    58  	// that have not yet passed verification.
    59  	unverifiedBlocks cache.Cacher[ids.ID, *BlockWrapper]
    60  	// missingBlocks is an LRU cache of missing blocks
    61  	missingBlocks cache.Cacher[ids.ID, struct{}]
    62  	// string([byte repr. of block]) --> the block's ID
    63  	bytesToIDCache    cache.Cacher[string, ids.ID]
    64  	lastAcceptedBlock *BlockWrapper
    65  }
    66  
    67  // Config defines all of the parameters necessary to initialize State
    68  type Config struct {
    69  	// Cache configuration:
    70  	DecidedCacheSize, MissingCacheSize, UnverifiedCacheSize, BytesToIDCacheSize int
    71  
    72  	LastAcceptedBlock     snowman.Block
    73  	GetBlock              func(context.Context, ids.ID) (snowman.Block, error)
    74  	UnmarshalBlock        func(context.Context, []byte) (snowman.Block, error)
    75  	BatchedUnmarshalBlock func(context.Context, [][]byte) ([]snowman.Block, error)
    76  	BuildBlock            func(context.Context) (snowman.Block, error)
    77  	BuildBlockWithContext func(context.Context, *block.Context) (snowman.Block, error)
    78  	GetBlockIDAtHeight    func(context.Context, uint64) (ids.ID, error)
    79  }
    80  
    81  // Block is an interface wrapping the normal snowman.Block interface to be used in
    82  // association with passing in a non-nil function to GetBlockIDAtHeight
    83  type Block interface {
    84  	snowman.Block
    85  
    86  	SetStatus(choices.Status)
    87  }
    88  
    89  // produceGetStatus creates a getStatus function that infers the status of a block by using a function
    90  // passed in from the VM that gets the block ID at a specific height. It is assumed that for any height
    91  // less than or equal to the last accepted block, getBlockIDAtHeight returns the accepted blockID at
    92  // the requested height.
    93  func produceGetStatus(s *State, getBlockIDAtHeight func(context.Context, uint64) (ids.ID, error)) func(context.Context, snowman.Block) (choices.Status, error) {
    94  	return func(ctx context.Context, blk snowman.Block) (choices.Status, error) {
    95  		internalBlk, ok := blk.(Block)
    96  		if !ok {
    97  			return choices.Unknown, fmt.Errorf("expected block to match chain Block interface but found block of type %T", blk)
    98  		}
    99  		lastAcceptedHeight := s.lastAcceptedBlock.Height()
   100  		blkHeight := internalBlk.Height()
   101  		if blkHeight > lastAcceptedHeight {
   102  			internalBlk.SetStatus(choices.Processing)
   103  			return choices.Processing, nil
   104  		}
   105  
   106  		acceptedID, err := getBlockIDAtHeight(ctx, blkHeight)
   107  		switch err {
   108  		case nil:
   109  			if acceptedID == blk.ID() {
   110  				internalBlk.SetStatus(choices.Accepted)
   111  				return choices.Accepted, nil
   112  			}
   113  			internalBlk.SetStatus(choices.Rejected)
   114  			return choices.Rejected, nil
   115  		case database.ErrNotFound:
   116  			// Not found can happen if chain history is missing. In this case,
   117  			// the block may have been accepted or rejected, it isn't possible
   118  			// to know here.
   119  			internalBlk.SetStatus(choices.Processing)
   120  			return choices.Processing, nil
   121  		default:
   122  			return choices.Unknown, fmt.Errorf("%w: failed to get accepted blkID at height %d", err, blkHeight)
   123  		}
   124  	}
   125  }
   126  
   127  func (s *State) initialize(config *Config) {
   128  	s.verifiedBlocks = make(map[ids.ID]*BlockWrapper)
   129  	s.getBlock = config.GetBlock
   130  	s.buildBlock = config.BuildBlock
   131  	s.buildBlockWithContext = config.BuildBlockWithContext
   132  	s.unmarshalBlock = config.UnmarshalBlock
   133  	s.batchedUnmarshalBlock = config.BatchedUnmarshalBlock
   134  	if config.GetBlockIDAtHeight == nil {
   135  		s.getStatus = func(_ context.Context, blk snowman.Block) (choices.Status, error) {
   136  			return blk.Status(), nil
   137  		}
   138  	} else {
   139  		s.getStatus = produceGetStatus(s, config.GetBlockIDAtHeight)
   140  	}
   141  	s.lastAcceptedBlock = &BlockWrapper{
   142  		Block: config.LastAcceptedBlock,
   143  		state: s,
   144  	}
   145  	s.decidedBlocks.Put(config.LastAcceptedBlock.ID(), s.lastAcceptedBlock)
   146  }
   147  
   148  func NewState(config *Config) *State {
   149  	c := &State{
   150  		verifiedBlocks: make(map[ids.ID]*BlockWrapper),
   151  		decidedBlocks: cache.NewSizedLRU[ids.ID, *BlockWrapper](
   152  			config.DecidedCacheSize,
   153  			cachedBlockSize,
   154  		),
   155  		missingBlocks: &cache.LRU[ids.ID, struct{}]{Size: config.MissingCacheSize},
   156  		unverifiedBlocks: cache.NewSizedLRU[ids.ID, *BlockWrapper](
   157  			config.UnverifiedCacheSize,
   158  			cachedBlockSize,
   159  		),
   160  		bytesToIDCache: cache.NewSizedLRU[string, ids.ID](
   161  			config.BytesToIDCacheSize,
   162  			cachedBlockBytesSize,
   163  		),
   164  	}
   165  	c.initialize(config)
   166  	return c
   167  }
   168  
   169  func NewMeteredState(
   170  	registerer prometheus.Registerer,
   171  	config *Config,
   172  ) (*State, error) {
   173  	decidedCache, err := metercacher.New[ids.ID, *BlockWrapper](
   174  		"decided_cache",
   175  		registerer,
   176  		cache.NewSizedLRU[ids.ID, *BlockWrapper](
   177  			config.DecidedCacheSize,
   178  			cachedBlockSize,
   179  		),
   180  	)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  	missingCache, err := metercacher.New[ids.ID, struct{}](
   185  		"missing_cache",
   186  		registerer,
   187  		&cache.LRU[ids.ID, struct{}]{Size: config.MissingCacheSize},
   188  	)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	unverifiedCache, err := metercacher.New[ids.ID, *BlockWrapper](
   193  		"unverified_cache",
   194  		registerer,
   195  		cache.NewSizedLRU[ids.ID, *BlockWrapper](
   196  			config.UnverifiedCacheSize,
   197  			cachedBlockSize,
   198  		),
   199  	)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	bytesToIDCache, err := metercacher.New[string, ids.ID](
   204  		"bytes_to_id_cache",
   205  		registerer,
   206  		cache.NewSizedLRU[string, ids.ID](
   207  			config.BytesToIDCacheSize,
   208  			cachedBlockBytesSize,
   209  		),
   210  	)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	c := &State{
   215  		verifiedBlocks:   make(map[ids.ID]*BlockWrapper),
   216  		decidedBlocks:    decidedCache,
   217  		missingBlocks:    missingCache,
   218  		unverifiedBlocks: unverifiedCache,
   219  		bytesToIDCache:   bytesToIDCache,
   220  	}
   221  	c.initialize(config)
   222  	return c, nil
   223  }
   224  
   225  var errSetAcceptedWithProcessing = errors.New("cannot set last accepted block with blocks processing")
   226  
   227  // SetLastAcceptedBlock sets the last accepted block to [lastAcceptedBlock].
   228  // This should be called with an internal block - not a wrapped block returned
   229  // from state.
   230  //
   231  // This also flushes [lastAcceptedBlock] from missingBlocks and unverifiedBlocks
   232  // to ensure that their contents stay valid.
   233  func (s *State) SetLastAcceptedBlock(lastAcceptedBlock snowman.Block) error {
   234  	if len(s.verifiedBlocks) != 0 {
   235  		return fmt.Errorf("%w: %d", errSetAcceptedWithProcessing, len(s.verifiedBlocks))
   236  	}
   237  
   238  	// [lastAcceptedBlock] is no longer missing or unverified, so we evict it from the corresponding
   239  	// caches.
   240  	//
   241  	// Note: there's no need to evict from the decided blocks cache or bytesToIDCache since their
   242  	// contents will still be valid.
   243  	lastAcceptedBlockID := lastAcceptedBlock.ID()
   244  	s.missingBlocks.Evict(lastAcceptedBlockID)
   245  	s.unverifiedBlocks.Evict(lastAcceptedBlockID)
   246  	s.lastAcceptedBlock = &BlockWrapper{
   247  		Block: lastAcceptedBlock,
   248  		state: s,
   249  	}
   250  	s.decidedBlocks.Put(lastAcceptedBlockID, s.lastAcceptedBlock)
   251  
   252  	return nil
   253  }
   254  
   255  // Flush each block cache
   256  func (s *State) Flush() {
   257  	s.decidedBlocks.Flush()
   258  	s.missingBlocks.Flush()
   259  	s.unverifiedBlocks.Flush()
   260  	s.bytesToIDCache.Flush()
   261  }
   262  
   263  // GetBlock returns the BlockWrapper as snowman.Block corresponding to [blkID]
   264  func (s *State) GetBlock(ctx context.Context, blkID ids.ID) (snowman.Block, error) {
   265  	if blk, ok := s.getCachedBlock(blkID); ok {
   266  		return blk, nil
   267  	}
   268  
   269  	if _, ok := s.missingBlocks.Get(blkID); ok {
   270  		return nil, database.ErrNotFound
   271  	}
   272  
   273  	blk, err := s.getBlock(ctx, blkID)
   274  	// If getBlock returns [database.ErrNotFound], State considers
   275  	// this a cacheable miss.
   276  	if err == database.ErrNotFound {
   277  		s.missingBlocks.Put(blkID, struct{}{})
   278  		return nil, err
   279  	} else if err != nil {
   280  		return nil, err
   281  	}
   282  
   283  	// Since this block is not in consensus, addBlockOutsideConsensus
   284  	// is called to add [blk] to the correct cache.
   285  	return s.addBlockOutsideConsensus(ctx, blk)
   286  }
   287  
   288  // getCachedBlock checks the caches for [blkID] by priority. Returning
   289  // true if [blkID] is found in one of the caches.
   290  func (s *State) getCachedBlock(blkID ids.ID) (snowman.Block, bool) {
   291  	if blk, ok := s.verifiedBlocks[blkID]; ok {
   292  		return blk, true
   293  	}
   294  
   295  	if blk, ok := s.decidedBlocks.Get(blkID); ok {
   296  		return blk, true
   297  	}
   298  
   299  	if blk, ok := s.unverifiedBlocks.Get(blkID); ok {
   300  		return blk, true
   301  	}
   302  
   303  	return nil, false
   304  }
   305  
   306  // GetBlockInternal returns the internal representation of [blkID]
   307  func (s *State) GetBlockInternal(ctx context.Context, blkID ids.ID) (snowman.Block, error) {
   308  	wrappedBlk, err := s.GetBlock(ctx, blkID)
   309  	if err != nil {
   310  		return nil, err
   311  	}
   312  
   313  	return wrappedBlk.(*BlockWrapper).Block, nil
   314  }
   315  
   316  // ParseBlock attempts to parse [b] into an internal Block and adds it to the
   317  // appropriate caching layer if successful.
   318  func (s *State) ParseBlock(ctx context.Context, b []byte) (snowman.Block, error) {
   319  	// See if we've cached this block's ID by its byte repr.
   320  	cachedBlkID, blkIDCached := s.bytesToIDCache.Get(string(b))
   321  	if blkIDCached {
   322  		// See if we have this block cached
   323  		if cachedBlk, ok := s.getCachedBlock(cachedBlkID); ok {
   324  			return cachedBlk, nil
   325  		}
   326  	}
   327  
   328  	// We don't have this block cached by its byte repr.
   329  	// Parse the block from bytes
   330  	blk, err := s.unmarshalBlock(ctx, b)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  	blkID := blk.ID()
   335  	s.bytesToIDCache.Put(string(b), blkID)
   336  
   337  	// Only check the caches if we didn't do so above
   338  	if !blkIDCached {
   339  		// Check for an existing block, so we can return a unique block
   340  		// if processing or simply allow this block to be immediately
   341  		// garbage collected if it is already cached.
   342  		if cachedBlk, ok := s.getCachedBlock(blkID); ok {
   343  			return cachedBlk, nil
   344  		}
   345  	}
   346  
   347  	s.missingBlocks.Evict(blkID)
   348  
   349  	// Since this block is not in consensus, addBlockOutsideConsensus
   350  	// is called to add [blk] to the correct cache.
   351  	return s.addBlockOutsideConsensus(ctx, blk)
   352  }
   353  
   354  // BatchedParseBlock implements part of the block.BatchedChainVM interface. In
   355  // addition to performing all the caching as the ParseBlock function, it
   356  // performs at most one call to the underlying VM if [batchedUnmarshalBlock] was
   357  // provided.
   358  func (s *State) BatchedParseBlock(ctx context.Context, blksBytes [][]byte) ([]snowman.Block, error) {
   359  	blks := make([]snowman.Block, len(blksBytes))
   360  	idWasCached := make([]bool, len(blksBytes))
   361  	unparsedBlksBytes := make([][]byte, 0, len(blksBytes))
   362  	for i, blkBytes := range blksBytes {
   363  		// See if we've cached this block's ID by its byte repr.
   364  		blkID, blkIDCached := s.bytesToIDCache.Get(string(blkBytes))
   365  		idWasCached[i] = blkIDCached
   366  		if !blkIDCached {
   367  			unparsedBlksBytes = append(unparsedBlksBytes, blkBytes)
   368  			continue
   369  		}
   370  
   371  		// See if we have this block cached
   372  		if cachedBlk, ok := s.getCachedBlock(blkID); ok {
   373  			blks[i] = cachedBlk
   374  		} else {
   375  			unparsedBlksBytes = append(unparsedBlksBytes, blkBytes)
   376  		}
   377  	}
   378  
   379  	if len(unparsedBlksBytes) == 0 {
   380  		return blks, nil
   381  	}
   382  
   383  	var (
   384  		parsedBlks []snowman.Block
   385  		err        error
   386  	)
   387  	if s.batchedUnmarshalBlock != nil {
   388  		parsedBlks, err = s.batchedUnmarshalBlock(ctx, unparsedBlksBytes)
   389  		if err != nil {
   390  			return nil, err
   391  		}
   392  	} else {
   393  		parsedBlks = make([]snowman.Block, len(unparsedBlksBytes))
   394  		for i, blkBytes := range unparsedBlksBytes {
   395  			parsedBlks[i], err = s.unmarshalBlock(ctx, blkBytes)
   396  			if err != nil {
   397  				return nil, err
   398  			}
   399  		}
   400  	}
   401  
   402  	i := 0
   403  	for _, blk := range parsedBlks {
   404  		for ; ; i++ {
   405  			if blks[i] == nil {
   406  				break
   407  			}
   408  		}
   409  
   410  		blkID := blk.ID()
   411  		if !idWasCached[i] {
   412  			blkBytes := blk.Bytes()
   413  			blkBytesStr := string(blkBytes)
   414  			s.bytesToIDCache.Put(blkBytesStr, blkID)
   415  
   416  			// Check for an existing block, so we can return a unique block
   417  			// if processing or simply allow this block to be immediately
   418  			// garbage collected if it is already cached.
   419  			if cachedBlk, ok := s.getCachedBlock(blkID); ok {
   420  				blks[i] = cachedBlk
   421  				continue
   422  			}
   423  		}
   424  
   425  		s.missingBlocks.Evict(blkID)
   426  		wrappedBlk, err := s.addBlockOutsideConsensus(ctx, blk)
   427  		if err != nil {
   428  			return nil, err
   429  		}
   430  		blks[i] = wrappedBlk
   431  	}
   432  	return blks, nil
   433  }
   434  
   435  // BuildBlockWithContext attempts to build a new internal Block, wraps it, and
   436  // adds it to the appropriate caching layer if successful.
   437  // If [s.buildBlockWithContext] is nil, returns [BuildBlock].
   438  func (s *State) BuildBlockWithContext(ctx context.Context, blockCtx *block.Context) (snowman.Block, error) {
   439  	if s.buildBlockWithContext == nil {
   440  		return s.BuildBlock(ctx)
   441  	}
   442  
   443  	blk, err := s.buildBlockWithContext(ctx, blockCtx)
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  
   448  	return s.deduplicate(ctx, blk)
   449  }
   450  
   451  // BuildBlock attempts to build a new internal Block, wraps it, and adds it
   452  // to the appropriate caching layer if successful.
   453  func (s *State) BuildBlock(ctx context.Context) (snowman.Block, error) {
   454  	blk, err := s.buildBlock(ctx)
   455  	if err != nil {
   456  		return nil, err
   457  	}
   458  
   459  	return s.deduplicate(ctx, blk)
   460  }
   461  
   462  func (s *State) deduplicate(ctx context.Context, blk snowman.Block) (snowman.Block, error) {
   463  	blkID := blk.ID()
   464  	// Defensive: buildBlock should not return a block that has already been verified.
   465  	// If it does, make sure to return the existing reference to the block.
   466  	if existingBlk, ok := s.getCachedBlock(blkID); ok {
   467  		return existingBlk, nil
   468  	}
   469  	// Evict the produced block from missing blocks in case it was previously
   470  	// marked as missing.
   471  	s.missingBlocks.Evict(blkID)
   472  
   473  	// wrap the returned block and add it to the correct cache
   474  	return s.addBlockOutsideConsensus(ctx, blk)
   475  }
   476  
   477  // addBlockOutsideConsensus adds [blk] to the correct cache and returns
   478  // a wrapped version of [blk]
   479  // assumes [blk] is a known, non-wrapped block that is not currently
   480  // in consensus. [blk] could be either decided or a block that has not yet
   481  // been verified and added to consensus.
   482  func (s *State) addBlockOutsideConsensus(ctx context.Context, blk snowman.Block) (snowman.Block, error) {
   483  	wrappedBlk := &BlockWrapper{
   484  		Block: blk,
   485  		state: s,
   486  	}
   487  
   488  	blkID := blk.ID()
   489  	status, err := s.getStatus(ctx, blk)
   490  	if err != nil {
   491  		return nil, fmt.Errorf("could not get block status for %s due to %w", blkID, err)
   492  	}
   493  	switch status {
   494  	case choices.Accepted, choices.Rejected:
   495  		s.decidedBlocks.Put(blkID, wrappedBlk)
   496  	case choices.Processing:
   497  		s.unverifiedBlocks.Put(blkID, wrappedBlk)
   498  	default:
   499  		return nil, fmt.Errorf("found unexpected status for blk %s: %s", blkID, status)
   500  	}
   501  
   502  	return wrappedBlk, nil
   503  }
   504  
   505  func (s *State) LastAccepted(context.Context) (ids.ID, error) {
   506  	return s.lastAcceptedBlock.ID(), nil
   507  }
   508  
   509  // LastAcceptedBlock returns the last accepted wrapped block
   510  func (s *State) LastAcceptedBlock() *BlockWrapper {
   511  	return s.lastAcceptedBlock
   512  }
   513  
   514  // LastAcceptedBlockInternal returns the internal snowman.Block that was last accepted
   515  func (s *State) LastAcceptedBlockInternal() snowman.Block {
   516  	return s.LastAcceptedBlock().Block
   517  }
   518  
   519  // IsProcessing returns whether [blkID] is processing in consensus
   520  func (s *State) IsProcessing(blkID ids.ID) bool {
   521  	_, ok := s.verifiedBlocks[blkID]
   522  	return ok
   523  }