Consensus Mechanics#
This page documents how the GenVM implementation realises the leader/validator execution split sketched in Sub-VM Architecture. The actual consensus algorithm (block production, voting, finality) is owned by the node, not by GenVM; what follows is the GenVM-side machinery that produces the artifacts the node consumes.
Roles and the is_leader flag#
A GenVM run is either a leader run or a validator run, decided once when the
node starts the run. The supervisor exposes this as
Supervisor::is_leader() (executor/src/rt/supervisor/mod.rs:209); the flag is
derived from whether the node has handed in a vector of leader non-deterministic
results:
Leader run:
leader_nondet_results == None. EveryRunNondetcall executes the leader sub-program and the result is pushed into the supervisor’snondet_resultsvector, indexed bycall_no.Validator run:
leader_nondet_results == Some(vec).RunNondetretrieves the leader’s result atcall_nofrom this vector and feeds it back to the contract as the second argument of the non-det block. If the leader produced fewer entries than the validator demands, the run aborts withVmError::absent_leader_nondet_output.
call_no is a monotonically increasing counter incremented per RunNondet
invocation. The hard cap is public_abi::top_limits::NONDET_BLOCKS (4096);
exceeding it produces VmError::oom().ram().limit(). The counter is what binds
a leader’s i-th non-det result to the validator’s i-th non-det check — the
ordering of RunNondet calls in deterministic code MUST match between leader
and validator, otherwise the contract is non-replayable.
Leader Output Format#
Each entry in the leader’s nondet_results vector is a single ResultCode
byte (the public_abi::ResultCode enum: Return, UserError, VmError)
followed by an encoded payload:
Return— raw bytes returned by the non-det block.UserError— either a calldata-encoded value (when the first four bytes are zero, indicating a calldata prefix) or a UTF-8 string fallback.VmError— UTF-8 error code from Constantsvm_error.
The validator reconstructs an rt::vm::RunOk from these bytes
(genlayer_sdk.rs:1502) and the contract observes the same value as the leader did.
Validator Comparison#
GenVM itself does not run the agreement algorithm. After the validator finishes
executing the non-det block over the leader’s result, it returns its own
ResultCode/payload pair to the host. The host (or a Lua greybox script — see
Greyboxing Documentation) compares the two and decides whether to
emit an “agree” or “disagree” vote.
For prompt-template calls (EqComparative /
EqNonComparativeLeader / EqNonComparativeValidator) the comparison is
performed inside the LLM module’s Lua entry point: the validator script receives
the leader’s answer and the validator’s own answer (or context) and returns a
boolean. The boolean is the agreement vote. See
Greyboxing Documentation “Template Contracts”.
For raw RunNondet blocks, agreement is whatever the contract code chooses to
return from its validator function — typically a boolean.
Disagreement and Timeouts#
A validator that produces a different result than the leader simply returns it; no GenVM-internal mechanism flags the divergence. The node decides what to do with the disparate votes.
Timeouts are enforced two layers above GenVM:
The manager kills a GenVM child process when its overall session exceeds the configured budget.
The fee evaluator (see Fees, Buckets and Expressions) drains the relevant bucket and causes the next
consume_*call to fail with an OOM-class error.
Anti-Cheating#
The error fingerprinting mechanism described in Sub-VM Architecture “Error Fingerprinting” is what prevents a validator from voting “agree” without doing the work: any error result is bundled with a BLAKE3 hash of the WASM memory at the failure point, and a node fabricating errors without execution cannot produce a matching hash. There is no analogous fingerprint for the successful path — agreement on successful non-det results relies on the validator actually re-running the contract under the leader’s substituted values, which is what the GenVM run-loop forces it to do.