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

     1  package state
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  
     9  	"github.com/dgraph-io/badger/v2"
    10  
    11  	"github.com/onflow/flow-go/engine/execution"
    12  	"github.com/onflow/flow-go/engine/execution/storehouse"
    13  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    14  	"github.com/onflow/flow-go/ledger"
    15  	"github.com/onflow/flow-go/ledger/common/convert"
    16  	"github.com/onflow/flow-go/model/flow"
    17  	"github.com/onflow/flow-go/module"
    18  	"github.com/onflow/flow-go/module/trace"
    19  	"github.com/onflow/flow-go/storage"
    20  	badgerstorage "github.com/onflow/flow-go/storage/badger"
    21  	"github.com/onflow/flow-go/storage/badger/operation"
    22  	"github.com/onflow/flow-go/storage/badger/procedure"
    23  )
    24  
    25  var ErrExecutionStatePruned = fmt.Errorf("execution state is pruned")
    26  var ErrNotExecuted = fmt.Errorf("block not executed")
    27  
    28  // ReadOnlyExecutionState allows to read the execution state
    29  type ReadOnlyExecutionState interface {
    30  	ScriptExecutionState
    31  
    32  	// ChunkDataPackByChunkID retrieve a chunk data pack given the chunk ID.
    33  	ChunkDataPackByChunkID(flow.Identifier) (*flow.ChunkDataPack, error)
    34  
    35  	GetExecutionResultID(context.Context, flow.Identifier) (flow.Identifier, error)
    36  
    37  	GetHighestExecutedBlockID(context.Context) (uint64, flow.Identifier, error)
    38  }
    39  
    40  // ScriptExecutionState is a subset of the `state.ExecutionState` interface purposed to only access the state
    41  // used for script execution and not mutate the execution state of the blockchain.
    42  type ScriptExecutionState interface {
    43  	// NewStorageSnapshot creates a new ready-only view at the given block.
    44  	NewStorageSnapshot(commit flow.StateCommitment, blockID flow.Identifier, height uint64) snapshot.StorageSnapshot
    45  
    46  	// CreateStorageSnapshot creates a new ready-only view at the given block.
    47  	// It returns:
    48  	// - (nil, nil, storage.ErrNotFound) if block is unknown
    49  	// - (nil, nil, state.ErrNotExecuted) if block is not executed
    50  	// - (nil, nil, state.ErrExecutionStatePruned) if the execution state has been pruned
    51  	CreateStorageSnapshot(blockID flow.Identifier) (snapshot.StorageSnapshot, *flow.Header, error)
    52  
    53  	// StateCommitmentByBlockID returns the final state commitment for the provided block ID.
    54  	StateCommitmentByBlockID(flow.Identifier) (flow.StateCommitment, error)
    55  
    56  	// Any error returned is exception
    57  	IsBlockExecuted(height uint64, blockID flow.Identifier) (bool, error)
    58  }
    59  
    60  func IsParentExecuted(state ReadOnlyExecutionState, header *flow.Header) (bool, error) {
    61  	// sanity check, caller should not pass a root block
    62  	if header.Height == 0 {
    63  		return false, fmt.Errorf("root block does not have parent block")
    64  	}
    65  	return state.IsBlockExecuted(header.Height-1, header.ParentID)
    66  }
    67  
    68  // FinalizedExecutionState is an interface used to access the finalized execution state
    69  type FinalizedExecutionState interface {
    70  	GetHighestFinalizedExecuted() uint64
    71  }
    72  
    73  // TODO Many operations here are should be transactional, so we need to refactor this
    74  // to store a reference to DB and compose operations and procedures rather then
    75  // just being amalgamate of proxies for single transactions operation
    76  
    77  // ExecutionState is an interface used to access and mutate the execution state of the blockchain.
    78  type ExecutionState interface {
    79  	ReadOnlyExecutionState
    80  
    81  	UpdateHighestExecutedBlockIfHigher(context.Context, *flow.Header) error
    82  
    83  	SaveExecutionResults(
    84  		ctx context.Context,
    85  		result *execution.ComputationResult,
    86  	) error
    87  
    88  	// only available with storehouse enabled
    89  	// panic when called with storehouse disabled (which should be a bug)
    90  	GetHighestFinalizedExecuted() uint64
    91  }
    92  
    93  type state struct {
    94  	tracer             module.Tracer
    95  	ls                 ledger.Ledger
    96  	commits            storage.Commits
    97  	blocks             storage.Blocks
    98  	headers            storage.Headers
    99  	collections        storage.Collections
   100  	chunkDataPacks     storage.ChunkDataPacks
   101  	results            storage.ExecutionResults
   102  	myReceipts         storage.MyExecutionReceipts
   103  	events             storage.Events
   104  	serviceEvents      storage.ServiceEvents
   105  	transactionResults storage.TransactionResults
   106  	db                 *badger.DB
   107  
   108  	registerStore execution.RegisterStore
   109  	// when it is true, registers are stored in both register store and ledger
   110  	// and register queries will send to the register store instead of ledger
   111  	enableRegisterStore bool
   112  }
   113  
   114  // NewExecutionState returns a new execution state access layer for the given ledger storage.
   115  func NewExecutionState(
   116  	ls ledger.Ledger,
   117  	commits storage.Commits,
   118  	blocks storage.Blocks,
   119  	headers storage.Headers,
   120  	collections storage.Collections,
   121  	chunkDataPacks storage.ChunkDataPacks,
   122  	results storage.ExecutionResults,
   123  	myReceipts storage.MyExecutionReceipts,
   124  	events storage.Events,
   125  	serviceEvents storage.ServiceEvents,
   126  	transactionResults storage.TransactionResults,
   127  	db *badger.DB,
   128  	tracer module.Tracer,
   129  	registerStore execution.RegisterStore,
   130  	enableRegisterStore bool,
   131  ) ExecutionState {
   132  	return &state{
   133  		tracer:              tracer,
   134  		ls:                  ls,
   135  		commits:             commits,
   136  		blocks:              blocks,
   137  		headers:             headers,
   138  		collections:         collections,
   139  		chunkDataPacks:      chunkDataPacks,
   140  		results:             results,
   141  		myReceipts:          myReceipts,
   142  		events:              events,
   143  		serviceEvents:       serviceEvents,
   144  		transactionResults:  transactionResults,
   145  		db:                  db,
   146  		registerStore:       registerStore,
   147  		enableRegisterStore: enableRegisterStore,
   148  	}
   149  
   150  }
   151  
   152  func makeSingleValueQuery(commitment flow.StateCommitment, id flow.RegisterID) (*ledger.QuerySingleValue, error) {
   153  	return ledger.NewQuerySingleValue(ledger.State(commitment),
   154  		convert.RegisterIDToLedgerKey(id),
   155  	)
   156  }
   157  
   158  func RegisterEntriesToKeysValues(
   159  	entries flow.RegisterEntries,
   160  ) (
   161  	[]ledger.Key,
   162  	[]ledger.Value,
   163  ) {
   164  	keys := make([]ledger.Key, len(entries))
   165  	values := make([]ledger.Value, len(entries))
   166  	for i, entry := range entries {
   167  		keys[i] = convert.RegisterIDToLedgerKey(entry.Key)
   168  		values[i] = entry.Value
   169  	}
   170  	return keys, values
   171  }
   172  
   173  type LedgerStorageSnapshot struct {
   174  	ledger     ledger.Ledger
   175  	commitment flow.StateCommitment
   176  
   177  	mutex     sync.RWMutex
   178  	readCache map[flow.RegisterID]flow.RegisterValue // Guarded by mutex.
   179  }
   180  
   181  func NewLedgerStorageSnapshot(
   182  	ldg ledger.Ledger,
   183  	commitment flow.StateCommitment,
   184  ) snapshot.StorageSnapshot {
   185  	return &LedgerStorageSnapshot{
   186  		ledger:     ldg,
   187  		commitment: commitment,
   188  		readCache:  make(map[flow.RegisterID]flow.RegisterValue),
   189  	}
   190  }
   191  
   192  func (storage *LedgerStorageSnapshot) getFromCache(
   193  	id flow.RegisterID,
   194  ) (
   195  	flow.RegisterValue,
   196  	bool,
   197  ) {
   198  	storage.mutex.RLock()
   199  	defer storage.mutex.RUnlock()
   200  
   201  	value, ok := storage.readCache[id]
   202  	return value, ok
   203  }
   204  
   205  func (storage *LedgerStorageSnapshot) getFromLedger(
   206  	id flow.RegisterID,
   207  ) (
   208  	flow.RegisterValue,
   209  	error,
   210  ) {
   211  	query, err := makeSingleValueQuery(storage.commitment, id)
   212  	if err != nil {
   213  		return nil, fmt.Errorf("cannot create ledger query: %w", err)
   214  	}
   215  
   216  	value, err := storage.ledger.GetSingleValue(query)
   217  	if err != nil {
   218  		return nil, fmt.Errorf(
   219  			"error getting register (%s) value at %x: %w",
   220  			id,
   221  			storage.commitment,
   222  			err)
   223  	}
   224  
   225  	return value, nil
   226  }
   227  
   228  func (storage *LedgerStorageSnapshot) Get(
   229  	id flow.RegisterID,
   230  ) (
   231  	flow.RegisterValue,
   232  	error,
   233  ) {
   234  	value, ok := storage.getFromCache(id)
   235  	if ok {
   236  		return value, nil
   237  	}
   238  
   239  	value, err := storage.getFromLedger(id)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  
   244  	storage.mutex.Lock()
   245  	defer storage.mutex.Unlock()
   246  
   247  	storage.readCache[id] = value
   248  	return value, nil
   249  }
   250  
   251  func (s *state) NewStorageSnapshot(
   252  	commitment flow.StateCommitment,
   253  	blockID flow.Identifier,
   254  	height uint64,
   255  ) snapshot.StorageSnapshot {
   256  	if s.enableRegisterStore {
   257  		return storehouse.NewBlockEndStateSnapshot(s.registerStore, blockID, height)
   258  	}
   259  	return NewLedgerStorageSnapshot(s.ls, commitment)
   260  }
   261  
   262  func (s *state) CreateStorageSnapshot(
   263  	blockID flow.Identifier,
   264  ) (snapshot.StorageSnapshot, *flow.Header, error) {
   265  	header, err := s.headers.ByBlockID(blockID)
   266  	if err != nil {
   267  		return nil, nil, fmt.Errorf("cannot get header by block ID: %w", err)
   268  	}
   269  
   270  	// make sure the block is executed
   271  	commit, err := s.commits.ByBlockID(blockID)
   272  	if err != nil {
   273  		// statecommitment not exists means the block hasn't been executed yet
   274  		if errors.Is(err, storage.ErrNotFound) {
   275  			return nil, nil, fmt.Errorf("block %v is never executed: %w", blockID, ErrNotExecuted)
   276  		}
   277  
   278  		return nil, header, fmt.Errorf("cannot get commit by block ID: %w", err)
   279  	}
   280  
   281  	// make sure we have trie state for this block
   282  	ledgerHasState := s.ls.HasState(ledger.State(commit))
   283  	if !ledgerHasState {
   284  		return nil, header, fmt.Errorf("state not found in ledger for commit %x (block %v): %w", commit, blockID, ErrExecutionStatePruned)
   285  	}
   286  
   287  	if s.enableRegisterStore {
   288  		isExecuted, err := s.registerStore.IsBlockExecuted(header.Height, blockID)
   289  		if err != nil {
   290  			return nil, header, fmt.Errorf("cannot check if block %v is executed: %w", blockID, err)
   291  		}
   292  		if !isExecuted {
   293  			return nil, header, fmt.Errorf("block %v is not executed yet: %w", blockID, ErrNotExecuted)
   294  		}
   295  	}
   296  
   297  	return s.NewStorageSnapshot(commit, blockID, header.Height), header, nil
   298  }
   299  
   300  type RegisterUpdatesHolder interface {
   301  	UpdatedRegisters() flow.RegisterEntries
   302  	UpdatedRegisterSet() map[flow.RegisterID]flow.RegisterValue
   303  }
   304  
   305  // CommitDelta takes a base storage snapshot and creates a new storage snapshot
   306  // with the register updates from the given RegisterUpdatesHolder
   307  // a new statecommitment is returned from the ledger, along with the trie update
   308  // any error returned are exceptions
   309  func CommitDelta(
   310  	ldg ledger.Ledger,
   311  	ruh RegisterUpdatesHolder,
   312  	baseStorageSnapshot execution.ExtendableStorageSnapshot,
   313  ) (flow.StateCommitment, *ledger.TrieUpdate, execution.ExtendableStorageSnapshot, error) {
   314  
   315  	updatedRegisters := ruh.UpdatedRegisters()
   316  	keys, values := RegisterEntriesToKeysValues(updatedRegisters)
   317  	baseState := baseStorageSnapshot.Commitment()
   318  	update, err := ledger.NewUpdate(ledger.State(baseState), keys, values)
   319  
   320  	if err != nil {
   321  		return flow.DummyStateCommitment, nil, nil, fmt.Errorf("cannot create ledger update: %w", err)
   322  	}
   323  
   324  	newState, trieUpdate, err := ldg.Set(update)
   325  	if err != nil {
   326  		return flow.DummyStateCommitment, nil, nil, fmt.Errorf("could not update ledger: %w", err)
   327  	}
   328  
   329  	newCommit := flow.StateCommitment(newState)
   330  
   331  	newStorageSnapshot := baseStorageSnapshot.Extend(newCommit, ruh.UpdatedRegisterSet())
   332  
   333  	return newCommit, trieUpdate, newStorageSnapshot, nil
   334  }
   335  
   336  func (s *state) StateCommitmentByBlockID(blockID flow.Identifier) (flow.StateCommitment, error) {
   337  	return s.commits.ByBlockID(blockID)
   338  }
   339  
   340  func (s *state) ChunkDataPackByChunkID(chunkID flow.Identifier) (*flow.ChunkDataPack, error) {
   341  	chunkDataPack, err := s.chunkDataPacks.ByChunkID(chunkID)
   342  	if err != nil {
   343  		return nil, fmt.Errorf("could not retrieve stored chunk data pack: %w", err)
   344  	}
   345  
   346  	return chunkDataPack, nil
   347  }
   348  
   349  func (s *state) GetExecutionResultID(ctx context.Context, blockID flow.Identifier) (flow.Identifier, error) {
   350  	span, _ := s.tracer.StartSpanFromContext(ctx, trace.EXEGetExecutionResultID)
   351  	defer span.End()
   352  
   353  	result, err := s.results.ByBlockID(blockID)
   354  	if err != nil {
   355  		return flow.ZeroID, err
   356  	}
   357  	return result.ID(), nil
   358  }
   359  
   360  func (s *state) SaveExecutionResults(
   361  	ctx context.Context,
   362  	result *execution.ComputationResult,
   363  ) error {
   364  	span, childCtx := s.tracer.StartSpanFromContext(
   365  		ctx,
   366  		trace.EXEStateSaveExecutionResults)
   367  	defer span.End()
   368  
   369  	err := s.saveExecutionResults(ctx, result)
   370  	if err != nil {
   371  		return fmt.Errorf("could not save execution results: %w", err)
   372  	}
   373  
   374  	if s.enableRegisterStore {
   375  		// save registers to register store
   376  		err = s.registerStore.SaveRegisters(
   377  			result.BlockExecutionResult.ExecutableBlock.Block.Header,
   378  			result.BlockExecutionResult.AllUpdatedRegisters(),
   379  		)
   380  
   381  		if err != nil {
   382  			return fmt.Errorf("could not save updated registers: %w", err)
   383  		}
   384  	}
   385  
   386  	//outside batch because it requires read access
   387  	err = s.UpdateHighestExecutedBlockIfHigher(childCtx, result.ExecutableBlock.Block.Header)
   388  	if err != nil {
   389  		return fmt.Errorf("cannot update highest executed block: %w", err)
   390  	}
   391  	return nil
   392  }
   393  
   394  func (s *state) saveExecutionResults(
   395  	ctx context.Context,
   396  	result *execution.ComputationResult,
   397  ) (err error) {
   398  	header := result.ExecutableBlock.Block.Header
   399  	blockID := header.ID()
   400  
   401  	err = s.chunkDataPacks.Store(result.AllChunkDataPacks())
   402  	if err != nil {
   403  		return fmt.Errorf("can not store multiple chunk data pack: %w", err)
   404  	}
   405  
   406  	// Write Batch is BadgerDB feature designed for handling lots of writes
   407  	// in efficient and atomic manner, hence pushing all the updates we can
   408  	// as tightly as possible to let Badger manage it.
   409  	// Note, that it does not guarantee atomicity as transactions has size limit,
   410  	// but it's the closest thing to atomicity we could have
   411  	batch := badgerstorage.NewBatch(s.db)
   412  
   413  	defer func() {
   414  		// Rollback if an error occurs during batch operations
   415  		if err != nil {
   416  			chunks := result.AllChunkDataPacks()
   417  			chunkIDs := make([]flow.Identifier, 0, len(chunks))
   418  			for _, chunk := range chunks {
   419  				chunkIDs = append(chunkIDs, chunk.ID())
   420  			}
   421  			_ = s.chunkDataPacks.Remove(chunkIDs)
   422  		}
   423  	}()
   424  
   425  	err = s.events.BatchStore(blockID, []flow.EventsList{result.AllEvents()}, batch)
   426  	if err != nil {
   427  		return fmt.Errorf("cannot store events: %w", err)
   428  	}
   429  
   430  	err = s.serviceEvents.BatchStore(blockID, result.AllServiceEvents(), batch)
   431  	if err != nil {
   432  		return fmt.Errorf("cannot store service events: %w", err)
   433  	}
   434  
   435  	err = s.transactionResults.BatchStore(
   436  		blockID,
   437  		result.AllTransactionResults(),
   438  		batch)
   439  	if err != nil {
   440  		return fmt.Errorf("cannot store transaction result: %w", err)
   441  	}
   442  
   443  	executionResult := &result.ExecutionReceipt.ExecutionResult
   444  	err = s.results.BatchStore(executionResult, batch)
   445  	if err != nil {
   446  		return fmt.Errorf("cannot store execution result: %w", err)
   447  	}
   448  
   449  	err = s.results.BatchIndex(blockID, executionResult.ID(), batch)
   450  	if err != nil {
   451  		return fmt.Errorf("cannot index execution result: %w", err)
   452  	}
   453  
   454  	err = s.myReceipts.BatchStoreMyReceipt(result.ExecutionReceipt, batch)
   455  	if err != nil {
   456  		return fmt.Errorf("could not persist execution result: %w", err)
   457  	}
   458  
   459  	// the state commitment is the last data item to be stored, so that
   460  	// IsBlockExecuted can be implemented by checking whether state commitment exists
   461  	// in the database
   462  	err = s.commits.BatchStore(blockID, result.CurrentEndState(), batch)
   463  	if err != nil {
   464  		return fmt.Errorf("cannot store state commitment: %w", err)
   465  	}
   466  
   467  	err = batch.Flush()
   468  	if err != nil {
   469  		return fmt.Errorf("batch flush error: %w", err)
   470  	}
   471  
   472  	return nil
   473  }
   474  
   475  func (s *state) UpdateHighestExecutedBlockIfHigher(ctx context.Context, header *flow.Header) error {
   476  	if s.tracer != nil {
   477  		span, _ := s.tracer.StartSpanFromContext(ctx, trace.EXEUpdateHighestExecutedBlockIfHigher)
   478  		defer span.End()
   479  	}
   480  
   481  	return operation.RetryOnConflict(s.db.Update, procedure.UpdateHighestExecutedBlockIfHigher(header))
   482  }
   483  
   484  // deprecated by storehouse's GetHighestFinalizedExecuted
   485  func (s *state) GetHighestExecutedBlockID(ctx context.Context) (uint64, flow.Identifier, error) {
   486  	if s.enableRegisterStore {
   487  		// when storehouse is enabled, the highest executed block is consisted as
   488  		// the highest finalized and executed block
   489  		height := s.GetHighestFinalizedExecuted()
   490  		finalizedID, err := s.headers.BlockIDByHeight(height)
   491  		if err != nil {
   492  			return 0, flow.ZeroID, fmt.Errorf("could not get header by height %v: %w", height, err)
   493  		}
   494  		return height, finalizedID, nil
   495  	}
   496  
   497  	var blockID flow.Identifier
   498  	var height uint64
   499  	err := s.db.View(procedure.GetHighestExecutedBlock(&height, &blockID))
   500  	if err != nil {
   501  		return 0, flow.ZeroID, err
   502  	}
   503  
   504  	return height, blockID, nil
   505  }
   506  
   507  func (s *state) GetHighestFinalizedExecuted() uint64 {
   508  	if !s.enableRegisterStore {
   509  		panic("could not get highest finalized executed height without register store enabled")
   510  	}
   511  	return s.registerStore.LastFinalizedAndExecutedHeight()
   512  }
   513  
   514  // IsBlockExecuted returns true if the block is executed, which means registers, events,
   515  // results, etc are all stored.
   516  // otherwise returns false
   517  func (s *state) IsBlockExecuted(height uint64, blockID flow.Identifier) (bool, error) {
   518  	if s.enableRegisterStore {
   519  		return s.registerStore.IsBlockExecuted(height, blockID)
   520  	}
   521  
   522  	// ledger-based execution state uses commitment to determine if a block has been executed
   523  	_, err := s.StateCommitmentByBlockID(blockID)
   524  
   525  	// statecommitment exists means the block has been executed
   526  	if err == nil {
   527  		return true, nil
   528  	}
   529  
   530  	// statecommitment not exists means the block hasn't been executed yet
   531  	if errors.Is(err, storage.ErrNotFound) {
   532  		return false, nil
   533  	}
   534  
   535  	return false, err
   536  
   537  }