vm

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2021 License: Apache-2.0, BSD-2-Clause Imports: 9 Imported by: 0

README

VM abstraction

Contents

  • General
  • The VM
    • Calling the VM
    • Result of running the VM
    • Structure of the VM
    • Deployment of the smart contract
    • VM type
    • Program binary and blob hash
  • Processor and the sandbox interface
    • Sandbox interface
    • SandboxView interface
  • Implementation of EVM. A Virtual Ethereum

General

By VM abstraction in ISCP we understand a collection of abstract interfaces which makes the whole architecture of ISCP and Wasp node agnostic about what exactly kind of deterministic computation machinery is used to run smart contract programs.

In ISCP we distinguish two things:

  • The VM (Virtual Machine) itself
  • VM plugins, a pluggable part of VM

The VM itself is a deterministic executable, a "black box", which is used by the distributed part of the protocol, to calculate output (result) from inputs before coming to consensus among multiple VMs and finally submitting the result it to the ledger as the state update of the chain, the block.
Naturally, results of calculations, the output, is fully defined by inputs.

The VM contains multiple dynamically attached VM plugins or processors.
The VM invokes VM plugins to perform user-defined algorithms, the smart contracts.
The processor is attached to the VM through iscp.VMProcessor interface.
Each VM type has own implementation of VMProcessor interface.
Usually, one processor represents one smart contract, however one processor can represent entirely new plugged-in VM, such as EVM.

For more details about implementation of VMProcessor interface see below.

In Wasp node, the VM-related code is mostly located in wasp/packages/vm directory.
The globally defined data types and definitions are located in wasp/packages/iscp.

The VM

Calling the VM

The entry point to the VM in the Wasp codebase is the function MustRunVMTaskAsync function.
It is called each time the Wasp node needs to run computations.

The function MustRunVMTaskAsync start a parallel goroutine to run calculations.
Upon completion the VM notifies the calling code through the callback.
The MustRunVMTaskAsync takes as a parameter vm.VMTask:

type VMTask struct {
	Processors         *processors.ProcessorCache
	ChainInput         *ledgerstate.AliasOutput
	VirtualState       state.VirtualState 
	Requests           []iscp.Request
	Timestamp          time.Time
	Entropy            hashing.HashValue
	ValidatorFeeTarget *iscp.AgentID
	Log                *logger.Logger
	// call when finished
	OnFinish           func(callResult dict.Dict, callError error, vmError error)
	// result
	ResultTransaction  *ledgerstate.TransactionEssence
	ResultBlock        state.Block
}

At input, the most important parameters are:

	Requests           []iscp.Request
	VirtualState       state.VirtualState 
  • VirtualState represents current state of the chain, a collection of key/value pairs. It is virtual in a sense that all updates to that state produced by smart contracts are accumulated in memory and only are written (committed) into the database upon confirmation of the block

  • Requests represents a batch of requests. Each request carries call parameters as well as attached tokens (digital assets). Each request is processed by a smart contract it is targeting. The requests update the virtual state sequentially one by one, in each step producing a new virtual state.

Result of running the VM

The result of running the task by the VM consists of:

  • final state of the VirtualState
  • ResultBlock, a sequence of mutations to the Virtual state.
  • ResultTransaction, an essence part (unsigned yet) of the anchor transaction which will be sent to the Tangle ledger for confirmation.

The VirtualState at the output of the task is always equal to the VirtualState at the output with applied all mutations contained in the block.

The VirtualState has state hash (Merkle root or similar) which is deterministically calculated from the initial VirtualState and the resulting block.

The hash of the resulting VirtualState is contained in the ResultTransaction, therefore upon confirmation of the transaction on the Tangle ledger the virtual state is immutably anchored.

Structure of the VM

The VM wraps many processors. The VM wrapper implements fee logic, call between processors, smart contract
deployment and other generic logic. In general, one processor repesents one smart contract, the VM plugin.
A processor may implemenent any deterministic calculations as long as it conforms to the VMProcessor
and other related interfaces.

Significant part of the VM logic is implemented as core smart contracts. The core smart contracts also expose
core logic of each ISCP chain to outside users: the core smart contracts can be called by requests just like any other
smart contact.

The implementation of core smart contracts is hardcoded into the Wasp. Implementations of all core contract as well
as their unit tests can be found in wasp/packages/vm/code.

Except core smart contracts, all other processors are plugged into the VM dynamically, hence VM plugins.

All processors are alike: core contracts are attached to the VM just like any other VM plugin.

