github.com/onflow/flow-go@v0.33.17/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 errors "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 storageTxn = blockDatabase.NewSnapshotReadTransaction(stateParameters) 165 case TransactionProcedureType, BootstrapProcedureType: 166 storageTxn, err = blockDatabase.NewTransaction( 167 proc.ExecutionTime(), 168 stateParameters) 169 default: 170 return nil, ProcedureOutput{}, fmt.Errorf( 171 "invalid proc type: %v", 172 proc.Type()) 173 } 174 175 if err != nil { 176 return nil, ProcedureOutput{}, fmt.Errorf( 177 "error creating derived transaction data: %w", 178 err) 179 } 180 181 executor := proc.NewExecutor(ctx, storageTxn) 182 err = Run(executor) 183 if err != nil { 184 return nil, ProcedureOutput{}, err 185 } 186 187 err = storageTxn.Finalize() 188 if err != nil { 189 return nil, ProcedureOutput{}, err 190 } 191 192 executionSnapshot, err := storageTxn.Commit() 193 if err != nil { 194 return nil, ProcedureOutput{}, err 195 } 196 197 return executionSnapshot, executor.Output(), nil 198 } 199 200 // GetAccount returns an account by address or an error if none exists. 201 func (vm *VirtualMachine) GetAccount( 202 ctx Context, 203 address flow.Address, 204 storageSnapshot snapshot.StorageSnapshot, 205 ) ( 206 *flow.Account, 207 error, 208 ) { 209 blockDatabase := storage.NewBlockDatabase( 210 storageSnapshot, 211 0, 212 ctx.DerivedBlockData) 213 214 storageTxn := blockDatabase.NewSnapshotReadTransaction( 215 state.DefaultParameters(). 216 WithMaxKeySizeAllowed(ctx.MaxStateKeySize). 217 WithMaxValueSizeAllowed(ctx.MaxStateValueSize). 218 WithMeterParameters( 219 meter.DefaultParameters(). 220 WithStorageInteractionLimit(ctx.MaxStateInteractionSize))) 221 222 env := environment.NewScriptEnv( 223 context.Background(), 224 ctx.TracerSpan, 225 ctx.EnvironmentParams, 226 storageTxn) 227 account, err := env.GetAccount(address) 228 if err != nil { 229 if errors.IsLedgerFailure(err) { 230 return nil, fmt.Errorf( 231 "cannot get account, this error usually happens if the "+ 232 "reference block for this query is not set to a recent "+ 233 "block: %w", 234 err) 235 } 236 return nil, fmt.Errorf("cannot get account: %w", err) 237 } 238 return account, nil 239 }