The Flow Virtual Machine (FVM) augments the Cadence runtime with the domain-specific functionality required by the Flow protocol.
import (
"github.com/onflow/cadence/runtime"
"github.com/onflow/flow-go/fvm"
"github.com/onflow/flow-go/fvm/state"
"github.com/onflow/flow-go/model/flow"
)
vm := fvm.New(runtime.NewInterpreterRuntime())
tx := flow.NewTransactionBody().
SetScript([]byte(`transaction { execute { log("Hello, World!") } }`))
ctx := fvm.NewContext()
ledger := state.NewMapLedger()
txIndex := uint32(0)
txProc := fvm.Transaction(tx, txIndex)
err := vm.Run(ctx, txProc, ledger)
if err != nil {
panic("fatal error during transaction procedure!")
}
fmt.Println(txProc.Logs[0]) // prints "Hello, World!"The FVM interacts with the Flow execution state by running atomic procedures against
a shared ledger. A Procedure is an operation that can be applied to a Ledger.
A TransactionProcedure is an operation that mutates the ledger state.
A transaction procedure can be created from a flow.TransactionBody:
var tx flow.TransactionBody
var txIndex uint32
i := fvm.Transaction(tx, txIndex)A transaction procedure has the following steps:
- Verify all transaction signatures against the current ledger state
- Validate and increment the proposal key sequence number
- Deduct transaction fee from the payer account
- Invoke transaction script with provided arguments and authorizers
A ScriptProcedure is an operation that reads from the ledger state.
foo := fvm.Script([]byte(`fun main(): Int { return 42 }`))A script can have optionally bound arguments:
script := fvm.Script([]byte(`fun main(x: Int): Int { x * 2 }`))
foo := script.WithArguments(argA)
bar := script.WithArguments(argB)The VM runs procedures inside of an execution context that defines the host
functionality provided to the Cadence runtime. A Context instance is
an immutable object, and any changes to a context must be made by spawning
a new child context.
vm := fvm.New(runtime.NewInterpreterRuntime())
globalCtx := fvm.NewContext()
// create separate contexts for different blocks
block1Ctx := fvm.NewContextFromParent(globalCtx, fvm.WithBlockHeader(block1))
block2Ctx := fvm.NewContextFromParent(globalCtx, fvm.WithBlockHeader(block2))
// contexts can be safely used in parallel
go executeBlock(vm, block1Ctx)
go executeBlock(vm, block2Ctx)TODO: document context options
The structure of the FVM package is intended to promote the following:
- Determinism: the VM should be consistent and predictable, producing identical results given the same inputs.
- Performance: the VM should expose functionality that allows the caller to exploit parallelism and pre-computation.
- Flexibility: the VM should support a variety of use cases including not only execution and verification, but also developer tooling and network emulation.
A Context is a reusable component that simply defines the parameters of an execution environment.
For example, all transactions within the same block would share a common Context configured with the relevant
block data.
Contexts can be arbitrarily nested. A child context inherits the parameters of its parent, but is free to override any parameters that it chooses.
A HostEnvironment is a short-lived component that is consumed by a procedure. A HostEnvironment is the
interface through which the VM communicates with the Cadence runtime. The HostEnvironment provides information about
the current Context and also collects logs, events and metrics emitted from the runtime.
The diagram below illustrates the relationship between contexts, host environments and procedures. Multiple procedures
can reuse the same context, each of which is supplied with a unique HostEnvironment instance.
- Improve error handling
- Create list of all missing test cases
- Implement missing test cases
- Documentation
- Transaction validation (signatures, sequence numbers)
- Fee payments
- Address generation
- Account management
- Bootstrapping
- Ledger usage
- Currently the caller is responsible for providing the correct
Ledgerinstance when running a procedure. When performing atomic operations in batches (i.e blocks or chunks), multiple ledgers are spawned in a hierarchy similar to that of multipleContextinstances. Does it make sense for thefvmpackage to manage ledgers as well?