Deployment of the smart contract

The process of plugging a new smart contract (processor) into the VM is called deployment. The deployment
is handled by sending it the deployContract request to the root contract. As a result of the request,
a new smart contract (a processor, VM plugin) is deployed on the chain. The registry of deployed smart contracts
is maintained by the root contract as a part of the chain's state.

The deployContract request takes two parameters :

  • VM type parameter defines interpreter of the smart contract binary code
  • blob hash parameter is a hash of the binary (a reference to it) which must be loaded into the interpreter to create a processor. The binary, the blob, must be uploaded into the chain beforehand, usually with IPFS as a uploading/downloading service.
VM type

All VM types are statically predefined in the Wasp node. It means, to implement a new type of VM plugin, you will need to modify the Wasp node by adding a new VM type. The VM type is part of VM abstraction, so adding a new VM type is transparent to the rest of the Wasp code.

A new VM Type is introduced to the rest of the VM abstraction logic through the call to the function processors.RegisterVMType.

The call to processors.RegisterVMType takes name of the new VM type and the constructor, a function which creates
new iscp.Processor object from the binary data of the program.

The following VM types are pre-defined in the current release of the Wasp:

  • core represents core contracts
  • native represents example and other contracts (e.g. the evmchain contract) which conform to the native interface and are hardcoded before run
  • wasmtime represents Wasmtime WebAssembly interpreter and native Rust/Wasm environment to create smart contracts.

To implement new types of interpreters, other languages or interpreters, a new VM Type must be implemented into Wasp.

Program binary and blob hash

To dynamically deploy a smart contract we need code of it in some binary format and dynamical linking of it
to be able to call from VM. The very idea is to make the binary executable code of the smart contract immutable,
which means it must be a part of the chain's state.

For example, WebAssembly (wasm) smart contracts produced by the Rust/Wasm environment provided together
with the Wasp, are represented by wasm binaries. Other VM types may take different formats to represent its
executable code.

To deploy a wasmtime smart contract on the chain, first we need to upload the corresponding wasm binary.
All wasm binaries (as well as any other files of data) are kept in the registry handled by the blob core contact.
To upload a wasm binary to the chain one must send a request to the blob. Each blob on the chain is referenced by
its hash.

The smart contract deployment takes VM type and binary blob hash as parameters. It makes the smart contract
deployment process completely transparent to the VM types and binary data formats of executables.

The only thing which is needed is to implement is the constructor function for the VM type and register
it with processors.RegisterVMType. The rest is handled by the generic logic of the VM.

Processor and the sandbox interface

In native and wasmtime implementations one processor represents one smart contract. It gives full power to the smart contracts on the ISCP chain, such as manipulate native IOTA assets, call other smart contracts (processors) on the same chain and send requests and assets to other ISCP chains.

Each processor object implements two simple interfaces: iscp.VMProcessor:and iscp.VMProcessorEntryPoint.

type VMProcessor interface {
	GetEntryPoint(code Hname) (VMProcessorEntryPoint, bool)
	GetDefaultEntryPoint() VMProcessorEntryPoint 
	GetDescription() string
}

type VMProcessorEntryPoint interface {
	Call(ctx interface{}) (dict.Dict, error)
	IsView() bool
}

The smart contract is "plugged" into the VM with this interface.

Entry points

A processor (smart contract) is a collection of callable entry points.

Each entry point is identified in the processor with its hname (hashed name), a 4 byte value, normally first 4 bytes of blake2b hash of the smart contract function's name or signature. See iscp.Hname.

Function GetEntryPoint returns entry point object with the existence flag.

GetDefaultEntryPoint must always return default entry point. It will be called each time when entry point with given hname is not found.

VMProcessorEntryPoint interface allows to Call() the entry point and passes it a context handler.
The call returns a dictionary of resulting values, a collection of key/value pairs and, optionally, error code.

There are two types of entry points: full entry points and view entry points.

  • full entry point only accepts context handlers of iscp.Sandbox interface type. This type of context provides full access to the state of the smart contract so that the smart contract could modify it.

  • view entry point only accepts context handler of iscp.SandboxView interface type. It provides limited read-only access to the state.

The type of entry point is recognized by IsView() function. Using Call() with the wrong context type will result panic in the VM.

The VM provides implementation of iscp.Sandbox and iscp.SandboxView interfaces. It limits access to the smart contract's state partition and its on-chain account of tokens.

Each new VM type has to provide its own VMProcessor and VMProcessorEntryPoint implementations.

