Skip to content

Fix Dave QA findings - #303

Merged
stephenctw merged 7 commits into
mainfrom
fix/qa-report-findings
Oct 29, 2026
Merged

stephenctw merged 7 commits into
mainfrom
fix/qa-report-findings

Conversation

@stephenctw

@stephenctw stephenctw commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

DaveConsensus could seal more inputs than its computation can represent and later report the unprocessed tail finalized. DaveAppFactory derived the application address from the application parameters alone, so newDaveApp called with a victim's parameters and other consensus settings could put a live application, behind an attacker-chosen DaveConsensus, at the victim's predicted address. This PR caps seals at the input span, leaves excess inputs pending, and binds the application address to the full consensus settings. It does not close the shared-factory squat: anyone can still deploy a validator-less Application at a predicted address through the shared ApplicationFactory, which makes newDaveApp revert, so send value to a predicted address only after DaveAppCreated. That path waits on the caller-reserved entrypoint in cartesi/rollups-contracts#561 (docs/todo.md, Before mainnet funds).

On main the node never read foreclosure: it kept defending, recovering bonds and planning settlement, and never stopped. Once the dispute has a result, it now reads foreclosure and skips sentry claims, staging and acceptance while foreclosure is visible. Dispute moves and bond recovery continue, and it exits 30 only after recovery completes and foreclosure is finalized. It also pages after committing an input that makes the application terminal, and checks a candidate against the standing already read, without an extra RPC.

This is a new deployment generation. Both contract changes move deterministic addresses; ApplicationCreated is still emitted by the shared ApplicationFactory. The node requires a capped DaveConsensus. Earlier uncapped deployments are unsupported: the node stalls at the first seal past the span. ABI selectors are unchanged.

A halt or exception is a valid transition: the node defends the true state, wins and holds it, and the guardian forecloses the application. PRT can finish normally even though DaveConsensus cannot settle that state. Foreclosure must never abandon an active dispute.

Finding dispositions

"alpha.6" refers to #287 and #299. #301 is on main but unreleased. Existing fixes are not credited to this PR.

Finding Status on main before this PR This PR
SLN-3 Fixed in alpha.6: defend terminal epochs and never stage them Real-contract regression across restart, plus a halt page after the input batch commits
SLN-4 Gas burn fixed in alpha.6 by estimation; never stopping was documented policy Scoped foreclosure handling that preserves defense and paid bond recovery before exit
SLN-38 Fixed in alpha.6: index, bound and pinned-count checks No new fix
SLN-8 Fixed in alpha.6: estimate and skip reverting calls; inclusion races remain No new fix
SLN-37 Fixed in alpha.6: estimates, funding errors and paging No new fix
SLN-56 Fixed in alpha.6: split any failed multi-block log query and page on stalled progress Keep main's rule; add a provider-code regression including Alchemy's -32600 range cap
CTR-1 Open Partially fixed: the application salt binds every consensus setting, closing the different-settings path through newDaveApp. The shared-factory squat stays open pending cartesi/rollups-contracts#561 (todo, Before mainnet funds); no value before DaveAppCreated. New deployment generation.
PRT-1 Accepted on cost Reverse that decision: enforce the input span structurally and preserve unprocessed inputs for later epochs
SEAM-2 Dave follows LibMachineValidityProof; payload-read fix is in #301 One real-contract regression; reference-node alignment remains cross-project
SLN-6 Fixed in alpha.6: retain and prune the recovery tree No new fix
SLN-9 Reference-node-only historical-state RPC requirement Cross-project follow-up only
SLN-15 Documented policy in dimensioning.md: child finality fits within T + G while finality stays within T; only excess and root-join finality draw on C No new fix
SLN-16 Fixed in alpha.6: boot errors exit 1 without panicking No new fix
SLN-39 Fixed in alpha.6: startup errors propagate and exit 1 No new fix
SLN-44 Fixed in alpha.6: 12 s Arbitrum clock entries, marked experimental No new fix
SLN-46 Fixed in alpha.6: classify bare underpriced errors and retry once at the maximum tip No new fix
SLN-59 Fixed in #301: sweep staging before seeding Add the published-boundary assertion to main's existing recovery test
SEAM-4 Dave, Lua and docs have followed the step since alpha.6 No new Dave fix; reference-node alignment remains cross-project
SLN-17 Fixed in alpha.6: refuse logs without block metadata No new fix
SLN-42 Rust and Lua input-span overrun was accepted on cost Cap and coordinated Rust/Lua handling; Go rollover still needs a cross-project change
SLN-60 Restart recovery fixed in alpha.6; in-process completeness was a trusted-RPC assumption Defense in depth using the existing standing read; a contradiction discards and refolds the cache

