github.com/onflow/flow-go@v0.33.17/engine/execution/storehouse/block_end_snapshot.go (about)

     1  package storehouse
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  
     7  	"github.com/onflow/flow-go/engine/execution"
     8  	"github.com/onflow/flow-go/fvm/storage/snapshot"
     9  	"github.com/onflow/flow-go/model/flow"
    10  	"github.com/onflow/flow-go/storage"
    11  )
    12  
    13  var _ snapshot.StorageSnapshot = (*BlockEndStateSnapshot)(nil)
    14  
    15  // BlockEndStateSnapshot represents the storage at the end of a block.
    16  type BlockEndStateSnapshot struct {
    17  	storage execution.RegisterStore
    18  
    19  	blockID flow.Identifier
    20  	height  uint64
    21  
    22  	mutex     sync.RWMutex
    23  	readCache map[flow.RegisterID]flow.RegisterValue // cache the reads from storage at baseBlock
    24  }
    25  
    26  // the caller must ensure the block height is for the given block
    27  func NewBlockEndStateSnapshot(
    28  	storage execution.RegisterStore,
    29  	blockID flow.Identifier,
    30  	height uint64,
    31  ) *BlockEndStateSnapshot {
    32  	return &BlockEndStateSnapshot{
    33  		storage:   storage,
    34  		blockID:   blockID,
    35  		height:    height,
    36  		readCache: make(map[flow.RegisterID]flow.RegisterValue),
    37  	}
    38  }
    39  
    40  // Get returns the value of the register with the given register ID.
    41  // It returns:
    42  // - (value, nil) if the register exists
    43  // - (nil, nil) if the register does not exist
    44  // - (nil, storage.ErrHeightNotIndexed) if the height is below the first height that is indexed.
    45  // - (nil, storehouse.ErrNotExecuted) if the block is not executed yet
    46  // - (nil, storehouse.ErrNotExecuted) if the block is conflicting with finalized block
    47  // - (nil, err) for any other exceptions
    48  func (s *BlockEndStateSnapshot) Get(id flow.RegisterID) (flow.RegisterValue, error) {
    49  	value, ok := s.getFromCache(id)
    50  	if ok {
    51  		return value, nil
    52  	}
    53  
    54  	value, err := s.getFromStorage(id)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	s.mutex.Lock()
    60  	defer s.mutex.Unlock()
    61  
    62  	// TODO: consider adding a limit/eviction policy for the cache
    63  	s.readCache[id] = value
    64  	return value, err
    65  }
    66  
    67  func (s *BlockEndStateSnapshot) getFromCache(id flow.RegisterID) (flow.RegisterValue, bool) {
    68  	s.mutex.RLock()
    69  	defer s.mutex.RUnlock()
    70  
    71  	value, ok := s.readCache[id]
    72  	return value, ok
    73  }
    74  
    75  func (s *BlockEndStateSnapshot) getFromStorage(id flow.RegisterID) (flow.RegisterValue, error) {
    76  	value, err := s.storage.GetRegister(s.height, s.blockID, id)
    77  	if err != nil {
    78  		if errors.Is(err, storage.ErrNotFound) {
    79  			// if the error is not found, we return a nil RegisterValue,
    80  			// in this case, the nil value can be cached, because the storage will not change it
    81  			return nil, nil
    82  		}
    83  		// if the error is not ErrNotFound, such as storage.ErrHeightNotIndexed, storehouse.ErrNotExecuted
    84  		// we return the error without caching
    85  		return nil, err
    86  	}
    87  	return value, nil
    88  }