Repository navigation
perf(platform-wallet): prove shield bundles while fetching nonces - #5279
PastaPastaPasta wants to merge 3 commits into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
🕓 Review not started yet because this PR is a draft.
Commit b57379b. Normal review starts when eligible; priority review starts as soon as a slot is available. |
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Phase 1 + Phase 2
The changes preserve the existing funding-binding rules and signing order while overlapping proof generation with nonce fetching; no blocking correctness, security, consensus, or layering issues were found. One non-blocking test gap remains in the newly added post-fetch binding-change retry. Verification was static: the supplied CI snapshot shows successful Kotlin SDK checks but no dedicated DPP or platform-wallet Rust-suite results.
🟡 1 suggestion(s)
Review provenance
Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 5: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 7: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — The intricate split in shield.rs and shield_from_identity.rs changes cryptographic proof construction and binding checks, while operations.rs changes funds-moving shield sends to prove concurrently with nonce fetching and re-prove when protocol bindings change. - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— rust-quality (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-3.8-flash-high(antigravity below 15% reserve: weekly 15% left, 5h 100% left),glm-5.3-flash(not used above high effort; tier asks max) - Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
- Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— architecture-layering (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— platform-versioning (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— security-auditor (completed, effort xhigh); agentphase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-platform-wallet/src/wallet/shielded/operations.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/shielded/operations.rs:683-688: Exercise the post-fetch binding-change retry
Neither the new DPP binding tests nor the fetch/prove overlap tests execute this retry or its ShieldFromIdentity counterpart at lines 1062–1066. Those tests would still pass if the retry were removed, even though a nonce fetch that advances the SDK across the binding activation would then cause assembly to fail. Add deterministic coverage for unchanged binding, changed binding with a replacement proof using the post-fetch version, and replacement-proof failure before signing or broadcast. Assert that the retry reuses the fetched nonce and does not fetch it again: get_identity_nonce(..., true, ...) advances the nonce cache. If the SDK mock cannot drive the version change, expose a small test seam for the version getter and proving closure rather than requiring a network-based test.
| if !proved | ||
| .is_bound_for(&inputs_with_nonce, platform_version) | ||
| .map_err(|e| PlatformWalletError::ShieldedBuildError(e.to_string()))? | ||
| { | ||
| proved = prove_at(platform_version).await?; | ||
| } |
There was a problem hiding this comment.
🟡 Suggestion: Exercise the post-fetch binding-change retry
Neither the new DPP binding tests nor the fetch/prove overlap tests execute this retry or its ShieldFromIdentity counterpart at lines 1062–1066. Those tests would still pass if the retry were removed, even though a nonce fetch that advances the SDK across the binding activation would then cause assembly to fail. Add deterministic coverage for unchanged binding, changed binding with a replacement proof using the post-fetch version, and replacement-proof failure before signing or broadcast. Assert that the retry reuses the fetched nonce and does not fetch it again: get_identity_nonce(..., true, ...) advances the nonce cache. If the SDK mock cannot drive the version change, expose a small test seam for the version getter and proving closure rather than requiring a network-based test.
source: gpt-6.1-sol (phase2-reviewer: rust-quality)
There was a problem hiding this comment.
Partly addressed in a59c644. The re-prove step on both paths (here and the ShieldFromIdentity one) now goes through reprove_if_unbound in prover.rs, which has four unit tests:
- a still-bound proof is kept and not re-proved;
- a stale proof is re-proved exactly once;
- a re-prove error is returned;
- a binding-check error is returned without re-proving.
All of these errors happen before signing or broadcast. The helper takes no fetch, so the retry can only reuse the nonce that was already fetched. It cannot call get_identity_nonce(.., true, ..) a second time.
What is still not covered is an end-to-end run where the SDK's version changes partway through a fetch. The mock SDK can't drive that, and adding an SDK-version seam just for this test isn't worth the extra code in this PR. The safety property doesn't rely on the wallet anyway: DPP assembly recomputes the binding and refuses a mismatch, and DPP tests cover that. So the worst case if this step regressed is a failed send at the version boundary, never a wrongly bound transition.
🤖 Posted autonomously by Claude on behalf of pasta.
a59c644 to
4f75009
Compare
Add `prove_shield_bundle` / `build_shield_transition_from_proved_bundle` and `prove_shield_from_identity_bundle` / `build_shield_from_identity_transition_from_proved_bundle`, so a client can prove the Orchard bundle before it knows the input or identity nonces. The bundle's binding signature commits only to the funding addresses (Shield) or identity id (ShieldFromIdentity); nonces and per-input amounts are committed by the witnesses / identity signature over the assembled transition. A proved bundle records the extra sighash data it was bound with, and assembly recomputes it from the real inputs / identity and protocol version, refusing a mismatch (`is_bound_for`). The existing `build_shield_transition` and `build_shield_from_identity_transition` now delegate to the two steps; their output is unchanged, and the fee-strategy check still runs before proving. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Shield-from-addresses fetched the input nonces/balances and shield-from-identity fetched the identity nonce before starting the proof. The bundle does not depend on either, so prove on the blocking pool while the fetch runs (`fetch_while_proving`) and assemble/sign once both are done. Error precedence matches the old sequential order: a fetch error (e.g. insufficient balance) returns as soon as it is known and the in-flight proof is discarded; a proof error surfaces only after the fetch succeeded. Because the fetch can advance the SDK's protocol version, assembly uses the version current after the fetch and the bundle is re-proved if its binding no longer matches. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Move the "re-prove if the binding changed during the nonce fetch" step from both shield send paths into `reprove_if_unbound`, and unit-test it. A still-bound proof is kept. A stale one is re-proved exactly once. An error from the binding check or the re-prove is returned before anything is signed. The helper takes no fetch, so the fetched nonces are reused, not fetched again. Behavior is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
180b997 to
797c1aa
Compare
4f75009 to
b57379b
Compare
Issue being fixed or feature implemented
Shield sends (Platform address → Shielded, and Identity → Shielded) fetched input nonces over the network before starting the Orchard proof. That added a full round trip in series with a multi-second proof on a phone.
The Orchard bundle doesn't depend on those nonces:
shield_funding_digest_v0,rs-dpp/src/shielded/sighash.rs:791-819). Consensus rebuilds exactly that from the transition (shielded_proof.rs:1239). Nonces and per-input amounts never enter it.shielded_proof.rs:1255) and the block path (transform_into_action/v0:104).(0,0)placeholder nonces and then validate with real ones (shield/tests.rs:136-147).What was done?
shielded-clientfeature only; no validation or serialization change):prove_shield_bundle/build_shield_transition_from_proved_bundle, andprove_shield_from_identity_bundle/build_shield_from_identity_transition_from_proved_bundle.is_bound_for).ProvedShield*Bundlefields are private, so callers can't skip the check.build_shield_transition/build_shield_from_identity_transitiondelegate to the new pair, with the same calls in the same order, so their output is unchanged.fetch_while_proving, proof on the blocking pool from perf(platform-wallet)!: build the Orchard proving key once off the async runtime and prove off tokio workers #5278).credit_pool_bundle_binding). Assembly therefore uses the post-fetch version and re-proves if the binding changed. The DPP assembly check refuses any mismatch.get_identity_nonce(bump = true)still runs to completion, and the unresolved-debit check still runs first.Saves about one network round trip (identity-nonce or address-nonce fetch with proof verification) per shield send.
How Has This Been Tested?
cargo test -p dpp --features shielded-client,state-transition-signing --lib -- shielded::builder::shield: 21 pass, including:fetch_while_provingordering and error precedence on a paused clock, plus a deterministic overlap test that uses channels and a real blocking proof.platform-wallet+platform-wallet-ffisuites withshieldedpass (1401 + 426 lib tests). Clippy-D warnings(wallet, FFI, dpp withshielded-client) andcargo fmt --checkare clean.cargo check -p dpp/-p platform-walletwith default (server-like) features is clean; the split code isn't compiled there.reprove_if_unbound. Not tested end to end: a version change during a live fetch, because the mock SDK can't drive one. The DPP assembly check that backs it is tested.Breaking Changes
None. Additive DPP client API; the existing builders keep their signatures and output.
Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull requestFor repository code-owners and collaborators only
🤖 Generated with Claude Code