Retain the rejected dispositions for SLN-5, SLN-7, SLN-10, SLN-11, SLN-18, SLN-41, SLN-43 and SLN-45, and the informational dispositions for SLN-12 through SLN-14.

Cross-project changes required

No reference-node implementation or patch is included in this PR. These items remain open for the rollups-node team:

Finding Required reference-node work
SEAM-2 Align accepted-yield lengths other than 32 and unknown automatic notifications. Coordinate continued defense when the declared outputs root differs from reconstructed outputs.
SEAM-4 Treat a pending RX_ACCEPTED at mcycle == imcyclemax as awaiting input, as the step does.
SLN-9 Document the historical-state/archive RPC prerequisite for catch-up.
PRT-1 / SLN-42 Select each sealed epoch's inputs by its index bounds, including inputs carried from earlier blocks. The alpha.13 reader starts at the previous seal block, so catch-up stalls after a capped seal. Misfiling during continuous operation remains a lead, not an end-to-end reproduction.

Validation and limits

  • just check passed: formatting, linting, Clippy, 447 PRT contract tests, 19 rollups contract tests, 62 Lua tests, 319 node unit tests and the rest of the Rust workspace.
  • All 32 node-harness tests passed after rebuilding the three-level devnet, including foreclosure during a live dispute, paid bond recovery, restart, a halted guest and an oversized accepted yield.
  • Mutation checks confirmed that removing the finalized foreclosure read fails the foreclosure regression, and emitting the uncapped input count fails both capacity tests. Restored production code passes again.
  • The capacity tests check storage and emitted EpochSealed bounds at construction and acceptance, backlog rollover and the exact input span. The reader rejects both late uncapped seals and oversized seals in the same chunk as their inputs. Factory tests pin consensus-settings binding: different settings, the identical pair from another caller and fuzzed settings. The foreclosure regression recovers the bond before foreclosure and checks that latest-only foreclosure does not exit.

The input cap reverses the previous cost-based acceptance because wasInputFinalized must not finalize inputs the step cannot feed. Pricing remains against the reference price: a full cap of fillers buys backlog delay for about 5.06e11 gas, roughly 3.9-5.6 times that price. The cap bounds a seal's runner work but not its drain on C, which the delay ledger counts. The spill wait draws nothing from C, and the delay it adds is recorded as a door. Linux measurement and re-deriving the reference price remain follow-ups.

Not validated: live-provider fault injection, live Arbitrum cadence, a live mempool front-running race, a full 2^24-input flood, e2e smoke, a simultaneous Go/Dave stack, or the new lifecycle harness cases on canonical two-level geometry. Contract cap tests mock input counts. The harness uses the three-level devnet. The candidate guard's pure projection and refold are tested; its context call-site has no direct regression, accepted as a guard against provider behavior outside the trust model.

Gas estimation cannot eliminate inclusion races. The node still trusts its RPC provider, including log completeness. A foreclosure racing read and inclusion can cost one transaction's gas; a node between epochs may join the next root and wait out its allowance before exiting. A fresh directory replays history, and an eliminated commitment in an abandoned root can keep waiting indefinitely.

@stephenctw
stephenctw force-pushed the fix/qa-report-findings branch from 4dc351d to 7419c8c Compare October 20, 2026 13:07
@stephenctw
stephenctw requested a review from GCdePaula October 20, 2026 13:08
@stephenctw stephenctw self-assigned this Oct 20, 2026
@stephenctw

Copy link
Copy Markdown
Member Author

@jplgarcia I can't add you as a reviewer, but your feedback is very valuable to us.

@stephenctw
stephenctw force-pushed the fix/qa-report-findings branch from 7419c8c to e4ba98d Compare October 22, 2026 14:54
@stephenctw
stephenctw force-pushed the fix/qa-report-findings branch from 91c58f1 to a6748d4 Compare October 24, 2026 02:33
@stephenctw stephenctw changed the title fix: address Dave QA report findings fix: harden epoch sealing, deployment and foreclosure handling Oct 24, 2026
@stephenctw
stephenctw force-pushed the fix/qa-report-findings branch from a6748d4 to 8b68486 Compare October 26, 2026 13:27
@stephenctw stephenctw changed the title fix: harden epoch sealing, deployment and foreclosure handling Fix Dave QA findings Oct 27, 2026
Uncapped acceptance could finalize inputs the state transition cannot feed.
Cap construction and acceptance at the Machine input span, preserving
excess inputs for later epochs and finalizing only processed inputs.
Check the emitted EpochSealed bounds as well as storage.

