Skip to content

fix(platform-wallet): preserve reported-consumed asset-lock recovery - #4357

Merged
QuantumExplorer merged 9 commits into
v4.2-devfrom
codex/fix-stale-shielded-asset-lock
Aug 11, 2026
Merged

fix(platform-wallet): preserve reported-consumed asset-lock recovery#4357
QuantumExplorer merged 9 commits into
v4.2-devfrom
codex/fix-stale-shielded-asset-lock

Conversation

@llbartekll

@llbartekll llbartekll commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • reconcile structured exact-outpoint already-consumed reports in the shared asset-lock orchestration layer
  • apply the same conservative handling to identity registration, identity top-up, platform-address funding, and shielded funding
  • reuse or obtain a ChainLock proof and durably persist RecoveredFromChain while returning stable code 24
  • never infer that the requested Platform operation succeeded; unrelated and mismatched errors remain unchanged
  • document code 24 as consumption unknown and make Swift/Kotlin invitation examples retain their status and in-flight marker
  • rename the composite persistence capability to ASSET_LOCK_RECONCILIATION without changing capability bits or FFI/API types

Tests

  • cargo test -p platform-wallet --features shielded --lib wallet::shielded::fund_from_asset_lock::tests
  • cargo test -p platform-wallet --lib wallet::asset_lock::orchestration::tests
  • cargo test -p platform-wallet-ffi --features shielded --lib persistence::tests
  • cargo test -p platform-wallet-ffi --features shielded --lib error::tests
  • cargo check -p platform-wallet --lib
  • cargo check -p platform-wallet --features shielded --lib
  • cargo check -p platform-wallet-storage
  • python3 scripts/check_sdk_parity_manifest.py
  • python3 -m unittest discover -s scripts/tests -p 'test_*.py'
  • cargo fmt --all -- --check
  • cargo clippy -p platform-wallet -p platform-wallet-ffi --features shielded --lib --tests -- -D warnings

Focused Swift source parsing passed. The local Swift unit target could not import the generated DashSDKFFI module, and the local Kotlin Gradle test could not configure without an Android SDK; CI covers the repository integration environment.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of already-consumed asset locks during funding, retries, and shielded sends.
    • Preserved matching asset-lock errors while surfacing unrelated failures normally.
    • Recovered matching locks with ChainLock proofs when Platform-side consumption remains unknown.
    • Improved persistence, rollback, and consistency across immediate and deferred storage modes.
    • Invitation reclaim attempts now remain unresolved rather than incorrectly indicating success or a third-party claim.
  • Documentation

    • Clarified recovery states, proof retention, and Platform finality limitations.
    • Updated error descriptions for unconfirmed one-shot funding completion.
    • Added tracked asset-lock persistence capability reporting.

@github-actions github-actions Bot added this to the v4.2.0 milestone Aug 10, 2026
@thepastaclaw

thepastaclaw commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

🕓 Ready for review — next in queue (commit e6241a5)
Queue position: 1/1
ETA: start ~08:40 UTC · complete ~08:51 UTC (median 11m across 30 recent reviews; 2 slots)
Queued 50s ago · Last checked: 2026-08-11 08:40 UTC

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The wallet detects exact consumed asset-lock reports, records matching locks as RecoveredFromChain, persists recovery state, and preserves typed errors across funding, FFI, Kotlin, and Swift flows.

Changes

Asset-lock reconciliation

