What is Radix Engine?
Radix Engine is a deterministic, transaction-based state machine that updates ledger state by sequentially executing transactions.
Radix Engine was built out of the lack of Blockchain VMs specifically optimized for a DeFi shared computing environment in terms of usability, security, performance, and modularity.
Unlike the EVM and other blockchain VMs, Radix Engine:
- Is Object-Oriented and Type-Safe
- Enforces move/ownership semantics
- Has the ability to add system plugins to extend/modify system functionality
Radix Babylon Network
The Radix Engine running on the Radix Babylon network is a specific system configuration of Radix Engine which makes asset and auth management a native/core feature of the system.
Architecture
Radix Engine is organized into 5 layers. Each layer has specific responsibilities and provides an API to the layer above. Middle layers also provide a Callback API which the layer above must implement.
Layer Name | Responsibilities |
---|---|
Application | Defines Blueprint Application Logic |
VM | Executes Application Code |
System | Defines Actor abstraction (Memory Protection) Defines Package, Blueprint, Object abstractions Defines System Standards such as Authorization and Versioning |
Kernel | Defines Node, Partition, Substate abstractions Maintains Call Frame Stack Maintains Ownership/Reference invariants |
Database | Defines PartitionKey, SortKey abstractions |
Application Layer
The application layer is responsible for defining high level logic which manipulates objects and produces events for the eventual use by off-ledger systems such as wallets and DApps.
Implementation
An application is added to the system by publishing a Package, which contain zero or more Blueprints. Each blueprint defines object type information and logic which can create, manipulate and destroy objects of that blueprint type.
Object Model
Unlike Ethereum where the ledger state is a flat mapping between addresses and account states, Radix Engine organizes its state into a forest of objects. Child objects are exclusively owned by its parent in the tree hierarchy. Each root object is assigned a global address.
Each object has:
- Static Type Information which includes:
- A BlueprintId
- An optional Outer Object
- A list of Features
- A list of Generic Substitutions
- Application State
- A set of Object Modules
Blueprint Id
Every object is associated with a blueprint id which consists of
<package_address> + <blueprint_name>
.
A blueprint id uniquely identifies the blueprint of an object. The blueprint of an object specifies both the structure and the logic of the object.
Outer Objects
Objects which have a blueprint which is an Inner Blueprint will have an associated Outer object of a given outer Blueprint. Objects of an Inner Blueprint may directly access the state of its outer object avoiding invocation and new call frame overhead + costs.
Features
An object's features describes a subset of the total features defined in the object's blueprint. Having a feature enabled/disabled can modify both behavior and state stored for the object.
Generic Substitutions
If an object's blueprint defines generic arguments then these are replaced with the types specified in an object's generic substitutions list.
Application State
Each object may have a set of fields and collections which represent that object's state. When created or updated, these fields and collections must conform to the schema specified by the object's blueprint.
Object Modules
The system may define additional state/logic to be stored per globalized object known as an Object Module. The system can define whether an Object Module is required or optional.
An Object Module itself has a Blueprint type along with associated logic to manipulate the state of the object module.
NOTE: In Radix Babylon Network, there currently exists three object modules:
- RoleAssignment (Required)
- Metadata (Required)
- Component Royalties (Optional)
Blueprint
A Blueprint is the Radix Engine equivalent of Classes/Types in Object-Oriented Languages. It acts as a type identifier for an object and describes shared properties and behavior of all objects of that blueprint.
Each blueprint has a unique name within its package and are globally identifiable by
<package_address> + <blueprint_name>
.
Inner and Outer Blueprints
NOTE: In Radix Babylon Network, Inner Blueprints are currently only available for use by native packages.
A blueprint may be specified as either an Outer or Inner Blueprint. Inner blueprints must specify an associated outer blueprint defined in the same package.
Inner blueprint objects may only be instantiated by an object of the associated outer blueprint.
Transience
NOTE: In Radix Babylon Network, Transience is currently only available for use by native packages.
If a blueprint is specified to be transient, all objects of this blueprint cannot be persisted and must be created/dropped within the lifecycle of a transaction.
Features
NOTE: In Radix Babylon Network, Features are currently only available for use by native packages.
Features provide a mechanism to express conditional execution and conditional stored state (using Field Conditions). The set of features to be used are specified per object on instantiation.
Features are identified by string.
Generics
NOTE: In Radix Babylon Network, Generics are currently only available for use by native packages.
Generics to a blueprint requires an object instantiator to specify generic substitutions during instantiation. Such a generic can then be used in defining function or state schemas.
Generics in a Blueprint are identified by index.
Fields
NOTE: In Radix Babylon Network, use of more than one Field, Field Conditions and Field Transience are currently only available for use by native packages.
A field is object state which gets loaded at once and maps to a single substate. A schema which describes what is in the data must be specified for every field.
Fields are identified by field index.
Field Condition
Fields may be conditionally included in an object depending on the features instantiated with that object. There are currently three options for field conditions:
Name | Description |
---|---|
Always | Always include the field |
IfFeature | Only include the field if a given feature is specified |
IfOuterFeature | Only include the field if a given feature in the associated outer object is specified |
Field Transience
Fields may be specified to be transient. In this case, the field is never persisted. Instead, a default value is initially loaded on first read and may be updated over the course of a transaction. At the end of a transaction the field's value gets discarded.
Collections
NOTE: In Radix Babylon Network, Collections are currently only available for use by native packages.
A collection is a set of data which share the same schema. There are currently three types of collections:
- Key-Value Collection
- Index Collection
- Sorted Index Collection
Collections are identified by collection index.
Events
Events may be emitted during runtime for off-chain processing. A schema which describes the data of every event type must be specified.
Events are identified by string.
Functions/Methods
Functions/Methods define executable logic. A function is executable logic which does not directly depend on the internal state of an object. A method is executable logic which directly depends on internal state of an object, known as receiver.
Every function/method is defined by an input/output schema which describes the signature of the function/method.
Functions/Methods are identified by string.
Hooks
NOTE: In Radix Babylon Network, Hooks are currently only available for use by native packages.
Hooks define logic which get executed when certain system events occur.
There are currently three types of hooks:
Hook Name | Description |
---|---|
OnVirtualize | Called when a substate fault occurs on a virtual address of this blueprint type. |
OnMove | Called when an object of this blueprint type is moved between call frames. |
OnDrop | Called when an object of this blueprint type is dropped. |
Types
A blueprint may associate a schema to a name. This name combined with the blueprint ID can be used to specify a type.
Types are identified by string.
Blueprint Modules
The system may define additional state to be stored per blueprint known as Blueprint modules. If defined, every blueprint definition must initialize these modules.
NOTE: In Radix Babylon Network, there currently exists two blueprint modules:
Package
A package is a special native object which contains 0 or more blueprint definitions. Because it is an object, packages inherit object-like qualities such as the ability to have object modules (like metadata).
Package Blueprint and Package Package
This type of relationship creates the following circular definition:
An Object is of some Blueprint type.
A Blueprint is part of a Package.
A Package is an Object.
This circular definition creates the notion of the Package Blueprint and the Package Package (similar to Class.class in java). A Package Blueprint is the blueprint type of a Package object and Package Package is the package which contains the Package Blueprint.
Due to the circular dependency, the first object (Package object) is created without following standard object creation process. It's directly flashed into the database.
Type System
The Type System checks that payloads coming from the application layer match the schema defined in the Blueprint. This includes:
- Object Fields/Collections
- Function Input/Output
- Events
The Type Checking System supports generics.
Actor
An actor is the acting entity currently executing and determines what state can be directly read.
There are five types of actors:
Actor Name | Description |
---|---|
Root | The initial application of all transactions. |
Method | A call on an object. Has direct access to state of the running object. |
Function | A stateless function call. Has no direct access to any state. |
Method Hook | A callback call on an object defined by the system. Has direct access to state of the running object. |
Function Hook | A callback stateless function call defined by the system. Has no direct access to any state. |
VM Layer
The VM Layer is responsible for passing control from the system to the application as well as providing the application layer a Turing-complete computing environment and the interface to the system layer.
Radix Engine currently supports two VM environments:
- A Scrypto WASM VM which exposes the system layer through WASM extern functions
- A Native VM which directly compiles applications with Radix Engine in the host's environment
Implementation
The VM Layer is implemented by defining the System Callback Object, which requires two callback implementations:
init
which is called on transaction bootup to initialize the vm layerinvoke
which is the entrypoint for any function or method invocation
On invoke
, the VM layer determines the appropriate VM environment and then calls the associated
application layer logic.
Scrypto Wasm VM
On invoke
of a method which executes in a Scrypto Wasm VM, the VM layer loads the WASM code
associated with the invocation and creates a new WASM instance with a fresh heap and stack.
The exported function associated with the invocation is then called with the
invocation arguments.
Extern functions are mapped to subset of the system layer's api which the application can call.
Native VM
On invoke
of a function/method which executes natively, the function must have been compiled with
Radix Engine and is just called directly. Because all applications using the Native VM are trusted,
Native VM applications are not isolated from the Radix Engine and share the same memory space.
The full system layer API is also exposed to Native VM applications.
System Layer
The System Layer is responsible for:
- Defining the Object, Blueprint, Type System, and Package abstraction
- Defining Actor abstraction and memory protection
- Maintaining a set of System Modules, or pluggable software, which extends the functionality of the system.
Implementation
The System Layer is implemented by defining the Kernel Callback Object and defining the Actor/Package/Blueprint/Object abstractings on top of the kernel's Node/Partition/Substate abstractions.
Object Implementation
The system layer defines the Object abstraction on top of the kernel's Node/Partition/Substate abstraction.
The system layer maps every object to a unique NodeId and under every NodeId the partitions are mapped in the following manner:
Partition Number | |
---|---|
Type Info | 0 |
Schema Data | 1 |
Object Modules | 2-31 |
Reserved | 32-63 |
Application State | 64-255 |
Type Info
For a given object, type-related info is stored under the object's NodeId
in the TypeInfo
substate found in PartitionNumber 0
and SubstateKey::Field 0
. This includes information such as:
Blueprint Implementation
State to Partition Mapping
The mapping from the Fields and Collection indices to Partition Number is managed by the System Layer and done at a per blueprint basis. This includes the Fields and Collection indices of the object modules for and object.
Package Implementation
The package abstraction is implemented as a native blueprint. In order to get around the circular definition problem, the package logic and structure must be flashed into the system at genesis.
Type System
The system layer is responsible for implementing the type system abstraction.
For a given object, the BlueprintId
, GenericSubstitutions
, and other type-related info is stored
under the object's NodeId
in the TypeInfo substate found in PartitionNumber 0
and SubstateKey::Field 0
.
Local Scrypto Schemas for the object are stored in the object's NodeId
with PartitionNumber 2
with
a content addressable substate key.
Remote Scrypto Schemas are stored in the blueprint's package NodeId
with PartitionNumber 2
with
a content addressable substate key.
Actor Implementation
The system layer is responsible for defining the Actor abstraction.
The state of the current actor is stored per call frame as CallFrameData
. The system exposes an
interface which can access the state of the currently acting object (if there is one). Thus, the system
prevents higher layers from accessing state of call frame objects which aren't the actor.
System Modules
System Modules are modules which can be added to the system to extend functionality.
System Modules are stateful and may expose a set of functions to the application layer as well as execute additional logic within a system call.
Kernel Layer
The kernel layer is responsible for:
- Defining the Node/Partition/Substate abstraction
- Defining the Call Frame abstraction
- Maintaining Ownership/Reference invariants
- Managing transaction state updates, which are to be subsequently committed to the database at the end of the transaction
Implementation
The kernel layer is implemented on top of the database layer's Partition Key and Sort Key abstractions.
Database Layer
The database layer is responsible for defining the partition-key / sort-key abstractions.
Implementation
The database layer is implemented on top of a key-value database.
Transaction Lifecycle
Radix Engine is a transactional state machine which accepts a transaction and a given state and outputs a state change and additional output.
radix_engine(State, Transaction) -> (StateChange, Output)
The state change can then be applied to the database to update it's state:
state_commit(State, StateChange) -> State
Three Stages
There are three stages in the transaction lifecycle:
- Bootup, which consists of initializing the layers of the stack
- Execution, which is the execution of the application logic specified by the transaction
- Shutdown, which consists of cleaning up each layer and creating the final StateChange and Output
Transaction Bootup
The bootup and initialization of a transaction consists of two steps:
- Initialize Stack
- Invoke Transaction Processor
Initialize Stack
Before a transaction is executed, initialization of the Kernel/System/VM stack occurs. During this initialization phase, configuration is loaded from the database and the state of each layer is initialized.
Layer | Initialization Description |
---|---|
Kernel | Load Kernel version Check transaction references Create Initial Call Frame |
System | Load System module configurations Verify transaction has not been previously executed Verify transaction is valid within epoch bounds Initialize enabled System Modules |
VM | Load Scrypto VM version |
Invoke Transaction Processor
Once the entire stack has been initialized along with the initial call frame, an invocation of a
well-known blueprint, TRANSACTION_PROCESSOR
, is made with the arguments specified in the transaction.
From this point forward, normal transaction execution occurs.
Transaction Runtime
Once transaction bootup has finished, the Transaction Processor Blueprint
function run
is then executed with transaction data as its argument. It executes on top of the initial call frame created
during kernel initialization in a standard application environment.
Once the run
function has finished executing transaction shutdown begins.
Transaction Shutdown
Once the TRANSACTION_PROCESSOR
call returns, the transaction shutdown procedure begins.
Transaction Shutdown consists of two steps:
- Finalize State Updates
- Create a Transaction Receipt
Application Environment
Every method/function execution has a call frame associated with it managed by the Kernel.
A call frame contains all owned and referenced objects usable by the running function. These objects
are referrable by NodeId
and system-defined indices.
Invocations
Owned and referenced objects may have methods invoked (creating a new call frame). Owned objects may be passed in as arguments and may be received in these invocations.
Object Creation/Destruction/Globalization
Objects of the current blueprint may be instantiated, creating a new owned object into the call frame, or dropped, in which case the owned object gets removed from the call frame.
Actor State Read/Write
A call frame also contains a reference to the actor, or callee object (i.e. self in object-oriented languages). This is maintained to allow read/writes of state for the given actor.
System Module Functions
Additional system functions are available to the application layer implemented by System Modules. Currently, these include:
- Events
- Logging
- Costing
- Transaction Runtime
Object Lifecycle
Instantiation
An object may be instantiated by using one of the system calls:
object_new_simple_object
object_new_object
On instantiation the set of features, generic arguments, and initial state must be passed in to construct the object. Only blueprints of the currently acting package may be instantiated. If the blueprint is an inner blueprint, only an acting outer blueprint component may instantiate that inner blueprint.
Destruction
An object may be dropped by using the object_drop
system call.
Globalization
An object may be globalized using one of the system calls:
object_globalize
object_globalize_with_address_and_create_inner_object_and_emit_event
Once globalized an object is associated with a global address and may be referenced without ownership of the object. Thus, it may be referenced in a transaction or in blueprint code.
Moved to ownership by another object
A call frame owned object may be moved to ownership by another object if it is moved via one of the system calls:
object_new_simple_object
object_new_object
key_value_entry_set
field_write
Invocations
An invocation is started by the application layer by calling one of the invocation system calls:
object_call_method
object_call_direct_access_method
object_call_module_method
blueprint_call_function
On one of these calls, the system then follows three phases:
- Call Frame Setup
- Execution
- Call Frame Exit and Return
Call Frame Setup
System module does its own checks (e.g. auth).
Kernel invoke is called which setups a new call frame. The arguments are verified against the input schema of the function defined by the blueprint definition.
Execution
Once the new call frame is setup, execution is passed to the application layer which may then execute it's own logic in its application environment.
Call Frame Exit
Once finished the system layer checks that the return value is of the correct schema given by the blueprint definition. The kernel verifies that owned objects and references in the return value are valid and the caller call frame is updated with any of these owned objects/references.
State Reads/Writes
State Reads and Writes from objects may be only be done by the current actor using the following system calls:
actor_open_field
actor_open_key_value_entry
actor_index_insert
actor_index_remove
actor_index_scan_keys
actor_index_drain
actor_sorted_index_insert
actor_sorted_index_remove
actor_sorted_index_scan
Key Value Stores are a special type of object though which may be read/written to as long as one has a reference to that key value store. This is accessible via the system call:
key_value_store_open_entry
Fields
Collections
Transaction Processor
The transaction processor is the initial application layer call frame made during the transaction boot process and executes a transaction manifest which is encoded in a transaction.
It consists of a blueprint with a single run
function.
Transaction Processor Blueprint
The transaction processor blueprint has a single run
function which accepts a transaction
manifest.
The manifest is a series of instructions which the transaction processor interprets and executes.
Resources
The Resource system manages asset logic across the system.
It is composed of:
- Resource Package which consists of
- Fungible/Non-Fungible Bucket Blueprint
- Fungible/Non-Fungible Proof Blueprint
- Fungible/Non-Fungible Vault Blueprint
- Fungible/Non-Fungible Resource Manager Blueprint
Auth
Unlike the majority of blockchains which rely on a caller identifier for access control, the Auth system uses a more distributed "Proof" system. Before accessing a protected method a caller must provide specific "Proofs" of resources they have access to. These proofs must then match the requirements of the callee function or method.
The Access Control System is composed of four parts:
- An Access Control Blueprint Module, which defines function rules and roles available to use for a given blueprint in a package and which roles are able to access which methods.
- A Role Assignment Object Module, which assigns access rules for each role on object instantiation.
- An AuthZone Blueprint, which allows a caller to update the proofs in their authzone.
- An Access Control System Module, which creates a new AuthZone for every new call frame and verifies that AuthZone proofs match the requirements of accessing an object's method.
Auth Blueprint Module
The auth blueprint module defines three things for every blueprint:
- Function AccessRules
- Method accessibility
- Role Specification
Function AccessRules
Each function is assigned an immutable access rule.
Method Accessibility
Each method is assigned an accessibility rule, of which there are four options:
Accessibility Rule | Description |
---|---|
Public | Anyone can access the method |
Outer Object Only | Only outer objects may access the method |
Role Protected | Only callers who have satisfied any role in a given list may access the method |
Own Package Only | Only the package this method is a part of may access the method |
Role Specification
The roles which must be assigned on object instantiated are defined in role specification. Furthermore, roles which may update the rules of other roles must be specified.
For inner blueprints, it is also possible to defer role specification to the outer blueprint.
Role Assignment Object Module
The role assignment object module assigns each role to an access rule.
The rules associated with a role may be updated or defaulted to the "owner role".
AuthZone
To call a protected method, the caller must place these proofs into their AuthZone, a space dedicated for using proofs for the purpose of authorized method access.
An AuthZone has the following methods:
pop
push
create_proof_of_amount
create_proof_of_non_fungibles
create_proof_of_all
drop_proofs
drop_signature_proofs
drop_regular_proofs
drain
assert_access_rule
Auth System Module
The Auth System Module is a system module which operates on every invocation:
- Creates a new AuthZone
- Resolve the requirements to access the method/function invocation
- Verifies that the Global Caller AuthZone has sufficient proofs to meet the requirements
AuthZone Creation
At the start of every invocation, the access control system module creates a new AuthZone in the call frame of the caller and adds a reference to this object in the callee's call frame. This AuthZone effectively becomes the "Local AuthZone" of the callee.
Every AuthZone references a global caller AuthZone and a parent AuthZone, the values of which are dependent on if the invocation is a global object context switch or not.
If the invocation is a global object context switch, the global caller of the new AuthZone will reference the caller's AuthZone and will not have a parent AuthZone. If the invocation is a local context switch, the caller's global caller is copied into the new AuthZone and the parent will reference the caller's AuthZone.
This pattern generates a stack which looks like:
Permission Resolving
Permission resolving involves loading up relevant state of the callee and generating a permission object from this state.
If the callee is a function then the permission is loaded from the function access rules specified in the blueprint's access control blueprint module.
If the callee is a method then the Method Accessibility is loaded from the callee's access control blueprint module as well as the state in the callee's Role Assignment Object Module. From these two states, the permission to access the method is derived.
Auth Verification
Auth verification then checks the resolved permission against the AuthZones in the current global context as well as the Global Caller's context.
In the above drawing, Call Frame 6 is making a new invocation and the AuthZones checked are 3/4/5/6, the AuthZones belonging to the current Global Context as well as the Global Caller's Context.
Costing / Limits
The Costing and Limits System is responsible for bounding physical resources used in a transaction. It does this by maintaining a System Module which interacts with the resource system such that resources used in transaction require a payment of some resource.
Royalties
Royalties allow a package deployer and a component owner to receive royalties on every function or method call.
This is implemented in two parts:
Package Royalties Blueprint Module
The package royalties blueprint module allows the blueprint owner to set royalties per function and withdraw earned royalties.
Component Royalties Object Module
The component royalties object module allows the owner to set royalties per method and withdraw earned royalties.
Metadata
Metadata stores string keys with metadata values for any object and package.
This is implemented as a Metadata Object Module.
Metadata Object Module
The metadata object module allows a user to set/update/delete metadata associated with a given object.
Genesis Bootstrap
Bootstrapping a Radix Engine requires flashing several system substates and then the execution of several genesis transactions.
Specifically, the substates of the Package
blueprint and object module blueprints are flashed.
Once flashed, Package::publish
calls may now be called to create the rest of the native
blueprints.
Protocol Updates
Similar to genesis bootstrapping, a protocol update is a series of transactions which includes a set of substates to flash and a set of transactions to execute.