github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/state/state.go (about)

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