Skip to content

feat(rpc): scan ranged and multipath public descriptors - #1676

Merged
gosunuts merged 5 commits into
mainfrom
lidarbtc/scantxoutset-descriptors
Oct 28, 2026
Merged

gosunuts merged 5 commits into
mainfrom
lidarbtc/scantxoutset-descriptors

Conversation

@lidarbtc

@lidarbtc lidarbtc commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Closes #1600.

scantxoutset previously rejected public wildcard and multipath descriptors even though deriveaddresses could expand them. It now accepts ranged pkh, wpkh, sh(wpkh) and key-only tr inputs and feeds their scripts into the existing authoritative UTXO scan.

Behavior and limits

  • Scalar and [begin,end] ranges are inclusive. Omitted/null ranges on wildcards use [0,1000] per path. Fixed descriptors still validate and ignore a supplied range.
  • Parse all scan objects and admit at most 1,024 total script expansions across objects and paths before public-key derivation. Duplicate expansions consume work budget; retained scripts remain deduplicated with first-descriptor attribution and the existing 1 MiB script limit. A two-path wildcard with no explicit range exceeds the work budget and is rejected.
  • Emit scripts directly into the existing lookup map, checking the shared abort/shutdown/deadline before each expansion. Preserve the existing reservation, traversal, retained-tip projection and failure cleanup.
  • Reject private/secret-dependent derivation, unsupported descriptor families and repeated alternatives within a multipath specifier. Repeated alternatives return Core's error code -5 with native diagnostic text. API-35, the registry and generated RPC reference retain Deviation; this is not whole-method Core parity.

Synchronization and ownership

This changes read-only RPC input preparation, not an authoritative sync, validation or commit path. Descriptor parsing and derivation finish before the existing chain-transition exclusion is acquired. There is no new scan/index owner, persistent state or storage-format change.

The relevant obligations are SYNC-03 (bounded optional RPC work) and SYNC-05 (coherent committed consumer views). The smallest affected proofs are the scan budget/cancellation tests, scantxoutset_waits_for_one_consistent_utxo_and_tip_transition, scan_projection_uses_retained_branch_after_tip_changes, and the existing real-HTTP scantxoutset_lifecycle test. Broader convergence and foreground-performance claims are outside this change.

Validation

Linux x86_64, Rust 1.99.0, source d200d03e07478dd6d0279fe837f418c5c50308e9 based on 8fb1f30b23fe6731f7e1e26322f4ba33154ef874:

  • RPC library: 494 passed, including the affected scan and existing descriptor tests.
  • Pinned unmodified Bitcoin Core 31.1 process comparison: 3 tests passed. Core-created regtest blocks fund all four supported key forms, both branches, and inside/outside-range indexes. Comparisons check complete successful results, fixed-versus-ranged scans, first attribution, explicit/implicit origins, default endpoint 1000, the 1,024-expansion boundary, existing fixed scans/disconnect behavior and parameter errors. Duplicate-path rejection is compared by code, with the diagnostic difference explicit.
  • HTTP scan lifecycle: 1 passed. Manifest/reference coverage: 12 passed; the intentionally ignored regeneration test was run separately and passed.
  • Strict RPC all-target and affected E2E-target Clippy, workspace formatting, diff checks and the Fjall daemon build passed.
Commands and artifact identities

Cargo commands used --locked and, after dependency provisioning, --offline. Build settings: CARGO_BUILD_JOBS=4, CARGO_PROFILE_DEV_DEBUG=0, CARGO_PROFILE_TEST_DEBUG=0; isolated CARGO_TARGET_DIR=/mnt/code/git/bitcoin-rs/target/issue-1600.

cargo test --locked --offline -p bitcoin-rs-rpc --no-default-features --lib -- --quiet
cargo build --locked --offline --bin bitcoin-rs --no-default-features --features fjall
BITCOIN_RS_NODE=/mnt/code/git/bitcoin-rs/target/issue-1600/debug/bitcoin-rs cargo test --locked --offline -p bitcoin-rs-e2e --test scantxoutset -- --test-threads=1
REGEN_RPC_REFERENCE=1 cargo test --locked --offline -p bitcoin-rs-rpc --no-default-features --test manifest_coverage -- --ignored regenerate_reference
cargo test --locked --offline -p bitcoin-rs-rpc --no-default-features --test manifest_coverage --test scantxoutset_lifecycle
cargo clippy --locked --offline -p bitcoin-rs-rpc --no-default-features --all-targets -- -D warnings
cargo clippy --locked --offline -p bitcoin-rs-e2e --test scantxoutset -- -D warnings
cargo fmt --all -- --check
git diff --check
  • Core binary SHA-256: 986e63b3c8770f08d0059820ad3dd085d1ab9e1bea23946c243f858a06888a08 (repository-pinned Core 31.1).
  • Candidate daemon SHA-256: 30c89dfe2278c75d828859735e9b1d866235cef3839ab5a92533c6e99b3de817.
  • Cargo.lock SHA-256: c1ad7e974ece71b19e0a3c63b543576a5f94fee967051950578eb40843a842a3.
  • The existing E2E harness records raw request/reply comparisons, binary identities and launch settings under target/process-harness/e2e/.

