Skip to content

feat(key-wallet): add chainlock handling to the wallet#756

Merged
ZocoLini merged 2 commits into
v0.42-devfrom
feat/wallet-chainlock-finalization
May 12, 2026
Merged

feat(key-wallet): add chainlock handling to the wallet#756
ZocoLini merged 2 commits into
v0.42-devfrom
feat/wallet-chainlock-finalization

Conversation

@xdustinface
Copy link
Copy Markdown
Collaborator

@xdustinface xdustinface commented May 11, 2026

Adds chainlock-driven transaction finalization in the wallet layer:

  • WalletMetadata::last_applied_chain_lock persists the highest ChainLock applied, establishing the wallet's finality boundary.
  • WalletEvent::TransactionsChainlocked carries the chainlock and the net-new finalized txids per account.
  • WalletEvent::BlockProcessed carries chain_lock: Option<ChainLock>, Some only when the block is at or below the wallet's finality boundary.

Summary by CodeRabbit

  • New Features

    • Added chainlock support to wallet processing: wallets now detect and record chainlock proofs and expose promoted transactions as a distinct event.
    • New wallet callback and logging surface for chainlock notifications and chainlock-aware block processing.
  • Tests

    • Expanded integration and unit tests to cover chainlock promotion, caching, and finality semantics across wallet flows.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fe091a41-1596-4ea7-a60f-29124b5dcd89

📥 Commits

Reviewing files that changed from the base of the PR and between 1b6074c and d643d8e.

📒 Files selected for processing (1)
  • dash-spv/src/sync/chainlock/manager.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • dash-spv/src/sync/chainlock/manager.rs

📝 Walkthrough

Walkthrough

This PR implements chainlock-driven transaction finalization across the Dash SPV wallet stack. It models chainlock state in wallet events, tracks applied chainlocks in wallet metadata, promotes in-block transactions to finalized when covered by a chainlock, coordinates chainlock dispatch during sync completion, and validates the behavior through integration and unit tests.

Changes

Chainlock Wallet Integration

Layer / File(s) Summary
Event model and FFI callback contract
key-wallet-manager/src/events.rs, dash-spv-ffi/src/callbacks.rs, dash-spv-ffi/src/bin/ffi_cli.rs
WalletEvent::BlockProcessed gains chain_lock: Option<ChainLock> and WalletEvent::TransactionsChainlocked carries proof plus per-account finalized txids. FFI callbacks extended to pass chainlock proof and deliver finalized-txid arrays.
ChainLock manager pending validation and masternode sync
dash-spv/src/sync/chainlock/manager.rs, dash-spv/src/sync/chainlock/sync_manager.rs
ChainLockManager caches highest pre-ready chainlocks in pending_validation, retries validation on masternode-ready via on_masternode_ready(), persists/promotes validated chainlocks, and rebroadcasts validated chainlocks into sync events.
Wallet metadata and WalletInterface
key-wallet/src/wallet/metadata.rs, key-wallet-manager/src/wallet_interface.rs, key-wallet-manager/src/test_utils/mock_wallet.rs
WalletMetadata records last_applied_chain_lock; WalletInterface adds apply_chain_lock; test mocks implement apply_chain_lock (panic in unsupported mocks).
Block processing and transaction context promotion
key-wallet-manager/src/process_block.rs
process_block_for_wallets sets TransactionContext::InChainLockedBlock when all wallets' applied finality boundary covers the block; finalize_block_advance includes chain_lock in BlockProcessed; apply_chain_lock helper applies chainlocks and emits TransactionsChainlocked.
ManagedAccount chainlock promotion implementation
key-wallet/src/managed_account/managed_core_keys_account.rs, key-wallet/src/managed_account/managed_core_funds_account.rs
Accounts implement apply_chain_lock to promote InBlock records at or below the chainlock height to InChainLockedBlock, returning promoted txids and optionally dropping full records when retention is disabled.
ManagedWalletInfo chainlock coordination
key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs
ManagedWalletInfo delegates to accounts, aggregates per-account finalized txids into a BTreeMap<AccountType, Vec<Txid>>, and advances metadata.last_applied_chain_lock on forward progress.
Chainlock dispatch task and sync coordinator integration
dash-spv/src/client/event_handler.rs, dash-spv/src/client/sync_coordinator.rs
New spawn_chainlock_wallet_dispatch task buffers validated chainlocks during initial SyncComplete { cycle: 0 }, serializes apply_chain_lock to the wallet, and is integrated into sync startup/error/shutdown joins.
Test helpers, integration, and FFI test wiring
dash-spv/tests/dashd_masternode/helpers.rs, dash-spv/tests/dashd_masternode/tests_chainlock.rs, dash-spv/tests/dashd_masternode/tests_instantsend.rs, dash-spv-ffi/tests/dashd_sync/callbacks.rs
Added wait_for_wallet_txs_chainlocked and wait_for_wallet_tx_chainlocked helpers; integration test mines a chainlock and asserts TransactionsChainlocked; InstantSend test extended to wait for finalization; FFI test callbacks wired for new chainlock params.
Unit test coverage for event semantics
key-wallet-manager/src/event_tests.rs, key-wallet/src/tests/keep_finalized_transactions_tests.rs
Unit tests validate promotion emission, idempotency, boundary advancement without double-emission, BlockProcessed.chain_lock consistency, and feature-flag–dependent retention behavior.

