Skip to content

feat(platform-wallet)!: report a freshly built asset lock's outpoint to the caller - #5476

Open
romchornyi wants to merge 13 commits into
v5.1-devfrom
feat/asset-lock-built-observer
Open

romchornyi wants to merge 13 commits into
v5.1-devfrom
feat/asset-lock-built-observer

Conversation

@romchornyi

@romchornyi romchornyi commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Basic explanation

What this does: When a wallet app moves Dash from its Core balance to the shielded pool or to a Platform address, the SDK builds a new asset lock inside one long-running call. This lets the app pass a callback that receives the new lock's outpoint as soon as the lock is signed and tracked, before the SDK saves its row or broadcasts it.

Value: The app can tell its own new lock apart from every other lock row while the call runs. Today it has to guess by wallet, funding type and creation time. That guess picks the wrong row whenever the SDK re-inserts an old lock during recovery: recovered_status brings historical locks back at Broadcast/InstantSendLocked with a fresh timestamp. With the exact outpoint the app shows a running transfer as running and never offers "Finish now" on a lock that a running call is still consuming (dashwallet-ios#1212, the follow-up to dashwallet-ios#1042 and #5460).

Risks: Low. No consensus or storage change. The callback is optional and passing none behaves as before. It runs at most once per fresh build, synchronously on the funding worker thread, before the lock's row is persisted. One error-code change: the BIP44 shielded fund and resume exports now report a broadcast rejection as ErrorTransactionBroadcastRejected (26) and an unconfirmed broadcast as ErrorTransactionBroadcastUnconfirmed (20), as the CoinJoin-drain export already did, instead of ErrorWalletOperation (6).

Issue being fixed or feature implemented

dashwallet-ios#1212 marks an asset lock as "in flight" while the funding call that consumes it is running, so the transaction details and the Home recovery surfaces neither offer a second attempt nor a removal. For a fresh transfer the app learns the lock only by polling PersistentAssetLock rows and taking the newest row of the wallet and funding type created after the call started. Review found that a reconstructed historical lock can match that filter, and that a stuck-lock resume can race the new lock before the poll claims it. Neither can be closed from the app without knowing the lock's outpoint.

What was done?

  • platform-wallet: AssetLockBuiltObserver (dyn Fn(OutPoint) + Send + Sync). broadcast_funded_asset_lock_with_funding calls it right after track_asset_lock and before the synchronous queue_asset_lock_changeset, with nothing awaited in between, so a dropped future cannot hand out an untracked outpoint. It is skipped when the build fails, the drain floor abandons it, or the wallet is gone when tracking. The observer reaches the builder through the new create_funded_asset_lock_proof_observed / …_with_funding_observed (the public wrappers pass None) and resolve_funding_with_is_timeout_fallback (the FromWalletBalance, DrainAccountBalance and DrainWalletBalance arms; resume never builds a lock). On the pooled wallet-drain path (DrainWalletBalance) the timing differs: the observer fires after operation.set(Pending, Some(out_point)) and before the durable store, and a lock whose store fails is recovered by the operation id. The FFI wallet-drain export passes None, so hosts see the callback only on the non-drain paths. Wiring tests drive every hop from a public entry point to the builder with a refused broadcast and assert one report: resolve_funding_with_is_timeout_fallback (FromWalletBalance, DrainAccountBalance), fund_from_asset_lock, fund_from_asset_lock_external, and shielded_fund_from_asset_lock for a fresh lock and for the pooled wallet drain. The Kotlin SDK does not take the callback yet: the JNI exports pass None, and adding it there is left to a follow-up (see platform-wallet: one notification model for hosts — asset-lock lifecycle events keyed by operation id through the event bus #5539, which proposes one notification model for all hosts). shielded_fund_from_asset_lock, fund_from_asset_lock and fund_from_asset_lock_external gain an on_asset_lock_built: Option<&AssetLockBuiltObserver> parameter. Identity registration/top-up, invitations and the seed pool pass None, because no host needs them yet.
  • platform-wallet-ffi: AssetLockBuiltFn, a nullable void (*)(void *context, const uint8_t *out_point_36) typedef, where out_point_36 uses the AssetLockEntryFFI.out_point layout. It is added with a context pointer to platform_wallet_manager_shielded_fund_from_asset_lock, …_coinjoin_drain, platform_address_wallet_fund_from_asset_lock_signer and …_external_signer. map_asset_lock_funding_result keeps TransactionBroadcast / TransactionBroadcastUnconfirmed typed, so a host that holds the reported outpoint can tell a removed lock (26) from a kept one.
  • swift-sdk: AssetLockBuiltHandler (@Sendable (String) -> Void, receiving the outpoint as PersistentAssetLock.outPointHex) and onAssetLockBuilt: AssetLockBuiltHandler? = nil on shieldedFundFromAssetLock, shieldedFundFromCoinJoinDrain, fundFromAssetLock and fundFromAssetLockExternal. It uses the same box/trampoline pattern as the seed-pool progress callback, with the imported AssetLockBuiltFn type.
  • rs-unified-sdk-jni: passes no callback. No Kotlin caller needs it yet.

How Has This Been Tested?

  • platform-wallet asset_lock/build.rs:
    • should_report_the_built_outpoint_before_persisting_or_broadcast: the observer runs once, with the outpoint the build returns, while the lock is tracked but not stored and the broadcaster has not been called;
    • should_not_report_an_outpoint_for_an_abandoned_build: an undersized drain never reaches the observer;
    • should_report_a_lock_whose_broadcast_is_then_rejected: reported once, then the row is removed;
    • should_keep_a_reported_lock_whose_broadcast_may_have_been_sent: reported once, and the row stays tracked and stored.
  • platform-wallet-ffi:
    • asset_lock/built_callback.rs: an absent function yields no callback, and the observer passes the caller's context and the entry-layout outpoint;
    • map_asset_lock_funding_result tests: codes 26 and 20 for fresh and resume.
  • Test runs:
    • cargo test -p platform-wallet --lib: 1430 with --all-features, 1203 default;
    • -p platform-wallet-ffi --all-features: 432;
    • -p rs-unified-sdk-jni: 43.
  • Clippy (--all-features --tests) and cargo fmt --check are clean. scripts/check_sdk_parity_manifest.py is OK.
  • build_ios.sh --target sim and a dashwallet-ios dashpay build against this SDK.

Breaking Changes

  • Rust: PlatformWallet::shielded_fund_from_asset_lock, PlatformAddressWallet::fund_from_asset_lock and fund_from_asset_lock_external take a trailing on_asset_lock_built argument. Pass None to keep the old behaviour.
  • C ABI: the four functions listed above take two trailing arguments, on_asset_lock_built and on_asset_lock_built_context. Pass NULL, NULL to keep the old behaviour.
  • Swift is source compatible: the new parameter defaults to nil.
  • Error codes: platform_wallet_manager_shielded_fund_from_asset_lock and platform_wallet_manager_shielded_resume_fund_from_asset_lock return 26 / 20 instead of 6 for a rejected / unconfirmed broadcast. The message no longer carries the shielded fund-from-asset-lock failed: prefix.

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
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · a063b37

  • Bots — thepastaclaw requested changes — dismiss the review or push a fix, 1 thread unresolved — resolve it
  • Self-review — post /self-reviewed
  • Build green
  • Approvals
    • rs-platform-wallet-ffi (packages/rs-platform-wallet-ffi/src/asset_lock/built_callback.rs, packages/rs-platform-wallet-ffi/src/asset_lock/mod.rs, packages/rs-platform-wallet-ffi/src/platform_addresses/fund_from_asset_lock.rs and 2 more) — approved by llbartekll
    • rs-platform-wallet (packages/rs-platform-wallet/src/lib.rs, packages/rs-platform-wallet/src/test_support.rs, packages/rs-platform-wallet/src/wallet/asset_lock/build.rs and 8 more) — approved by llbartekll
    • files with no dedicated owner (packages/rs-unified-sdk-jni/src/funding.rs, packages/rs-unified-sdk-jni/src/wallet_manager.rs) — QuantumExplorer or shumkov
    • swift-sdk — you own it

When every merge requirement is met, the PR Hygiene check passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.

Summary by CodeRabbit

  • New Features
    • Asset-lock funding supports optional callbacks that report a newly built lock’s transaction ID and output index, including when broadcasting fails.
    • Callbacks are available for platform-address, external, shielded, and CoinJoin-drain funding in supported SDKs. Resuming funding with an existing lock does not trigger a callback.
  • Bug Fixes
    • Shielded funding now distinguishes transaction-broadcast rejection and unconfirmed-transaction errors instead of reporting them as generic wallet-operation errors.

…to the caller

A host that watches persisted asset-lock rows while a funding call runs
cannot tell its own new lock from rows the wallet re-inserts during
recovery (`recovered_status` brings historical locks back at
Broadcast/InstantSendLocked with a fresh timestamp). Guessing by wallet,
funding type and creation time picks the wrong row.

Add an optional `on_asset_lock_built` observer to the fresh-build funding
paths: `shielded_fund_from_asset_lock`, `fund_from_asset_lock` and
`fund_from_asset_lock_external`. The asset-lock builder calls it with the
outpoint once the lock is tracked in memory, before its row is persisted
or the lock is broadcast, so no persisted row exists before the host knows
the outpoint. It does not run on resume or for a build abandoned before
tracking. It does run for a lock whose broadcast then fails; the row stays
unless the rejection is definite.

Expose it over the C ABI as an optional `AssetLockBuiltFn` plus context on
the BIP44 and CoinJoin-drain shielded fund functions and the two
platform-address fund functions, and in Swift as an
`onAssetLockBuilt` handler (default `nil`) that receives the outpoint in
`PersistentAssetLock.outPointHex` form. The JNI bindings pass no callback.

The BIP44 shielded fund export now reports a broadcast rejection and an
unconfirmed broadcast with their typed codes (26 and 20), as the
CoinJoin-drain export already does, so a host can tell a removed lock from
a kept one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v5.0.0 milestone Oct 16, 2026
romchornyi pushed a commit to dashpay/dashwallet-ios that referenced this pull request Oct 16, 2026
…SDK reports

A fresh Core → Shielded or Core → Platform transfer found its new asset
lock by polling for the newest row of its wallet and funding type created
since the attempt started. The SDK re-inserts historical locks during
recovery with a fresh timestamp, so the guess could pick another lock, and
a lock could look stuck before the poll claimed it.

The funding calls now pass `onAssetLockBuilt` (dashpay/platform#5476). The
handler claims the lock in `ShieldedAssetLocksInFlight` on the FFI worker
thread before the lock's row is persisted, the poll reads that exact row,
and the attempt takes over the claim when the call returns. "Try again"
is armed with the reported outpoint unless the broadcast was definitely
rejected.

This removes the per-wallet new-lock slot, the `mayBeUnclaimedNewLock`
resume gate and the "Another transfer is still in progress" refusal. The
registry still counts fresh transfers from before authorization, so
removals stay refused while one runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 3e5d167 and a063b37.

Files selected for processing: 13
  • packages/rs-platform-wallet-ffi/src/asset_lock/built_callback.rs
  • packages/rs-platform-wallet/src/test_support.rs
  • packages/rs-platform-wallet/src/wallet/asset_lock/build.rs
  • packages/rs-platform-wallet/src/wallet/asset_lock/mod.rs
  • packages/rs-platform-wallet/src/wallet/asset_lock/orchestration.rs
  • packages/rs-platform-wallet/src/wallet/asset_lock/sync/proof.rs
  • 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
  • packages/rs-platform-wallet/src/wallet/shielded/wallet_drain_tests.rs
  • packages/rs-unified-sdk-jni/src/funding.rs
  • packages/rs-unified-sdk-jni/src/wallet_manager.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/AssetLockBuiltCallback.swift

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

Funding APIs now accept an optional asset-lock-built callback. The wallet reports a newly built lock’s outpoint before persistence or broadcast. FFI, JNI, and Swift paths carry the callback arguments, while resume paths pass no observer.

Changes: Asset-lock built callback

Layer / File(s) Summary
Observer contract and build timing
packages/rs-platform-wallet/src/wallet/asset_lock/build.rs, packages/rs-platform-wallet/src/wallet/asset_lock/orchestration.rs, packages/rs-platform-wallet/src/wallet/asset_lock/mod.rs, packages/rs-platform-wallet/src/lib.rs, packages/rs-platform-wallet/src/wallet/asset_lock/sync/proof.rs, packages/rs-platform-wallet/src/wallet/identity/network/registration.rs, packages/rs-platform-wallet/src/wallet/shielded/seed_pool.rs
The optional observer is threaded through asset-lock construction and funding resolution. The build pipeline reports qualifying outpoints before persistence or broadcast. Resume paths and existing callers without an observer use None. The seed-pool call no longer supplies a chain-lock fallback timeout.
Wallet funding entry points and tests
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, packages/rs-platform-wallet/src/wallet/shielded/wallet_drain_tests.rs, packages/rs-platform-wallet/src/test_support.rs, packages/rs-platform-wallet-ffi/src/shielded_wallet_drain.rs
Platform-address and shielded funding APIs forward the optional observer. Tests check outpoint reporting, rejected broadcasts, tracking, and wallet-drain operation state. Test support adds chain-locked funding helpers and a signed wallet fixture. The FFI wallet-drain call passes no observer.
C callback adapter and FFI funding exports
packages/rs-platform-wallet-ffi/src/asset_lock/*, packages/rs-platform-wallet-ffi/src/platform_addresses/fund_from_asset_lock.rs, packages/rs-platform-wallet-ffi/src/shielded_send.rs, packages/rs-unified-sdk-jni/src/*
The FFI adapter stores the opaque context, serializes outpoints, and invokes the C callback. Fresh funding exports construct and forward the observer; resume calls pass none. Shielded funding preserves typed broadcast errors. JNI calls pass no callback and a null context.
Swift callback API and forwarding
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/AssetLockBuiltCallback.swift, packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformAddressWallet.swift, packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerShieldedFunding.swift, packages/swift-sdk/SwiftTests/SwiftDashSDKTests/AssetLockBuiltCallbackTests.swift
Swift adds an outpoint type, a handler type, and a C trampoline. The bridge decodes the 36-byte outpoint and keeps the handler box alive for the FFI call. Platform-address and shielded funding APIs accept and forward optional handlers. Tests cover decoding, pointer handling, byte ownership, and handler lifetime.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant FundingResolver
  participant AssetLockBuild
  participant FfiCallbackAdapter
  participant SwiftTrampoline
  participant AssetLockBuiltHandler
  FundingResolver->>AssetLockBuild: pass optional observer
  AssetLockBuild->>FfiCallbackAdapter: report built outpoint
  FfiCallbackAdapter->>SwiftTrampoline: pass serialized outpoint and context
  SwiftTrampoline->>AssetLockBuiltHandler: deliver decoded outpoint
Loading

Possibly related issues

Suggested reviewers: thepastaclaw

Merge Risk: Low · up to a063b

A narrow wallet-removal race can leave a caller without the outpoint of a broadcast lock. The PR is mergeable with that known risk explicitly accepted or addressed.

Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title "feat(platform-wallet)!: report a freshly built asset lock's outpoint to the caller" matches the main change in the changeset. The raw summary shows a new optional AssetLockBuiltObserver/Ass…
Docstring Coverage Passed Docstring coverage is inconclusive: files from earlier reviews still need to be rechecked within the check's analysis limits. Not blocking.
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.
Full details: Title check

Explanation

The title "feat(platform-wallet)!: report a freshly built asset lock's outpoint to the caller" matches the main change in the changeset. The raw summary shows a new optional AssetLockBuiltObserver/AssetLockBuiltFn callback that reports the outpoint of a newly built asset lock, threaded through Rust, FFI, and Swift funding entry points. The breaking-change marker (!) fits the summary, which notes that Rust and C ABI signatures gained required arguments. The title is specific and concise, and a teammate scanning history would understand the primary change.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai ✓ · thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@thepastaclaw

thepastaclaw commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

⛔ Final review complete — 1 blocking finding(s) (commit a063b37)

@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 — Phase 1 + Phase 2

Verified the supplied findings against exact head 13509f3. No blocking defects were confirmed; one non-blocking Swift callback test-coverage suggestion remains. Validation was static only: the supplied CI snapshot reports successful Rust workspace, Swift SDK, and Kotlin SDK checks, while the dedicated Rust wallet tests were skipped and PR Hygiene remained pending.

🟡 1 suggestion(s)

Review provenance

Source: reviewer 1: gemini-3.8-flash-high (agent: phase1-reviewer, role: general); reviewer 2: gemini-3.8-flash-high (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — The cross-language change adds asset-lock observation callbacks and typed error propagation without changing funds movement, coin selection, signing, or persistence rules themselves.
  • Phase 1 reviewers: gemini-3.8-flash-high — general (completed, effort high); agent phase1-reviewer, gemini-3.8-flash-high — rust-quality (completed, effort high); agent phase1-reviewer
  • Phase 1 model: gemini-3.8-flash-high — antigravity quota: weekly 71% left, 5h 100% left
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-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/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/AssetLockBuiltCallback.swift`:
- [SUGGESTION] packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/AssetLockBuiltCallback.swift:44-51: Add Swift tests for the optional callback bridge and outpoint decoding
  The new Swift bridge has no direct coverage in SwiftTests. The Rust callback tests verify context forwarding and raw outpoint bytes, but they do not exercise Swift's box recovery, handler invocation, or conversion into the persisted outpoint key used to identify an in-flight transfer. Add tests through `withAssetLockBuiltCallback`: assert that a nil handler supplies two nil arguments, then invoke the supplied function pointer with its context and a synthetic 36-byte buffer and verify the handler receives the expected display-order txid and vout. Use nonuniform txid bytes and a multibyte vout so the test detects byte-order regressions. The private trampoline can remain private because the helper exposes its function pointer to the test.
Out-of-scope follow-up suggestions (1)

These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.

  • Extend asset lock built observer to identity registration and top-up flows — The PR explicitly states that identity registration and top-up callers pass None because no host currently needs these notifications. Both registration and top-up retain the existing funding behavior through the shared resolver; extending their public APIs is outside the stated goal and is not required for the new funding callbacks to work.
    • Follow-up: Consider creating a separate issue or author/maintainer-requested PR for this.

@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Oct 16, 2026
`AssetLockBuiltHandler` now receives an `AssetLockBuiltOutPoint` with the
wire-order txid, the vout and the `PersistentAssetLock.outPointHex` key,
decoded once from the FFI's 36 bytes. A host no longer has to parse the
hex string back into bytes to claim or resume the lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
romchornyi pushed a commit to dashpay/dashwallet-ios that referenced this pull request Oct 16, 2026
…arsing it

`onAssetLockBuilt` now hands over an `AssetLockBuiltOutPoint` with the
wire-order txid and vout (dashpay/platform#5476), so the coordinator no
longer parses the hex string. That drops `parseOutPoint`, whose delegation
to `DWIdentityRegistrationCoordinator` did not compile in the dashwallet
target, and the unreadable-outpoint branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai ✓ · thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added waiting-bots Waiting for the review bots to report on this head and removed waiting-self-review Waiting for the author to post /self-reviewed labels Oct 16, 2026

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

Re-review — Final validation — Phase 1 + Phase 2

Static verification of the complete diff at 3fab1ac found no blocking issues; the remaining suggestion is targeted Swift callback-bridge coverage. The observer ordering, borrowed-buffer copying, and blocking-call context lifetime are consistent across the Rust, C, and Swift layers. The supplied CI snapshot reports successful Rust workspace, Swift, and Kotlin checks; the dedicated Rust wallet job was skipped and PR Hygiene remains pending.

🟡 1 suggestion(s)

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

Review provenance

Source: reviewer 1: gemini-3.8-flash-high (agent: phase1-reviewer, role: general); reviewer 2: gemini-3.8-flash-high (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 11: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 12: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — The cross-language callback plumbing and error mapping require careful review, but the diff adds notification around existing asset-lock operations rather than changing funds movement, coin selection, signing, or consensus rules.
  • Phase 1 reviewers: gemini-3.8-flash-high — general (completed, effort high); agent phase1-reviewer, gemini-3.8-flash-high — rust-quality (completed, effort high); agent phase1-reviewer
  • Phase 1 model: gemini-3.8-flash-high — antigravity quota: weekly 71% left, 5h 100% left
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-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/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/AssetLockBuiltCallback.swift`:
- [SUGGESTION] packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/AssetLockBuiltCallback.swift:70-77: Add Swift tests for the optional callback bridge and outpoint decoding
  (existing thread: https://github.com/dashpay/platform/pull/5476#discussion_r4296059400)
  No tests under SwiftTests exercise this helper or AssetLockBuiltOutPoint. The Rust tests cover observer ordering and delivery to a C callback, but cannot detect regressions in Swift's optional callback/context pairing, Unmanaged context recovery, or typed decoding. Add offline tests that verify a nil handler supplies two nil arguments and synchronously invoke the supplied callback/context with a nonuniform txid and a multi-byte little-endian vout. Assert the wire-order txid, numeric vout, and independently expected display-order outPointHex, since those values identify the persisted row and provide the resume arguments. Retain the delivered value and overwrite the source buffer to verify ownership; also cover captured-state release after normal and throwing body completion. The existing @testable import permits these tests without expanding the public API. No current decoding or lifetime defect is asserted.

@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Oct 16, 2026
…coding

Six offline tests through `withAssetLockBuiltCallback`:
- a nil handler gets no callback and no context;
- a nonuniform txid and a multi-byte vout decode in wire order, and the
  `outPointHex` row key matches a hand-built display-order string, after
  the borrowed buffer is overwritten in place;
- a NULL context or outpoint is ignored;
- a callback at the end of the body still reaches the handler;
- the handler is released after the body returns and after it throws, and
  the body's own error is rethrown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai not yet · thepastaclaw ✓. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added waiting-bots Waiting for the review bots to report on this head and removed waiting-self-review Waiting for the author to post /self-reviewed labels Oct 16, 2026

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

Re-review — Final validation — Phase 1 + Phase 2

Independently reviewed the complete diff at 4b0f4b4, including notification ordering, Rust/C/Swift callback lifetimes, outpoint encoding, broadcast-error propagation, and updated callers; no actionable in-scope defects were found. The six added Swift tests resolve the prior callback-bridge coverage finding. Validation was static only: current CI reports Kotlin build/tests and the Swift frozen-schema check passing, while Rust workspace tests remain pending; the author's reported Swift test runs were not independently executed.

🔴 0 blocking | 🟡 0 suggestion(s) | 💬 0 nitpick(s)

Review provenance

Source: reviewer 1: gemini-3.8-flash-high (agent: phase1-reviewer, role: general); reviewer 2: gemini-3.8-flash-high (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 11: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 12: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — The cross-language change adds callback plumbing and typed broadcast-error reporting without changing transaction construction, coin selection, signing, or funds-movement rules, so it does not meet the critical bar.
  • Phase 1 reviewers: gemini-3.8-flash-high — general (completed, effort high); agent phase1-reviewer, gemini-3.8-flash-high — rust-quality (completed, effort high); agent phase1-reviewer
  • Phase 1 model: gemini-3.8-flash-high — antigravity quota: weekly 71% left, 5h 100% left
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify the current code and confirm that no unresolved issues remain.

No unresolved findings remain from the prior review on this head.

@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-self-review Waiting for the author to post /self-reviewed label Oct 17, 2026
@github-actions github-actions Bot removed the waiting-self-review Waiting for the author to post /self-reviewed label Oct 23, 2026
@github-actions
github-actions Bot requested a review from llbartekll October 23, 2026 07:02
llbartekll
llbartekll previously approved these changes Oct 23, 2026

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

Re-approving at e3e4f33. The new commit adds the wiring tests through every funding entry point and narrows the observer, FFI and Swift docs, which addresses both points from my earlier comment. It changes only tests and docs. LGTM.

shumkov
shumkov previously approved these changes Oct 23, 2026

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

I'm approving the rs-unified-sdk-jni part (the files with no dedicated owner). Passing None, std::ptr::null_mut() matches the new C ABI, and the Kotlin build is green. Nothing here touches dpp, drive or consensus.

Merge order: this PR conflicts with #5460 in rs-platform-wallet/src/wallet/platform_addresses/fund_from_asset_lock.rs and wallet/shielded/fund_from_asset_lock.rs. Since this one is the follow-up, please rebase it once #5460 lands, and recheck the observer-wiring tests against #5460's InstantSend → ChainLock resubmit path.

Nit: the "yet" in "No Kotlin caller observes the built lock yet" will go stale. Something like "Kotlin hosts do not take the callback" ages better.

@github-actions

Copy link
Copy Markdown
Contributor

Policy satisfied — this can merge.
Full checklist in the description.

@github-actions github-actions Bot removed ready-for-human Bots have reported, the author has self-reviewed, and the build is green: this needs a human. bot-review-skipped A required review bot did not report; it was skipped by the window or by a person. labels Oct 23, 2026
@Claudius-Maginificent

Copy link
Copy Markdown
Collaborator

Heads-up from #5645 (DIP-11 identity creation keys), which overlaps this PR and #5460 in registration.rs, asset-lock tracking and fund_from_asset_lock.rs. It follows the ownership direction from #5460 (comment) (the wallet owns in-flight asset-lock state; the app only asks and displays):

  • IdentityWallet::resume_key_layout(outpoint) lets the wallet, not the app, decide which key layout a tracked lock resumes with. A legacy lock that cannot fund the DIP-11 key set keeps its 4 keys.
  • register_identity_with_funding refuses an underfunded wallet-balance registration before the lock is built, and raises the drain minimum_lock_duffs floor to the identity-create minimum.

Nothing in #5645 depends on the on_built outpoint callback. Expect textual conflicts in registration.rs and the drain floor; whichever PR merges second keeps both changes.

🤖 Co-authored by Claudius the Magnificent AI Agent

romchornyi and others added 2 commits October 29, 2026 15:42
…uilt-observer

Conflicts (imports only): both fund_from_asset_lock.rs keep
AssetLockBuiltObserver and drop submit_with_cl_height_retry, which
#5460 replaced with submit_with_chain_lock_fallback. The two
FromExistingAssetLock tests #5460 added in sync/proof.rs pass no
on_built observer: an existing lock is not built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…llback

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@romchornyi
romchornyi dismissed stale reviews from shumkov, llbartekll, and lklimek via 0aac302 October 29, 2026 13:42
@romchornyi

Copy link
Copy Markdown
Contributor Author

@shumkov #5460 landed, so I merged v5.1-dev in (39f9d29, a merge commit rather than a rebase, to keep the approved history).

CI here will still fail dash-sdk lib tests until #5667 lands: #5460 and #5562 together left two test calls on the old broadcast_with_retries signature on v5.1-dev.

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Oct 29, 2026
@romchornyi

Copy link
Copy Markdown
Contributor Author

@thepastaclaw review

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

Re-review — Final validation — Phase 1 + Phase 2

The callback wiring and lifetime handling are correct, and all three prior findings are fixed. However, the PR changes four existing exported C symbols by appending arguments; an already-built host using the old prototypes can cause the new function to read arbitrary ABI argument locations and potentially call an invalid callback pointer. The Rust workspace test failure appears unrelated and comes from pre-existing rs-sdk call sites, but the C ABI issue requires changes.

🔴 1 blocking | 🟡 1 suggestion(s)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 11: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 12: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — This is a large cross-cutting behavioral change spanning Rust wallet funding orchestration, Swift FFI-facing wrappers, JNI plumbing, callbacks, and broadcast error handling, but it does not itself alter consensus, cryptography, key handling, or storage migrations.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-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-ffi/src/platform_addresses/fund_from_asset_lock.rs`:
- [BLOCKING] packages/rs-platform-wallet-ffi/src/platform_addresses/fund_from_asset_lock.rs:76-90: Do not change existing C export signatures in place
  The existing exported symbol `platform_address_wallet_fund_from_asset_lock_signer` now requires `AssetLockBuiltFn` and its context, and the same in-place ABI change is made to `platform_address_wallet_fund_from_asset_lock_external_signer` and the two shielded funding exports. A host compiled against the previous C prototype will still call the unchanged symbol with the old argument count. The new callee then reads the callback and context from registers or stack locations that the old caller did not provide; if the callback value is non-null, `AssetLockBuiltCallback::notify` can call an arbitrary address, causing undefined behavior or a process crash. Marking the PR as breaking and telling source callers to pass `NULL, NULL` does not make an already-built binary compatible. Preserve the old symbols with their old signatures and add versioned/new symbols for the callback-enabled ABI, or introduce an explicit ABI version boundary and update rebuilt Swift/JNI callers to use it.

In `packages/rs-platform-wallet/src/wallet/asset_lock/build.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/asset_lock/build.rs:33: Keep the built-lock observer with the asset-lock build layer
  `build.rs` imports `AssetLockBuiltObserver` from `asset_lock::orchestration`, while `orchestration.rs` imports `AssetLockBuildAmount` from `build`. The observer describes the lifecycle boundary owned by the asset-lock builder, so placing it in `build` or `asset_lock::mod.rs` would avoid making the lower-level transaction-building module depend on the higher-level funding-choice orchestration module and would give both layers a shared downward dependency.
Out-of-scope follow-up suggestions (1)

These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.

  • Coordinate asset-lock tracking, persistence, and broadcast with wallet teardown — The asset-lock flow can still race wallet teardown between tracking, persistence, and broadcast, potentially leaving a funded transaction without durable local tracking or recovery state. The same tracking-to-broadcast sequence exists on the base revision, so this callback change does not introduce or worsen it; the issue is nevertheless a concrete funds-recovery risk.
    • Follow-up: Continue the tracked teardown-coordination fix in #5516, covering both ordinary and wallet-drain asset-lock builds with the wallet generation payment guard.

Comment on lines 76 to 90
@@ -79,6 +85,8 @@ pub unsafe extern "C" fn platform_address_wallet_fund_from_asset_lock_signer(
signer_address_handle: *mut SignerHandle,
core_signer_handle: *mut MnemonicResolverHandle,
out_changeset: *mut PlatformAddressChangeSetFFI,
on_asset_lock_built: AssetLockBuiltFn,
on_asset_lock_built_context: *mut c_void,
) -> PlatformWalletFFIResult {

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.

🔴 Blocking: Do not change existing C export signatures in place

The existing exported symbol platform_address_wallet_fund_from_asset_lock_signer now requires AssetLockBuiltFn and its context, and the same in-place ABI change is made to platform_address_wallet_fund_from_asset_lock_external_signer and the two shielded funding exports. A host compiled against the previous C prototype will still call the unchanged symbol with the old argument count. The new callee then reads the callback and context from registers or stack locations that the old caller did not provide; if the callback value is non-null, AssetLockBuiltCallback::notify can call an arbitrary address, causing undefined behavior or a process crash. Marking the PR as breaking and telling source callers to pass NULL, NULL does not make an already-built binary compatible. Preserve the old symbols with their old signatures and add versioned/new symbols for the callback-enabled ABI, or introduce an explicit ABI version boundary and update rebuilt Swift/JNI callers to use it.

source: muse-spark-1.3-contributor (phase1-reviewer: general, rust-quality)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not changing this one: these exports have no separately built consumer, so the old-binary-on-new-library case does not occur.

  • Swift links librs_unified_sdk_ffi.a from DashSDKFFI.xcframework, which build_ios.sh builds from one tree together with the cbindgen header that Swift compiles against. The released xcframework zip ships both, and dashwallet-ios builds it at its .platform-ref. A mismatched prototype is a compile error there.
  • Kotlin reaches these functions through rs-unified-sdk-jni, which calls them as Rust functions over the rlib (already updated in this PR). The Java_* entry points Kotlin calls keep their signatures, and build_android.sh drops the standalone libplatform_wallet_ffi.so.
  • Nothing in the repository loads the cdylib dynamically.

The repository does not keep these C prototypes stable across builds. The coding conventions treat even removing an extern "C" function as ordinary work described in the PR body, and there is precedent on these very exports: #3793 added surplus_output_ptr/surplus_output_len to platform_wallet_manager_shielded_fund_from_asset_lock in place, and #5649 replaced dash_sdk_derive_connect_key_with_resolver without keeping the old symbol. Versioned duplicates here would add four exports that no matching build calls. Binary stability for the FFI would be a crate-wide policy, which is worth an issue of its own if maintainers want it. This change is listed under "C ABI" in the description.

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.

The bundled Swift static library and the JNI path are rebuilt together, so those in-tree consumers are not the counterexample. However, platform-wallet-ffi still declares staticlib, cdylib, and rlib, and its README explicitly documents consuming the generated .so/.dylib from C or C++; those consumers can load a newer library with an older caller and are not protected by the synchronized build. The prior in-place changes do not establish ABI compatibility, and marking this release as breaking does not prevent the old symbol from being called with the old argument list, so the existing exports still need to retain their signatures or be supplemented with new symbols.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The README's C/C++ section builds the library from source, and the same build runs cbindgen (build.rs) to regenerate platform_wallet_ffi.h, so a consumer that follows it gets the new prototype along with the new library. No release publishes libplatform_wallet_ffi.so/.dylib: release-swift-sdk.yml ships the xcframework (static library plus header) and release-kotlin-sdk.yml ships the Android AAR, whose libdash_sdk_jni.so is called only by the Kotlin classes packaged with it. Neither artifact lets an older caller resolve a newer library.

The repository also does not treat these exports as a stable ABI: the coding conventions (book/src/contributing/coding-conventions.md, "Commits, pull requests") class removing an extern "C" function as ordinary feat:/refactor: work described in the PR body. A changed signature is no stronger a break than a removed symbol. The error-code registry is a different case: those integers are mirrored by hand in Swift and Kotlin, so a renumbering compiles cleanly and is misread. A changed prototype fails to compile against the regenerated header.

A versioned C ABI for platform-wallet-ffi (old symbols kept as wrappers, suffixed callback-capable ones added) would be a policy for the whole crate and should be settled with the maintainers in its own issue, not introduced for four functions here. I'm leaving this thread open for them.

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.

The synchronized Swift/JNI builds protect only callers rebuilt from this checkout; they do not protect a previously compiled C/C++ object or an application that links an older object against a newly built static or dynamic library. This crate explicitly produces staticlib and cdylib, and its README documents C/C++ consumption, so regenerating the header makes a source rebuild fail or succeed correctly but does not make the existing symbol ABI-safe for already-compiled callers. Prior in-place breaks are precedent for the same compatibility problem, not evidence that this change is safe; these exports still need old-signature wrappers or distinct callback-enabled symbols.

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.

Weighing in as an rs-platform-wallet-ffi owner: I agree with Roman and don't consider this blocking.

  • The repository does not treat the C prototypes as a stable ABI. book/src/contributing/coding-conventions.md classes removing an extern "C" function as ordinary feat:/refactor: work, and feat(drive): shielded fees for Shield/ShieldFromAssetLock + shield credit conservation #3793 already appended surplus_output_ptr/surplus_output_len to platform_wallet_manager_shielded_fund_from_asset_lock in place.
  • Nothing ships the cdylib on its own. The Swift release carries the xcframework and the cbindgen header built from one tree, and Kotlin reaches these functions through the JNI crate over the rlib. A caller built against the old header fails to compile against the new one; it cannot resolve the new symbol with the old arity.
  • ERROR_CODE_REGISTRY.md's "shipped host binary" rule is about the integer codes, which Swift and Kotlin mirror by hand and which renumber silently. That is a different failure mode from a changed prototype.

If maintainers want versioned C exports for this crate, that's a crate-wide policy and belongs in its own issue, not in this PR.

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.

I still consider this blocking. The repository itself documents the opposite compatibility assumption in core_wallet_signed_payment_finalize: it explicitly preserves an old C signature because a host's compiled code and DashSDKFFI.xcframework can meet at different versions, and warns that adding an argument in place can make the callee treat an absent argument as an address. That applies to these exports as well; the README documents both staticlib and independently consumable cdylib outputs, so rebuilding the generated header only protects source rebuilds, not an already-compiled C/C++ caller or separately packaged binary. The prior in-place change is precedent for the same ABI hazard, not evidence that this four-export change is safe; please retain wrappers with the old signatures or add distinct callback-enabled symbols.

use crate::ASSET_LOCK_FUNDING_SOURCES;

use super::manager::{AssetLockManager, DEFAULT_FEE_PER_KB};
use super::orchestration::AssetLockBuiltObserver;

@thepastaclaw thepastaclaw Oct 29, 2026 •

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.

✅ Resolved at a063b376; see the replies below.

🟡 Suggestion: Keep the built-lock observer with the asset-lock build layer

build.rs imports AssetLockBuiltObserver from asset_lock::orchestration, while orchestration.rs imports AssetLockBuildAmount from build. The observer describes the lifecycle boundary owned by the asset-lock builder, so placing it in build or asset_lock::mod.rs would avoid making the lower-level transaction-building module depend on the higher-level funding-choice orchestration module and would give both layers a shared downward dependency.

source: gpt-6.1-sol (phase2-reviewer: architecture-layering)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moved in a063b37: AssetLockBuiltObserver now lives in build.rs and is re-exported from asset_lock; orchestration imports it from build, so build no longer imports from orchestration outside tests.

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.

Resolved (re-reviewed at a063b376): This is fixed in a063b37: AssetLockBuiltObserver now lives in build.rs, is re-exported from asset_lock, and orchestration.rs imports it from the builder layer, removing the upward dependency.

@github-actions

Copy link
Copy Markdown
Contributor

Your move: thepastaclaw requested changes on this head; dismiss the review or push a fix; thepastaclaw left review threads unresolved; resolve them.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Oct 29, 2026
…t-lock builder

AssetLockBuiltObserver moves from orchestration.rs to build.rs, so the
builder no longer imports from the funding orchestration above it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Your move: thepastaclaw left review threads unresolved; resolve them.
Full checklist in the description.

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Your move: thepastaclaw requested changes on this head; dismiss the review or push a fix; thepastaclaw left review threads unresolved; resolve them.
Full checklist in the description.

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

Re-review — Final validation — Phase 1 + Phase 2

The observer is now correctly owned by the asset-lock build layer and is wired through the Rust, FFI, JNI, and Swift paths with focused lifecycle coverage. One blocking compatibility defect remains: four existing public C ABI symbols were changed in place by appending callback parameters, so previously compiled native callers can supply arbitrary values that may be invoked as a callback. The changed-package and runner-selection CI checks were still queued at the captured head; no related failure was reported.

🔴 1 blocking

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

Review provenance

Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 3: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: critical by gpt-6.1-sol (effort low) — This is a large, intricate cross-language change affecting asset-lock construction, tracking, broadcasting, persistence ordering, wallet draining, and funds-movement error handling in platform-wallet.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), 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; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-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-ffi/src/platform_addresses/fund_from_asset_lock.rs`:
- [BLOCKING] packages/rs-platform-wallet-ffi/src/platform_addresses/fund_from_asset_lock.rs:76-90: Do not change existing C export signatures in place
  (existing thread: https://github.com/dashpay/platform/pull/5476#discussion_r4414066754)
  `platform_address_wallet_fund_from_asset_lock_signer` and its external-signer sibling now append `AssetLockBuiltFn` and a context pointer to existing `#[no_mangle] extern "C"` symbols. The shielded funding and CoinJoin-drain exports in `shielded_send.rs` have the same change. A caller compiled against the previous header still resolves the old symbol and calls it with the old arity; the new callee can read unspecified register or stack contents as the callback and context. If the accidental callback value is non-null, `AssetLockBuiltCallback::notify` invokes it as a function pointer, potentially crashing or corrupting the host. Marking the PR as breaking and documenting `NULL, NULL` only helps callers rebuilt against the new declaration; it does not make existing binaries safe. Preserve the old symbols and signatures as wrappers that pass no observer, and add suffixed or versioned callback-capable exports for rebuilt Swift and other native consumers. The repository's FFI registry explicitly treats shipped ABI values as public and notes that shipped host binaries retain the ABI they were built against.
Out-of-scope follow-up suggestions (1)

These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.

  • Coordinate wallet teardown with in-flight asset-lock broadcasts — The non-operation asset-lock path can still reach broadcast after wallet teardown removes the wallet, potentially leaving a broadcast lock without reliable wallet-owned tracking or reservation state. This behavior predates this PR and is not fixed by the callback change; it has been tracked separately as #5516.

@romchornyi

Copy link
Copy Markdown
Contributor Author

On the open blocking finding about the C ABI (thread): this PR appends AssetLockBuiltFn and its context to four existing exports. Those are platform_address_wallet_fund_from_asset_lock_signer, its _external_signer sibling, and the two shielded funding exports in shielded_send.rs. The review asks to keep the old symbols with their old signatures and add suffixed callback-capable ones, so that a host compiled against the old header cannot call the new library with the old argument list.

I'm not doing that here, because no consumer can be in that position:

If maintainers want a stable C ABI for platform-wallet-ffi, that's a policy for the whole crate and belongs in its own issue. For this PR it would mean four wrappers with the old signatures, four new symbols, and Swift moving to the new names. I can add that here if you'd rather have it before merge.

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

Re-approving at a063b37 for rs-platform-wallet and rs-platform-wallet-ffi.

Since my approval at e3e4f33 the PR content changed in two places, and I checked both against an interdiff of the PR against its base before and after the v5.1-dev merges:

  • a063b37 moves AssetLockBuiltObserver from orchestration.rs to build.rs and re-exports it from asset_lock. The doc text and the type are unchanged; orchestration now imports from build, and the lib.rs export still resolves. The builder no longer depends upward.
  • 0aac302 rewords three JNI comments.

The merges from v5.1-dev (incl. #5460) did not disturb the wiring: on_built still reaches broadcast_funded_asset_lock_with_operation from every fresh-build arm of resolve_funding_with_is_timeout_fallback, both platform-address entry points and shielded_fund_from_asset_lock; resume paths and the identity/seed-pool callers still pass None; the four C exports and the Swift handler are as reviewed. The entry-point wiring tests from e3e4f33 are in place, and CI at this head is green (Rust workspace tests, Swift SDK build + tests, Kotlin SDK).

On the open thepastaclaw thread about changing the C export signatures in place: I replied there. I don't consider it blocking for this crate given the repo's stated conventions and the #3793 precedent, and no artifact ships the cdylib for an older caller to load.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-self-review Waiting for the author to post /self-reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants