Skip to content

test(platform-wallet): shielded (Orchard) e2e suite — spec + Wave H harness#3727

Draft
lklimek wants to merge 26 commits into
feat/rs-platform-wallet-e2efrom
test/rs-platform-wallet-shielded-e2e
Draft

test(platform-wallet): shielded (Orchard) e2e suite — spec + Wave H harness#3727
lklimek wants to merge 26 commits into
feat/rs-platform-wallet-e2efrom
test/rs-platform-wallet-shielded-e2e

Conversation

@lklimek
Copy link
Copy Markdown
Contributor

@lklimek lklimek commented May 22, 2026

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 merged feat/rs-platform-wallet-e2e branch (#3549).

What was done?

Adds the shielded e2e test specification to packages/rs-platform-wallet/tests/e2e/TEST_SPEC.md (single-file change):

  • New ### 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.
  • §2 capability matrix Shielded row rewritten from "out of scope" → "in scope behind --features shielded + Wave H".
  • §5 out-of-scope item 1 flipped to in-scope.
  • New Wave H harness plan in §4 (warmed CachedOrchardProver OnceCell, FileBacked bind_shielded helper, 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):

Finding Sev What it proves Pin
Found-027 HIGH InMemoryShieldedStore::witness() unconditionally returns Err (store.rs:409-416) — spends are structurally non-functional on the in-memory store while FileBackedShieldedStore::witness() works; a silent backing-store-dependent split SH-005 (red-by-design)
Found-028 HIGH shielded_add_account (platform_wallet.rs:439-457) updates only the per-wallet keys slot and never calls coordinator.register_wallet with the expanded set — notes for the added account never sync SH-006 (red-by-design)
Found-030 LOW anchor-semantics doc drift between extract_spends_and_anchor (operations.rs:601-611) and FileBackedShieldedStore::witness (file_store.rs:162-165) — depth-0 described two different ways SH-030 doc note
Found-029 (FIXED) pre-bind notes were permanently unwitnessable; fixed by v3.1-dev #3603 (sync.rs now marks every commitment position) SH-007 GREEN regression guard

DX gap noted: there is no public PlatformWallet::shielded_shield_from_asset_lock wrapper 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:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

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):

Case Attack
SH-020 Double-spend — same nullifier spent in two transitions
SH-022 Value not conserved — outputs exceed inputs (mint-from-nothing)
SH-025 Forged proof — bundle carries a proof that does not verify against its public inputs
SH-033 Duplicate nullifier within a single bundle
SH-034 Tampered binding signature
SH-035 Replayed Type-18 asset-lock — re-use of an already-consumed AssetLockProof

Mechanism — [INJECT] seam: these cases bypass the client-side guards (which would refuse to build an invalid bundle), reach into the dpp builder, mutate the SerializedBundle directly, and submit via broadcast_raw. This is what lets the test hand Drive a transition the honest client would never produce. The whole adversarial cohort is gated behind the PLATFORM_WALLET_E2E_SHIELDED_ADVERSARIAL environment 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.

Run 2 2026-05-22: genesis fixed; blocked at SPV P2P handshake.

ID Test description Expected outcome Actual outcome Comment
HARNESS/SPV-P2P SPV mn-list sync on porter devnet (precondition for all network + shielded cases) Peers handshake; mn-list syncs; setup() succeeds Genesis pre-seed FIXED (no more "no known genesis"); but 0/11 porter peers complete the P2P handshake (drop after version, before verack) → wait_for_mn_list_synced times out 600s CRITICAL infra blocker (run #2). dash-spv 0.42.0 (proto 70237) ↔ porter Core 23.1.2 (proto 70240). Shielded + adversarial suite still unrun. Under investigation.
SH-001..SH-035 Shielded functional + adversarial suite (per spec) NOT RUN — setup()-gated by the SPV blocker above No shielded/backend probe executed; deferred until SPV devnet genesis is wired.
found_021 InstantLock dropped on context promotion RED-by-design (asserts known wallet defect) FAILED as designed (offline) Expected red; not a backend finding.
found_022 asset-lock builder consumes change index on failed build RED-by-design FAILED as designed (offline) Expected red; not a backend finding.

🤖 Generated with Claude Code

…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>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3e7637eb-12d7-422b-bcda-01b3178f992b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/rs-platform-wallet-shielded-e2e

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

lklimek and others added 13 commits May 22, 2026 11:20
…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>
…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>
The TODO blamed an upstream protocol-version mismatch (dash-spv 70237
vs Dash Core 70240). That diagnosis was wrong — rust-dashcore sets
PROTOCOL_VERSION=70237 and dash-spv's peer-acceptance floor is 60001,
so no version-based rejection applies. The actual cause was a broken
Core on the porter devnet (now fixed); no SPV-side workaround was ever
warranted.

Removing the speculation so future readers don't pursue a phantom
upstream protocol bump.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…et-shielded-e2e

# Conflicts:
#	packages/rs-platform-wallet/Cargo.toml
#	packages/rs-platform-wallet/src/wallet/shielded/operations.rs
… + shielded operations

Combine the e2e `serde` feature with the shielded `test-utils` feature, and
keep the shielded Type-18 shield-from-asset-lock helpers. Resolves leftover
conflict markers from the v3.1-dev cascade merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… merge

The v3.1-dev cascade merge auto-combined the import header of
shielded/operations.rs, dropping `build_shield_from_asset_lock_transition`
from the builder import group and duplicating a SecretKey alias, which broke
the `shielded` feature build. Restore the canonical shielded version of the
file so `--all-features` compiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lklimek and others added 2 commits June 1, 2026 10:21
…ormWalletManager::new

The cascade kept #3727's PlatformWalletManager::new(.., app_handlers:
Vec<Arc<dyn PlatformEventHandler>>) signature but brought in the
shielded_sync / shielded_sync_paloma examples from v3.1-dev, which
still passed a single Arc<dyn PlatformEventHandler>. Wrap the handler
in vec![..] to match the multi-handler API every other call-site
(basic_usage, ffi, e2e harness, spv_sync) already uses. Fixes the
--all-features example build (E0308 expected Vec, found Arc).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lklimek lklimek added this to the v4.1.0 milestone Jun 1, 2026
lklimek and others added 10 commits June 2, 2026 10:37
…et-shielded-e2e

