BM-3091: feat(cli): default prover fulfill to the on-chain assessor - #2064
Merged
Conversation
The fulfill command's --assessor-selector flag now defaults to the on-chain assessor (0x00000022). In that mode the assessor guest is neither downloaded nor proven: only the order claims are aggregated, and the batch is sealed with an EIP-712 FulfillmentBatchAuth signature verified by the OnChainAssessor adapter, whose address is discovered through the market's router. Passing any other selector (e.g. 0x00000024) keeps the previous guest-based R0 assessor path.
jonastheis
requested review from
a team,
Wollac,
bobbobbio,
capossele,
nahoc,
unizarr,
willemolding and
zeroecco
as code owners
July 14, 2026 02:19
The open-path reveal is broadcast via a send() whose gas estimation simulates against the current head. On nodes where the pending tag aliases latest (op-geth), that is the commit's own block, where committedBlock + COMMIT_REVEAL_MIN_BLOCKS > block.number holds and the estimation reverts MissingFulfillmentCommitment before the reveal is ever broadcast. Awaiting the commit receipt only guarantees the reveal is mined in a later block, not that it is estimated against one. After recording the commitment, wait (bounded) for the chain head to pass the commit block so the estimation context satisfies the same condition the mined transaction will. Chains that only mine on demand (anvil) time out of the grace period and proceed; their estimation runs on a next-block env and passes anyway. Found live on Base Sepolia staging, where the race hit on every first attempt; verified fixed there via both the CLI fulfiller and the broker open-path submission.
…e head The unconditional post-commit wait burned its full grace period on chains that only mine on demand (anvil), delaying every open-path reveal by ~10s and handing the slasher's poll loop a guaranteed win over the reveal in test_slash_fulfilled - turning a pre-existing flaky race into a deterministic CI failure. Drop the wait and retry the reveal dispatch (bounded) when its gas estimation reverts MissingFulfillmentCommitment: nodes that estimate on a next-block env (anvil, L1 geth) pass on the first attempt with no added latency, while nodes where the pending tag aliases latest (op-geth) retry until the head passes the commit block - the case observed live on Base Sepolia staging.
capossele
approved these changes
Jul 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
boundless prover fulfillwas hardwired to the R0 zkVM assessor:--assessor-selectorwas a required flag and the assessor guest was always proven, even though the router-native market ships a cheaper native assessor. The flag now defaults to the on-chain assessor (0x00000022), bringing the CLI in line with the broker's preferred path (#2005, #2040). Any other selector (e.g.0x00000024) keeps the previous guest-based flow, so the old behavior remains one flag away.Main changes
AssessorModeenum onOrderFulfiller(crates/boundless-cli/src/lib.rs):R0 { selector }proves the assessor guest and seals with its set-inclusion proof (unchanged behavior);Onchain { selector, adapter, signer }skips the assessor guest entirely — no ELF download, no proof, order claims only in the set-builder aggregation — and seals the batch with an EIP-712FulfillmentBatchAuthsignature via the SDK'sbuild_onchain_assessor_seal.prover fulfill(commands/prover/fulfill.rs):--assessor-selectordefaults toONCHAIN_ASSESSOR_SELECTOR; on the default theOnChainAssessoradapter address is discovered through the market's router (router_entry_impl), with a clear error pointing at the R0 selector when the market has no on-chain assessor registered. The selected assessor is shown in the command output.boundless-ffi(unchanged behavior — its Forge deployment test pins the R0 selector explicitly), slasher and indexer test helpers.test_fulfill_onchain_assessor: asserts the seal isselector ‖ 65-byte signaturerecovering to the prover over the exact hashOnChainAssessorreconstructs, and covers the single-leaf aggregation with the assessor guest skipped.Seal construction per mode
Commit-reveal fix (SDK)
Live testing on Base Sepolia staging surfaced a client-side race in the open-path commit-reveal flow, fixed here in
boundless-market: the reveal's gas estimation simulates against the current head, and on nodes where the pending tag aliases latest (op-geth) that is the commit's own block —committedBlock + COMMIT_REVEAL_MIN_BLOCKS > block.numberholds and the estimation revertsMissingFulfillmentCommitmentbefore the reveal is ever broadcast. The fix retries the reveal dispatch (bounded, 15s) when its estimation revertsMissingFulfillmentCommitment: nodes that estimate on a next-block env (anvil, L1 geth) pass on the first attempt with no added latency, while op-geth-style nodes retry until the head passes the commit block. Verified live on Base staging (estimation revert observed, one 500ms retry, reveal landed two blocks after the commit) and against the anvil test suite (no timing impact — an earlier pre-wait variant of this fix deterministically lost thetest_slash_fulfilledrace to the slasher's poll loop, which is why the retry design won). This affects every open-path submitter — the CLI fulfiller and the broker alike.Live validation (Base Sepolia staging, market
0x7abb…ca8e)Ran the T6 cells of
docs/2026-07-09_staging-router-proof-matrix-runbook.mdagainst the shared staging market:00000022(sig)0x8319…08230x1723…b756--assessor-selector 0x0000002400000024(STARK guest)0xc8d0…27ad0xdead…e0fd00000022(sig)0xac3e…f9070x2b42…29a1Every reveal landed ≥ 1 block after its commit, and replaying any reveal's exact calldata reverts
MissingFulfillmentCommitment(0x4b46580b).Related: #1982 (router decoupling, migrated the CLI to the batched ABI), #2005 (OnChainAssessor), #2040 (broker assessor selection), #2060 (Digest wire format, validated live by the second cell).