Customizing a subnet
Tutorial to add a new syscall to Filecoin Virtual Machine and create a new built-in actor in IPC to activate the syscall
IPC is uniquely hyper customizable as a scalability framework. Subnets are highly customizable and can be temporal, allowing subnet operators to spin up customized subnets for various needs, including modular consensus, gas option, configurable chain primitives, customized features vis pluggable syscalls and build-in actor.
In this tutorial, we will focus on how to extend features to your IPC subnet by customizing syscalls that are 'pluggable' as needed.
IPC uses the Filecoin Virtual Machine (FVM) as its execution layer, which is a WASM-based polyglot VM. FVM exposes all system features and information through syscalls, as part of its SDK. The FVM SDK is designed to be pluggable to enable user-defined custom features with syscalls, while implementing default features from FVM kernel. Use cases includes:
Extending chain-specific syscalls once IPC supports more root chains. Because other chains may have their own special syscalls different as Filecoin (proof validation, etc.).
Extending features to support better development tools. E.g. adding special debugging syscalls, adding randomness syscalls, and supporting more ECC curve, etc.
Pre-requisite knowledge for tutorial:
IPC/fendermint implementation
Rust
Instructions
These instructions describe the steps required to create a new kernel which implements a new syscall along with an example built-in actor that shows how you would call that syscall. Full example here.
TIP: For clarity, the instructions may have skipped certain files (like long Cargo.toml
files) so make sure to refer to the above full example, if you want to follow along and get this compiling on your machine.
1. Define the custom syscall
In this example, we will be creating a simple syscall which accesses the filesystem. Inside syscalls, you can run external processes, link to rust libraries, access network, call other syscalls, etc.
We’ll call this new syscall
my_custom_syscall
and its defined as follows:fendermint/vm/interpreter/src/fvm/examples/mycustomkernel.rs#L23
Define a struct
CustomKernelImpl
which extendsDefaultKernel
. We use theambassador
crate to automatically delegate calls which reduces the boilerplate code we need to write. Here we simply delegate all calls to existing syscall to theDefaultKernel
.fendermint/vm/interpreter/src/fvm/examples/mycustomkernel.rs#L27
2. Implementing all necessary functions for the syscall
Implement
my_custom_syscall
Here is where we implement our custom syscall:
fendermint/vm/interpreter/src/fvm/examples/mycustomkernel.rs#L42
Next we need to implement the
Kernel
trait for the newCustomKernelImpl
. You can treat this as boilerplate code and you can just copy it as is:
fendermint/vm/interpreter/src/fvm/examples/mycustomkernel.rs#L61
3. Link syscalls to the kernel
Next we need to implement the
SyscallHandler
trait for theCustomKernelImpl
and link all the syscalls to that kernel. We need to explicitly list each of the syscall traits (ActorOps, SendOps, etc) manually here in addition to theCustomKernel
trait. Then inside thelink_syscalls
method we plug in the actor invocation to the kernel function that should process that syscall. We can link all the existing syscalls using thelink_syscalls
on theDefaultKernel
and then link our custom syscall.
fendermint/vm/interpreter/src/fvm/examples/mycustomkernel.rs#L112
4. Expose the customized syscall
Once this function is linked to a syscall and exposed publicly, we can use this syscall by calling
my_custom_kernel.my_custom_syscall
fendermint/vm/interpreter/src/fvm/examples/mycustomkernel.rs#L136
5. Replace existing IPC kernel with new custom kernel
Since the customized syscall is implemented in a
CustomKernelImpl
which extends and implements all the behaviors forDefaultKernel
, we can plug it into IPC instead ofDefaultKernel
\To use this kernel in fendermint code, replace
DefaultKernel
withCustomKernelImpl
for theexecutor
declaration infendermint/vm/interpreter/src/fvm/state/exec.rs
fendermint/vm/interpreter/src/fvm/state/exec.rs#L86
6. Use syscall in your IPC subnet
Now, we are all set to use the custom syscall in the IPC subnet. The custom syscall can be called in IPC actors to utilize the extended feature. For this tutorial, we can create a simple actor to demonstrate how to import and call the custom syscall and then confirm that its working correctly.
Let’s create a
customsyscall
folder inipc/fendermint/actors/
and then create a file called actor.rs in that new folder. Here we want to create a very simple actor, which when invoked (received a message on its Invoke method) will call the new syscall and return its value:
fendermint/actors/customsyscall/src/actor.rs#L14
Even though this is Rust code, IPC will compile it as a Wasm target and then run the compiled Wasm code inside FVM as an actor. However, we want to share some of the code between Wasm and IPC, such as the actor name
CUSTOMSYSCALL_ACTOR_NAME
and theInvoke
method enum. We will define these in a separate file calledshared.rs
as follows:
fendermint/actors/customsyscall/src/shared.rs
We next need to write a
lib.rs
file which exports the shared code and only compilesactor.rs
if we are building the Wasm actor.
fendermint/actors/customsyscall/src/lib.rs
NOTE: There are several other files you need to change to compile this actor and package it with the other actors that IPC uses. Please refer to the full example here for the following other files you need to change:
fendermint/actors/customsyscall/Cargo.toml
: The package for your new actor and all its dependenciesfendermint/actors/Cargo.toml
: Add your new actor as a Wasm targetfendermint/actors/build.rs
: Include your new actor in theACTORS
array so it will get included in the bundle.fendermint/actors/src/manifest.rs
: Add your new actor in theREQUIRED_ACTORS
array so we can confirm it was correctly bundled on IPC startupfendermint/vm/actor_interface/src/customsyscall.rs
: A macro which assigns an ID to your new actor and declares constants for accessing it by ID and Addressfendermint/vm/actor_interface/src/lib.rs
: export the constants to IPC
7. Load and deploy actor at genesis
We have so far created a new kernel and syscall, switched IPC to use that kernel and created an actor which calls the new syscall. However, in order to call this actor in IPC, we must load it from the custom_actors_bundle.
To do this open
fendermint/vm/interpreter/src/fvm/genesis.rs
file and in theinit
function add our customsyscall actor right after creating thechainmetadata
actor:
fendermint/vm/interpreter/src/fvm/genesis.rs#L251
Your actor has now been deployed and we should be able to send it messages!
8. Invoke the actor
In the last step in this tutorial we will send our customsyscall actor messages which will cause it to run its Invoke method and execute the custom syscall. Here, we will simply call it for every new block height. Go to fendermint/vm/interpreter/src/fvm/exec.rs
and inside the begin
function add the following code:
fendermint/vm/interpreter/src/fvm/exec.rs#L115
This code sends a message to the customsyscall
actor and parses it output after it has been executed. We print out the return value from the actor, which will be the return value of our custom syscall.
9. Test your actor
In order to see this working end to end in IPC, you can run one of our integration tests. These tests run IPC in docker containers so make sure to have docker installed on your machine if you are following along.
We must first need to build a new docker container for the fendermint image which will contain all the code you have added so for. To do this run:
After the fendermint docker image has been built, you can run one of the integration tests
View fendermint logs and see the output generated by calling the customsyscall
actor in each epoch:
View the docker logs:
You can now run cargo make teardown
to stop the containers.
Last updated