Validation is scoped to Linux/regtest. Full-workspace, other-platform and mainnet-performance campaigns were not run locally.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no actionable defects were found.

Summary

Adds bounded public wildcard and multipath support to scantxoutset.

  • scantxoutset scans bounded wildcard and multipath public descriptors.
  • The lockfiles pin ctutils to version 0.4.6.

No actionable issues found. The full PR diff was reviewed because the previous and current heads have different merge bases with the target commit. No previous review threads were supplied. Tests were not run during this review.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Reserve scan] --> B[Parse descriptors and ranges]
  B --> C{Total expansions at most 1024?}
  C -->|No| D[Return error and release reservation]
  C -->|Yes| E[Expand with cancellation checks]
  E --> F[Keep distinct scripts and first attribution]
  F --> G[Scan under chain-transition exclusion]
  G --> H[Build response from retained tip]
  H --> I[Release reservation]
Loading

Reviews (5) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." · Reviewed by Greptile

Copilot AI balanced review requested due to automatic review settings October 24, 2026 08:01
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T02:07:22.022665Z dd8c63d Manual request
🔒 Security Review ✅ Completed 2026-10-24T08:04:46.167284Z d200d03 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review Summary

This PR extends scantxoutset to accept public wildcard and multipath descriptors for pkh, wpkh, sh(wpkh), and key-only tr forms. The overall design is well-structured: a two-phase approach (work budget check before any derivation) is correct and the scan-cancel integration is thorough.

Critical Finding

One logic bug was identified in window_tx_count_between at line 485 of crates/rpc/src/handlers/chain.rs — the second call to log_prefix_count passes end_id instead of start_id as the branch_tip argument. This means the start count's prefix validation walks the end node's ancestors instead of the start node's ancestors, silently dropping window_tx_count to None (field omitted from response) for any historical getchaintxstats window that falls back to the log path. Note: this line is not in the PR diff — it is a pre-existing bug, but the PR's new code in window_stats continues to call window_tx_count_between for historical selections introduced by this change's range-handling path, so it is directly relevant.

No Other Blocking Issues Found

  • The two-phase descriptor budget check (count before derive) correctly prevents hardened-path descriptors from consuming CPU before being rejected.
  • The early-return on cancellation in parse_scan_scripts (replacing the old break) is correct.
  • Network validation and duplicate-path rejection for multipath specifiers are properly enforced.
  • The scan_descriptor fixed-descriptor range handling (silently ignoring a caller-supplied range for non-wildcards) matches the documented behavior in the PR description.
  • The ScanDescriptor::script_count() arithmetic uses saturating_mul to prevent overflow on the budget check.

The pre-existing window_tx_count_between bug should be fixed before or alongside this merge to avoid silently incorrect transaction counts for historical scan windows.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 6 files

Tip: Get a Slack DM when cubic finishes reviewing your PRs. Connect Slack

Turn on auto-fix | Re-trigger cubic

Comment thread docs/contracts/external-api.md Resolved
Comment thread docs/contracts/external-api.md Resolved
Comment thread docs/contracts/external-api.md Resolved
Copilot AI balanced review requested due to automatic review settings October 25, 2026 00:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

Copilot AI balanced review requested due to automatic review settings October 25, 2026 04:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

Copy link
Copy Markdown
Member Author

Follow-up to Amazon Q's review: the suggested end_id -> start_id change in window_tx_count_between is not needed.

  • window_stats obtains start_id from node_at_height_from(selected_id, start_height). The start node is therefore an ancestor on the selected end node's branch.
  • In log_prefix_count, branch_tip selects the branch, while the separate height argument bounds the counted and validated log prefix. Its precondition allows the branch tip to be at or above that height. Passing end_id with start.height validates exactly the start prefix on the same selected branch.
  • The two calls share one log read guard and one OnceCell branch index. The end-prefix call initializes that index if it was not already initialized by count_through; changing the second call's branch-tip argument would not change the cached branch. It cannot explain the reported omission.
  • This PR does not change getchaintxstats, window_stats or window_tx_count_between. Descriptor derivation ranges in scantxoutset do not call this transaction-statistics path.

