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

     1  package execution
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/onflow/flow-go/fvm/environment"
     7  
     8  	"github.com/rs/zerolog"
     9  
    10  	"github.com/onflow/flow-go/engine/execution/computation"
    11  	"github.com/onflow/flow-go/engine/execution/computation/query"
    12  	"github.com/onflow/flow-go/fvm"
    13  	"github.com/onflow/flow-go/fvm/storage/derived"
    14  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    15  	"github.com/onflow/flow-go/model/flow"
    16  	"github.com/onflow/flow-go/module"
    17  	"github.com/onflow/flow-go/storage"
    18  )
    19  
    20  // RegisterAtHeight returns register value for provided register ID at the block height.
    21  // Even if the register wasn't indexed at the provided height, returns the highest height the register was indexed at.
    22  // If the register with the ID was not indexed at all return nil value and no error.
    23  // Expected errors:
    24  // - storage.ErrHeightNotIndexed if the given height was not indexed yet or lower than the first indexed height.
    25  type RegisterAtHeight func(ID flow.RegisterID, height uint64) (flow.RegisterValue, error)
    26  
    27  type ScriptExecutor interface {
    28  	// ExecuteAtBlockHeight executes provided script against the block height.
    29  	// A result value is returned encoded as byte array. An error will be returned if script
    30  	// doesn't successfully execute.
    31  	// Expected errors:
    32  	// - storage.ErrNotFound if block or register value at height was not found.
    33  	// - storage.ErrHeightNotIndexed if the data for the block height is not available
    34  	ExecuteAtBlockHeight(
    35  		ctx context.Context,
    36  		script []byte,
    37  		arguments [][]byte,
    38  		height uint64,
    39  	) ([]byte, error)
    40  
    41  	// GetAccountAtBlockHeight returns a Flow account by the provided address and block height.
    42  	// Expected errors:
    43  	// - storage.ErrHeightNotIndexed if the data for the block height is not available
    44  	GetAccountAtBlockHeight(ctx context.Context, address flow.Address, height uint64) (*flow.Account, error)
    45  }
    46  
    47  var _ ScriptExecutor = (*Scripts)(nil)
    48  
    49  type Scripts struct {
    50  	executor         *query.QueryExecutor
    51  	headers          storage.Headers
    52  	registerAtHeight RegisterAtHeight
    53  }
    54  
    55  func NewScripts(
    56  	log zerolog.Logger,
    57  	metrics module.ExecutionMetrics,
    58  	chainID flow.ChainID,
    59  	entropy query.EntropyProviderPerBlock,
    60  	header storage.Headers,
    61  	registerAtHeight RegisterAtHeight,
    62  	queryConf query.QueryConfig,
    63  	derivedChainData *derived.DerivedChainData,
    64  	enableProgramCacheWrites bool,
    65  ) *Scripts {
    66  	vm := fvm.NewVirtualMachine()
    67  
    68  	options := computation.DefaultFVMOptions(chainID, false, false)
    69  	blocks := environment.NewBlockFinder(header)
    70  	options = append(options, fvm.WithBlocks(blocks)) // add blocks for getBlocks calls in scripts
    71  	options = append(options, fvm.WithMetricsReporter(metrics))
    72  	options = append(options, fvm.WithAllowProgramCacheWritesInScriptsEnabled(enableProgramCacheWrites))
    73  	vmCtx := fvm.NewContext(options...)
    74  
    75  	queryExecutor := query.NewQueryExecutor(
    76  		queryConf,
    77  		log,
    78  		metrics,
    79  		vm,
    80  		vmCtx,
    81  		derivedChainData,
    82  		entropy,
    83  	)
    84  
    85  	return &Scripts{
    86  		executor:         queryExecutor,
    87  		headers:          header,
    88  		registerAtHeight: registerAtHeight,
    89  	}
    90  }
    91  
    92  // ExecuteAtBlockHeight executes provided script against the block height.
    93  // A result value is returned encoded as byte array. An error will be returned if script
    94  // doesn't successfully execute.
    95  // Expected errors:
    96  // - Script execution related errors
    97  // - storage.ErrHeightNotIndexed if the data for the block height is not available
    98  func (s *Scripts) ExecuteAtBlockHeight(
    99  	ctx context.Context,
   100  	script []byte,
   101  	arguments [][]byte,
   102  	height uint64,
   103  ) ([]byte, error) {
   104  
   105  	snap, header, err := s.snapshotWithBlock(height)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	value, compUsage, err := s.executor.ExecuteScript(ctx, script, arguments, header, snap)
   111  	// TODO: return compUsage when upstream can handle it
   112  	_ = compUsage
   113  	return value, err
   114  }
   115  
   116  // GetAccountAtBlockHeight returns a Flow account by the provided address and block height.
   117  // Expected errors:
   118  // - Script execution related errors
   119  // - storage.ErrHeightNotIndexed if the data for the block height is not available
   120  func (s *Scripts) GetAccountAtBlockHeight(ctx context.Context, address flow.Address, height uint64) (*flow.Account, error) {
   121  	snap, header, err := s.snapshotWithBlock(height)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	return s.executor.GetAccount(ctx, address, header, snap)
   127  }
   128  
   129  // snapshotWithBlock is a common function for executing scripts and get account functionality.
   130  // It creates a storage snapshot that is needed by the FVM to execute scripts.
   131  func (s *Scripts) snapshotWithBlock(height uint64) (snapshot.StorageSnapshot, *flow.Header, error) {
   132  	header, err := s.headers.ByHeight(height)
   133  	if err != nil {
   134  		return nil, nil, err
   135  	}
   136  
   137  	storageSnapshot := snapshot.NewReadFuncStorageSnapshot(func(ID flow.RegisterID) (flow.RegisterValue, error) {
   138  		return s.registerAtHeight(ID, height)
   139  	})
   140  
   141  	return storageSnapshot, header, nil
   142  }