Split pending input windows at the same boundary in Rust, and reject seals
past that span with the capped-consensus requirement. Lua selects inputs
by sealed bounds and refuses an epoch past the span; it does not split.
Go rollover must select inputs by sealed index bounds across block ranges.

Keep the single delay ledger and reference price explicit: capped work
can still drain C, while spill-epoch queue waits do not. Re-derive the
price below the throughput threshold after Linux qualification.

Addresses move. Earlier uncapped deployments are unsupported and stall
at the first seal past the span. Addresses PRT-1 and SLN-42.
A caller could use newDaveApp with a victim's application parameters and
attacker-chosen consensus settings to occupy the predicted app address.
Bind the salt to every consensus setting in both deploy and predict.

Retain the shared ApplicationFactory. Its direct-deployment squatting
path remains open pending the reserved API in cartesi/rollups-contracts#561;
do not send value before DaveAppCreated. If no release ships it before
mainnet funds or the audit scope freeze, choose between waiting and
creating Application directly.

Test different settings, the identical pair and fuzzed address binding.
Predicted addresses move. Partially addresses CTR-1.
The underlying terminal-epoch, HTIF-read and startup-sweep fixes are
already on main. Pin their behavior without claiming new fixes: defend a
halted guest through a leaf proof across restarts, stage an oversized
yield through settlement, and assert that initial seeding publishes its
boundary after sweeping stale staging.

Share the tiny guest builder with the engine tests and use one
nonstandard-yield world. Keep the existing split-any-error RPC policy
under provider range codes, including Alchemy's -32600.

Covers SLN-3, SEAM-2, SLN-59 and the SLN-56 regression.
Main kept defending through foreclosure but never stopped. Read
foreclosure once the dispute has a result, suppress settlement while
visible, and exit 30 only after defense and bond recovery are complete
and foreclosure is finalized. Restarting resumes the same obligations.

Keep Hero and recovery active. Enable the harness guardian and test
foreclosure during a live dispute and after staging. Recover the bond
before foreclosure in the latter case, so latest-only foreclosure
cannot be mistaken for a finalized exit. Pin the exit across restart.

Addresses the remaining SLN-4 behavior.
Waiting for the terminal epoch's staging error can delay the guardian's
first page by a root allowance while users keep sending deposits.

Detect the transition from awaiting input to a terminal state and emit
one ERROR after its batch commits. Retried or abandoned batches do not
page twice; later inputs in the absorbing state do not page again.
Continue defending the true state and ask the guardian to foreclose.

The real-runner test pins the first awaiting-to-terminal transition the
guard reads, including the absorbing state on a later input; it does not
capture the page or test retries. An open batch can still wait for its
snapshot gap or the seal. Relates to SLN-3.
An omitted pairing can leave the event-derived candidate idle while its
match clock runs. Detect that contradiction only under active matches,
where waiting can lose defense, using the standing already read at the
same head rather than another RPC.

Retain the active candidate in the projection and propagate a typed
mismatch so the reader discards and refolds its cache. Logs remain a
trusted provider property; this is defense in depth for SLN-60.
Release-list discovery reported the published pinned just version missing
and stopped CI before tests. Fetch the exact Linux assets for both host
architectures and verify their checksums before exposing the executable.

Keep the version pinned: floating latest silently changes CI's toolchain.
The native installer and both archive checksums were verified locally.
@stephenctw
stephenctw force-pushed the fix/qa-report-findings branch from 8b68486 to 2e48dcb Compare October 27, 2026 12:00
@GCdePaula
GCdePaula requested a review from guidanoli October 27, 2026 16:54

@GCdePaula GCdePaula left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Let's wait for @guidanoli to give a final review too on the smart contracts.

@guidanoli guidanoli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contracts look good to me!

@stephenctw
stephenctw merged commit 7eb6c6b into main Oct 29, 2026
9 checks passed
@stephenctw
stephenctw deleted the fix/qa-report-findings branch October 29, 2026 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants