test(platform-wallet): shielded (Orchard) e2e suite — spec + Wave H harness#3727
Draft
lklimek wants to merge 9 commits into
Draft
test(platform-wallet): shielded (Orchard) e2e suite — spec + Wave H harness#3727lklimek wants to merge 9 commits into
lklimek wants to merge 9 commits into
Conversation
…ification Verified the proposed shielded (Orchard) test cases against the MERGED v3.1-dev feat tree and applied user-approved full scope: - Found-027 (InMemory witness Err) STILL LIVE — SH-005 stays red-by-design. - Found-028 (shielded_add_account skips coordinator.register_wallet) STILL LIVE — SH-006 stays red-by-design. - Found-029 (pre-bind notes unwitnessable) FIXED by #3603 (sync.rs marks every commitment position; verified sync.rs:291-310). Dropped as a red pin; SH-007 repurposed into a GREEN regression guard locking in the fix. - Found-030 (anchor-semantics doc drift) STILL LIVE — SH-030 doc note. - Coupling recorded: Found-027 (in-memory witness) is independent of #3603; the fix only helps the FileBacked path, which all spend-side SH cases use. - SH-018 (Type 18 shield-from-asset-lock) and SH-019 (Type 19 withdraw to L1) un-deferred to P1, gated on a new Core-L1 harness requirement (asset-lock funding + Layer-1 payout observation); may run RED until plumbing lands. - Wave H gains a best-effort + logged teardown shielded fund-sweep (unshield residual balance back to the bank platform address) to prevent bank-fund leak; RED-by-design / broken-witness cases must NOT fail teardown. Changelog, §2 matrix, quick index, Found-NNN table, §4 Wave H, §5 register all updated. Tally: 2 HIGH live (027, 028) + 1 LOW (030) = 3 live findings + 1 guarded-fix regression test (SH-007/Found-029). Spec only — no test implementation, no production code touched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
…H-020..SH-035)
Rewrites the suite's stated purpose: attempt to BREAK THE BACKEND (Drive
consensus / state-transition validation + Orchard proof verifier), not confirm
happy paths. Adds 16 adversarial cases, each asserting backend rejection / safe
behavior; a RED is the deliverable (proves a malformed transition was accepted
or mishandled).
Cases: SH-020 double-spend, SH-021 nullifier replay after restart, SH-022 value
not conserved, SH-023 fee underpayment, SH-024 u64/i64 boundary, SH-025 forged
proof, SH-026 anchor mismatch (Found-030 dynamic probe), SH-027 malformed note
serde, SH-028 interrupt-sync, SH-029 reorg/out-of-order/rescan-from-0, SH-030
cross-network/own-address/self-transfer, SH-031 rebind-different-seed, SH-032
exact-change boundary, SH-033 intra-bundle duplicate nullifier, SH-034 tampered
binding sig, SH-035 replayed asset-lock proof. Consensus-critical attacks
(020/022/025/033/034/035) re-ranked P0/P1, CRITICAL-if-they-fail.
Methodology: client-side wallet guards must NOT mask the backend test —
[INJECT]-marked cases construct/mutate transitions at the protocol boundary
(public dpp::shielded::builder build_*_transition -> mutable SerializedBundle
{anchor,proof,value_balance,binding_signature} -> BroadcastStateTransition) and
broadcast directly, bypassing PlatformWallet::shielded_* guards.
Wave H gains an adversarial injection hooks block (raw build/broadcast, bundle-
byte mutation, TamperingProver, build-against-known-note, store-seed-malformed-
note, scriptable mock sync source, asset-lock reuse) behind a
PLATFORM_WALLET_E2E_SHIELDED_ADVERSARIAL gate.
Changelog, SH intent note, quick index, Wave H updated. Spec only — no test
implementation, no production code touched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…o test/rs-platform-wallet-shielded-e2e
…wait, sweep, inject hooks) Adds the framework/shielded.rs module unlocking the SH (Orchard) area: - shielded_prover(): process-wide warmed CachedOrchardProver behind the prover module's OnceLock — warm once, borrow &'static everywhere. - bind_shielded(): per-test FileBacked NetworkShieldedCoordinator over a fresh per-call SQLite path under the workdir slot, plus a ShieldedHandle (sync(true) driver + per-account balances). FileBacked is mandatory — the in-memory store's witness() is a hard Err (Found-027). - new_file_backed_coordinator(): bind-free coordinator for SH-007's controlled bind-ordering hook. - in_memory_store(): InMemory backing for SH-005's witness split. - wait_for_shielded_balance(): force-sync poller mirroring the tokens::wait_for_token_balance shape + STEP_TIMEOUT. - shielded_default_address_43(): SH-003 transfer-recipient plumbing. - teardown_sweep_shielded(): best-effort, log-on-error unshield of residual shielded balance back to the bank platform address. Swallows every error (broken-witness cases must NOT fail teardown). Adversarial injection hooks (scaffolded for the SH-020..SH-035 follow-up, gated behind PLATFORM_WALLET_E2E_SHIELDED_ADVERSARIAL): build_raw_shielded_transition, broadcast_raw, mutate_serialized_bundle, TamperingProver, build_against_note, seed_malformed_note, reuse_asset_lock_proof, MockSyncSource. The seams pin the inputs the abuse cases need; live bodies land in the follow-up wave. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the functional/baseline shielded (Orchard) tier per TEST_SPEC.md §3 '### Shielded (SH)'. All gated behind the e2e feature (pulls shielded); no #[ignore]. Tests assert CORRECT behavior — RED-by-design cases are left failing to pin live bugs. GREEN (happy-path + correctness): - SH-001 shield from account (Type 15) - SH-002 shield→unshield round-trip (Type 15→17) - SH-003 shielded transfer between accounts (Type 16) - SH-004 shielded_balances reflects note only after sync - SH-008 unshield insufficient-balance typed error + reservation release - SH-009 zero-amount rejection (RED arm if transfer/unshield lack a guard) - SH-010 double-spend guard: concurrent spends reserve disjoint notes - SH-011 note-selection convergence + u64::MAX overflow guard - SH-012 sync watermark idempotency (double-sync stable + spendable) - SH-013 bind empty accounts → typed ShieldedKeyDerivation - SH-014 spend before bind → ShieldedNotBound; unbound account → KeyDerivation - SH-007 GREEN regression guard: pre-bind note witnessable/spendable (#3603) RED-by-design (pin live bugs — do NOT fix from inside tests): - SH-005 InMemory witness() hard-Err vs FileBacked success (Found-027) - SH-006 shielded_add_account never re-registers on coordinator (Found-028) Core-L1 gated (MAY run RED until plumbing exists — documents the seam): - SH-018 shield from asset lock (Type 18) — flags two production gaps: no public shielded_shield_from_asset_lock wrapper, and no test seam returning the one-time asset-lock private key. - SH-019 shielded withdraw to L1 (Type 19) — shielded-side asserted unconditionally; L1 payout observation left as a documented TODO. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…o test/rs-platform-wallet-shielded-e2e # Conflicts: # packages/rs-platform-wallet/tests/e2e/cases/mod.rs
Implements the adversarial/abuse tier per TEST_SPEC.md §3 — each ATTACKS the protocol boundary and asserts the BACKEND must reject (or behave safely). All gated behind e2e + shielded + PLATFORM_WALLET_E2E_SHIELDED_ADVERSARIAL (no-op pass when the env is unset, so the default suite stays green). No #[ignore]. Tests assert CORRECT rejection — no weakened assertions. Live backend/wallet-reaching (achievable via public API, no prod-seam change): - SH-027 malformed note serde: seeds a non-115-byte note via the public ShieldedStore trait and drives operations::unshield → deserialize_note; asserts a typed error (no panic = no DoS, no silent corruption). - SH-030 cross-network/wrong-HRP/malformed recipient: client parse + network-mismatch guard fires with a typed ShieldedBuildError. - SH-031 rebind-different-seed: asserts seed_A's note does NOT leak into seed_B's balance and re-discovers cleanly on rebind-back (no key mix). - SH-032 exact-change boundary: note == amount+fee leaves ZERO change; amount+fee-1 is rejected ShieldedInsufficientBalance. Harness hooks fleshed out: broadcast_raw (StateTransition deserialize + broadcast, gated), seed_malformed_note (live via ShieldedStore trait). RED-by-gap (flagged production-seam gaps — NOT fixed, per instructions): - SH-020/021/022/023/024/025/026/033/034: reaching Drive with a valid-except-for-the-tamper transition needs a build-only shielded capture seam (shielded operations::* build AND broadcast internally; extract_spends_and_anchor / reserve_unspent_notes / build_spend_bundle are private; the public dpp build_*_transition enforce value/fee/overflow guards internally). See framework::shielded::ADVERSARIAL_SEAM_MISSING. - SH-028/029: no injectable sync source (sync_notes_across is pub(super), fetches from the SDK directly) — needs a SyncSource production seam. - SH-035: stacks the SH-018 Core-L1 private-key gap + the asset-lock-proof reuse seam. The 6 CRITICAL-if-red consensus attacks (SH-020/022/025/033/034/035) and the HIGH-if-red ones are pinned with their attack + expected consensus error (NullifierAlreadySpentError 40901, ShieldedInvalidValueBalanceError 10822, AnchorMismatch) ready to assert once the capture seam lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… seams + wire SH-018/020-035 Closes the production-seam gaps the adversarial wave needed, then wires the abuse cases to actually reach Drive. GAP 1 — build/broadcast split (production): - operations.rs: each spend gains a build_*_st entrypoint returning the signed StateTransition WITHOUT broadcast (build_shield_st, build_shield_from_asset_lock_st, build_unshield_st, build_transfer_st, build_withdraw_st) + a shared broadcast_st. The existing combined shield/unshield/transfer/withdraw/shield_from_asset_lock are now thin build-then-broadcast wrappers — PlatformWallet::shielded_* and all callers unchanged. GAP 4 — public Type-18 wrapper (production): - PlatformWallet::shielded_shield_from_asset_lock added, mirroring the other four spend wrappers; delegates to operations::shield_from_asset_lock. GAP 2 + GAP 5 — test-utils feature (NOT in default; pulled by e2e): - New cargo feature. operations::test_utils exposes reserve_unspent_notes_for_test, extract_spends_and_anchor_for_test, unspent_notes_for_test (build-against-chosen-note / skip-reservation), and derive_asset_lock_private_key (seed,path -> one-time key, Gap 5). Harness (framework/shielded.rs): broadcast_raw now takes a StateTransition; mutate_serialized_bundle tampers proof/binding_signature/anchor/amount via the public V0 fields (no byte offsets); capture_unshield_st + build_unshield_st_against_notes + unspent_notes build real transitions through the new seams. Removed the stub MockSyncSource / RawShieldedKind / ADVERSARIAL_SEAM_MISSING. Adversarial cases now REACH the backend (assert backend rejection; RED iff accepted/mishandled): - SH-022/024/025/026/034: capture a valid unshield, byte-tamper value/proof/anchor/binding-sig, broadcast_raw. - SH-020/021/033: build against a chosen note skipping reservation (double-spend, replay-after-confirm, intra-bundle duplicate nullifier). - SH-018/035: public Type-18 wrapper + Gap-5 key helper + create_funded_asset_lock_proof (Core-L1 gated, may run RED). - SH-023: client fee-floor asserted; backend-floor arm flagged as a residual gap (no post-build fee seam). - SH-027/030/031/032: unchanged (already reached wallet/backend). BLOCKED + removed: SH-028/SH-029 (no injectable sync-source seam — sync_notes_across is pub(super), fetches from the SDK directly). Marked BLOCKED in TEST_SPEC.md. SH-018 spec line restored to implemented. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…o test/rs-platform-wallet-shielded-e2e
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.
Issue being fixed or feature implemented
Imagine you are a wallet engineer about to ship Orchard shielded transfers to real users. You can shield, transfer privately, and unshield — but how confident are you that the credits actually land where they should, that a broken backing store fails loudly instead of silently eating funds, and that a note you received before binding your wallet is still spendable? Right now that confidence rests on hope. This PR lays the spec for the test suite that turns hope into a green (or honestly-red) checkmark.
This is the spec-first slice of the shielded (Orchard) e2e suite for
rs-platform-wallet, targeting the mergedfeat/rs-platform-wallet-e2ebranch (#3549).What was done?
Adds the shielded e2e test specification to
packages/rs-platform-wallet/tests/e2e/TEST_SPEC.md(single-file change):### Shielded (SH)test area in §3 — SH-001..SH-019 covering all five shielded transition types (shield/transfer/unshield/shield-from-asset-lock/withdraw-to-L1) plus store/note-selection/sync correctness pins.--features shielded+ Wave H".CachedOrchardProverOnceCell, FileBackedbind_shieldedhelper,wait_for_shielded_balance, best-effort teardown unshield-sweep to the bank address).Scope of this PR: spec only. The Wave H harness and the SH test implementations land in follow-up commits on this branch — no test code or production code is touched here.
Findings the suite is designed to prove (verified against the merged v3.1-dev feat tree):
InMemoryShieldedStore::witness()unconditionally returnsErr(store.rs:409-416) — spends are structurally non-functional on the in-memory store whileFileBackedShieldedStore::witness()works; a silent backing-store-dependent splitshielded_add_account(platform_wallet.rs:439-457) updates only the per-wallet keys slot and never callscoordinator.register_walletwith the expanded set — notes for the added account never syncextract_spends_and_anchor(operations.rs:601-611) andFileBackedShieldedStore::witness(file_store.rs:162-165) — depth-0 described two different wayssync.rsnow marks every commitment position)DX gap noted: there is no public
PlatformWallet::shielded_shield_from_asset_lockwrapper for the Type-18 (shield-from-asset-lock) path — SH-018 has to reach through lower-level plumbing. Worth a first-class wrapper as a follow-up.How Has This Been Tested?
Not applicable to this commit — it adds a Markdown specification only. The findings it cites were each verified by inspection against the merged feat tree (288ea92): Found-027/028/030 confirmed still-live, Found-029 confirmed fixed-by-#3603.
Note on test intent: these tests are designed to prove issues. The Found-027 and Found-028 pins (SH-005, SH-006) are red-by-design — they are expected to fail and will be left red for triage. A failing test here is a feature, not a regression.
Breaking Changes
None. Documentation-only change.
Checklist:
For repository code-owners and collaborators only
Adversarial / break-the-backend cases (SH-020..SH-035)
Marvin sharpened the suite with an abuse pass. The purpose of these cases is not to confirm happy paths — it is to attack Drive's consensus / state-transition validation and the Orchard proof verifier with malformed, forged, and replayed shielded transitions. A RED is the deliverable: a failing assertion here means the backend accepted a transition it should have rejected — i.e. a real consensus/proof-verification hole. A green means the backend correctly refused the attack.
The 16 adversarial cases (SH-020..SH-035) each construct a deliberately-invalid shielded transition and assert the backend rejects it. The six that are CRITICAL if they go red (backend accepted the attack):
AssetLockProofMechanism —
[INJECT]seam: these cases bypass the client-side guards (which would refuse to build an invalid bundle), reach into thedppbuilder, mutate theSerializedBundledirectly, and submit viabroadcast_raw. This is what lets the test hand Drive a transition the honest client would never produce. The whole adversarial cohort is gated behind thePLATFORM_WALLET_E2E_SHIELDED_ADVERSARIALenvironment flag (off by default).These findings only materialize in a LIVE run against Drive — they exercise server-side consensus and proof verification, which a local/unit run cannot reproduce. Until the suite runs against a live Drive node, SH-020..SH-035 are spec-only intent.
Failed Tests
Living ledger — updated after every live run against funded porter devnet. A RED here for an adversarial (SH-020..SH-035) case is a backend finding, not a defect in the test.
🤖 Generated with Claude Code