Rollups Machine
The Rollups Machine provides a high-level interface for interacting with Cartesi rollups machines, supporting both advance and inspect requests, and exposing a generator-based pattern for streaming outputs and reports.
Features
The API is centered around the RollupsMachine interface, which allows you to:
- Send advance or inspect requests to a Cartesi machine;
- Stream outputs, reports, and progress updates as they are produced;
- Handle input rejection and fatal errors;
- Automatic rollback behavior;
- Store and shutdown the machine.
The following sections will show step by step how to create, advance and inspect a Cartesi rollups machine.
Creating a machine
import { } from "@cartesi/machine";
// create a rollups machine from a snapshot stored at .cartesi/image
const = (".cartesi/image");In this rollups constructor, that takes a directory, a remote machine is spawned and the snapshot is loaded. The RemoteCartesiMachine instance is managed by the RollupsMachine.
If the user wants to keep ownership of the RemoteCartesiMachine he can instantiate it himself, and use the rollups constructor that takes a RemoteCartesiMachine instance instead.
Rollback
A rollups input is all-or-nothing: an application that rejects one must leave the machine exactly as it was, or the next input runs against state the rollups protocol never accepted. The machine is therefore snapshotted before each input, and restored if that input is rejected.
How the snapshot is taken depends on the machine:
- a RemoteCartesiMachine
forks its server, so the snapshot costs no memory copy — the operating system shares the pages until one side writes to them. This is what the rollups constructor that takes a directory sets up; - a local machine — including the WebAssembly one, where there is no process to fork — stores itself to a directory instead, and loads that snapshot back on rejection. Pass
snapshotDirto choose where those go; it defaults to/tmp/cartesi-rollups.
Pass noRollback: true to skip the snapshot entirely. It is faster, and correct only when every input is known to be accepted — a re-execution of an epoch already validated, for instance.
Creating an input
The rollups framework expects an input format that is the ABI encoding of the EvmAdvance function of the Inputs.sol smart contract.
Using the viem utility encodeFunctionData and the Inputs.sol ABI, it's quite easy to build a rollups input.
import { , } from "viem";
import { } from "./inputsAbi";
const = "0x"; // the actual input payload
export const = (
({
: ,
: "EvmAdvance",
: [
1n, // mainnet
"0x4c1E74EF88a75C24e49eddD9f70D82A94D19251c", // honeypot application
"0x60247492F1538Ed4520e61aE41ca2A8447592Ff5", // sender
1n, // block number
1752129n, // block timestamp in seconds
0n, // prevRandao
1n, // input index
,
],
}),
);export const = [
{
: "function",
: "EvmAdvance",
: [
{ : "chainId", : "uint256", : "uint256" },
{ : "appContract", : "address", : "address" },
{ : "msgSender", : "address", : "address" },
{ : "blockNumber", : "uint256", : "uint256" },
{
: "blockTimestamp",
: "uint256",
: "uint256",
},
{ : "prevRandao", : "uint256", : "uint256" },
{ : "index", : "uint256", : "uint256" },
{ : "payload", : "bytes", : "bytes" },
],
: [],
: "nonpayable",
},
] as ;Advance
Now that we have a rollups machine and an input, we can advance the machine and collect the produced outputs, reports, and progress updates along the execution.
import { } from "@cartesi/machine";
import { } from "./input";
// create a rollups machine from a snapshot stored at .cartesi/image
const = (".cartesi/image");
for (const of .()) {
switch (.) {
case "output":
.();
break;
case "report":
.();
break;
case "progress":
.("Progress:", .);
break;
}
}import { , } from "viem";
import { } from "./inputsAbi";
const = "0x"; // the actual input payload
export const = (
({
: ,
: "EvmAdvance",
: [
1n, // mainnet
"0x4c1E74EF88a75C24e49eddD9f70D82A94D19251c", // honeypot application
"0x60247492F1538Ed4520e61aE41ca2A8447592Ff5", // sender
1n, // block number
1752129n, // block timestamp in seconds
0n, // prevRandao
1n, // input index
,
],
}),
);export const = [
{
: "function",
: "EvmAdvance",
: [
{ : "chainId", : "uint256", : "uint256" },
{ : "appContract", : "address", : "address" },
{ : "msgSender", : "address", : "address" },
{ : "blockNumber", : "uint256", : "uint256" },
{
: "blockTimestamp",
: "uint256",
: "uint256",
},
{ : "prevRandao", : "uint256", : "uint256" },
{ : "index", : "uint256", : "uint256" },
{ : "payload", : "bytes", : "bytes" },
],
: [],
: "nonpayable",
},
] as ;Inspect
Inspects are simpler than advances. The input, called query in this case, is just a Uint8Array with data.
Inspects can only yield reports, so it yields the reports data itself.
The example below shows a simple inspect request.
import { } from "@cartesi/machine";
import { } from "viem";
const = (".cartesi/image");
for (const of .(("test"))) {
.();
}
.();