# Conflicts:
#	packages/rs-platform-wallet/tests/e2e/framework/spv.rs
…floor so adversarial probes reach Drive

Every SH case funded only 90M (FUNDING_CREDITS) and shielded 50M, but the
protocol's minimum shield fee (compute_minimum_shielded_fee) is folded into
the spend requirement as `amount + fee`, so each shield/unshield bounced in
note_selection with ShieldedInsufficientBalance BEFORE broadcast — 0 backend
coverage despite the gate working.

Per-group funding (fee headroom sized to clear the floor with margin):

- Shield succeeds, no real-API spend (raw INJECT seam or shield-only):
  SH-001/004/031/020/022/023/024/025/026/033/034 — FUNDING_CREDITS → 1.2e9,
  SHIELD_AMOUNT stays 50M (adversarial payload amounts untouched).

- Real fee-checked spend (unshield/transfer/withdraw): SH-002/003/005/006/
  007/012/019/021 — SHIELD_AMOUNT → 1.12e9 (covers the ≤20M spend + fee),
  FUNDING_CREDITS → 2.22e9 (covers the shield + its fee).

- Boundary / insufficiency cases, intent preserved:
  - SH-008: SHIELD 1.12e9 so the SATISFIABLE 3M unshield can pay its fee from
    the pool; OVERDRAW raised to 2.0e9 so it still exceeds the shielded
    balance and trips ShieldedInsufficientBalance (at the old 50M it would
    now be satisfiable and break the test).
  - SH-032: only FUNDING → 2.3e9 (covers the dynamic exact_note shield); the
    note size is derived from the REAL compute_minimum_shielded_fee at
    runtime, so the boundary semantics are already fee-correct.
  - SH-010: each note 1.11e9 so the two concurrent single-note unshields
    each cover UNSHIELD_EACH + fee.
  - SH-011: SHIELD_EACH 600M, MULTI_NOTE_UNSHIELD 650M (raw amount > a single
    note → forces multi-note selection independent of the exact fee), FUNDING
    1.7e9.

- Asset-lock cases SH-018/035: ASSET_LOCK_DUFFS → 1.2M (1.2e9 credits, above
  Drive's 100k-duff asset-lock floor AND the shield fee), TEST_WALLET_CORE_FUNDING
  → 1.4M duffs (lock + L1 fee), so the shield-from-asset-lock — and SH-035's
  REPLAY leg — execute against Drive.

Client-guard / no-broadcast cases (SH-009/013/014/027/030) need no funding
change — they reject before reaching a fee floor.

Bank Platform min: each SH test now draws up to ~2.3e9 from the bank's
Platform pool. Use the existing env knob PLATFORM_WALLET_E2E_MIN_BANK_CREDITS
(maps to Mins.platform via mins_from_config; default 500M) — set it to ~3e9
at run time so the planner asset-locks enough Core→Platform for the largest
single (serial) SH test. No constant change needed; bank Core ~102 tDASH
covers it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s (adversarial gate, real shield fee, quorum-retirement SPV caveat, AL-001/PA-007/ID-002b status)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ield fee so malformed-spend probes can build (Fix 1)

The adversarial cases shielded a 50M note then tried to capture/build a
Type-17 unshield to tamper with, but the real Orchard unshield fee dwarfs
50M (1-action 111_548_800; 2-action 123_097_600), so the spend's
value check failed (`available 50000000, required 131548800`) and the
malformed transition was never built — 0 backend coverage.

Raise each note above its case's spend + fee so the build succeeds and the
probe reaches the broadcast/INJECT path:

- 1-action probes (capture/build-against one note, unshield 20M):
  SH-020 (double-spend two transitions), SH-022 (value-not-conserved),
  SH-024 (u64 overflow), SH-025 (forged proof), SH-026 (anchor mismatch),
  SH-034 (tampered binding sig): SHIELD_AMOUNT 50M → 200M
  (>= 20M + 111.5M fee + headroom), FUNDING_CREDITS 1.2e9 → 1.4e9 so the
  shield still clears the ~1e9 client fee reserve.

- 2-action probe SH-033 (duplicate nullifier in one bundle, [note, note]
  sum = 2×note, unshield 60M): SHIELD_AMOUNT 50M → 200M so 2×200M=400M
  covers 60M + 123M 2-action fee; FUNDING 1.2e9 → 1.4e9.

Intent preserved: SH-020 still spends one note across two transitions
(note covers ONE unshield); SH-022 still forges FORGED_AMOUNT (1e9) far
above the now-200M note; SH-033's duplicate-nullifier bundle still
"covers" its value so the probe is the dup, not insufficiency. Stale
"50M note" literals in SH-022/SH-033 comments updated to reference the
constants (present-state).

Scope: only these 7 note-size adversarial cases. The HRP-blocked cases
(SH-002/005/007/008/012/021/032) are untouched (separate investigation);
Fix 2 (Testnet/Devnet HRP mismatch) still gates the unshield destination,
so these probes don't reach Drive yet — not run live here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e-devnet e2e

Blanket RUST_LOG=trace against a live devnet floods logs from Orchard's
shardtree and the h2 crate at hot-loop volume (~8.4GB in ~4min of SPV sync),
filling disk and stalling the run. Document the shardtree=warn,h2=warn (or
narrow-scope) suppression mitigation in the e2e "Running tests" section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…avoid per-case full re-sync

Each sh_* case minted a fresh empty commitment-tree DB, so its watermark
was 0 and the suite re-streamed the entire ~1M-note Orchard history from
position 0 every case. Route all cases (except SH-007/SH-013, which need a
controlled private tree) through one process-shared NetworkShieldedCoordinator
over one persisted tree, and seed each freshly-bound account's watermark to
the shared tree_size so cases 2..N fetch only the tip delta — ~25-30x on the
Orchard-scan portion of the suite. teardown unregisters the wallet to keep
the shared registry bounded; the chain-wide tree is left intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arify SH-007 failure label

SP-001: unregister_wallet ran only from teardown_sweep_shielded, so the four
shared-coordinator cases that don't sweep (SH-009/030/035, SH-014 step 2)
leaked SubwalletId registrations, taxing every later case's per-batch
trial-decrypt. Lift the shared coordinator to a module OnceCell and call an
idempotent unregister from the universal SetupGuard::teardown — bounds the
registry for all 30 cases; no-op for non-shielded and SH-007/013 private trees.
SP-004: SH-007's unshield expect now surfaces the real error instead of
presuming a mark-every-position witness regression (correct pre/post #3781).
SP-002: document the shared OnceCell's serial/single-process/single-network
assumption.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ChainLock liveness finding (needs re-repro before reporting)

Expand the AL-001 detail block + Quick-index row + changelog with the run-4
evidence (paloma 2026-06-02: 2/3 concurrent asset-lock txs missed IS-locks in
300s, ChainLock fallback also missed -> FinalityTimeout; solo build got IS-lock
in 0.67s). Frame the server-side liveness conclusion as the working hypothesis
and mark it OBSERVED — needs re-repro + root-cause before any upstream report;
NOT reported.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…0 (SD-002); soften quorum-gap-fragile 40901 readback assertion

Port the SD-002 SH-020 from verify-run4 (1014782039): add the wait_commit_raw
harness helper and judge the double-spend on the AUTHORITATIVE post-execution
on-chain STATE delta (both distinct destinations credited = double-spend; exactly
one = correct). Soften the secondary "rejected leg failed nullifier-already-spent
(40901)" check from a hard assert to a logged best-effort observation: on devnet
the rejected ST never commits, so its proof-verified readback times out, and the
rust-dashcore quorum-by-hash gap can mask the 40901 reason — a hard assert there
false-REDs even though credited_count==1 already proves the double-spend was
rejected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…est wallets (interim HRP unblock; superseded by #3781)

The bech32m decoder lossily maps `tdash` -> Testnet, so a devnet recipient
decodes as Testnet and the strict equality guard in shielded_unshield_to
rejected it, blocking all devnet unshield/transfer e2e cases. Relax the guard
to accept a Testnet-decoded address on a Devnet/Regtest wallet; the
genuine-mismatch branch (e.g. Mainnet recipient on a devnet wallet) is
unchanged. Superseded by #3781's network-agnostic decoder + HRP-class guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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