I reran cargo test --locked --offline -p bitcoin-rs-rpc --no-default-features --lib chaintxstats: 29 passed, including historical block selection, complete-log window counts, mismatched-prefix refusal and reorg branch selection. The existing behavior is preserved; this follow-up changes documentation only.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Turn on auto-fix | Re-trigger cubic

Comment thread docs/bip-support.md Resolved
Comment thread docs/contracts/external-api.md Resolved
- BIP 380/389 merged-scope rows stay Implemented; the scantxoutset
  descriptor-scan portions move to new Planned rows that link this PR,
  per the bip-adoption policy that open-PR capabilities must not be
  marked Implemented.
- external-api.md: cite the scantxoutset unit tests and e2e cases as the
  API-35 evidence alongside the CLI proof.
- Update ctutils to 0.4.6 for RUSTSEC-2026-0385.

(fmt and cargo deny verified; test lanes skipped — docs and lockfile
only.)
Copilot AI balanced review requested due to automatic review settings October 26, 2026 01:17
@devin-ai-integration

Copy link
Copy Markdown
Contributor

Pushed dd8c63d64 addressing the open review findings:

  • docs/bip-support.md: the BIP 380/389 merged-scope rows stay ✅ Implemented; the not-yet-merged scantxoutset descriptor-scan scope now has separate ☐ Planned rows linking this PR, per the bip-adoption policy that open-PR capabilities must not be marked Implemented.
  • docs/contracts/external-api.md: API-35 now cites the scantxoutset_tests unit cases plus the e2e/tests/scantxoutset.rs cases as evidence.
  • Also bumped ctutils to 0.4.6 for RUSTSEC-2026-0385 (advisories lane).

Written by Devin

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@metaphoric-dev

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: dd8c63d644

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…-descriptors

# Conflicts:
#	Cargo.lock
#	fuzz/Cargo.lock
Copilot AI balanced review requested due to automatic review settings October 27, 2026 00:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@gosunuts gosunuts left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed adaac15. No actionable defect found in the changed descriptor scan path. Aggregate expansion work is admitted before key derivation and before deduplication; cancellation and reservation cleanup remain intact, as do the existing coherent UTXO/applied-tip read boundary and first-descriptor attribution. On WSL/Linux with Rust 1.99.0, all 15 scantxoutset unit tests passed. Built the daemon from this exact checkout with fjall and ran the scantxoutset process suite against the pinned Core 31.1: all three tests passed. The documented 1,024-expansion limit and supported-descriptor deviations remain; this is not full Core parity or mainnet-performance qualification.

@gosunuts
gosunuts added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit fa187f9 Oct 28, 2026
17 checks passed
@gosunuts
gosunuts deleted the lidarbtc/scantxoutset-descriptors branch October 28, 2026 10:01
metaphoric-dev pushed a commit that referenced this pull request Oct 28, 2026
Main brings #1684 opt-in Tor/I2P overlay, #1685 public-listener
PeerRemote design, plus #1676 descriptor scan and #1677 compact-scan
bound. Branch brings state-payload plumbing (BlockPayload wire,
BodyInterest admission, index bundle validation).

Resolutions:
- crates/node/src/config.rs: keep both validations; branch
  validate_index_bundle plus main p2p.overlay.validate.
- crates/p2p/src/listener.rs send_block: main PeerRemote
  parameter on branch BlockPayload body; single-conversion entry.
- crates/p2p/src/listener.rs handle_compact_outcome: keep
  branch Option follow-up return (single enqueue_owned_body_fetch
  publisher per branch 5dee503) with main PeerRemote parameter.
  Main header-carried body_fetch_owned mark not restored: branch
  sync/headers.rs and inbound.rs intentionally supersede it with
  one atomic enqueue; restoring would reintroduce the racy
  double-mark 5dee503 deleted.
- aggregate_budget_tests.rs: main PeerEndpoint address with
  branch is_none assertion matching the Option return.
- crates/rpc/tests/core_compat.rs: main ipv4/ipv6/onion/i2p
  networks assertion (branch had dropped the stale len-3 check).