Sequence Diagram

sequenceDiagram
  participant ChainLockManager
  participant SyncManager
  participant Wallet
  participant FFI
  ChainLockManager->>SyncManager: emit SyncEvent::ChainLockReceived{validated}
  SyncManager->>Wallet: deliver ChainLock (subscribe/forward)
  Wallet->>Wallet: apply_chain_lock(chain_lock) -> promote txs, update metadata
  Wallet->>FFI: WalletEvent::TransactionsChainlocked(chain_lock, per_account)
  Wallet->>FFI: WalletEvent::BlockProcessed(..., chain_lock: Option)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A chainlock chains tx fate, finality sealed tight,
When blocks are locked and wallets know, transactions shine so bright,
The manager caches, validates, then hands the proof on through,
Metadata keeps tally, accounts promote what’s due,
Hooray — the rabbit hops, "Finality for you!" ✨

🚥 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 PR title accurately reflects the main change: adding chainlock handling support to the wallet layer, including finalization events and metadata tracking.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/wallet-chainlock-finalization

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@xdustinface xdustinface force-pushed the feat/wallet-chainlock-finalization branch from b2f1eaf to 053bdaf Compare May 11, 2026 23:28
@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

❌ Patch coverage is 66.78700% with 92 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.25%. Comparing base (78cf339) to head (d643d8e).
⚠️ Report is 3 commits behind head on v0.42-dev.

Files with missing lines Patch % Lines
dash-spv-ffi/src/callbacks.rs 15.90% 37 Missing ⚠️
dash-spv-ffi/src/bin/ffi_cli.rs 0.00% 19 Missing ⚠️
dash-spv/src/client/event_handler.rs 74.46% 12 Missing ⚠️
key-wallet-manager/src/events.rs 0.00% 8 Missing ⚠️
dash-spv/src/client/sync_coordinator.rs 66.66% 6 Missing ⚠️
...allet/managed_wallet_info/wallet_info_interface.rs 82.85% 6 Missing ⚠️
dash-spv/src/sync/chainlock/manager.rs 93.33% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           v0.42-dev     #756    +/-   ##
===========================================
  Coverage      72.25%   72.25%            
===========================================
  Files            320      320            
  Lines          69651    70275   +624     