Sandbox interface

The iscp.Sandbox interface implements a number of functions which can be used by the processor's implementation
(a smart contract). Here are some of them:

  • Params() returns a dictionary (key/value pairs) of the call parameters
  • State() returns access to the VirtualState in the context of the call: a collection of key/value pairs.

All key/value pair storages, like virtual state and call parameters are defined in the kv package.
It defines different kinds of key/value pair collections and different encoding/decoding options for the binary data.

  • Balances() returns a collection of color/value pairs: balances of colored tokens which are in the control of the current smart contract
  • IncomingTransfer() represent colored balances which are coming with this call. Those tokens are already part of Balances()
  • Call() invokes entry point of another smart contract (processor) on the same chain. Note that the call is agnostic about type of VM of the called entry point.
  • Caller() is a secure identification of the calling entity: an address or another smart contract
  • Send() allows sending requests and funds to another chains, smart contracts or ordinary wallets.
  • Utils() implements a number of utility function which may be called by a smart contract on the VM host. For example hashing function or other deterministic state-less cryptographic functions which normally are faster to run on the host than on an interpreter.
SandboxView interface

The view entry points are called from outside, for example by a web server to query state of specific
smart contracts. By intention those entry points cannot modify the state of the chain.

The SandboxView interface must be passed as a parameter to the view entry points.
The SandboxView implements limited access to the state, fo example it doesn't have a concept of
IncomingTransfer or possibility of Send() tokens.
The State() interface provides read-only access to the VirtualState.

The logic of the VM ensures that full entry points can call all other entry points, while view entry points can only call
other view entry points.

Implementation of EVM on a Virtual Ethereum

The IOTA Foundation is contemplating a plan to ensure binary compatibility with EVM/Solidity ecosystem.
The goal is to be able to run EVM smart contracts on ISCP chains. The EVM should be implemented in the framework of
the ISCP VM Abstraction.
The EVM would be implemented as a processor and it will be able to access key/value store of the state through State()
interface of the Sandbox(). It essentially means the whole EVM chain would be implemented as a state of one ISCP smart contact.
This way EVM would run in an isolated environment and Solidity code won't be able to access and manipulate native IOTA assets, hence Virtual Ethereum. To open EVM to access all spectrum of ISCP functions would be the next step.

The Virtual Ethereum project is in the phase of definition therefore it is open for all kind of suggestions to architectural design with the final goal in mind: to be able to run native EVM code (binary compatibility) as
a VM on the ISCP chain.

The external interfaces of Virtual Ethereum would be wrapped into the native transactions and calls of IOTA and ISCP.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type VMRunner added in v0.2.0

type VMRunner interface {
	Run(task *VMTask)
}

type VMTask

type VMTask struct {
	ACSSessionID             uint64
	Processors               *processors.Cache
	ChainInput               *ledgerstate.AliasOutput
	VirtualStateAccess       state.VirtualStateAccess
	SolidStateBaseline       coreutil.StateBaseline
	Requests                 []iscp.Request
	ProcessedRequestsCount   uint16
	Timestamp                time.Time
	Entropy                  hashing.HashValue
	ValidatorFeeTarget       *iscp.AgentID
	Log                      *logger.Logger
	OnFinish                 func(callResult dict.Dict, callError error, vmError error)
	ResultTransactionEssence *ledgerstate.TransactionEssence // if not nil it is a normal block
	RotationAddress          ledgerstate.Address             // if not nil, it is a rotation
	StartTime                time.Time
}

VMTask is task context (for batch of requests). It is used to pass parameters and take results It is assumed that all requests/inputs are unlock-able by aliasAddress of provided ChainInput at timestamp = Timestamp + len(Requests) nanoseconds

Directories

Path Synopsis
blocklog
in the blocklog core contract the VM keeps indices of blocks and requests in an optimized way for fast checking and timestamp access.
in the blocklog core contract the VM keeps indices of blocks and requests in an optimized way for fast checking and timestamp access.
governance
in the blocklog core contract the VM keeps indices of blocks and requests in an optimized way for fast checking and timestamp access.
in the blocklog core contract the VM keeps indices of blocks and requests in an optimized way for fast checking and timestamp access.
root/rootimpl
'root' a core contract on the chain.
'root' a core contract on the chain.
testcore/sbtests/sbtestsc
smart contract for testing
smart contract for testing
sandbox_utils
package sb_utils implements Sandbox utility functions
package sb_utils implements Sandbox utility functions

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL