Skip to content

refactor: avoid rebuilding script_pubkeys for BIP158 filter matching#781

Open
xdustinface wants to merge 1 commit into
devfrom
refactor/address-cached-script-pubkey
Open

refactor: avoid rebuilding script_pubkeys for BIP158 filter matching#781
xdustinface wants to merge 1 commit into
devfrom
refactor/address-cached-script-pubkey

Conversation

@xdustinface
Copy link
Copy Markdown
Collaborator

@xdustinface xdustinface commented May 20, 2026

check_compact_filters_for_addresses ran address.script_pubkey() per address per filter batch, pushing OP_DUP / OP_HASH160 / hash / OP_EQUALVERIFY / OP_CHECKSIG into a fresh ScriptBuf every call. A dhat profile of a full testnet sync showed ScriptBuf::new_p2pkh was hit ~7 million times for ~172 MB of lifetime bytes, dominated by this path.

AddressInfo already stores script_pubkey: ScriptBuf from address generation. Read from that cache instead of reconstructing:

  • AddressPool, ManagedAccountType, ManagedAccountRef, and ManagedAccountTrait expose all_script_pubkeys alongside all_addresses.
  • WalletInfoInterface and WalletInterface expose monitored_script_pubkeys and monitored_script_pubkeys_for. The parallel Vec<Address> accessor is dropped, no remaining caller.
  • BlockProcessingResult.new_scripts and SyncEvent::BlockProcessed.new_scripts carry Vec<ScriptBuf> so the rescan path on FiltersBatch consumes cached scripts directly.
  • check_compact_filters_for_addresses becomes check_compact_filters_for_script_pubkeys, taking &[ScriptBuf] and handing the inner bytes to BlockFilter::match_any.

Summary by CodeRabbit

Release Notes

  • Refactor
    • Wallet synchronization has been optimized to use script pubkeys instead of addresses for filtering and matching blocks against monitored wallets. This change affects how newly discovered monitored assets are tracked during block processing and how wallets are rescanned to identify relevant transactions across the network.

Review Change Stack

`check_compact_filters_for_addresses` ran `address.script_pubkey()` per address per filter batch, pushing OP_DUP / OP_HASH160 / hash / OP_EQUALVERIFY / OP_CHECKSIG into a fresh `ScriptBuf` every call. A dhat profile of a full testnet sync showed `ScriptBuf::new_p2pkh` was hit ~7 million times for ~172 MB of lifetime bytes, dominated by this path.

`AddressInfo` already stores `script_pubkey: ScriptBuf` from address generation. Read from that cache instead of reconstructing:

- `AddressPool`, `ManagedAccountType`, `ManagedAccountRef`, and `ManagedAccountTrait` expose `all_script_pubkeys` alongside `all_addresses`.
- `WalletInfoInterface` and `WalletInterface` expose `monitored_script_pubkeys` and `monitored_script_pubkeys_for`. The parallel `Vec<Address>` accessor is dropped, no remaining caller.
- `BlockProcessingResult.new_scripts` and `SyncEvent::BlockProcessed.new_scripts` carry `Vec<ScriptBuf>` so the rescan path on `FiltersBatch` consumes cached scripts directly.
- `check_compact_filters_for_addresses` becomes `check_compact_filters_for_script_pubkeys`, taking `&[ScriptBuf]` and handing the inner bytes to `BlockFilter::match_any`.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

This PR refactors wallet monitoring across the SPV sync pipeline from address-based to script-pubkey-based tracking. The SyncEvent::BlockProcessed payload switches from carrying per-wallet discovered addresses to per-wallet discovered scriptPubKeys, with corresponding updates throughout event handling, filter matching, wallet interfaces, and tests.

Changes

Script-Based Wallet Monitoring Pipeline