===========================================
+ Hits           50326    50780   +454     
- Misses         19325    19495   +170     
Flag Coverage Δ
core 76.30% <ø> (ø)
ffi 46.10% <11.11%> (+0.81%) ⬆️
rpc 20.00% <ø> (ø)
spv 89.72% <82.53%> (-0.19%) ⬇️
wallet 71.26% <84.09%> (+0.04%) ⬆️
Files with missing lines Coverage Δ
dash-spv/src/sync/chainlock/sync_manager.rs 87.09% <100.00%> (ø)
key-wallet-manager/src/process_block.rs 92.83% <100.00%> (+0.35%) ⬆️
key-wallet-manager/src/wallet_interface.rs 15.00% <ø> (ø)
.../src/managed_account/managed_core_funds_account.rs 76.72% <100.00%> (+0.24%) ⬆️
...t/src/managed_account/managed_core_keys_account.rs 57.71% <100.00%> (+5.80%) ⬆️
dash-spv/src/sync/chainlock/manager.rs 93.25% <93.33%> (-0.04%) ⬇️
dash-spv/src/client/sync_coordinator.rs 76.34% <66.66%> (-1.58%) ⬇️
...allet/managed_wallet_info/wallet_info_interface.rs 83.15% <82.85%> (-0.07%) ⬇️
key-wallet-manager/src/events.rs 67.47% <0.00%> (-3.81%) ⬇️
dash-spv/src/client/event_handler.rs 93.88% <74.46%> (-2.07%) ⬇️
... and 2 more

... and 14 files with indirect coverage changes

@xdustinface xdustinface force-pushed the feat/wallet-chainlock-finalization branch from 053bdaf to bbf78d1 Compare May 11, 2026 23:39
Sets up the wallet-layer foundation for chainlock-driven transaction finalization, surfacing chainlock finality and the signing proof to consumers through wallet events.

