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 }