Layer / File(s) Summary
Core Event and Wallet Interface Contracts
dash-spv/src/sync/events.rs, key-wallet-manager/src/wallet_interface.rs
SyncEvent::BlockProcessed enum variant changes to carry new_scripts: BTreeMap<WalletId, Vec<ScriptBuf>> instead of addresses. BlockProcessingResult struct field and WalletInterface trait method updated to expose per-wallet monitored scriptPubKeys.
Wallet Script Pubkey Accessors
key-wallet/src/managed_account/address_pool.rs, managed_account_type.rs, managed_account_ref.rs, managed_account_trait.rs, key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs
New all_script_pubkeys() methods added across wallet hierarchy to expose cached ScriptBuf values. WalletInfoInterface trait gains monitored_script_pubkeys() method aggregating scripts from all accounts.
Wallet Interface Implementation and Block Processing
key-wallet-manager/src/process_block.rs, test_utils/mock_wallet.rs
process_block_for_wallets aggregates script_pubkey values into new_scripts field. WalletInterface impl updated to provide monitored_script_pubkeys_for(). Mock wallet implementations switched from address-based to script-based monitoring.
Compact Filter Matching API
key-wallet-manager/src/matching.rs, lib.rs
check_compact_filters_for_addresses removed and replaced with check_compact_filters_for_script_pubkeys operating on &[ScriptBuf]. All tests refactored to derive scripts from addresses and validate script-based matching behavior.
Filters Batch Script Collection API
dash-spv/src/sync/filters/batch.rs
FiltersBatch internal storage changes from collected_addresses: HashMap<WalletId, HashSet<Address>> to collected_scripts: HashMap<WalletId, HashSet<ScriptBuf>>. Public API methods add_addresses_for_wallet / take_collected_addresses replaced with add_scripts_for_wallet / take_collected_scripts.
Filters Manager Rescan and Scan Logic
dash-spv/src/sync/filters/manager.rs
rescan_batch signature updated to accept new_scripts instead of addresses; matching flow builds union of scriptPubKeys and calls check_compact_filters_for_script_pubkeys. scan_batch snapshots monitored scriptPubKeys per wallet and attributes filter matches back to wallets by re-testing scripts. Unit tests updated to use script-based inputs.
Event Emission and Sync Handler Processing
dash-spv/src/sync/blocks/manager.rs, sync_manager.rs, dash-spv/src/sync/mempool/sync_manager.rs
process_buffered_blocks computes new_scripts_total and emits BlockProcessed with new_scripts field. handle_sync_event processes per-wallet scripts into batches via add_scripts_for_wallet. Mempool tests updated to construct events with correct field.
FFI Callbacks and Test Helpers
dash-spv-ffi/src/callbacks.rs, dash-spv/tests/dashd_sync/helpers.rs
FFI dispatch sums new_scripts lengths and passes count to callback. Progress event helper checks new_scripts for non-empty entries. Callback test constructs BlockProcessed with script payloads.
Integration Test Validation
key-wallet-manager/tests/spv_integration_tests.rs
Tests validate result.all_new_scripts() contents, verify each new script is present in monitored address scriptPubKey values, and check empty results use new_scripts field.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • dashpay/rust-dashcore#694: Previous refactoring of per-wallet BlockProcessed event structure; this PR replaces address-based payloads with script-pubkey-based ones.

Suggested labels

ready-for-review

Suggested reviewers

  • ZocoLini
  • QuantumExplorer

Poem

🐰 Scripts now trace what addresses once knew,
Through filter layers, new paths break through—
From address to bytes, the monitoring flows,
Each wallet's scripts in the compact prose! ✨

🚥 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 accurately describes the main objective of the PR: refactoring to avoid redundant script_pubkey rebuilding during BIP158 filter matching by using cached values instead.
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 refactor/address-cached-script-pubkey

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.

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.

🧹 Nitpick comments (2)
key-wallet-manager/src/matching.rs (1)

34-38: ⚡ Quick win

Short-circuit the empty-script case.

When script_pubkeys is empty, this still scans every filter and calls match_any on each one. An early return keeps the no-op path O(1), which fits the perf goal of this refactor.

Suggested change
 pub fn check_compact_filters_for_script_pubkeys(
     input: &HashMap<FilterMatchKey, BlockFilter>,
     script_pubkeys: &[ScriptBuf],
     min_height: CoreBlockHeight,
 ) -> BTreeSet<FilterMatchKey> {
+    if script_pubkeys.is_empty() {
+        return BTreeSet::new();
+    }
+
     let match_filter = |(key, filter): (&FilterMatchKey, &BlockFilter)| {
         if key.height() <= min_height {
             return None;
         }
🤖 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 `@key-wallet-manager/src/matching.rs` around lines 34 - 38, The function
check_compact_filters_for_script_pubkeys currently iterates all filters even
when script_pubkeys is empty; add an early return at the start of
check_compact_filters_for_script_pubkeys that returns an empty BTreeSet when
script_pubkeys.is_empty() to avoid scanning input and calling
BlockFilter::match_any unnecessarily (references:
check_compact_filters_for_script_pubkeys, FilterMatchKey, BlockFilter,
match_any).
key-wallet-manager/src/lib.rs (1)

24-24: ⚡ Quick win

Consider keeping a deprecated alias for the old public helper.

This re-export drops a previously public symbol, so downstream callers will stop compiling even though the underlying change is mostly representational. If this is not meant to be a semver-major break, keep check_compact_filters_for_addresses as a deprecated shim for one release and forward it to the new script-based helper.

Compatibility-oriented shape
-pub use matching::{check_compact_filters_for_script_pubkeys, FilterMatchKey};
+pub use matching::{
+    check_compact_filters_for_addresses,
+    check_compact_filters_for_script_pubkeys,
+    FilterMatchKey,
+};
#[deprecated(note = "use check_compact_filters_for_script_pubkeys instead")]
pub fn check_compact_filters_for_addresses(
    input: &HashMap<FilterMatchKey, BlockFilter>,
    addresses: Vec<Address>,
    min_height: CoreBlockHeight,
) -> BTreeSet<FilterMatchKey> {
    let script_pubkeys: Vec<ScriptBuf> =
        addresses.into_iter().map(|address| address.script_pubkey()).collect();
    check_compact_filters_for_script_pubkeys(input, &script_pubkeys, min_height)
}
🤖 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 `@key-wallet-manager/src/lib.rs` at line 24, The pub re-export removed the old
helper check_compact_filters_for_addresses which will break downstream code; add
a deprecated, publicly exported shim named check_compact_filters_for_addresses
that keeps the original signature (taking &HashMap<FilterMatchKey, BlockFilter>,
Vec<Address>, CoreBlockHeight) and simply maps the addresses to their
script_pubkey() values (collecting into Vec<ScriptBuf>) then calls and returns
check_compact_filters_for_script_pubkeys(input, &script_pubkeys, min_height);
annotate the shim with #[deprecated(note = "use
check_compact_filters_for_script_pubkeys instead")] so callers get a deprecation
warning but remain compatible.
🤖 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.

Nitpick comments:
In `@key-wallet-manager/src/lib.rs`:
- Line 24: The pub re-export removed the old helper
check_compact_filters_for_addresses which will break downstream code; add a
deprecated, publicly exported shim named check_compact_filters_for_addresses
that keeps the original signature (taking &HashMap<FilterMatchKey, BlockFilter>,
Vec<Address>, CoreBlockHeight) and simply maps the addresses to their
script_pubkey() values (collecting into Vec<ScriptBuf>) then calls and returns
check_compact_filters_for_script_pubkeys(input, &script_pubkeys, min_height);
annotate the shim with #[deprecated(note = "use
check_compact_filters_for_script_pubkeys instead")] so callers get a deprecation
warning but remain compatible.

In `@key-wallet-manager/src/matching.rs`:
- Around line 34-38: The function check_compact_filters_for_script_pubkeys
currently iterates all filters even when script_pubkeys is empty; add an early
return at the start of check_compact_filters_for_script_pubkeys that returns an
empty BTreeSet when script_pubkeys.is_empty() to avoid scanning input and
calling BlockFilter::match_any unnecessarily (references:
check_compact_filters_for_script_pubkeys, FilterMatchKey, BlockFilter,
match_any).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4fb404b0-e099-4395-8887-3973d192ff5c

📥 Commits

Reviewing files that changed from the base of the PR and between f569e7b and dd4558c.

📒 Files selected for processing (19)
  • dash-spv-ffi/src/callbacks.rs
  • dash-spv/src/sync/blocks/manager.rs
  • dash-spv/src/sync/events.rs
  • dash-spv/src/sync/filters/batch.rs
  • dash-spv/src/sync/filters/manager.rs
  • dash-spv/src/sync/filters/sync_manager.rs
  • dash-spv/src/sync/mempool/sync_manager.rs
  • dash-spv/tests/dashd_sync/helpers.rs
  • key-wallet-manager/src/lib.rs
  • key-wallet-manager/src/matching.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-manager/tests/spv_integration_tests.rs
  • key-wallet/src/managed_account/address_pool.rs
  • key-wallet/src/managed_account/managed_account_ref.rs
  • key-wallet/src/managed_account/managed_account_trait.rs
  • key-wallet/src/managed_account/managed_account_type.rs
  • key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs

@codecov
Copy link
Copy Markdown

codecov Bot commented May 21, 2026

Codecov Report

❌ Patch coverage is 76.36364% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.19%. Comparing base (4af39af) to head (dd4558c).
⚠️ Report is 4 commits behind head on dev.

Files with missing lines Patch % Lines
...allet/managed_wallet_info/wallet_info_interface.rs 0.00% 7 Missing ⚠️
key-wallet-manager/src/process_block.rs 16.66% 5 Missing ⚠️
...-wallet/src/managed_account/managed_account_ref.rs 0.00% 5 Missing ⚠️
key-wallet/src/managed_account/address_pool.rs 0.00% 3 Missing ⚠️
...allet/src/managed_account/managed_account_trait.rs 0.00% 3 Missing ⚠️
...wallet/src/managed_account/managed_account_type.rs 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #781      +/-   ##
==========================================
- Coverage   72.69%   72.19%   -0.50%     
==========================================
  Files         321      321              
  Lines       70350    70396      +46     
==========================================
- Hits        51140    50822     -318     
- Misses      19210    19574     +364     
Flag Coverage Δ
core 76.30% <ø> (ø)
ffi 45.60% <100.00%> (-3.55%) ⬇️
rpc 20.00% <ø> (ø)
spv 89.93% <100.00%> (+0.01%) ⬆️
wallet 71.14% <42.22%> (-0.13%) ⬇️
Files with missing lines Coverage Δ
dash-spv-ffi/src/callbacks.rs 83.39% <100.00%> (-0.19%) ⬇️
dash-spv/src/sync/blocks/manager.rs 96.93% <100.00%> (ø)
dash-spv/src/sync/events.rs 93.61% <100.00%> (ø)
dash-spv/src/sync/filters/batch.rs 97.60% <100.00%> (ø)
dash-spv/src/sync/filters/manager.rs 97.50% <100.00%> (ø)
dash-spv/src/sync/filters/sync_manager.rs 100.00% <ø> (ø)
dash-spv/src/sync/mempool/sync_manager.rs 98.72% <100.00%> (ø)
key-wallet-manager/src/lib.rs 75.80% <ø> (ø)
key-wallet-manager/src/matching.rs 95.83% <100.00%> (+0.18%) ⬆️
key-wallet-manager/src/wallet_interface.rs 15.00% <100.00%> (ø)
... and 6 more

... and 24 files with indirect coverage changes

@github-actions github-actions Bot added the ready-for-review CodeRabbit has approved this PR label May 21, 2026
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.

1 participant