Layer / File(s) Summary
Consumed asset-lock detection and reconciliation
packages/rs-platform-wallet/src/error.rs, packages/rs-platform-wallet/src/wallet/asset_lock/orchestration.rs, packages/rs-platform-wallet-ffi/src/shielded_send.rs
Matches exact outpoints, reconciles matching reports, and preserves AssetLockAlreadyConsumed through FFI funding and resume entry points.
Persistence capabilities and commit handling
packages/rs-platform-wallet/src/changeset/..., packages/rs-platform-wallet/src/wallet/persister.rs, packages/rs-platform-wallet-storage/src/sqlite/persister.rs, packages/rs-platform-wallet-ffi/src/persistence.rs
Advertises tracked asset-lock persistence, reports inline commits, and defines post-commit notification behavior.
Tracked-lock recovery and funding flows
packages/rs-platform-wallet/src/wallet/asset_lock/..., packages/rs-platform-wallet/src/wallet/identity/network/registration.rs, packages/rs-platform-wallet/src/wallet/platform_addresses/fund_from_asset_lock.rs, packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs
Retains effective proofs across retries, records consumption-unknown state, persists reconciliation changes, and tests rollback and flush-failure behavior.
Kotlin and Swift reclaim classification
packages/kotlin-sdk/..., packages/swift-sdk/..., docs/sdk/sdk-parity-manifest.json
Classifies typed and exact legacy consumed errors as CONSUMPTION_UNKNOWN or consumptionUnknown, preserves reclaim state, and updates capability expectations and tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FundingFlow
  participant AssetLockManager
  participant AssetLockTracker
  participant PersistenceBackend
  participant SDKExample
  FundingFlow->>AssetLockManager: submit asset-lock funding result and effective proof
  AssetLockManager->>AssetLockTracker: reconcile matching consumed report
  AssetLockTracker->>PersistenceBackend: store and flush RecoveredFromChain state
  PersistenceBackend-->>AssetLockTracker: persistence result
  AssetLockTracker-->>FundingFlow: typed error and recovery result
  FundingFlow-->>SDKExample: AssetLockAlreadyConsumed
  SDKExample-->>SDKExample: classify as consumption unknown and retain reclaim state
Loading

Possibly related PRs

Suggested reviewers: lklimek, shumkov

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: preserving recovery handling for reported-consumed asset locks.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-stale-shielded-asset-lock

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

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Preliminary review — Codex only

