Expand description
SputnikVM implementation, traits and structs.
SputnikVM works on two different levels. It handles:
- a transaction, or
- an Ethereum execution context.
To interact with the virtual machine, you usually only need to work with VM methods.
A SputnikVM’s Lifecycle
A VM can be started after it is given a Transaction
(or
Context
) and a BlockHeader
. The user can then fire
or step
to run it. fire
runs the EVM code
(given in field code
of the transaction) until it finishes or
cannot continue. However step
only runs at most one instruction. If the virtual machine needs
some information (accounts in the current block, or block hashes
of previous blocks) it fails, returning a
RequireError
enumeration. With
the data returned in the RequireError
enumeration, one can use
the methods
commit_account
and
commit_blockhash
to
commit the information to the VM. fire
or step
can be
subsequently called to restart from that point. The current VM
status can always be obtained using the status
function. Again,
see VM for a list of methods that can be applied.
Patch: Specifying a Network and Hard-fork
Every VM is associated with a Patch
. This patch tells the VM
which Ethereum network and which hard fork it is on. You will need
to specify the patch as the type parameter. To interact with
multiple patches at the same time, it is recommended that you use
trait objects.
The example below creates a new SputnikVM and stores the object in
vm
which can be used to fire
, step
or get status on. To do
this, it must first create a transaction and a block header. The
patch associated with the VM is either EmbeddedPatch
or
VMTestPatch
depending on an arbitrary block number value set at
the beginning of the program.
extern crate bigint;
extern crate sputnikvm;
use sputnikvm::{EmbeddedPatch, VMTestPatch,
HeaderParams, ValidTransaction, TransactionAction,
VM, SeqTransactionVM};
use bigint::{Gas, U256, Address};
use std::rc::Rc;
fn main() {
let block_number = 1000;
let transaction = ValidTransaction {
caller: Some(Address::default()),
gas_price: Gas::zero(),
gas_limit: Gas::max_value(),
action: TransactionAction::Create,
value: U256::zero(),
input: Rc::new(Vec::new()),
nonce: U256::zero()
};
let header = HeaderParams {
beneficiary: Address::default(),
timestamp: 0,
number: U256::zero(),
difficulty: U256::zero(),
gas_limit: Gas::zero()
};
let vm = if block_number < 500 {
SeqTransactionVM::<VMTestPatch>::new(
transaction, header);
} else {
SeqTransactionVM::<EmbeddedPatch>::new(
transaction, header);
};
}
Transaction Execution
To start a VM on the Transaction level, use the TransactionVM
struct. Usually, you want to use the sequential memory module
which can be done using the type definition
SeqTransactionVM
.
Calling TransactionVM::new
or SeqTransactionVM::new
requires
the transaction passed in to be valid (according to the rules for
an Ethereum transaction). If the transaction is invalid, the VM
will probably panic. If you want to handle untrusted transactions,
you should use SeqTransactionVM::new_untrusted
, which will not
panic but instead return an error if the transaction is invalid.
Context Execution
To start a VM on the Context level, use the ContextVM
struct. Usually, you use the sequential memory module with the
type definition SeqContextVM
. Context execution, as with other
EVM implementations, will not handle transaction-level gas
reductions.
Re-exports
pub use self::errors::OnChainError;
pub use self::errors::NotSupportedError;
pub use self::errors::RequireError;
pub use self::errors::CommitError;
pub use self::errors::PreExecutionError;
Modules
Structs
Vec
for internal
representation.RequireError
if trying to access non-existing storage.jsontests
crate.Enums
Opcode
except PUSH
, which might take longer length.u8
value.Statics
Traits
Type Definitions
SeqContextVM
except
it runs at transaction level.