The computation-hash test suite is a portable, manifest-driven integration test for the real
cartesi-machine.lua command-line interface. It builds deterministic machine and uarch fixtures,
generates stored-machine templates and CMIO inputs, executes the manifest from fresh template copies,
and validates the resulting computation hashes with both behavioral checks and independent
hash-tree reconstruction.
No machine.cpp behavior or reset semantics were changed for these tests.
- The Makefile-managed toolchain builds the machine and uarch assembly fixtures.
tests/lua/create-computation-hash-examples.luacreates stored-machine templates, binary CMIO inputs, and a portablemanifest.json.tests/lua/test-computation-hash-examples.luareads the manifest and invokes each CLI argv array from the artifact root.- The driver validates process results, diagnostics, terminal counters, and computation-hash files.
- Selected cases are checked with independently assembled hash trees, and equivalent execution modes are required to produce byte-identical roots.
The generated artifact has this layout:
tests/build/computation-hash-examples/
├── manifest.json
├── templates/
├── inputs/
└── results/
All paths stored in the manifest are relative to the artifact root, so the artifact remains relocatable. It contains no stored golden computation hashes. Hashes are produced by executing the manifest commands.
tests/machine/src/computation_hash.S implements a bare-metal CMIO guest controlled by a compact
binary command format. Each command contains:
- the
CHT1magic. - an action: accept, reject, exception, halt, or an unexpected manual yield.
- an automatic TX-output count.
- a manual-yield reason.
- the requested terminal mcycle.
- an optional action payload.
The guest can emit automatic TX outputs before its terminal action. It waits away from the final instruction tail, then jumps into a calibrated NOP sled so that the terminal HTIF store retires at the exact requested mcycle. The artifact generator probes this invariant before publishing normal or boundary cases.
After an accept or reject, the guest returns to its command loop so a case can process multiple
inputs. An accepted response declares a 32-byte output payload. The suite disables
check_outputs_merkle_root, because this bare-metal control guest does not compute the expected
outputs root. Output-root verification is outside the computation-hash scenarios' scope.
Two uarch programs exercise limits that would otherwise be impractical:
tests/uarch/computation-hash-near-limit.Shalts atUARCH_CYCLE_MAX - 1.tests/uarch/computation-hash-overflow.Sloops until collection reaches the exact uarch-overflow diagnostic.
The generator reads the stock uarch RAM image from a default machine and overlays each custom
program at offset UARCH_RAM_LENGTH // 2. The boundary template begins with its uarch PC at that
custom program.
The collector can therefore capture the custom boundary tail once, while an ordinary reset still
returns to the stock uarch entrypoint. A later rejected input reuses the separately captured tail.
This covers near-limit capture, reset, and rejection without adding a test hook or changing reset
behavior.
The generator asserts that the near-limit program halts at exactly UARCH_CYCLE_MAX - 1 without
changing mcycle.
tests/lua/create-computation-hash-examples.lua creates four templates:
normal.mcycle-boundary, starting close toMCYCLE_MAX.near-limit-uarch-tail.uarch-overflow-tail.
It writes each case's CMIO commands as binary input files, constructs the corresponding relative
cartesi-machine.lua argv array, and serializes the manifest with the existing cartesi.tojson.
Temporary custom uarch images used to build stored templates are removed afterward.
The manifest is a top-level array of cases. Each case includes a comment describing its expected hash structure, along with its level, template, inputs, collection geometry, command-line argv, expected outcome, equivalence relationship, and optional independent oracle. It currently contains 37 cases: 18 mcycle cases and 19 uarch cases.
The uarch collection period uses the smallest value whose epoch computation hash tree does not exceed height 63:
ROLLUP_LOG2_MAX_ADVANCE_STATES_PER_EPOCH + ROLLUP_LOG2_MAX_MCYCLES_PER_ADVANCE_STATE - 63.
In general, an epoch computation hash has
2^(ROLLUP_LOG2_MAX_ADVANCE_STATES_PER_EPOCH + ROLLUP_LOG2_MAX_MCYCLES_PER_ADVANCE_STATE - log2_period)
state hashes. The frontier represents this tree structurally without materializing its capacity.
The mcycle cases cover:
- an empty epoch and a single accepted input.
- local versus remote execution.
- multiple inputs and automatic TX outputs.
- rejection followed by acceptance and a final rejected input.
- exception, zero and nonzero halt, and unexpected manual-yield paths.
- non-fixed-point truncation.
- fixed points before the first sample, on a sample boundary, and inside a sampling period.
- bundled versus unbundled collection.
- a terminal fixed point at
MCYCLE_MAX - 1. - exact
MCYCLE_MAX, including overflow precedence.
The uarch cases cover:
- an empty epoch and a target input that is never processed.
- termination before the selected window.
- fixed points at the window start, first, middle, penultimate, and final positions.
- termination immediately after the window.
- automatic TX output within the selected window.
- accept, reject/revert, exception, halt, mcycle overflow, unexpected manual yield, and truncation.
- substitution of the captured revert-uarch tail.
- a captured tail ending at
UARCH_CYCLE_MAX - 1followed by stock reset and later rejection. - exact uarch overflow during boundary-tail collection.
- bundled versus unbundled uarch collection.
tests/lua/test-computation-hash-examples.lua reads manifest.json with the existing
cartesi.fromjson. It uses the repository's luaposix dependency to execute each case:
forkcreates a child process.- The child changes directory to the artifact root.
- Standard input is redirected from
/dev/null, and stdout and stderr are redirected to per-case files. execpinvokes the manifest argv directly, without passing the CLI arguments through a shell.- The parent uses
waitto obtain the exit status.
The runner checks every case for:
- the declared success or failure category.
- hash-file presence or absence.
- a 32-byte hash when one is expected.
- equality between the hash printed by the CLI and the stored binary hash.
- required literal diagnostics.
- the declared terminal mcycle.
Cases may be selected with COMPUTATION_HASH_CASE or excluded with COMPUTATION_HASH_SKIP.
The suite includes checks that do not merely compare two outputs from the computation-hash CLI path:
- Empty mcycle epoch: obtain the template's fixed-point state hash and repeatedly hash it with itself through the exported tree height.
- Small mcycle tree: run a machine independently to the sampling boundary and terminal fixed
point, then assemble and pad the epoch frontier with
cartesi.hash-tree. - Small uarch tree: collect uarch bundles separately, assemble every mcycle subtree, validate reset-ending hashes against independently executed mcycle states, and assemble the selected period root.
- Mcycle-overflow uarch window: collect the 255 real mcycle transitions up to
MCYCLE_MAXdirectly, verify the overflow fixed-point group is present, and independently assemble it with fixed-point padding across the remainder of the 512-mcycle window.
The manifest also declares equivalence pairs. The driver requires byte-identical roots for:
- local and remote execution of the same accepted input.
- bundled and unbundled mcycle collection.
- bundled and unbundled uarch collection.
The production changes are in src/cartesi-machine.lua, src/cartesi/hash-tree.lua, and
src/cartesi/util.lua.
The mcycle and uarch computation-hash collectors previously calculated the end of an input with ordinary addition:
mcycle + MAX_MCYCLES_PER_INPUTThat expression can wrap when an input starts close to MCYCLE_MAX. Both collectors now use the
existing usaturating_add, making the input boundary stop at MCYCLE_MAX. The uarch collector also
saturates the selected window's start and end at that input boundary, so a window crossing
MCYCLE_MAX retains its real transitions instead of wrapping and being mistaken for an empty
window. This is a production overflow correction exposed by the boundary scenarios, not a
test-specific reset mechanism.
The computation-hash CLI now warns when the requested epoch tree height exceeds 63, but it no
longer rejects that geometry. Uarch period-index validation handles the complete unsigned index
range when the tree contains at least 2^64 leaves. The hash-tree frontier checks available
capacity structurally, so tall trees do not require 2^height to fit in a Lua integer.
Numeric CLI parsing now accepts decimal values only through the maximum signed Lua integer and accepts hexadecimal values as full 64-bit bit patterns. It rejects literal and suffix-shift overflow with specific diagnostics before downstream unsigned arithmetic is attempted.
tests/Makefile provides targets to build the fixtures, generate the artifact, run the manifest,
and clean the generated files. Fixture compilation and Lua formatting/static checks use the
Makefile-managed toolchain as required by the workspace build policy.
From the repository root, the main commands are:
make build-tests-computation-hash-examples-with-toolchain
make test-computation-hash-examplestest-computation-hash-examples regenerates the artifact when its generator is newer than the
manifest. Missing fixture binaries still require the explicit toolchain-managed build command.
The suite is part of the aggregate test target and is also generated and run while CI builds the
tests image. A complete release-build run of all 37 cases remains below the plan's 60-second target
on the development host.
The completed implementation was validated with:
- fixture compilation and artifact generation through the managed toolchain target.
- Lua formatting, format checks, and
luacheckinsidetoolchain-exec. - all 37 manifest cases using the native Lua fork/exec runner.
- targeted independent mcycle, ordinary uarch, and mcycle-overflow uarch oracle cases.
git diff --check.