Skip to content

fix: allow a witness-less RpcClient and type the unsatisfiable-range failure - #199

Draft
flyq wants to merge 1 commit into
liquan/perf/witness-state-read-allocationsfrom
liquan/fix/empty-witness-endpoints-and-typed-range-error
Draft

fix: allow a witness-less RpcClient and type the unsatisfiable-range failure#199
flyq wants to merge 1 commit into
liquan/perf/witness-state-read-allocationsfrom
liquan/fix/empty-witness-endpoints-and-typed-range-error

Conversation

@flyq

@flyq flyq commented Sep 3, 2026

Copy link
Copy Markdown
Member

Summary

PR 6/6 of the #170 split, stacked on #198. The validator's R2 witness mode stops handing the data endpoints to RpcClient as placeholder witness endpoints, and the caller-contract assert! that guarded the witness provider range becomes a typed error.

vincent's review of #170: "It is also strictly better than the old 'hand it the data endpoints as a placeholder' behavior, which silently pointed witness calls at the wrong endpoints. Still worth being deliberate about assert! vs a typed error in a validator that is expected to stay up."

The change

  • RpcClient::new_with_config accepts an empty witness_apis. In --witness-source r2 the client now carries no witness providers at all, instead of a copy of --rpc-endpoint.
  • witness_round_robin's assert! becomes WitnessFetchError::NoProviderInRange { skip, configured }, following the shape CodeFetchError already uses in this file (#[error(transparent)] Deadline(#[from] RpcDeadlineExceeded) plus the domain variant).

Being deliberate about it

One variant covers both failures, and that is the point. skip..len is unsatisfiable either because a caller's skip ran past the configured endpoints (a routing bug) or because there are no endpoints at all (0..0, the witness-less deployment). Both are wiring failures — neither is a transport condition — so both leave as the same typed error.

Why not keep the assert!. A routing bug in the trace server's witness-skip computation would take the process down on a request path. As a typed error it fails one request, and the reason label says which.

Why an empty witness list is nevertheless not a new runtime hazard. Both binaries already reject the configuration at startup: the validator errors on --witness-source rpc with no --witness-endpoint (app.rs), and the trace server's clap marks --witness-endpoint required_unless_present = "witness_generator_endpoint". Relaxing the constructor loses no startup diagnostic — it only stops forcing R2 mode to lie about where witnesses come from.

What deliberately did not change. Only get_witness_light_with_deadline_from — the one method taking a caller-computed range — returns WitnessFetchError. The full-range methods (get_witness_with_deadline, get_witness_light_with_deadline) keep RpcDeadlineExceeded; they pass 0..len, where the range variant is unreachable, and threading the enum through them would have rippled into the trace server's error classification for no behavioural gain. get_witness_light_first_provider_only stays infallible with a pinned 0..1, its .expect naming the startup contract — the same idiom as the .expect("None deadline cannot time out") beside it.

Trace-server classification

From<WitnessFetchError> for DataProviderError sends Deadline down the existing method-based path and NoProviderInRange to Internal. That distinction matters operationally: Timeout { Witness } feeds the deadline_witness error reason, whose entire value is meaning "an upstream witness fetch ran out of budget" — a wiring bug landing there would page for the wrong incident. witness_range_failure_is_internal_not_a_witness_timeout pins both halves.

Testing

cargo fmt --all --check, cargo clippy --workspace --all-targets --all-features (0 warnings), cargo sort --check, full workspace suite 483 passed / 0 failed, cargo test -p stateless-core --no-default-features --lib --no-run clean.

New: witness_fetch_out_of_range_returns_a_typed_error (both the out-of-range skip and the witness-less client) and witness_range_failure_is_internal_not_a_witness_timeout; the constructor test now pins that an empty witness list constructs. test_witness_fetch_skip_of_all_providers_panics is removed — superseded by the typed-error test over the same call. Both new tests were mutation-checked (disabling the range check, and misrouting the variant to Timeout { Witness }, each kill their test).

Notes

Closes the last of the #170 split. TODO-A-2 in the internal ledger is satisfied by the typed error.

…failure

R2 mode stops handing the data endpoints to the client as placeholder witness
endpoints, and an unsatisfiable witness provider range fails one request as
`WitnessFetchError::NoProviderInRange` instead of panicking the process.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CHVpMMX9N69sUNbuKgpBVY
@mega-maxwell

mega-maxwell Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude review status

Living comment — rewritten in place. The review workflow keeps this single comment up to date instead of posting a new one each round, so it always describes the latest reviewed commit and the earlier text is intentionally gone. No reply is needed here; reply to a finding in its own review thread, and answer an open question in a reply on this PR. The next review round reconciles your answer.

✅ Review clean

Last reviewed: head ae09a4f9 · updated 2026-09-03T11:47:33+00:00

New this round: 0 finding(s), 0 question(s) · Resolved this round: 0 · Open questions: 0

@claude

claude Bot commented Sep 3, 2026

Copy link
Copy Markdown

This PR has no labels. Given the diff (replacing an assert! that could panic the process with a typed WitnessFetchError::NoProviderInRange, plus the fix: commit type), the bug label looks appropriate here — please add it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae09a4f9a2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1644 to +1647
WitnessFetchError::NoProviderInRange { skip, configured } => unreachable!(
"full-range witness fetch on a client with no witness providers \
(skip={skip}, configured={configured})"
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return NoProviderInRange from every witness fetch API

When RpcClient is constructed with the now-supported empty witness list, both get_witness_with_deadline and get_witness_light_with_deadline pass NoProviderInRange to this unreachable!, while their unbounded wrappers and get_witness_light_first_provider_only panic through expect. This is reachable for the validator's fallback-less R2 configuration and contradicts the constructor documentation that a witness call returns the typed error; an accidental call therefore still takes down its task/process instead of failing structurally. Propagate WitnessFetchError through all witness-fetch APIs, or otherwise make the witness-less state impossible for APIs that cannot return it.

AGENTS.md reference: AGENTS.md:L154-L154

Useful? React with 👍 / 👎.

@flyq
flyq marked this pull request as draft September 4, 2026 00:28
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.

1 participant