- `WalletMetadata::last_applied_chain_lock` persists the highest `ChainLock` applied, establishing the wallet's finality boundary.
- `WalletEvent::TransactionsChainlocked` carries the chainlock and the net-new finalized txids per account.
- `WalletEvent::BlockProcessed` carries `chain_lock: Option<ChainLock>`, `Some` only when the block is at or below the wallet's finality boundary.
@xdustinface xdustinface force-pushed the feat/wallet-chainlock-finalization branch from bbf78d1 to 1b6074c Compare May 12, 2026 02:47
@xdustinface xdustinface marked this pull request as ready for review May 12, 2026 02:54
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@dash-spv/src/client/event_handler.rs`:
- Around line 159-230: Add in-module unit tests for
spawn_chainlock_wallet_dispatch that exercise the state machine: implement a
test WalletInterface stub that records calls to apply_chain_lock, spawn the task
with broadcast::channel, a CancellationToken, and on_failure mpsc; then send
SyncEvent::ChainLockReceived events (both validated = false and validated =
true) to assert that non-validated chainlocks are ignored, when initial sync is
not complete only the highest deferred_chain_lock (compare block_height) is
kept, a SyncEvent::SyncComplete { cycle: 0 } causes exactly one apply_chain_lock
of the buffered chainlock (deferred_chain_lock is cleared), and subsequent
validated ChainLockReceived events are applied immediately; also assert no extra
calls and ensure the task is shut down via shutdown.cancelled(); use tests
inside the same module with #[cfg(test)] to follow guidelines and reference
spawn_chainlock_wallet_dispatch, apply_chain_lock, SyncEvent::ChainLockReceived,
SyncEvent::SyncComplete, and deferred_chain_lock to locate the code under test.

In `@dash-spv/src/sync/chainlock/manager.rs`:
- Around line 88-99: The deferred ChainLock in pending_validation is promoted
without re-checking the block hash, which can allow an outdated header to pass;
when taking pending_validation in the readiness path inside manager.rs, re-run
the same block-hash verification used by process_chainlock() (i.e., call
verify_block_hash(...) or the same helper used there) before
validate_signature(&pending). Only if verify_block_hash returns true and
validate_signature(&pending).await succeeds should you increment progress, set
best_chainlock = Some(pending), and call save_best_chainlock().await; otherwise
treat it as invalid and call progress.add_invalid(1).
🪄 Autofix (Beta)

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

Run ID: ab2bc3ca-848b-4279-b53d-e4375e272fbb

📥 Commits

Reviewing files that changed from the base of the PR and between 29485bb and 1b6074c.

📒 Files selected for processing (21)
  • dash-spv-ffi/src/bin/ffi_cli.rs
  • dash-spv-ffi/src/callbacks.rs
  • dash-spv-ffi/tests/dashd_sync/callbacks.rs
  • dash-spv/src/client/event_handler.rs
  • dash-spv/src/client/sync_coordinator.rs
  • dash-spv/src/sync/chainlock/manager.rs
  • dash-spv/src/sync/chainlock/sync_manager.rs
  • dash-spv/tests/dashd_masternode/helpers.rs
  • dash-spv/tests/dashd_masternode/main.rs
  • dash-spv/tests/dashd_masternode/tests_chainlock.rs
  • dash-spv/tests/dashd_masternode/tests_instantsend.rs
  • key-wallet-manager/src/event_tests.rs
  • key-wallet-manager/src/events.rs
  • key-wallet-manager/src/process_block.rs
  • key-wallet-manager/src/test_utils/mock_wallet.rs
  • key-wallet-manager/src/wallet_interface.rs
  • key-wallet/src/managed_account/managed_core_funds_account.rs
  • key-wallet/src/managed_account/managed_core_keys_account.rs
  • key-wallet/src/tests/keep_finalized_transactions_tests.rs
  • key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs
  • key-wallet/src/wallet/metadata.rs

Comment thread dash-spv/src/client/event_handler.rs
Comment thread dash-spv/src/sync/chainlock/manager.rs
… ChainLock

`on_masternode_ready` was promoting `pending_validation` to `best_chainlock` after a successful BLS signature check only, but the cached chainlock's block-hash had been verified earlier under `process_chainlock`'s permissive rule: when the header for that height is still missing, `verify_block_hash` returns `true` so the chainlock isn't dropped before masternode state arrives. By the time `on_masternode_ready` fires, the header has usually resolved, and if its hash disagrees with the chainlock's claim the deferred path would persist a chainlock the local chain doesn't match.

Re-run `verify_block_hash` on the pending chainlock before calling `validate_signature`. Both must pass for promotion; a mismatch is counted as invalid and dropped, same as the live path in `process_chainlock`.
@github-actions github-actions Bot added the ready-for-review CodeRabbit has approved this PR label May 12, 2026
@ZocoLini ZocoLini merged commit 5297d61 into v0.42-dev May 12, 2026
40 checks passed
@ZocoLini ZocoLini deleted the feat/wallet-chainlock-finalization branch May 12, 2026 18:22
shumkov added a commit to dashpay/platform that referenced this pull request May 12, 2026
Picks up dashpay/rust-dashcore#756 which adds chainlock-driven
transaction finalization in the wallet layer. Previously,
`WalletInterface` had no `process_chain_lock` method and
`dash-spv`'s `SyncEvent::ChainLockReceived` was emitted but never
consumed, so wallet records were stuck at `TransactionContext::
InBlock(_)` forever even when the network produced a chainlock for
the containing block. The new pin promotes records `InBlock →
InChainLockedBlock` on chainlock arrival and emits a new
`WalletEvent::TransactionsChainlocked` variant carrying the
chainlock proof and per-account net-new finalized txids.

For our `wait_for_proof` poll loop this means the chainlock branch
(`record.context.is_chain_locked()`) actually flips when peers
deliver the chainlock — the iter-4 IS→CL fallback path now resolves
correctly instead of timing out at the secondary 180 s deadline.

The new `WalletEvent` variant forces match-arm coverage in two
sites:

- packages/rs-platform-wallet/src/changeset/core_bridge.rs
  `build_core_changeset` returns `CoreChangeSet::default()` for
  the new variant. The wallet has already mutated the in-memory
  record by the time the event fires (upstream is "mutate-then-
  emit"), and the poll loop reads `record.context.is_chain_locked()`
  directly, so no additional persister projection is needed today.
  A future enhancement could persist `WalletMetadata::
  last_applied_chain_lock` for crash recovery, but that's out of
  scope here.

- packages/rs-platform-wallet/src/wallet/core/balance_handler.rs
  `BalanceUpdateHandler::on_wallet_event` returns early for the
  new variant. Chainlocks promote finality (`InBlock →
  InChainLockedBlock`) without changing UTXO state, so there's no
  balance update to deliver.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review CodeRabbit has approved this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants