Crate lyquor_vm

Expand description

§Data-Driven Computation

The secret sauce for Lyquor is its categorization of on-chain vs. off-chain logic in a data-driven manner. Both the state variables and functions defined in a Lyquid are tagged by categories.

For the data that requires network-wide synchronization among all nodes hosting the same Lyquid, their operations should be driven ultimately by consensus. The network state is only modified through the events streamed from FCO. FCO provides a per-Lyquid sequence of events, which is consistent across all nodes, and ultimately backed by a chain.

For the data that do not require network-wide synchronization, local to the node running the instance of Lyquid, local events can drive the execution directly and concurrently. Like network states, the instance state is still persisted across the function calls, but may differ from node to node for the same Lyquid. This state is typically driven by messages from UPC, network requests from wallets/users, and other kinds of events that are off-chain.

§Direct Memory Access Model

WASM is a widely adopted bytecode and execution standard thanks to its capability of being Just-In-Time compiled (JIT) and run at native speed. In Web3, like other “VMs” (EVM/Move VM, etc.), the actual performance is limited by the state access bottleneck. LVM uses a Direct Memory Access (DMA) model that has never been used in the blockchain or cloud industry. It equips each Lyquid WASM code with its own virtual, non-volatile, byte-addressable memory, like what an OS offers to its processes.

Through DMA, Lyquid keeps developer’s state variables directly in the DMA memory named LyteMemory, which is mapped into the OS memory of the host. This allows JIT-compiled code to run at its full speed, without hitting the state access barrier frequently. In the meanwhile, it enjoys flexibility in resource allocation because we only need to materialize pages of the memory on demand when accessed. It also increases data locality without sacrificing ergonomics of programming because all states are defined and preserved right in an in-memory form.

Like smart contracts, there is no such concept like “shutdown” because the states are persisted at page level automatically, and functions are driven by events directly.

§Lyquid Instance = Functions + LyteMemory

For a Lyquid, if a node chooses to run it, it will form a Lyquid instance based on the given image (WASM code). Given the Lyquid image, the Lyquid instance is attached to LyteMemory which has three parts (“segments”) of its state during execution:

  1. Volatile segment: the memory allocated (variables created) during the invocation of a Lyquid function (method). The call stack and/or any temporary variables that are not captured in the variable catalog in the first half of the lyquid::lyquid macro are volatile and forgotten when the function finishes execution.
  2. Network segment: the memory allocated at the deployment of the Lyquid, all variables prefixed by network in the variable catalog of Lyquid are allocated in this memory. Any changes to this part of the memory can only be made through network functions (Lyquid functions prefixed with network). Network memory is consistent across all instances for a Lyquid at a given version (lyquor_primitives::LyquidNumber), which means all nodes will see the same set of values for these network variables when call is made for the same Lyquid number.
  3. Instance segment: the memory allocated at the deployment of the Lyquid, all variables prefixed by instance in the variable catalog of Lyquid are allocated in this memory. Any changes to this part of the memory can only be made through instance functions (Lyquid functions prefixed with instance). Instance memory is private and local for the instance running on the node. Unlike network memory, different nodes may have different values for the same instance variable.

In LyteMemory, both network and instance memory segments are persisted (non-volatile) across the calls.

A good analogy to think about Lyquid’s data model is to think of a Lyquid as a “class” in languages like Java, and a Lyquid instance as a distributed object (or “instance”) for the same class. Network variables are like class variables (fields) shared among all objects, and also be read and updated, whereas instance variables are member variables that are local for each object of the same class. Similar to Java’s case where class variables are changed by class methods, in Lyquor, Lyquid’s network variables can only be changed by network functions, and read by instance functions, whereas instance variables can only be accessed by instance functions. Following this analogy, Lyquid’s functions (methods) are event-driven, so only chain event can invoke “class methods”, whereas other events may invoke “instance methods”. The invocation of a network/instance function is called a Lyte call. Currently it can be triggered by:

  • FCO’s event: [lyquor_primitives::LyteEvent]
  • Universal Procedure Call (UPC) event: callee, request, response
  • Other user defined events

Because network segment is consistently updated by network function invocation, to precisely identify (and synchronize) its state evolution, the network segment is versioned. Whereas instance segment, because of its customizable nature and to help migrate/share data from version to version, is not versioned.

Re-exports§

pub use lyquid;

Modules§

barrel
instance
log
utils

Macros§

host_api
host_api_body

Structs§

CallHeader
Bundle of metadata required to initiate a UPC.
Env
LVM
Lyquor Virtual Machine implementation.
LyquidNumber
The verison number that uniquely identifies (and determines) the state of network variables.
MulticastContext
OnConsoleOutput
RemoteObj
Used for messages/data transmitted through network/IPC. It lazily encodes/decodes the data which minimized the unncessary computation/copy of the data (at most one encode/decode per instance).

Enums§

Error

Traits§

RuntimeEnv

Type Aliases§

Instance
InterCallSys