Handling Requests
A Cartesi application processes two kinds of requests:
- Advance — an input submitted on-chain. It can change application state and produce provable outputs (vouchers and notices).
- Inspect — an off-chain, read-only query. It can only answer with reports.
This page shows the request lifecycle; Emitting Outputs covers what you can produce while handling them.
The request cycle
Processing follows a strict cycle, inherited from how the machine and the blockchain interact:
- The application calls
finish, telling the machine whether it accepts or rejects the previous request. - The call blocks (the whole machine yields) until the next request arrives.
- The application handles the request, emitting outputs along the way.
- Back to step 1.
run implements this cycle; your handlers plug into step 3.
Advance requests
Advance handlers receive the decoded input metadata along with the payload:
import { } from "@cartesi/rollup";
const = new ();
await .({
(, ) {
.msgSenderInput sender address (0x-prefixed hex).
;
// every input is identified by its index and carries the L1 context
// it was included in: block number, timestamp and prevRandao
.(
.(
`input ${.} from ${.} ` +
`at block ${.} (chain ${.})`,
),
);
return true;
},
});Addresses arrive as 0x-prefixed hex strings, numeric fields as bigint, and the payload as a Buffer.
Inspect requests
Inspect handlers only get a payload — the query — and should answer with reports:
import { } from "@cartesi/rollup";
const = new ();
const = new <string, bigint>();
await .({
(, ) {
const = ..();
const = .() ?? 0n;
.(.(.()));
},
});Inside the machine, any state changes made while handling an inspect are reverted afterwards — inspects are queries, not transactions.
Accepting and rejecting
A handler's return value decides the input's fate:
trueorundefined(no return) — accept: outputs become part of the application's verifiable history.false— reject: the machine reverts to the state before the input; its outputs are discarded.- throwing — the exception is caught, emitted as a report (so you can see it from the outside), and the input is rejected.
import { } from "@cartesi/rollup";
const = new ();
await .({
(, ) {
if (.. === 0) {
return false; // reject empty inputs — state is rolled back
}
if (.. > 1024) {
// also rejects, and the message lands in a report
throw new ("payload too large");
}
.(.);
return true;
},
});These are the rules for the handler run calls. When that handler is a composition of several — see Composing Handlers — the ones inside it answer a narrower question, "did I claim this request?", and only the composition's answer decides the input's fate.
Async handlers
Handlers may be async — run awaits them before finishing the request:
import { } from "node:fs/promises";
import { } from "@cartesi/rollup";
const = new ();
await .({
async (, ) {
const = await ("/etc/app/config.json", "utf8");
.(.(`config loaded: ${.} bytes`));
return true;
},
});Remember that the machine is paused between requests, not within them: timers and I/O work normally while your handler runs, but nothing executes while the application waits for the next request.
Driving the loop yourself
run is a thin convenience. If you prefer explicit control — custom dispatch, graceful shutdown, request batching — call finish directly:
import { } from "@cartesi/rollup";
const = new ();
let = true;
for (;;) {
const request = .({ });
switch (.) {
case "advance":
.(.);
= true;
break;
case "inspect":
.(.);
= true;
break;
}
}The request is a discriminated union — narrowing on type gives you the right fields in each branch.
When the loop ends
run returns a Promise<void>, but under normal operation inside the machine the loop never ends — the promise simply never settles. It rejects when finish fails for a real reason, such as after close is called: let that propagate (the process exits) or catch it if you have cleanup to do.
It resolves in exactly one case: the host mock ran out of the inputs listed in CMT_INPUTS, the normal end of a test session. That is decided by the driver the binding was built against, not by the environment, so inside a real machine an exhaustion-looking errno is still an error.