The structured error matching and FFI code preservation are correct, but the reconciliation path trusts an unverified DAPI rejection and can permanently tombstone an asset lock that remains unspent. Persistence callback failures are also hidden from this new path, and the state-changing branch lacks direct orchestration coverage, so changes are required before merge.
Source: reviewer backends: gpt-5.6-sol (general), gpt-5.6-sol (rust-quality), gpt-5.6-sol (ffi-engineer); final verifier backend: gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated blockers were found in the Codex precheck. Opus is deferred until a fresh Codex revalidation clears the blocker gate.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — rust-quality (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet: not run (deferred by blocker gate)

🔴 1 blocking | 🟡 2 suggestion(s)

🤖 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/fund_from_asset_lock.rs`:
- [BLOCKING] packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs:439-447: Do not terminally consume a lock from an unauthenticated rejection
  The SDK handles a wait-stream error before verifying the response proof or quorum signature, and `submit_with_cl_height_retry` explicitly documents that consensus-error responses have no client-side proof or DAPI-quorum check. A malicious or malfunctioning endpoint can therefore inspect the submitted transition's outpoint and fabricate the matching already-consumed error; comparing the error to that outpoint does not authenticate the verdict. This branch then marks the lock `Consumed`, clears its proof, persists the terminal tombstone, and causes future resumes to reject it locally, potentially stranding an asset lock that Platform never consumed. Preserve the typed error if needed, but do not create terminal wallet state from this response alone; terminal reconciliation requires authenticated state evidence, or the wallet must retain a nonterminal/retryable reported-consumed state.
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs:445-447: Do not acknowledge reconciliation after host persistence fails
  `consume_asset_lock` mutates the in-memory entry and calls `queue_asset_lock_changeset`, but that method logs and discards every `WalletPersister::store` error. The FFI persistence backend returns an error when an asset-lock callback or changeset commit fails, so the `.await?` here cannot observe a host rollback and this branch still reports the typed already-consumed result. After restart, the host can rehydrate the stale ChainLocked/Broadcast row even though the operation claimed to have reconciled it. Propagate the persistence failure before returning `AssetLockAlreadyConsumed`, and restore the previous in-memory lock state if the durable update fails.
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs:437-457: Test the consumed-error reconciliation branch directly
  The added tests separately exercise the pure error matcher, `consume_asset_lock`, and FFI mapping, but none drives an `Err(dash_sdk::Error)` through this match and verifies the combined behavior. An integration mistake here—using the wrong outpoint, omitting consumption, or wrapping the result back into `PlatformWalletError::Sdk`—would leave every added test passing. Add a focused orchestration test, or extract this branch into a testable helper, and assert that a matching error updates the submitted lock and returns the typed wallet error while unrelated or mismatched-outpoint errors do not mutate persisted status.

Comment thread packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs Outdated
@llbartekll
llbartekll force-pushed the codex/fix-stale-shielded-asset-lock branch from 70a9297 to 8ac6e36 Compare August 10, 2026 16:23
@llbartekll llbartekll changed the title fix(platform-wallet): reconcile consumed shielded asset locks fix(platform-wallet): preserve reported-consumed asset-lock recovery Aug 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs (1)

399-451: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reuse the promoted ChainLock proof in reconciliation.

The IS→CL arm promotes the proof to chain_proof, but line 446 passes the original proof to reconcile_asset_lock_submit_error. In that path proof is still AssetLockProof::Instant, so the helper calls upgrade_to_chain_lock_proof a second time. With cl_wait == None that call waits without a bound again, even though the promotion already succeeded. Track the effective proof and pass it to reconciliation.

♻️ Proposed refactor
-        let submit_result = match submit_with_cl_height_retry(settings, |s| {
+        let mut effective_proof = proof.clone();
+        let submit_result = match submit_with_cl_height_retry(settings, |s| {
                 self.asset_locks.queue_asset_lock_changeset(cs);
+                effective_proof = chain_proof.clone();
                 submit_with_cl_height_retry(settings, |s| {
                 return reconcile_asset_lock_submit_error(
                     &self.asset_locks,
                     e,
                     &proof_out_point,
-                    &proof,
+                    &effective_proof,
                     cl_wait,
                 )
                 .await
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs`
around lines 399 - 451, Track the effective asset-lock proof across the submit
flow, updating it to the promoted chain_proof in the
is_instant_lock_proof_invalid arm. Pass this effective proof instead of the
original proof to reconcile_asset_lock_submit_error, while preserving the
existing proof for non-promotion paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs`:
- Around line 196-231: After successfully storing the recovery changeset in the
recovery flow around queue_asset_lock_changeset, flush the per-wallet changeset
before returning so RecoveredFromChain is durable. Handle flush failures using
the existing PersistenceErrorKind conventions, including rollback behavior where
required, and propagate the resulting persistence error consistently with the
current store-error path.

---

Nitpick comments:
In `@packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs`:
- Around line 399-451: Track the effective asset-lock proof across the submit
flow, updating it to the promoted chain_proof in the
is_instant_lock_proof_invalid arm. Pass this effective proof instead of the
original proof to reconcile_asset_lock_submit_error, while preserving the
existing proof for non-promotion paths.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5f69af23-c448-415c-8d47-d0b429bae91a

📥 Commits

Reviewing files that changed from the base of the PR and between 70a9297 and 8ac6e36.

📒 Files selected for processing (5)
  • packages/rs-platform-wallet-ffi/src/shielded_send.rs
  • packages/rs-platform-wallet/src/error.rs
  • packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs
  • packages/rs-platform-wallet/src/wallet/asset_lock/tracked.rs
  • packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/rs-platform-wallet-ffi/src/shielded_send.rs
  • packages/rs-platform-wallet/src/error.rs

Comment thread packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Codex/Sol only (Phase 2 disabled)

The previous unauthenticated-consumption blocker and both prior suggestions are fixed: matching reports now retain an authenticated ChainLock proof in nonterminal RecoveredFromChain state, immediate store failures roll back the in-memory candidate, and the extracted reconciliation helper has direct coverage. Two in-scope suggestions remain: the helper does not cross the persistence trait's flush durability boundary, and the InstantLock-to-ChainLock retry discards the effective ChainLock proof before reconciliation.
Source: reviewers gpt-5.6-sol (general, security-auditor, rust-quality, ffi-engineer); final verifier gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — rust-quality (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

🟡 2 suggestion(s)

🤖 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/asset_lock/sync/tracking.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs:214-217: Flush the recovery marker before acknowledging reconciliation
  `PlatformWalletPersistence::store` is explicitly a buffering operation, while `flush` is the durability boundary. `SqlitePersister` in `FlushMode::Manual`, for example, returns `Ok(())` here after only merging `RecoveredFromChain` into its in-memory accumulator. The caller consequently returns the typed `AssetLockAlreadyConsumed` result even though a process exit can lose the marker or a later flush can fail. Persist this update through `flush()` before acknowledging reconciliation, and route both store and flush failures through rollback handling. The failure path must also account for transient flush failures retaining the candidate in the backend buffer; otherwise a later flush could persist the candidate after the wallet's in-memory entry was rolled back.

In `packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs:439-447: Carry the effective ChainLock proof into error reconciliation
  When the initial InstantLock submission is rejected, the fallback obtains a valid `chain_proof`, records it, and uses it for the second submission. If that submission reports the outpoint as already consumed, this branch nevertheless passes the original InstantLock `proof` to reconciliation. The helper then repeats `upgrade_to_chain_lock_proof`, duplicating SPV and persistence work and potentially replacing the intended typed result with `AssetLockProofWait` if the transaction record becomes unavailable between lookups, even though this flow already owns a valid ChainLock proof. Track the effective proof across the retry, update it after promotion, and pass it to `reconcile_asset_lock_submit_error`; add coverage for an already-consumed result from the ChainLock retry.

Comment thread packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs Outdated

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Codex/Sol only (Phase 2 disabled)

The previous durability-boundary and effective-proof findings are fixed, and the recovery path now retains a ChainLock proof in nonterminal state. Two in-scope suggestions remain: a fatal FFI flush callback can make the live wallet disagree with the already-committed host store, and the fresh BIP44 funding entry point still flattens the newly reachable typed already-consumed result.
Source: reviewer backend model gpt-5.6-sol (general, security-auditor, rust-quality, ffi-engineer); final verifier backend model gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — rust-quality (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

🟡 2 suggestion(s)

1 additional finding(s) omitted (not in diff).

🤖 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/asset_lock/sync/tracking.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs:214-236: Do not roll back after the FFI store has already committed the marker
  A non-transient `flush()` error does not always mean the candidate failed to reach durable storage. `FFIPersister::store` invokes the per-kind callbacks and then `on_changeset_end_fn`; that callback's documented contract commits the host transaction before `store` returns `Ok(())`. The subsequent `FFIPersister::flush` only invokes `on_flush_fn`, and a nonzero result is reported as a fatal error while the already-committed host row cannot be undone. This branch then restores the previous in-memory lock even though the host may already contain `RecoveredFromChain`, leaving the live wallet inconsistent with restart state. Separate store and flush outcomes and align `FFIPersister`'s flush disposition with its actual commit boundary so rollback occurs only when the candidate is known not to be durable; add coverage for a fatal post-store flush failure.

In `packages/rs-platform-wallet-ffi/src/shielded_send.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/shielded_send.rs:1041-1047: Preserve the typed report for fresh asset-lock funding
  The wallet method now reconciles matching already-consumed reports for every funding mode, including `AssetLockFunding::FromWalletBalance`, so this fresh-funding entry point can return `PlatformWalletError::AssetLockAlreadyConsumed`. It still maps every failure to `ErrorWalletOperation`, unlike the resume entry point and CoinJoin entry point. Swift and Kotlin therefore receive the generic wallet-operation error instead of stable code 24 for an initial BIP44-funded submission, losing the consumption-unknown signal that this PR introduced. Preserve the typed variant here while retaining the existing generic mapping for unrelated errors.

Comment thread packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs Outdated
@llbartekll

Copy link
Copy Markdown
Contributor Author

Also addressed the review-body-only FFI suggestion in 4b68180: the fresh BIP44 shielded funding entry point now preserves PlatformWalletError::AssetLockAlreadyConsumed as stable FFI code 24, while unrelated errors remain ErrorWalletOperation. The existing resume mapping test and platform-wallet-ffi Clippy pass.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Codex/Sol only (Phase 2 disabled)

The PR now preserves authenticated Core finality in a nonterminal recovery state and correctly returns the typed already-consumed result from both fresh and resumed FFI funding. One persistence-phase bug remains around a post-commit FFI store notification, while the public wording and fresh-funding regression coverage need follow-up; no blocking defects were confirmed.
Source: reviewer backend model gpt-5.6-sol (general, security-auditor, rust-quality, ffi-engineer); final verifier backend model gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — rust-quality (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

🟡 2 suggestion(s)

1 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.

🤖 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/error.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/error.rs:262-266: Do not describe the unauthenticated report as confirmed consumption
  The documentation correctly states that this variant can represent an unauthenticated Platform report, but its rendered message still says the asset lock "has already been consumed." The public FFI documentation for code 24 similarly describes the one-shot output as already consumed. This PR now returns that message and code while deliberately storing `RecoveredFromChain` because Platform-side consumption remains unknown, so logs or host UI guidance can misclassify a retryable, nonterminal recovery state as authenticated completion. Keep the stable variant and FFI discriminant, but describe the result as a Platform-reported consumption conflict whose Platform completion is unconfirmed.

In `packages/rs-platform-wallet-ffi/src/shielded_send.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/shielded_send.rs:1041-1048: Exercise the fresh-funding FFI mapping through the tested helper
  The fresh BIP44 funding entry point now correctly preserves `AssetLockAlreadyConsumed`, but it implements a separate match from the tested resume mapper. The new unit test only calls `map_asset_lock_resume_result`, so it would still pass if this fresh path regressed to the generic `ErrorWalletOperation` mapping—the exact defect fixed by the final commit. Extract a shared asset-lock funding result mapper that accepts the operation-specific context, use it from both entry points, and test that shared mapping for the typed consumed report, unrelated errors, and success.

In `packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs:216-242: Do not roll back after the FFI store has already committed the marker
  (existing thread: https://github.com/dashpay/platform/pull/4357#discussion_r3752217867)
  The fatal-flush case from the prior review is fixed, but the same durable-store rollback invariant still fails during `store()` itself. `FFIPersister::store` invokes `on_changeset_end_fn`, whose documented contract commits the host transaction, before invoking `on_store_fn`; if that later notification returns nonzero, `store()` returns a fatal error even though `RecoveredFromChain` is already durable. This branch treats every `store()` error as rollback-safe and restores the previous in-memory lock, leaving the live wallet behind the state that will be loaded after restart. Move the fallible store notification before the commit and incorporate it into `round_success`, or expose a persistence outcome that distinguishes pre-commit failures from post-commit notification failures. Add coverage for a successful end callback followed by a failing `on_store_fn`.

Comment thread packages/rs-platform-wallet/src/error.rs Outdated
Comment thread packages/rs-platform-wallet-ffi/src/shielded_send.rs Outdated

@QuantumExplorer QuantumExplorer 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.

This doesn't really make sense to me, why are we only fixing asset lock submission errors when they are to sheilded pool, shouldn't we do it in all cases?

@llbartekll

Copy link
Copy Markdown
Contributor Author

Also addressed the carried-forward post-commit on_store_fn finding in 26eb8ba. The fallible notification now runs only after the per-kind phase succeeds and before on_changeset_end_fn, so a nonzero result is included in the round verdict and the host rolls staged writes back rather than reporting an error after commit. The callback contract docs were updated, and a focused test covers both notification failure before commit and skipping the notification for an already-rejected per-kind round. The Rust/FFI boundary re-review found no blockers; focused tests, the 47-test persistence module run, fmt, and Clippy pass.

@llbartekll

llbartekll commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

@QuantumExplorer Agreed — the earlier scope explanation is no longer current. Addressed in b803e5f by moving reconciliation into the shared asset-lock orchestration layer and routing the final submit result through it for identity registration, identity top-up, platform-address funding, and shielded funding (including the InstantLock → ChainLock retry path).

The handling remains conservative: only the structured exact-outpoint consumed report is reconciled, the lock is persisted as RecoveredFromChain with a ChainLock proof, and code 24 is returned without claiming that the requested Platform operation succeeded. Unrelated or mismatched errors are unchanged. Swift/Kotlin invitation examples now map both typed and legacy reports to consumptionUnknown and retain their existing status/in-flight marker.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Codex/Sol only (Phase 2 disabled)

The three prior findings are fixed: bracketed FFI rounds now include the store notification in their rollback verdict, the public error wording states that Platform completion is unconfirmed, and fresh and resumed funding share the tested typed-result mapper. One persistence callback-contract gap remains: the fix changes the phase of a legacy callback while still being unable to guarantee rollback for supported callback configurations without a changeset-end boundary.
Source: reviewer backend model gpt-5.6-sol (general, security-auditor, rust-quality, ffi-engineer); final verifier backend model gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — rust-quality (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

🟡 1 suggestion(s)

1 additional finding(s) omitted (not in diff).

🤖 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-ffi/src/persistence.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/persistence.rs:2224-2244: Do not use the legacy store notification as the rollback boundary
  Moving `on_store_fn` before `on_changeset_end_fn` makes failures rollback-safe only for hosts that provide an atomic end callback, while changing the observable phase of an established field in the unversioned public `PersistenceCallbacks` vtable. Existing external hosts previously received `on_store_fn` after the end callback had committed the round; they can now emit an irreversible notification while writes are still staged and may subsequently be rolled back. The new ordering also remains unsafe for a supported partially wired vtable with no `on_changeset_end_fn`: a per-kind callback may write immediately, then `on_store_fn` may fail, causing `store()` to claim that the changeset was rolled back and `mark_asset_lock_consumption_unknown` to restore the old in-memory entry even though the host already contains `RecoveredFromChain`. Preserve the legacy post-commit notification phase and add a distinct size/version-negotiated pre-commit callback or persistence outcome. The synchronous recovery path must require an atomic end boundary or otherwise distinguish committed, uncommitted, and indeterminate store failures before rolling back memory.

@llbartekll

Copy link
Copy Markdown
Contributor Author

Addressed in 06265f5. I restored on_store_fn to its legacy post-commit phase instead of using it as a rollback boundary. Its return is now explicitly advisory (a nonzero value is logged, while store() remains successful because host persistence is already committed), and the focused test pins both end-before-store ordering and that rejected per-kind writes never trigger the notification. I also corrected the begin-callback documentation to match its existing fatal behavior. Verified with the 47 persistence tests, cargo fmt, and Clippy with warnings denied.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Codex/Sol only (Phase 2 disabled)

The PR correctly retains authenticated ChainLock evidence in nonterminal recovery state, flushes the recovery marker, and preserves the typed FFI result. One in-scope persistence-contract issue remains: supported legacy or partially wired FFI callback tables cannot reliably distinguish durable, rolled-back, and indeterminate asset-lock writes, so reconciliation can still leave live state inconsistent with restart state.
Source: reviewer backend model gpt-5.6-sol (general, security-auditor, rust-quality, ffi-engineer); final verifier backend model gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — rust-quality (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

🟡 1 suggestion(s)

🤖 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-ffi/src/persistence.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/persistence.rs:2253-2277: Do not use the legacy store notification as the rollback boundary
  Restoring `on_store_fn` to its legacy post-commit phase fixes the callback-ordering regression, but the synchronous recovery path still assumes `store()` can classify every result as either durable success or rollback-safe failure. That is not true for supported legacy and partially wired callback tables. Without `on_changeset_end_fn`, an asset-lock callback can write directly and then return an error; `store()` reports that the changeset was rolled back, `store_commits_inline()` returns false, and `mark_asset_lock_consumption_unknown` restores only the in-memory entry even though the host may already contain `RecoveredFromChain`. Conversely, an existing legacy host may use `on_store_fn` as its durable-write boundary; its previously propagated failure is now ignored, allowing reconciliation to succeed when that write failed. A table without `on_persist_asset_locks_fn` also silently skips the recovery row while returning success. Require an attested capability covering atomic asset-lock upsert and restart restoration before this reconciliation, or return an outcome that distinguishes committed, rolled-back, and indeterminate writes. Preserve the legacy `on_store_fn` failure contract for callback sets without a separate atomic commit boundary.

Comment thread packages/rs-platform-wallet-ffi/src/persistence.rs
@llbartekll

Copy link
Copy Markdown
Contributor Author

Addressed the new persistence-contract review in b81e364.

The reconciliation path now requires an attested combination of atomic changesets, tracked asset-lock persistence, and wallet restoration before mutating the in-memory status. FFI derives the tracked-lock leg from the concrete callback, Swift/Kotlin declare it because they wire all required callbacks, and SQLite declares only tracked-lock persistence (it still cannot satisfy the full reconciliation contract without wallet restore). Legacy hosts without an atomic end callback also keep the original nonzero on_store_fn failure behavior; post-commit failures remain advisory only when an end callback has already committed the round.

Validation:

  • platform-wallet shielded reconciliation tests: 10 passed
  • persistence capability tests: 2 passed
  • platform-wallet-ffi persistence tests: 48 passed
  • cargo check -p platform-wallet-storage
  • cargo fmt --all -- --check
  • cargo clippy -p platform-wallet -p platform-wallet-ffi --features shielded --lib --tests -- -D warnings
  • Swift source parse passed

The focused Swift test target is unavailable locally because the generated DashSDKFFI module is absent, and the Kotlin Gradle test cannot configure without an Android SDK.

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.63%. Comparing base (e0b8baa) to head (e6241a5).
⚠️ Report is 1 commits behind head on v4.2-dev.

Additional details and impacted files
@@            Coverage Diff            @@
##           v4.2-dev    #4357   +/-   ##
=========================================
  Coverage     87.63%   87.63%           
=========================================
  Files          2670     2670           
  Lines        339434   339478   +44     
=========================================
+ Hits         297451   297495   +44     
  Misses        41983    41983           
Components Coverage Δ
dpp 88.86% <ø> (ø)
drive 86.25% <ø> (ø)
drive-abci 89.66% <ø> (ø)
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.88% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 48.02% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Codex/Sol only (Phase 2 disabled)

The exact head fixes the prior persistence-contract issue: shielded asset-lock reconciliation now fails before mutating memory unless the backend attests atomic changesets, tracked asset-lock persistence, and wallet restoration, and the FFI backend intersects that declaration with the required concrete callbacks. The recovery marker is stored and flushed before acknowledgment, while legacy store-notification behavior is preserved; no in-scope findings remain. Source: reviewer backend model gpt-5.6-sol (general, security-auditor, rust-quality, ffi-engineer); final verifier backend model gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — rust-quality (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

@llbartekll

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@llbartekll
llbartekll force-pushed the codex/fix-stale-shielded-asset-lock branch from 7d08996 to e6241a5 Compare August 11, 2026 08:36
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