github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/fvm.go (about) 1 package fvm 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/onflow/cadence" 8 9 "github.com/onflow/flow-go/fvm/environment" 10 "github.com/onflow/flow-go/fvm/errors" 11 "github.com/onflow/flow-go/fvm/meter" 12 "github.com/onflow/flow-go/fvm/storage" 13 "github.com/onflow/flow-go/fvm/storage/logical" 14 "github.com/onflow/flow-go/fvm/storage/snapshot" 15 "github.com/onflow/flow-go/fvm/storage/state" 16 "github.com/onflow/flow-go/model/flow" 17 ) 18 19 type ProcedureType string 20 21 const ( 22 BootstrapProcedureType = ProcedureType("bootstrap") 23 TransactionProcedureType = ProcedureType("transaction") 24 ScriptProcedureType = ProcedureType("script") 25 ) 26 27 type ProcedureOutput struct { 28 // Output by both transaction and script. 29 Logs []string 30 Events flow.EventsList 31 ServiceEvents flow.EventsList 32 ConvertedServiceEvents flow.ServiceEventList 33 ComputationUsed uint64 34 ComputationIntensities meter.MeteredComputationIntensities 35 MemoryEstimate uint64 36 Err errors.CodedError 37 38 // Output only by script. 39 Value cadence.Value 40 } 41 42 func (output *ProcedureOutput) PopulateEnvironmentValues( 43 env environment.Environment, 44 ) error { 45 output.Logs = env.Logs() 46 47 computationUsed, err := env.ComputationUsed() 48 if err != nil { 49 return fmt.Errorf("error getting computation used: %w", err) 50 } 51 output.ComputationUsed = computationUsed 52 53 memoryUsed, err := env.MemoryUsed() 54 if err != nil { 55 return fmt.Errorf("error getting memory used: %w", err) 56 } 57 output.MemoryEstimate = memoryUsed 58 59 output.ComputationIntensities = env.ComputationIntensities() 60 61 // if tx failed this will only contain fee deduction events 62 output.Events = env.Events() 63 output.ServiceEvents = env.ServiceEvents() 64 output.ConvertedServiceEvents = env.ConvertedServiceEvents() 65 66 return nil 67 } 68 69 type ProcedureExecutor interface { 70 Preprocess() error 71 Execute() error 72 Cleanup() 73 74 Output() ProcedureOutput 75 } 76 77 func Run(executor ProcedureExecutor) error { 78 defer executor.Cleanup() 79 err := executor.Preprocess() 80 if err != nil { 81 return err 82 } 83 return executor.Execute() 84 } 85 86 // An Procedure is an operation (or set of operations) that reads or writes ledger state. 87 type Procedure interface { 88 NewExecutor( 89 ctx Context, 90 txnState storage.TransactionPreparer, 91 ) ProcedureExecutor 92 93 ComputationLimit(ctx Context) uint64 94 MemoryLimit(ctx Context) uint64 95 ShouldDisableMemoryAndInteractionLimits(ctx Context) bool 96 97 Type() ProcedureType 98 99 // For transactions, the execution time is TxIndex. For scripts, the 100 // execution time is EndOfBlockExecutionTime. 101 ExecutionTime() logical.Time 102 } 103 104 // VM runs procedures 105 type VM interface { 106 NewExecutor( 107 Context, 108 Procedure, 109 storage.TransactionPreparer, 110 ) ProcedureExecutor 111 112 Run( 113 Context, 114 Procedure, 115 snapshot.StorageSnapshot, 116 ) ( 117 *snapshot.ExecutionSnapshot, 118 ProcedureOutput, 119 error, 120 ) 121 122 GetAccount(Context, flow.Address, snapshot.StorageSnapshot) (*flow.Account, error) 123 } 124 125 var _ VM = (*VirtualMachine)(nil) 126 127 // A VirtualMachine augments the Cadence runtime with Flow host functionality. 128 type VirtualMachine struct { 129 } 130 131 func NewVirtualMachine() *VirtualMachine { 132 return &VirtualMachine{} 133 } 134 135 func (vm *VirtualMachine) NewExecutor( 136 ctx Context, 137 proc Procedure, 138 txn storage.TransactionPreparer, 139 ) ProcedureExecutor { 140 return proc.NewExecutor(ctx, txn) 141 } 142 143 // Run runs a procedure against a ledger in the given context. 144 func (vm *VirtualMachine) Run( 145 ctx Context, 146 proc Procedure, 147 storageSnapshot snapshot.StorageSnapshot, 148 ) ( 149 *snapshot.ExecutionSnapshot, 150 ProcedureOutput, 151 error, 152 ) { 153 blockDatabase := storage.NewBlockDatabase( 154 storageSnapshot, 155 proc.ExecutionTime(), 156 ctx.DerivedBlockData) 157 158 stateParameters := ProcedureStateParameters(ctx, proc) 159 160 var storageTxn storage.Transaction 161 var err error 162 switch proc.Type() { 163 case ScriptProcedureType: 164 if ctx.AllowProgramCacheWritesInScripts { 165 // if configured, allow scripts to update the programs cache 166 storageTxn, err = blockDatabase.NewCachingSnapshotReadTransaction(stateParameters) 167 } else { 168 storageTxn = blockDatabase.NewSnapshotReadTransaction(stateParameters) 169 } 170 case TransactionProcedureType, BootstrapProcedureType: 171 storageTxn, err = blockDatabase.NewTransaction( 172 proc.ExecutionTime(), 173 stateParameters) 174 default: 175 return nil, ProcedureOutput{}, fmt.Errorf( 176 "invalid proc type: %v", 177 proc.Type()) 178 } 179 180 if err != nil { 181 return nil, ProcedureOutput{}, fmt.Errorf( 182 "error creating derived transaction data: %w", 183 err) 184 } 185 186 executor := proc.NewExecutor(ctx, storageTxn) 187 err = Run(executor) 188 if err != nil { 189 return nil, ProcedureOutput{}, err 190 } 191 192 err = storageTxn.Finalize() 193 if err != nil { 194 return nil, ProcedureOutput{}, err 195 } 196 197 executionSnapshot, err := storageTxn.Commit() 198 if err != nil { 199 return nil, ProcedureOutput{}, err 200 } 201 202 return executionSnapshot, executor.Output(), nil 203 } 204 205 // GetAccount returns an account by address or an error if none exists. 206 func (vm *VirtualMachine) GetAccount( 207 ctx Context, 208 address flow.Address, 209 storageSnapshot snapshot.StorageSnapshot, 210 ) ( 211 *flow.Account, 212 error, 213 ) { 214 blockDatabase := storage.NewBlockDatabase( 215 storageSnapshot, 216 0, 217 ctx.DerivedBlockData) 218 219 storageTxn := blockDatabase.NewSnapshotReadTransaction( 220 state.DefaultParameters(). 221 WithMaxKeySizeAllowed(ctx.MaxStateKeySize). 222 WithMaxValueSizeAllowed(ctx.MaxStateValueSize). 223 WithMeterParameters( 224 meter.DefaultParameters(). 225 WithStorageInteractionLimit(ctx.MaxStateInteractionSize))) 226 227 env := environment.NewScriptEnv( 228 context.Background(), 229 ctx.TracerSpan, 230 ctx.EnvironmentParams, 231 storageTxn) 232 account, err := env.GetAccount(address) 233 if err != nil { 234 if errors.IsLedgerFailure(err) { 235 return nil, fmt.Errorf( 236 "cannot get account, this error usually happens if the "+ 237 "reference block for this query is not set to a recent "+ 238 "block: %w", 239 err) 240 } 241 return nil, fmt.Errorf("cannot get account: %w", err) 242 } 243 return account, nil 244 }