metaphoric-dev pushed a commit that referenced this pull request Oct 28, 2026
Follow-up to 19344e7: merge origin/main advance to fa187f9
(#1676 ranged/multipath descriptor scan, #1677 compact-scan bound,
ctutils 0.4.5). Clean auto-merge preserving the existing
resolution; no new conflicts.
metaphoric-dev pushed a commit that referenced this pull request Oct 28, 2026
Merges origin/main fa187f9 (#1676 ranged/multipath descriptor scan,
#1677 compact-block scan near applied tip) into the mining-pool stack.

Prior main-merge resolution (462402d) preserved verbatim: PeerRemote
listener identity, branch BlockPayload/Option ownership contract,
both config validators, PeerEndpoint budget-test addr, 4-network compat
pins, AddrMan canonical v1, mempool union.

This merge auto-resolved with no conflicts; #1676 RPC scan and #1677
sync-scope changes apply cleanly onto the resolved tree.
metaphoric-dev pushed a commit that referenced this pull request Oct 28, 2026
Merge origin/main at fa187f9 (scan ranged and multipath public
descriptors #1676; compact-block scan scoping #1677) into the
index-snapshot lane. Clean automerge; prior main-merge resolution
(2609ba4: overlay validators, PeerRemote/BlockPayload listener hybrid,
PeerEndpoint tests, four-network RPC pin) preserved untouched.
metaphoric-dev pushed a commit that referenced this pull request Oct 28, 2026
Second main merge (fa187f9: #1676 ranged/multipath descriptor
scan, #1677 compact-blocks-near-tip). Prior merge resolution
(a74ef07) preserved untouched: union config validators, main
PeerRemote addressing with payload/Option listener lineage, main
compat assertions, branch-canonical AddrMan v1.

This merge auto-resolved with no conflicts: scantxoutset handler
additions, sync compact-near-tip gating, registry/docs updates.
metaphoric-dev pushed a commit that referenced this pull request Oct 28, 2026
Merge origin/main (46de452, #1684 Tor/I2P overlay + #1676/#1677 scans) into the Lightning lane.

Listener (crates/p2p/src/listener.rs, aggregate_budget_tests.rs): main's #1685 addressing wins — PeerRemote/PeerEndpoint throughout, no ListenerRole or rpc-public-bind restored. The compact-outcome path keeps the branch's enqueue-owned-body-fetch design (Option return, single linearizable enqueue site) because the merged tree carries the branch's 3-arg send_headers/InboundHeaders and enqueue-only BlockSync API; main's bool/record variant has no matching API left post-merge. Tests updated to PeerEndpoint with the is_none assertion.

All other auto-merged regions keep both intents: branch Lightning/ZMQ/admission work plus main's overlay, scan-bound, and RPC compat changes.
metaphoric-dev pushed a commit that referenced this pull request Oct 28, 2026
Merge origin/main (fa187f9: #1676 ranged/multipath descriptor scans, #1677 compact scan bound) into the Lightning lane.

Auto-merged with no conflicts. Keeps both intents: main's #1677 COMPACT_RECONSTRUCTION_MAX_AHEAD scan bound in BlockSync alongside the branch-owned enqueue-owned-body-fetch protocol; main's PeerRemote/PeerEndpoint addressing untouched. Branch Lightning/ZMQ/admission work plus main's scantxoutset RPC surface (handlers/chain, handlers/util, registry, e2e) combined.
metaphoric-dev pushed a commit that referenced this pull request Oct 28, 2026
Follow-up: merge origin/main fa187f9 (#1676 scan ranged and multipath public descriptors; #1677 compact blocks only near the applied tip) into 5491964. No conflicts; 12 files auto-merged (Cargo.lock, crates/p2p/src/sync.rs + head_sync tests, rpc chain/util handlers + registry, e2e scantxoutset, docs, fuzz/Cargo.lock).

The listener ruling from 5491964 stands (main PeerRemote/addressing design; branch-owned BlockPayload/enqueue protocol): neither listener file is touched by this merge.
metaphoric-dev pushed a commit that referenced this pull request Oct 28, 2026
Merges origin/main (fa187f9: #1676 ranged/multipath scantxoutset scans plus #1677 compact-scan bound near the applied tip) into the 03-rpc lane (observer interest, ZMQ body interest, retained RPC allocations, submitpackage).

scantxoutset conflicts (chain.rs, util.rs, registry.rs, bip-support.md, external-api.md, rpc-reference.md): branch and main convergently implemented bounded wildcard/multipath expansion, so main's #1676 two-phase implementation wins (count all expansions including duplicates before any public-key derivation, then expand with dedup and the 1 MiB script cap). Adopted main's ScanDescriptor/scan_descriptor/expand/scan_key_script, parse flow, error strings, registry wording, API-35 docs, and proof-test references, all verified present in the merged tree. Deleted the branch 2-arg scan_descriptor shim (name collision with main's 3-arg form) and retargeted its one retained caller plus the branch fixed-form test to scan_descriptor_range, preserving their assertions. rpc-reference.md splits the hunk: main's scantxoutset row with the branch submitpackage rows, which main did not touch and which remain implemented (registry plus handlers/tx/submit_package.rs).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rpc: support bounded public ranged and multipath descriptors in scantxoutset

4 participants