]> Untitled Git - bdk/log
bdk
54 min agoMerge bitcoindevkit/bdk#2394: test(chain): reuse indexer helper in apply_changeset... master github/master
merge-script [Sun, 18 Oct 2026 14:54:37 +0000 (01:54 +1100)]
Merge bitcoindevkit/bdk#2394: test(chain): reuse indexer helper in apply_changeset clamp test

b7931c88fe70a1a4633873b5cf6fab805ff4a62a test(chain): reuse indexer helper in apply_changeset clamp test (busayo-OD)

Pull request description:

  ### Description

  Reuse indexer_with_last_revealed_at in apply_changeset_clamps_out_of_range_index_and_derives_correct_spk to simplify the test setup. The helper is also moved above its first use for readability.

  Follow-up to #2265.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

ACKs for top commit:
  evanlinjin:
    ACK b7931c88fe70a1a4633873b5cf6fab805ff4a62a

Tree-SHA512: 9960ae6ee4813617e107cf050786b284dc75784a5d2f54008a4fa755972e44805518d588eb09b1defdf88fb050072ab56925b6d11500e9fe7dc43549a02f7931

57 min agoMerge bitcoindevkit/bdk#2382: docs(file_store): correct Store creation and recovery...
merge-script [Sun, 18 Oct 2026 14:51:10 +0000 (01:51 +1100)]
Merge bitcoindevkit/bdk#2382: docs(file_store): correct Store creation and recovery guidance

aa60b2dad847e8ff2f0cccf30a3e59c175194c4b docs(file_store): correct Store creation and recovery guidance (luangucun)

Pull request description:

  <!-- You can erase any parts of this template not applicable to your Pull Request. -->

  ### Description

  Correct two misleading `Store` API comments:

  - `Store::create` opens the file for both reading and writing.
  - Creating a recovered store at an existing path fails; replacing the original file requires a separate operation, as the recovery example demonstrates.

  ### Notes to the reviewers

  <!-- In this section you can include notes directed to the reviewers, like explaining why some parts
  of the PR were done in a specific way -->

  ### Changelog notice

  <!-- Notice the release manager should include in the release tag message changelog -->
  <!-- See https://keepachangelog.com/en/1.0.0/ for examples -->

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### New Features:

  * [ ] I've added tests for the new feature
  * [ ] I've added docs for the new feature

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [ ] I've added tests to reproduce the issue which are now passing
  * [ ] I'm linking the issue being fixed by this PR

ACKs for top commit:
  evanlinjin:
    ACK aa60b2dad847e8ff2f0cccf30a3e59c175194c4b

Tree-SHA512: e1ec2e8437288c6ec9927b6cde64067bc6ccf25d5c6f8f6824c4e8e874485a95fdefcbdf22424e07df41ee3b20b8d36035849a02e4d3cc599d110a6b197ae77a

67 min agodocs(file_store): correct Store creation and recovery guidance
luangucun [Thu, 15 Oct 2026 10:29:24 +0000 (18:29 +0800)]
docs(file_store): correct Store creation and recovery guidance

Signed-off-by: luangucun <luangucun@outlook.com>
77 min agoMerge bitcoindevkit/bdk#2349: fix(electrum): return error when tip height overflows u32
merge-script [Sun, 18 Oct 2026 14:31:28 +0000 (01:31 +1100)]
Merge bitcoindevkit/bdk#2349: fix(electrum): return error when tip height overflows u32

92ca220ce1c3346af9ec8ab7e36dc4280dc2c385 fix(electrum): return error when tip height overflows u32 (bhuvan-somisetty)

Pull request description:

  ### Description

  `fetch_tip_and_latest_blocks` cast the server's `usize` tip height with `height as u32`, so on 64-bit a height above `u32::MAX` was silently truncated. It now uses `u32::try_from` and returns `Error::Message` on overflow.

  Fixes #2294.

  ### Changelog notice

  - Fixed: an overflowing Electrum tip height now returns an error instead of being truncated (#2294).

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [ ] I've added tests to reproduce the issue which are now passing
  * [x] I'm linking the issue being fixed by this PR

ACKs for top commit:
  evanlinjin:
    ACK 92ca220ce1c3346af9ec8ab7e36dc4280dc2c385

Tree-SHA512: 88c9a187c99ba063a07b0f1cbcfe46c05c3b51f8ddb4e0ef6e2e3e3d9da25ec12576d8739c0a6445a36802b4a20554e816e56693680ee1785025ebae45caca43

96 min agofix(electrum): return error when tip height overflows u32
bhuvan-somisetty [Thu, 8 Oct 2026 02:48:08 +0000 (08:18 +0530)]
fix(electrum): return error when tip height overflows u32

Validate that the tip height returned from `block_headers_subscribe`
fits in `u32` via `u32::try_from` instead of silently truncating with
`as u32`.

Signed-off-by: bhuvan-somisetty <somisettybhuvan5@gmail.com>
2 hours agotest(chain): reuse indexer helper in apply_changeset clamp test
busayo-OD [Sun, 18 Oct 2026 08:05:56 +0000 (09:05 +0100)]
test(chain): reuse indexer helper in apply_changeset clamp test

Reuse `indexer_with_last_revealed_at` to simplify the test setup for
`apply_changeset_clamps_out_of_range_index_and_derives_correct_spk`.

Move the helper above its first use.

2 hours agoMerge bitcoindevkit/bdk#2334: fix(electrum): error on batch response length mismatch
merge-script [Sun, 18 Oct 2026 13:37:17 +0000 (00:37 +1100)]
Merge bitcoindevkit/bdk#2334: fix(electrum): error on batch response length mismatch

3f6c74cd25a055d86f8dbffdcad3703e55e18595 fix(electrum): check batch response lengths before pairing (Brijesh-Thakkar)

Pull request description:

  ### Description

  `BdkElectrumClient` pairs each batch request with its response using `zip`, without checking that the lengths match. With a short response, trailing scripts, txids and anchors are silently dropped while `sync` returns `Ok`. A short `batch_block_header` response makes `height_to_hash[&h]` panic.

  This adds a `check_batch_len` helper, called after each of the five batch calls. It returns `Error::Message` on a length mismatch. No public API changes.

  Fixes #2305

  ### Notes to the reviewers

  The stock `electrum_client::Client` can't return a short batch (its `batch_call` waits for every request id), so this only hardens custom `ElectrumApi` implementations. No tests: a mock `ElectrumApi` takes ~180 lines of stubs to guard five one-line checks.

  This issue was found by AI, and I used Claude Code to assist with this PR.

  ### Changelog notice

  Fixed: `bdk_electrum` returns an error instead of silently truncating results or panicking when an Electrum batch response length doesn't match the request.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [ ] I've added tests to reproduce the issue which are now passing
  * [x] I'm linking the issue being fixed by this PR

ACKs for top commit:
  evanlinjin:
    ACK 3f6c74cd25a055d86f8dbffdcad3703e55e18595

Tree-SHA512: 19dc656dd5635b4209e974d2025fc42c39b0ac09c40beddcf7d51872d953ae209dccc9fb912f2809ac911670f1eaa53f809db7c9032235f3a654efd179e4e4f2

2 hours agofix(electrum): check batch response lengths before pairing
Brijesh-Thakkar [Wed, 30 Sep 2026 19:21:08 +0000 (00:51 +0530)]
fix(electrum): check batch response lengths before pairing

`BdkElectrumClient` paired every batch request with its response via
`zip` without checking that the response had one entry per request. A
short response (possible with any non-stock `ElectrumApi` implementation)
silently dropped trailing scripts, txids and anchors while `sync` still
returned `Ok`, and a short `batch_block_header` response left
`height_to_hash` incomplete so `height_to_hash[&h]` panicked.

Add `check_batch_len` and call it after each of the five batch calls
(`batch_script_get_history` x3, `batch_block_header`,
`batch_transaction_get_merkle`), returning `Error::Message` on any
length mismatch, in line with the other server-misbehaviour checks in
this crate.

Fixes #2305

4 hours agoMerge bitcoindevkit/bdk#2361: fix(esplora): stop paging when the server does not...
merge-script [Sun, 18 Oct 2026 10:54:08 +0000 (21:54 +1100)]
Merge bitcoindevkit/bdk#2361: fix(esplora): stop paging when the server does not advance the cursor

19ba4e3d67516e4a3045b9870ccf7bfd6173579e fix(esplora): stop paging when the server does not advance the cursor (luhrhenz)

Pull request description:

  ### Description

  Fixes #2339.

  `bdk_esplora` pages each script pubkey's history by passing the previous page's last txid as the cursor, and stops only on a page of fewer than 25 txs. A server that ignores the cursor returns the same full page forever, so `sync`/`full_scan` never returns and memory grows without bound.

  The loop now records every cursor it pages from and returns `InvalidResponse` when a full page ends on one it has already seen. This catches both a repeated page and a cycle (`A -> B -> A`). Applied to both `async_ext.rs` and `blocking_ext.rs`.

  ### Notes to the reviewers

  - It returns an error rather than `break`ing, so that `sync` never reports a truncated history as success.
  - There is no page cap, so long histories aren't truncated. A correct server never repeats a cursor, so the check can't trigger on one.
  - The regression tests use a small `TcpListener` stub in `tests/common` that always returns the same page, since `TestEnv` can't fake a misbehaving server. `tokio`'s `time` dev-dep feature is for the async test's timeout.

  ### Changelog notice

  - Fixed `bdk_esplora`'s pagination loop hanging when a chain source returns a page that does not advance the `last_seen` cursor.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [x] I've added tests to reproduce the issue which are now passing
  * [x] I'm linking the issue being fixed by this PR

ACKs for top commit:
  evanlinjin:
    ACK 19ba4e3d67516e4a3045b9870ccf7bfd6173579e

Tree-SHA512: c67af0009ad6e84cf13d681aedcb4bf241bc22199d7cf32c76d0f0e2559bdc01253856523262f7016f31d1ae0b834bd1a4fc4a513232fe22b827d507da4b538d

5 hours agoMerge bitcoindevkit/bdk#2325: fix(electrum): propagate transport errors when syncing...
merge-script [Sun, 18 Oct 2026 10:39:52 +0000 (21:39 +1100)]
Merge bitcoindevkit/bdk#2325: fix(electrum): propagate transport errors when syncing outpoints

edbf4a38847f0fc96f7aad72fbda0221d9c7e063 test(electrum): cover transport errors when syncing outpoints (chaitika)
84b0acf323e2f95919f39a3c3102925ce64174dd fix(electrum): propagate transport errors when syncing outpoints (chaitika)

Pull request description:

  ### Description

  Fixes #2293

  `populate_with_outpoints` discarded every `fetch_tx` error, so a connection/TLS/timeout failure made `sync` return `Ok` without data for the outpoint.

  It now skips only on `Error::Protocol` and propagates other errors, matching `populate_with_txids`.

  ### Changelog notice

   - `bdk_electrum`: `sync` now returns an error instead of silently skipping an outpoint when fetching its transaction fails for a non-protocol reason

  ### Checklists
  #### All Submissions:
  * [X] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  #### Bugfixes:
  * [ ] This pull request breaks the existing API
  * [X] I've added tests to reproduce the issue which are now passing
  * [X] I'm linking the issue being fixed by this PR

ACKs for top commit:
  evanlinjin:
    ACK edbf4a38847f0fc96f7aad72fbda0221d9c7e063

Tree-SHA512: 182464b58b9ea3a1637f4b9fb9a1b0aff878b48aea806691c7938ca14459711043a4695402b739a000c5744cc6541f17cdaa68964a38536524213ded7db83a09

5 hours agoMerge bitcoindevkit/bdk#2377: fix(chain): don't panic on overlapping descriptors...
merge-script [Sun, 18 Oct 2026 10:18:54 +0000 (21:18 +1100)]
Merge bitcoindevkit/bdk#2377: fix(chain): don't panic on overlapping descriptors in release

bb7373ba163fccf578113a601e3e4fb5205bc230 fix(chain): don't panic on overlapping descriptors in release (志宇)

Pull request description:

  ### Description

  Fixes #2277. Supersedes #2327.

  Overlapping descriptors stay a caller contract violation. The docs now say so (debug builds panic), and `reveal_next_spk` returns `None` instead of panicking in release.

  ### Notes to the reviewers

  The regression test is gated on `cfg(not(debug_assertions))`, so it only runs with `--release` (not in CI).

  ### Changelog notice

  * Fixed: `KeychainTxOutIndex::reveal_next_spk` no longer panics in release builds on overlapping descriptors.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [x] I've added tests to reproduce the issue which are now passing
  * [x] I'm linking the issue being fixed by this PR

  🤖 Generated with [Claude Code](https://claude.com/claude-code)

ACKs for top commit:
  oleonardolima:
    utACK bb7373ba163fccf578113a601e3e4fb5205bc230
  evanlinjin:
    self-ACK bb7373ba163fccf578113a601e3e4fb5205bc230

Tree-SHA512: 4f65e9ebbb35ec09528c6c29dda9e898939c75ce72033ca9335180d5d6160ab2877d5091379d85d08621a07e75d0da34e2e8162743e5c3c33654885a6dffef93

6 hours agofix(esplora): stop paging when the server does not advance the cursor
luhrhenz [Fri, 9 Oct 2026 14:43:14 +0000 (15:43 +0100)]
fix(esplora): stop paging when the server does not advance the cursor

The per-script-pubkey pagination loop paged forward by asking for the
transactions following the last txid of the previous page, but never
checked that the cursor moved. A server that answers every request with
the same full page of 25 transactions kept the cursor in place, so the
loop re-requested that page forever while appending to an unbounded
`Vec`, and `sync`/`full_scan` never returned.

Track every cursor paged from, and return `InvalidResponse` if a full
page ends on one we have already seen. This catches a cursor that repeats
straight away as well as one that cycles (`A -> B -> A`). Erroring rather
than breaking avoids silently handing the caller a truncated transaction
history, which is how `chain_update` already treats a server it cannot
reconcile with.

Closes #2339

6 hours agoMerge bitcoindevkit/bdk#2335: fix(chain): reject an occupied index in insert_spk
merge-script [Sun, 18 Oct 2026 09:32:32 +0000 (20:32 +1100)]
Merge bitcoindevkit/bdk#2335: fix(chain): reject an occupied index in insert_spk

ee1909efdbe53261e268819cf7d1cd9260a03df0 fix(chain): reject an occupied index in insert_spk (hassan)

Pull request description:

  ### Description

  Fixes #2279.

  SpkTxOutIndex::insert_spk currently accepts a different script at an existing index. The forward lookup is overwritten while the old reverse lookup remains, so the two maps disagree.

  This returns false without changing state when the index is already occupied, matching the existing behavior for an occupied script. The method documentation now covers both cases.

  ### Notes to the reviewers

  Added regressions for both lookup directions, unused scripts and previously scanned outputs. A rejected insertion preserves the old output and used status, and the rejected script is not tracked.

  Testing on the refreshed master base:
  - Rust 1.85: bdk_chain with all features, 99 tests/doctests pass, 1 ignored.
  - Changed-crate clippy with all targets and features passes with warnings denied.
  - Workspace formatting check passes.
  - Full workspace pre-push suite has not been rerun on this base.

  ### Changelog notice

  Reject duplicate indices in SpkTxOutIndex::insert_spk without changing existing script or output state.

ACKs for top commit:
  evanlinjin:
    ACK ee1909efdbe53261e268819cf7d1cd9260a03df0

Tree-SHA512: 21c1220dabc4c36cc8161eeb7f8b0284baac6c575b64aac5802dfec04416dbdf964f672b0aad36f3cfe53271709c9f698ba644c3f018bb149848866896e7c43f

6 hours agofix(chain): reject an occupied index in insert_spk
hassan [Fri, 2 Oct 2026 00:18:20 +0000 (05:48 +0530)]
fix(chain): reject an occupied index in insert_spk

Reject an occupied index before changing either lookup map. Preserve existing script, scanned-output and used-status mappings on a rejected insertion, matching the existing behavior for an occupied script.

Signed-off-by: hassan <144530465+h55n@users.noreply.github.com>
11 hours agoMerge bitcoindevkit/bdk#2376: docs(chain): document that `apply_block` trusts the...
merge-script [Sun, 18 Oct 2026 04:08:23 +0000 (15:08 +1100)]
Merge bitcoindevkit/bdk#2376: docs(chain): document that `apply_block` trusts the given block

2cce69780e6ca215c450cb9d8ebd1c752339c99b docs(chain): document that `apply_block` trusts the given block (志宇)

Pull request description:

  Fixes #2278. Supersedes #2373.

  Documents that `apply_block` and `apply_block_relevant` don't validate the block (chain membership or merkle root), so the caller must supply it from a trusted source. No `debug_assert!`: a bad block comes from a bad source at runtime, where the assert is compiled out.

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  🤖 Generated with [Claude Code](https://claude.com/claude-code)

ACKs for top commit:
  oleonardolima:
    utACK 2cce69780e6ca215c450cb9d8ebd1c752339c99b

Tree-SHA512: bf155a1b7c94a5e23078b23449085f028a76852e72c0d6c2a52867337398c3f9331eb8072a52fb57d5b093d9afd685d3b901d540513f0557656910e110f312a3

12 hours agoMerge bitcoindevkit/bdk#2265: fix(chain): sanitize and bound-check derivation indices...
merge-script [Sun, 18 Oct 2026 03:34:48 +0000 (14:34 +1100)]
Merge bitcoindevkit/bdk#2265: fix(chain): sanitize and bound-check derivation indices near BIP32_MAX_INDEX

1ca8d29854d41269982a1f47b03603a98a221d99 fix(chain): fix integer overflow in lookahead_to_target (busayo-OD)
de43ac6941bd7196f1421bac3646e5dafea83290 fix(chain): fix off-by-one in replenish_inner_index stop bound (busayo-OD)
803fd968d7999bda911eb2b968cd54a585fac8b4 fix(chain): clamp derivation index before apply_changeset (busayo-OD)

Pull request description:

  Fixes [bdk_wallet/issues/60](https://github.com/bitcoindevkit/bdk_wallet/issues/60)

  ### Description

  `KeychainTxOutIndex::apply_changeset` accepted `ChangeSet::last_revealed` values with no upper bound, allowing an index greater than `BIP32_MAX_INDEX` (2^31 - 1) to be stored, violating an invariant relied on by `next_index()`.

  The changes in this PR address boundary issues around derivation indices:

  1. **`apply_changeset`**: clamps `last_revealed` to `BIP32_MAX_INDEX` before storing it, instead of storing the raw value.
  2. **`replenish_inner_index`**: fixes an off-by-one in the `stop_index` calculation that caused a panic (`"we just inserted it"`) when a keychain's derivation index was revealed exactly up to `BIP32_MAX_INDEX`. The exclusive upper bound was clamped to `BIP32_MAX_INDEX` instead of `BIP32_MAX_INDEX + 1`, silently excluding the boundary value itself.
  3. **`lookahead_to_target`**: fixes an integer overflow panic when called with `target_index == u32::MAX`, and changes out of range targets to clamp and replenish up to `BIP32_MAX_INDEX` instead of silently doing nothing.

  ### Notes to the reviewers

  This revisits the earlier implementation attempt (#1792) and implements the clamp-in-`apply_changeset` approach from that review discussion.

  The second and third fixes were found while adding the boundary tests requested in review of an earlier version of this PR.

  Also added a test documenting current saturation behavior: `reveal_next_spk`/`next_unused_spk` return the last revealed script with an empty `ChangeSet`, not `None`, once the index is saturated. This matches `reveal_next_spk`'s existing doc comment.

  Went through the other index-taking methods (`spk_at_index`, `is_used`, `mark_used`, `unmark_used`) as well. These are plain lookups with no boundary-sensitive logic, so no dedicated tests were added there.

  ### Changelog notice

  - Clamped `last_revealed` to `BIP32_MAX_INDEX` in `KeychainTxOutIndex::apply_changeset` to prevent it accepting an out-of-range derivation index.
  - Fixed an off-by-one in `replenish_inner_index` that could panic when revealing up to `BIP32_MAX_INDEX`.
  - Fixed an integer overflow in `lookahead_to_target` when called with `target_index == u32::MAX`, and changed out of range targets to clamp to `BIP32_MAX_INDEX` instead of silently doing nothing.

  ### Checklists

  **All Submissions**

  - [x] I've signed all my commits
  - [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  - [x]  I ran `just p` before committing

  **Bugfixes**

  - [ ] This pull request breaks the existing API
  - [x] I've added tests to cover the issue which are now passing
  - [x] I'm linking the issue being fixed by this PR

ACKs for top commit:
  evanlinjin:
    ACK 1ca8d29854d41269982a1f47b03603a98a221d99
  notmandatory:
    ACK 1ca8d29854d41269982a1f47b03603a98a221d99

Tree-SHA512: 2e2f86b24772424fe716453f76b5628d55121a70f7fe493f2bed7ca42a662ee705e94d79488f6e71c1d442570f81d863b4f59081bda1cddfbef59435483167e1

39 hours agoMerge bitcoindevkit/bdk#2381: docs(core): require `CheckPoint` data to be determined...
merge-script [Fri, 16 Oct 2026 23:50:08 +0000 (23:50 +0000)]
Merge bitcoindevkit/bdk#2381: docs(core): require `CheckPoint` data to be determined by its block hash

fbe1aa6335276b86f6d35fa409f473f8c797e02f docs(core): require `CheckPoint` data to be determined by its block hash (志宇)

Pull request description:

  ### Description

  The `ToBlockHash` docs said extra fields the block hash doesn't commit to "must be ignored", implying they're allowed. They aren't: `CheckPoint` and `LocalChain` treat data with equal hashes as interchangeable. The docs now require checkpoint data to be fully determined by its block hash, with anything else stored outside the checkpoint.

  The `BlockWithTime` test helper now commits to its time field.

  ### Changelog notice

  ```
  ### Changed
  - Documented that `CheckPoint` data must be fully determined by its block hash.
  ```

  ### Checklists

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  🤖 Generated with [Claude Code](https://claude.com/claude-code)

ACKs for top commit:
  oleonardolima:
    utACK fbe1aa6335276b86f6d35fa409f473f8c797e02f

Tree-SHA512: b9082833ce10149ac8be4a082537e542b59dd3748c65b6bed6912486d1ecc664bbc88966775c78db3d5cd9050e48b0a56078b775505a147cd90c0d351dd6aa27

2 days agoMerge bitcoindevkit/bdk#2372: fix(chain): skip coinbase txs with a `last_seen` in...
merge-script [Fri, 16 Oct 2026 03:57:26 +0000 (03:57 +0000)]
Merge bitcoindevkit/bdk#2372: fix(chain): skip coinbase txs with a `last_seen` in canonicalization

a921c78d7aaad2c231dc5c9919e8b9b04b105954 fix(chain): skip coinbase txs with a `last_seen` in canonicalization (志宇)

Pull request description:

  ### Description

  Alternative to #2322, taking the approach outlined in https://github.com/bitcoindevkit/bdk/pull/2322#issuecomment-6136246396. Addresses part of #2269.

  - **`SeenTxs` now skips coinbase txs.** A coinbase cannot be in the mempool, so a `last_seen` value on one is bad data and must not make it canonical. This was only a `debug_assert!`, so release builds canonicalized it at `ChainPosition::Unconfirmed`. The check now mirrors the one in `LeftOverTxs`.
  - **`CanonicalTxOut::is_mature` no longer asserts on a coinbase without a confirmation height.** It returns `false` (maturity cannot be proven), which is what release builds already did.

  Unlike #2322, a coinbase that is an ancestor of a canonical tx, or is in `assume_canonical`, stays canonical even when no anchor is held for it. A canonical spend of a coinbase implies the coinbase is in the chain, and excluding it leaves the view with a canonical tx whose parent is missing.

  ### Notes to the reviewers

  This does not change what `ChainPosition` such a coinbase resolves to: it is still `Unconfirmed`. How (or whether) to represent "canonical coinbase, no anchor held" is a breaking design question and is left for a separate issue.

  The `is_mature` assert was reachable through `balance()` on master: an unanchored coinbase with an unspent output, pulled in by an unconfirmed spend of another of its outputs. The second new test scenario covers this.

  ### Changelog notice

  - Fixed: a coinbase tx with a `last_seen` value is no longer canonicalized in release builds.
  - Fixed: `CanonicalTxOut::is_mature` no longer panics in debug builds for a canonical coinbase with no known anchor; it returns `false`.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [x] I've added tests to reproduce the issue which are now passing
  * [x] I'm linking the issue being fixed by this PR

  🤖 Generated with [Claude Code](https://claude.com/claude-code)

ACKs for top commit:
  noahjoeris:
    ACK a921c78d7aaad2c231dc5c9919e8b9b04b105954

Tree-SHA512: 495acb3f5c4c1c04db7d2162853cb926705b31a2b88cd4be6c68d558465869354d579bbb6826ef0e2bd904696ce4bed67875bddb488f0f41bef6f3bc2fc6f0bc

3 days agodocs(core): require `CheckPoint` data to be determined by its block hash
志宇 [Thu, 15 Oct 2026 10:11:16 +0000 (10:11 +0000)]
docs(core): require `CheckPoint` data to be determined by its block hash

The `ToBlockHash` docs allowed data types with extra fields that the
block hash does not commit to. `CheckPoint` and `LocalChain` treat data
with equal hashes as interchangeable, so such fields could be silently
kept or dropped. State that the data must be fully determined by its
block hash and that anything else belongs outside the checkpoint.

Make the `BlockWithTime` test helper commit to its time field so it
follows the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days agoMerge bitcoindevkit/bdk#2233: test(core): cover SyncRequest inspect progress and...
merge-script [Thu, 15 Oct 2026 09:19:37 +0000 (09:19 +0000)]
Merge bitcoindevkit/bdk#2233: test(core): cover SyncRequest inspect progress and expected txid merging

6c363335cf0462afdb8b467fd5385139cd30d1fa test(core): cover SyncRequest inspect progress and expected txid merging (haoshengzhen)

Pull request description:

  ### Description

  Adds unit tests for `SyncRequest` in `bdk_core::spk_client`:
  - the `inspect` callback sees progress *after* each spk/txid/outpoint is consumed.
  - `expected_spk_txids` merges txids across calls for the same spk, and other spks get none.

  ### Changelog notice

  None, test-only.

ACKs for top commit:
  evanlinjin:
    ACK 6c363335cf0462afdb8b467fd5385139cd30d1fa

Tree-SHA512: 532754e960ea14a665f1d59e2349bf0ecd270d1beb4ded8636d0c569bff0e60c3224ba28263b5187689c993e34180d9585cc6abe2024209ce2c20faf443124dd

3 days agotest(core): cover SyncRequest inspect progress and expected txid merging
haoshengzhen [Fri, 19 Jun 2026 02:30:23 +0000 (10:30 +0800)]
test(core): cover SyncRequest inspect progress and expected txid merging

Signed-off-by: haoshengzhen <haoshengzhen@outlook.com>
Co-Authored-By: 志宇 <hello@evanlinjin.me>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days agoMerge bitcoindevkit/bdk#2231: refactor(testenv)!: make `wait_until_electrum_sees_bloc...
merge-script [Thu, 15 Oct 2026 06:38:04 +0000 (06:38 +0000)]
Merge bitcoindevkit/bdk#2231: refactor(testenv)!: make `wait_until_electrum_sees_block` concrete

a98cf7c48664c37a717c3160883ece6ceaed1684 refactor(testenv)!: make `wait_until_electrum_sees_block` concrete (Claude)

Pull request description:

  ### Description

  A from-scratch reimplementation of bitcoindevkit/bdk#1640 on top of the current `master`.

  `TestEnv::wait_until_electrum_sees_block` previously only took a `timeout` and returned as soon as Electrum emitted *any* new-block notification. That made it ambiguous which block was actually being waited for, and it could return before Electrum had caught up to the block the test cared about.

  This PR makes the wait concrete:

  - `wait_until_electrum_sees_block` now takes `block_height: usize` and `block_hash: Option<BlockHash>`, so callers state exactly which block Electrum must be aware of. If the notified tip has already advanced past `block_height`, the header at that height is fetched explicitly so the expected `block_hash` can still be verified.
  - A new convenience wrapper `wait_until_electrum_tip_syncs_with_bitcoind` waits until Electrum's tip matches bitcoind's chain tip (both height and hash). All existing call sites are migrated to it, since in every case the intent was "wait until Electrum catches up to the freshly mined/reorged tip".

  ### Notes to the reviewers

  - **Why subscribe instead of polling:** polling Electrs for the header at `block_height` and checking its hash does not guarantee the spk histories are current. Receiving a block-tip notification does, because of how Electrs indexes. This is captured in a `NOTE` in the code.
  - **Tip advanced past target:** the original PR's reviewers asked what happens when the method is called after the chain tip has moved past the target height. This is handled explicitly: when the notified height is greater than `block_height`, the header at `block_height` is fetched directly to verify the hash, rather than relying on the notification header.
  - **`trigger()` placement:** `electrsd.trigger()` is kept inside the polling loop (matching the previous `master` behaviour) so Electrs keeps getting nudged to re-index — this is important for reorgs, where the tip changes without a height change.
  - The previous WIP artefacts from #1640 (a `// TODO: Fails here.` marker and a struck-through doc comment) are intentionally left out.

  ### Changelog notice

  - Changed: `TestEnv::wait_until_electrum_sees_block` now takes a `block_height` and an optional `block_hash` (breaking change to `bdk_testenv`).
  - Added: `TestEnv::wait_until_electrum_tip_syncs_with_bitcoind`.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### New Features:

  * [x] I've added docs for the new feature

  #### Bugfixes:

  * [x] This pull request breaks the existing API

  https://claude.ai/code/session_016gJvi98Q65QnanBFxe2UUn

  ---
  _Generated by [Claude Code](https://claude.ai/code/session_016gJvi98Q65QnanBFxe2UUn)_

ACKs for top commit:
  evanlinjin:
    ACK a98cf7c48664c37a717c3160883ece6ceaed1684

Tree-SHA512: 563934e0e1af7261facdb2b52fad15a45b1c364e143f99dee72d185b273ad716754313b069b8369cf09bfc22dab24fa187c7ffd0986285e546358b5ecd3e60ae

3 days agorefactor(testenv)!: make `wait_until_electrum_sees_block` concrete
Claude [Mon, 15 Jun 2026 21:36:50 +0000 (21:36 +0000)]
refactor(testenv)!: make `wait_until_electrum_sees_block` concrete

Previously `TestEnv::wait_until_electrum_sees_block` only took a `timeout`
and returned as soon as Electrum emitted any new-block notification, so it
was not clear which block was actually being waited for.

Add `block_height` and an optional `block_hash` so callers state exactly
which block Electrum must be aware of. When the notified tip has already
advanced past `block_height`, the header at that height is fetched
explicitly so the expected `block_hash` can still be verified.

Introduce `TestEnv::wait_until_electrum_tip_syncs_with_bitcoind`, a
convenience wrapper that waits until Electrum's tip matches bitcoind's
chain tip (both height and hash), and migrate all existing call sites to
it.

https://claude.ai/code/session_016gJvi98Q65QnanBFxe2UUn

3 days agoMerge bitcoindevkit/bdk#2316: fix(rpc): verify fetched mempool txids before caching...
merge-script [Thu, 15 Oct 2026 03:42:01 +0000 (03:42 +0000)]
Merge bitcoindevkit/bdk#2316: fix(rpc): verify fetched mempool txids before caching and document trust assumption

0d6fb41ac4a268675193beee31050343331996f8 fix(rpc): verify fetched mempool tx txids (Vihiga Tyonum)

Pull request description:

  ### Description

  This PR adds a check in `Emitter::mempool_at`: when a mempool transaction is fetched with `get_raw_transaction`, its computed txid must match the txid that was requested before it goes into `mempool_snapshot`. This stops an RPC response with the wrong transaction body from being emitted or cached under the requested txid. It also adds module-level documentation stating that `bdk_bitcoind_rpc` assumes a connection to a trusted `bitcoind` node.

  This addresses only the missing txid-validation part of #2282, following discussions on #2283.

  ### Notes to the reviewers

  - On a mismatch the check returns the existing `Error::UnexpectedStructure`, so it is non-breaking. A single mismatch fails the whole poll; callers retry on the next call.
  - Only freshly fetched transactions are checked. Entries already in `mempool_snapshot` were checked when they were inserted.
  - A txid does not commit to witness data, so a transaction served with altered witnesses is not detected. This is documented on `mempool_at`.
  - Out of scope / possible follow-up: `next_block` does not verify that the returned block matches the requested hash, merkle root, or witness commitment.
  - The test uses a mock `RpcApi` that implements only `call`. `serde`/`serde_json` come from the `bitcoincore_rpc::jsonrpc` re-exports, so no new dependencies are added.

  ### Changelog notice

  - `Emitter::mempool`/`mempool_at`: when a mempool transaction is fetched from the node, check that its computed txid matches the requested txid. A mismatch is rejected with `Error::UnexpectedStructure`.
  - Document that `bdk_bitcoind_rpc` assumes a trusted `bitcoind` RPC connection.

  <!-- Notice the release manager should include in the release tag message changelog -->
  <!-- See https://keepachangelog.com/en/1.0.0/ for examples -->

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [x] I've added tests to reproduce the issue which are now passing
  * [x] I'm linking the issue being fixed by this PR

ACKs for top commit:
  evanlinjin:
    ACK 0d6fb41ac4a268675193beee31050343331996f8

Tree-SHA512: 2910133931cb8e504c54e5465957e4674ee5ba0b98ee303db1409b643e34413ec17d9f2213883fe504632eb09c7114a2ebf7571b3f07d1a36daae6cb4f82909a

3 days agofix(rpc): verify fetched mempool tx txids
Vihiga Tyonum [Wed, 16 Sep 2026 06:22:13 +0000 (07:22 +0100)]
fix(rpc): verify fetched mempool tx txids

- Add `tx.compute_txid() == txid` check to validate that fetched raw
mempool transaction hashes against their requested txid before caching
in Emitter::mempool_at. Returns `Error::UnexpectedStructure` for a
mismatch.

- Add a regression test for the above check.

- Document that the crate assumes a trusted `bitcoind` connection, and
that the txid check does not cover witness data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days agoMerge bitcoindevkit/bdk#2299: refactor(chain)!: remove `Anchor::confirmation_height_u...
merge-script [Thu, 15 Oct 2026 03:09:08 +0000 (03:09 +0000)]
Merge bitcoindevkit/bdk#2299: refactor(chain)!: remove `Anchor::confirmation_height_upper_bound`

74de3a0d656448e6851771952e90b060e890b260 refactor(chain)!: remove `Anchor::confirmation_height` (志宇)
e6a63e450b8e8e56c9daeb3e2341a88b9f90a26e docs(chain): note false-pending in CanonicalView::balance (志宇)
dadeb37fd94ca7ded7adee2d2bf69047135f6d23 refactor(chain)!: rename Anchor::confirmation_height_upper_bound (志宇)
306e052be996ed8ed62ce3e6613dadffe8e04235 docs(chain): state that an anchor names the confirming block (志宇)

Pull request description:

  ### Description

  Fixes #2298.

  The `Anchor` trait said an anchor block *might* be a descendant of the confirming block. Nothing produces such anchors: Electrum validates a merkle proof, Esplora uses the confirming block from `TxStatus`, and `TxPosInBlock` carries the tx index inside that block.

  So an anchor's height is exact. This PR drops that framing from the `Anchor` docs and removes `Anchor::confirmation_height_upper_bound`. Callers use `anchor_block().height` directly. Every implementation already returned that, so there is no behaviour change.

  `ChainPosition::confirmation_height_upper_bound` and `confirmations_lower_bound` are kept. A transitively confirmed tx (`ChainPosition::Confirmed { transitively: Some(_), .. }`) carries a descendant's anchor, so for it the height really is an upper bound. Their docs, along with those of `CanonicalTxOut::is_mature` / `is_confirmed_and_spendable` and `CanonicalView::balance`, now attribute the bound to transitive confirmation. `transitively_confirmed_tx_reports_descendant_anchor_height` covers this case.

  ### Changelog notice

  **`bdk_chain`**

  - Removed `Anchor::confirmation_height_upper_bound`. An anchor names the confirming block, so use `anchor_block().height`.
  - Updated `Anchor` docs: an anchor block is no longer described as possibly a descendant of the confirming block.
  - Updated `ChainPosition::confirmation_height_upper_bound` and `confirmations_lower_bound` docs: the values are exact unless the position is confirmed transitively.
  - Updated `CanonicalView::balance` docs: a transitively confirmed output may be counted as pending by a confirmation-depth `is_settled`.

  ### Checklists

  #### All Submissions:
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### Bugfixes:
  * [x] This pull request breaks the existing API
  * [x] I'm linking the issue being fixed by this PR

  🤖 Generated with [Claude Code](https://claude.com/claude-code)

ACKs for top commit:
  evanlinjin:
    self-ACK 74de3a0d656448e6851771952e90b060e890b260

Tree-SHA512: e1623c08e04da24fe2abfbfa8fd80ecef01bab1e242a0a208eb89134862e9524f7458c3f645ad1218848352dadf06578426a273d5030be5c00f2e950b488fd6a

3 days agorefactor(chain)!: remove `Anchor::confirmation_height`
志宇 [Thu, 15 Oct 2026 02:45:25 +0000 (02:45 +0000)]
refactor(chain)!: remove `Anchor::confirmation_height`

An anchor names the confirming block, so the confirmation height is
always `anchor_block().height`. An overridable method could only
diverge from that, so callers use `anchor_block().height` directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days agofix(chain): don't panic on overlapping descriptors in release
志宇 [Thu, 15 Oct 2026 02:10:45 +0000 (02:10 +0000)]
fix(chain): don't panic on overlapping descriptors in release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 days agodocs(chain): document that `apply_block` trusts the given block
志宇 [Wed, 14 Oct 2026 04:48:48 +0000 (04:48 +0000)]
docs(chain): document that `apply_block` trusts the given block

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 days agofix(chain): skip coinbase txs with a `last_seen` in canonicalization
志宇 [Tue, 13 Oct 2026 01:45:43 +0000 (01:45 +0000)]
fix(chain): skip coinbase txs with a `last_seen` in canonicalization

A coinbase tx cannot be in the mempool, so a `last_seen` value on one is
bad data and must not make it canonical. Previously this was only a
`debug_assert!`, so release builds canonicalized it as unconfirmed.

A coinbase that is an ancestor of a canonical tx, or is assumed
canonical, stays canonical even if no anchor is held for it. As that is
a reachable state, `CanonicalTxOut::is_mature` no longer asserts on it
and returns `false` as maturity cannot be proven.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 days agodocs(chain): note false-pending in CanonicalView::balance
志宇 [Mon, 12 Oct 2026 08:49:54 +0000 (08:49 +0000)]
docs(chain): note false-pending in CanonicalView::balance

An output confirmed transitively carries a descendant's anchor, so its
confirmation height may be an upper bound. An `is_settled` predicate
that requires a minimum number of confirmations may therefore make
`balance` count it as pending even though it has enough.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 days agorefactor(chain)!: rename Anchor::confirmation_height_upper_bound
志宇 [Fri, 11 Sep 2026 01:51:10 +0000 (01:51 +0000)]
refactor(chain)!: rename Anchor::confirmation_height_upper_bound

An anchor names the block that confirmed the transaction, so its height
is exact, not an upper bound. Rename
`Anchor::confirmation_height_upper_bound` to
`Anchor::confirmation_height`.

This is a hard rename with no deprecated alias. The next `bdk_chain`
release is already breaking, so a downstream `Anchor` impl overriding
the old name should fail to compile rather than be silently ignored.
`ConfirmationBlockTime`'s override is removed as it was identical to the
default.

`ChainPosition::confirmation_height_upper_bound` and
`ChainPosition::confirmations_lower_bound` keep their names. A
transaction without its own anchor can be confirmed transitively during
canonicalization, in which case the position carries a descendant's
anchor and the height really is an upper bound. Their docs, and the
false-negative caveats on `CanonicalTxOut::is_mature` and
`is_confirmed_and_spendable`, now attribute the bound to transitive
confirmation instead of to the `Anchor` implementation. A test covers
the transitive case.

No behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NND8BhEmYTYdLRv4dbUUro
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 days agodocs(chain): state that an anchor names the confirming block
志宇 [Fri, 11 Sep 2026 01:46:53 +0000 (01:46 +0000)]
docs(chain): state that an anchor names the confirming block

The `Anchor` trait doc claimed an anchored transaction "could also mean
transaction A is confirmed in a parent block of B". No chain source in
this repo produces such an anchor: electrum only builds one after
`validate_merkle_proof` succeeds, esplora uses `TxStatus`'s confirming
block, and `TxPosInBlock` carries the transaction's index within that
block. The framing only leaves ambiguity for a case that does not occur.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NND8BhEmYTYdLRv4dbUUro

5 days agoMerge bitcoindevkit/bdk#2368: ci: pin proptest to 1.11.0 for MSRV
merge-script [Tue, 13 Oct 2026 01:32:36 +0000 (01:32 +0000)]
Merge bitcoindevkit/bdk#2368: ci: pin proptest to 1.11.0 for MSRV

1a457c8253d588d927e1be866654cbebaccf15ed ci: pin proptest to 1.11.0 for MSRV (志宇)

Pull request description:

  ### Description

  The MSRV (1.85.0) CI job is failing on `master` and on every open PR. `proptest` 1.12.0 declares `rust-version = "1.88"`, and since there is no committed lockfile, CI resolves to it:

  ```
  error: rustc 1.85.0 is not supported by the following package:
    proptest@1.12.0 requires rustc 1.88
  ```

  `proptest` is a dev-dependency of `bdk_core` and `bdk_chain` (`proptest = "1.2.0"`). This PR pins it to 1.11.0 in `ci/pin-msrv.sh`, the latest release that declares `rust-version = "1.85"`.

  Failing run on `master`: https://github.com/bitcoindevkit/bdk/actions/runs/38205608516/job/114668304248

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  🤖 Generated with [Claude Code](https://claude.com/claude-code)

ACKs for top commit:
  notmandatory:
    ACK 1a457c8253d588d927e1be866654cbebaccf15ed
  noahjoeris:
    tACK 1a457c8253d588d927e1be866654cbebaccf15ed

Tree-SHA512: a8361dbc5604935a409a280c324e9fae495d30d6a8699a83932d48b938ae5eb3f3b1bff2d2945b145cc91aeb9062bc49230fc8c8fa652c30e513686a8c0ce616

6 days agoci: pin proptest to 1.11.0 for MSRV
志宇 [Mon, 12 Oct 2026 09:30:59 +0000 (09:30 +0000)]
ci: pin proptest to 1.11.0 for MSRV

`proptest` 1.12.0 requires rustc 1.88, which breaks the 1.85.0 MSRV
job. 1.11.0 is the latest release that supports 1.85.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days agoMerge bitcoindevkit/bdk#2320: fix(electrum): refresh header cache with headers fetche...
merge-script [Mon, 12 Oct 2026 04:53:14 +0000 (04:53 +0000)]
Merge bitcoindevkit/bdk#2320: fix(electrum): refresh header cache with headers fetched for the chain update

6c5e9771814fe07fe13b1a401d06b7cc7b420cd7 test(electrum): cover tx re-mined at the same height after a reorg (志宇)
b4343702e7da3f3d634559b5cbdb12dbe419c94f fix(electrum): refresh header cache with headers fetched for the chain update (志宇)

Pull request description:

  ### Description

  Forward-port of #2319 to `master`.

  `block_header_cache` was only written by `batch_fetch_anchors`, so after a reorg it kept the pre-reorg header for that height. A tx re-mined at the same height then hit the anchor cache under the stale block hash, skipped proof validation, and was never re-anchored to the replacement block. This happens even for a 1-block reorg.

  `fetch_tip_and_latest_blocks` now writes the headers it fetches (the chain suffix and the agreement-walk checkpoints) into the header cache.

  ### Notes to the reviewers

  Only differences from #2319: the `fetch_tip_and_latest_blocks` signature keeps master's `CheckPoint<BlockHash>`, and the test uses master's `TestEnv` / `LocalChain` APIs. The test fails on `master` without the fix.

  Related: #2312.

  ### Changelog notice

  - Fixed `bdk_electrum` returning a stale anchor for a transaction re-mined at the same height after a reorg.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [x] I've added tests to reproduce the issue which are now passing
  * [ ] I'm linking the issue being fixed by this PR

  🤖 Generated with [Claude Code](https://claude.com/claude-code)

ACKs for top commit:
  noahjoeris:
    tACK 6c5e9771814fe07fe13b1a401d06b7cc7b420cd7

Tree-SHA512: 9e9aa3c654a9898dfefa27acf99de13a7563802d7d22577437cf8ac064542074e8ee893646b1f22580da9cccfd444be7bdd348bcc4b760f5991d00cca1d41eee

9 days agochore: update CODEOWNERS
Steve Myers [Thu, 8 Oct 2026 17:15:45 +0000 (12:15 -0500)]
chore: update CODEOWNERS

11 days agoMerge bitcoindevkit/bdk#2330: docs(electrum): drop stale MSRV section from README
merge-script [Wed, 7 Oct 2026 14:52:19 +0000 (17:52 +0300)]
Merge bitcoindevkit/bdk#2330: docs(electrum): drop stale MSRV section from README

04853e31e63ab75c11395ecc4d9d248d265a797b docs(electrum): drop stale MSRV section from README (artofbitcoin)

Pull request description:

  ### Description

  Remove the stale MSRV section from `crates/electrum/README.md`, which still claims Rust 1.75.0 and includes an obsolete dependency pinning command.

  The root README already documents the current MSRV and pinning instructions. Removing the duplicate section reduces maintenance and makes the Electrum README consistent with the other crate READMEs.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

ACKs for top commit:
  noahjoeris:
    ACK 04853e31e63ab75c11395ecc4d9d248d265a797b

Tree-SHA512: 45c82a13dd0a03540e6e719a1a9caff18cf64ff22ab7bb3601a5d8eb64ca489dd64f2cad0a1dec90aaad3304f3620b6599e24d48a462f677924551429baa3963

11 days agodocs(electrum): drop stale MSRV section from README
artofbitcoin [Sat, 3 Oct 2026 15:34:45 +0000 (17:34 +0200)]
docs(electrum): drop stale MSRV section from README

The root README already documents the current MSRV and dependency
pinning instructions. Remove the outdated duplicate section to reduce
maintenance and follow the other crate READMEs.

2 weeks agofix(chain,core): prevent serde from implicitly leaking std into no_std environments
Rishit Modi [Fri, 12 Jun 2026 23:11:57 +0000 (04:41 +0530)]
fix(chain,core): prevent serde from implicitly leaking std into no_std environments

2 weeks agodocs: add maintainers section to CONTRIBUTING.md
Steve Myers [Mon, 28 Sep 2026 21:30:54 +0000 (16:30 -0500)]
docs: add maintainers section to CONTRIBUTING.md

3 weeks agotest(electrum): cover transport errors when syncing outpoints
chaitika [Sat, 26 Sep 2026 15:17:41 +0000 (20:47 +0530)]
test(electrum): cover transport errors when syncing outpoints

Add a regression test: syncing an outpoint against a server that drops
the connection must return an error instead of an empty successful
update.

3 weeks agofix(electrum): propagate transport errors when syncing outpoints
chaitika [Sat, 26 Sep 2026 15:17:00 +0000 (20:47 +0530)]
fix(electrum): propagate transport errors when syncing outpoints

`populate_with_outpoints` discarded every error returned by `fetch_tx`,
so a connection, TLS or timeout failure made `sync` report success
without any data for the requested outpoint. A spend of that outpoint
was then missed and the UTXO kept being treated as unspent.

Skip the outpoint only on `Error::Protocol` and propagate all other
errors, matching `populate_with_txids`.

4 weeks agotest(electrum): cover tx re-mined at the same height after a reorg
志宇 [Fri, 18 Sep 2026 09:36:19 +0000 (09:36 +0000)]
test(electrum): cover tx re-mined at the same height after a reorg

Checks the tx is re-anchored to the replacement block both when the
reorged height is still in the synced chain suffix and when 20 blocks are
mined before the next sync.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4 weeks agofix(electrum): refresh header cache with headers fetched for the chain update
志宇 [Fri, 18 Sep 2026 09:36:19 +0000 (09:36 +0000)]
fix(electrum): refresh header cache with headers fetched for the chain update

`block_header_cache` was only written by `batch_fetch_anchors`, so after a
reorg it kept serving the pre-reorg header for that height. The anchor
cache is keyed by `(txid, hash)` from that header, so a tx re-mined at the
same height hit the anchor cache with the stale hash, skipped proof
validation, and was never re-anchored to the replacement block.

`fetch_tip_and_latest_blocks` now writes the headers it fetches (the
chain suffix and the agreement-walk checkpoints) into the header cache.

4 weeks agoMerge bitcoindevkit/bdk#2317: ci: pin yoke-derive to 0.8.2 for MSRV
merge-script [Fri, 18 Sep 2026 10:13:40 +0000 (10:13 +0000)]
Merge bitcoindevkit/bdk#2317: ci: pin yoke-derive to 0.8.2 for MSRV

4dc5e5a4fc1fe6d14f6b5457a48596197f45de24 ci: pin yoke-derive to 0.8.2 for MSRV (志宇)

Pull request description:

  ### Description

  The MSRV (1.85.0) CI job is failing. `yoke-derive` 0.8.3 (released 2026-09-15) calls `str::from_utf8` as a method on the `str` type. That form isn't available on Rust 1.85.0, and the crate doesn't declare a `rust-version`, so cargo doesn't avoid it:

  ```
  error[E0599]: no function or associated item named `from_utf8` found for type `str` in the current scope
     --> yoke-derive-0.8.3/src/lib.rs:202:32
      |
  202 |         let underscores = str::from_utf8(&underscores).expect("_ is ASCII and thus UTF-8");
      |                                ^^^^^^^^^ function or associated item not found in `str`
  ```

  `yoke-derive` gets pulled in through `url` -> `idna` -> the `icu_*` crates -> `zerovec` -> `yoke`. This PR pins it to 0.8.2 in `ci/pin-msrv.sh`. Version 0.8.2 calls `core::str::from_utf8` instead, and `yoke` 0.8.3 accepts `yoke-derive ^0.8.2`.

  Failing run: https://github.com/bitcoindevkit/bdk/actions/runs/35182209052/job/105076597730

  ### Notes to the reviewers

  I couldn't build with 1.85.0 locally, so the MSRV CI run on this PR is the real test. I did check that `ci/pin-msrv.sh` runs cleanly from a fresh lockfile and ends up with `yoke-derive` 0.8.2. The last green `master` run (2026-09-12) was before 0.8.3 came out.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  🤖 Generated with [Claude Code](https://claude.com/claude-code)

ACKs for top commit:
  tvpeter:
    tACK  4dc5e5a4fc1fe6d14f6b5457a48596197f45de24
  noahjoeris:
    tACK 4dc5e5a4fc1fe6d14f6b5457a48596197f45de24

Tree-SHA512: 812f4cf719c9ed4fda07fc90d81149e1d05169d11cd1d785667801cb88479dbf4628c94ba5477048415e718a4fb09ce7060c35850bde1fc0673649c5e4f5ff59

4 weeks agoci: pin yoke-derive to 0.8.2 for MSRV
志宇 [Thu, 17 Sep 2026 04:38:47 +0000 (04:38 +0000)]
ci: pin yoke-derive to 0.8.2 for MSRV

yoke-derive 0.8.3 calls the inherent `str::from_utf8`, which is not
available on Rust 1.85.0, and it does not declare a `rust-version`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5 weeks agoMerge bitcoindevkit/bdk#2246: feat(chain)!: taint-aware `CanonicalView::balance`
merge-script [Sat, 12 Sep 2026 11:34:56 +0000 (11:34 +0000)]
Merge bitcoindevkit/bdk#2246: feat(chain)!: taint-aware `CanonicalView::balance`

e2dad373b0e2023a58c67afb073406eeaf028374 bench(chain): benchmark classify_outpoints across canonical topologies (Dmenec)
01b466018c98af9aa3b7335b31c1ea23de0e3f42 test(chain): cover taint propagation and eligibility classification (Dmenec)
3f7c1bde852826cd3df0a12dd0ae8023a6f0bb07 feat(chain)!: derive balance trust from output ancestry (志宇)
98a7d5b88e975d529a4c8e6446ef8c3a4314281e feat(chain): classify outpoints by chain-level spend eligibility (Dmenec)

Pull request description:

  Solves #2267

  ### Description

  Takes over #2235 (thanks @evanlinjin for the go-ahead). It reworks `CanonicalView::balance` to derive trust from an output's unconfirmed ancestry, and adds `classify_outpoints`, a per-output spend-eligibility classifier that `balance` becomes a thin fold over.

  The old `balance` decided trust using a per-output `trust_predicate`, which cannot express transitive trust. As a result, owned outputs whose unconfirmed ancestry included foreign coins were counted as trusted. That is the root cause of the wallet trust-classification bugs (bitcoindevkit/bdk_wallet#16, bitcoindevkit/bdk_wallet#273).

  The API now takes two separate predicates, one per concern:

  - `does_taint(&tx)` - should this transaction be considered tainted? (e.g., because it spends a foreign unconfirmed output)
  - `is_settled(&pos)` - do we consider this chain position settled / final? (generalizes `min_confirmations`)

  For each unspent output, `classify_outpoints` reports its chain-level spend eligibility:

  - `Settled` if considered settled according to `is_settled`
  - `Immature` for a coinbase output that has not yet matured
  - `Unsettled(Trust)` otherwise, where `Trust` is:
    - `Trusted` if the whole unconfirmed ancestry only spends owned coins
    - `Untrusted` if the output itself, or any unconfirmed ancestor, is tainted
    - `Unknown` if part of the ancestry is missing from the view, so trust can't be determined

  `balance` then sums each output's value into the bucket corresponding to its `Eligibility`.

  ### Notes to the reviewers

  - Trust is resolved through a self-contained ancestry walk. The traversal is memoized: each visited transaction is cached so that already-classified transactions (and their ancestors) do not need to be walked again.
  - Kept `Balance::confirmed` and did not rename it to `settled`. That rename is out of scope here. The folding logic is slated to move into `bdk_wallet`, and the current `balance` function in chain will eventually be deprecated.
  - `balance` also drops the `O` generic and now takes plain `OutPoint`s, since the taint predicate operates on transactions rather than per-outpoint associated data.
  - `does_taint` is evaluated at most once per transaction.
  - More sophisticated classification rules (e.g., for coin control or locked funds) can be built on top of `classify_outpoints`. Left for a follow-up.

  ### Changelog notice

  - **Breaking**: `CanonicalView::balance` now takes `does_taint: impl FnMut(&CanonicalTx) -> bool` and `is_settled: impl Fn(&ChainPosition) -> bool` instead of a per-output trust predicate and `min_confirmations`, and plain `OutPoint`s instead of `(identifier, outpoint)` pairs (this drops the `O` generic). Trust is now derived from an output's unconfirmed ancestry.
  - **Breaking**: added `Balance::unknown_pending`, where outputs whose trust can't be determined are counted instead of being lumped into `untrusted_pending`.
  - Added `CanonicalView::classify_outpoints` and the `Eligibility` enum.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `just p` before pushing

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

  #### Bugfixes:

  * [x] This pull request breaks the existing API

ACKs for top commit:
  evanlinjin:
    ACK e2dad373b0e2023a58c67afb073406eeaf028374
  nymius:
    ACK e2dad373b0e2023a58c67afb073406eeaf028374

Tree-SHA512: 30db16f27b3068f7ed2e0ac43b199438437622a66ff1a1a8d9fd854d22895384979a3e2305139ca7955137087bde832c64d31807069f60f08293c5e194234693

5 weeks agofix(chain): fix integer overflow in lookahead_to_target
busayo-OD [Thu, 10 Sep 2026 22:42:56 +0000 (23:42 +0100)]
fix(chain): fix integer overflow in lookahead_to_target

lookahead_to_target treated an out-of-range target_index as a
silent no-op, which is wrong — it should clamp to BIP32_MAX_INDEX
and replenish up to that point, matching reveal_to_target's
best-effort semantics.

Use saturating_add/saturating_sub, which no longer needs checked
arithmetic since replenish_inner_index's stop_index now clamps
correctly on its own.

5 weeks agofix(chain): fix off-by-one in replenish_inner_index stop bound
busayo-OD [Thu, 10 Sep 2026 22:41:23 +0000 (23:41 +0100)]
fix(chain): fix off-by-one in replenish_inner_index stop bound

replenish_inner_index clamped stop_index (an exclusive bound) to
BIP32_MAX_INDEX, excluding the boundary itself and causing a panic
when a keychain's index was revealed exactly up to BIP32_MAX_INDEX.
The addition feeding into that clamp was also unchecked, risking
overflow if lookahead was large relative to an already-high
last_revealed.

Use saturating_add throughout, clamping to BIP32_MAX_INDEX + 1 so
the boundary value is included as documented.

5 weeks agobench(chain): benchmark classify_outpoints across canonical topologies
Dmenec [Wed, 22 Jul 2026 17:05:11 +0000 (19:05 +0200)]
bench(chain): benchmark classify_outpoints across canonical topologies

Times the memoized classification over fan-in, disjoint, untrusted, diamond
and deep-taint-mid-chain graphs at a range of widths and depths.

5 weeks agotest(chain): cover taint propagation and eligibility classification
Dmenec [Wed, 22 Jul 2026 16:43:47 +0000 (18:43 +0200)]
test(chain): cover taint propagation and eligibility classification

- taint propagates through a pending output's unsettled ancestry
- is_settled alone decides the settled boundary, even for unconfirmed outputs
- taint never crosses a settled ancestor
- an immature coinbase is classified apart from a settled output
- two UTXOs sharing a tainting ancestor are both untrusted (shared taint cache)
- classify_outpoints skips spent and out-of-view outpoints

Co-authored-by: 志宇 <hello@evanlinjin.me>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 weeks agofeat(chain)!: derive balance trust from output ancestry
志宇 [Wed, 22 Jul 2026 16:35:37 +0000 (18:35 +0200)]
feat(chain)!: derive balance trust from output ancestry

balance becomes a fold over classify_outpoints. A pending output's trust is now
taken from its unsettled ancestry (untrusted if an unsettled ancestor taints or
is missing from the view), not from a per-output flag.

BREAKING CHANGE: balance takes does_taint and is_settled instead of trust_predicate
and min_confirmations, and plain OutPoints instead of (identifier, outpoint)
pairs.

Co-authored-by: Dmenec <dmenec@proton.me>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 weeks agoMerge bitcoindevkit/bdk#2259: chore(ci): add stale issue triage workflow
merge-script [Thu, 10 Sep 2026 07:51:58 +0000 (07:51 +0000)]
Merge bitcoindevkit/bdk#2259: chore(ci): add stale issue triage workflow

5edb32302e5fd6a7e3e258980ca4d4cfd684e5d9 chore(ci): add stale issue triage workflow (Rishit Modi)

Pull request description:

  ### Description

  Follow-up to bitcoindevkit/bdk_wallet#497.

  Adds `.github/workflows/stale.yml`, based on the workflow introduced in the referenced PR, to automatically label and close inactive issues and abandoned pull requests in this repository.

  The workflow uses `actions/stale@v11`

  - Issues inactive for 180 days -> marked stale -> closed after 30 additional days
  - Pull requests inactive for 180 days -> marked stale -> closed after 30 additional days
  - Issue exemptions: `release`, `security`, `audit`, `do-not-close`, and `good first issue`
  - Pull request exemptions: `blocked`, `security`, and `do-not-close`
  - Issues and pull requests associated with milestones are exempt
  - Runs weekly on Mondays at 09:00 UTC
  - Pull request runs are automatically forced into dry-run mode for safe validation
  - Stale labels are removed when new activity occurs

  The timing and exemption policy are initially kept consistent with bitcoindevkit/bdk_wallet#497. They can be adjusted based on feedback.

  ### Notes to the reviewers

  The workflow runs with `debug-only: true` on pull request events. I also recommend running it manually with `debug_only=true` after merging and reviewing the results before the first scheduled Monday run.

  ### Changelog notice

  ci: add automated stale issue and pull request triage

  ### Checklists

  #### All Submissions:

  - [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

ACKs for top commit:
  notmandatory:
    ACK 5edb32302e5fd6a7e3e258980ca4d4cfd684e5d9

Tree-SHA512: 99bc6eb275feaf0ee4732e7570d5b46a46d96f482fa9b24406a3688b4824f9489bda65c51efdbc4b6ec34a72febe93219cd803b53c275bc3b273a4c4182bbcca

5 weeks agoMerge bitcoindevkit/bdk#2263: feat(chain): add ChainPosition::confirmations_lower_bound
merge-script [Thu, 10 Sep 2026 01:54:22 +0000 (01:54 +0000)]
Merge bitcoindevkit/bdk#2263: feat(chain): add ChainPosition::confirmations_lower_bound

7ebc9d3ca216eafb11fd1d976955f9e3500108c6 feat(chain): add ChainPosition::confirmations_lower_bound (Dmenec)

Pull request description:

  Suggested by @nymius in https://github.com/bitcoindevkit/bdk/pull/2246#discussion_r3796478212.

  ### Description

  Adds `ChainPosition::confirmations_lower_bound(tip)`, which returns the number of blocks mined on top of a confirmed position's block, given the chain `tip`. Returns `None` if the position is unconfirmed, or if the confirmation height is above `tip`.

  ### Changelog notice

  - Added `ChainPosition::confirmations_lower_bound`, returning the number of blocks mined on top of a confirmed position's block given the chain tip.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `just p` before committing

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

ACKs for top commit:
  evanlinjin:
    ACK 7ebc9d3ca216eafb11fd1d976955f9e3500108c6

Tree-SHA512: 32bc2ff794f9aa7a502589b6d8feca215c92dda1075b0dae25c7d5bb48d9c62f2af0c138bf47367bc75e5c0ee176e7290e0eeea152681b2dc172375cba6ebb37

5 weeks agofeat(chain): add ChainPosition::confirmations_lower_bound
Dmenec [Thu, 20 Aug 2026 10:37:29 +0000 (12:37 +0200)]
feat(chain): add ChainPosition::confirmations_lower_bound

5 weeks agofeat(chain): classify outpoints by chain-level spend eligibility
Dmenec [Wed, 22 Jul 2026 15:30:35 +0000 (17:30 +0200)]
feat(chain): classify outpoints by chain-level spend eligibility

Adds classify_outpoints, which decides per-output whether it's settled,
immature, or pending (trusted, untrusted, or unknown) based on its unsettled
ancestry. Trust is resolved with a memoized ancestry walk: a tainting ancestor
makes it untrusted, one missing from the view makes it unknown, and ancestors
shared by several outputs are only walked once.

Co-authored-by: 志宇 <hello@evanlinjin.me>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6 weeks agofix(chain): clamp derivation index before apply_changeset
busayo-OD [Sun, 6 Sep 2026 15:20:22 +0000 (16:20 +0100)]
fix(chain): clamp derivation index before apply_changeset

KeychainTxOutIndex::apply_changeset accepts ChangeSet::last_revealed
values above BIP32_MAX_INDEX, violating an invariant relied on by
the indexer.

Clamp each last_revealed value to BIP32_MAX_INDEX before storing
it, keeping changeset application infallible and monotone.

6 weeks agoMerge bitcoindevkit/bdk#2262: fix(chain): let the indexer decide how far to rescan
merge-script [Wed, 2 Sep 2026 00:52:43 +0000 (00:52 +0000)]
Merge bitcoindevkit/bdk#2262: fix(chain): let the indexer decide how far to rescan

f08eeac1547e4a4585933989573ede4db4dcb9ba fix(chain): let the indexer decide how far to rescan (LLFourn)

Pull request description:

  ### Description

  `IndexedTxGraph::reindex` was a single pass over `TxGraph::full_txs`, which iterates a `HashMap`. That made it **nondeterministic**: the same graph reindexed twice could produce two different `last_revealed` results.

  Each match inside `KeychainTxOutIndex::_index_txout` bumps `last_revealed` and then calls `replenish_inner_index`, whose stop index is `last_revealed + 1 + lookahead`. So a match *widens the derived window the remaining outputs are judged against*. An output far enough out to need that widening was skipped if it happened to be visited first, and no pass revisited it.

  Concretely, with `lookahead` 1000 and an empty frontier the derived set is `0..1001`. Given one tx paying index 900 and another paying 1850: visited 900-first, the window grows to 1901 and both are found; visited 1850-first, 1850 is missed and stays missed. Same inputs, different index, decided by `HashMap` iteration order.

  The usual objection — that callers should reveal before reindexing — does not hold. The lookahead exists precisely to catch indices the persisted frontier does not know about: a restored wallet, another signer on the same descriptor, an externally built PSBT paying one of our far indices. Whenever the lookahead does its job the frontier moves mid-walk, so the order dependence is present in the intended use, not just in misuse.

  ### The change

  Add `Indexer::rescan`, handed the whole `TxGraph` and returning the indexing it produced:

  ```rust
  fn rescan<A>(&mut self, graph: &TxGraph<A>) -> Self::ChangeSet
  where
      Self::ChangeSet: Merge,
  ```

  The default implementation offers every full transaction and floating output to `index_tx` / `index_txout` exactly once — all an indexer needs when what it recognizes is fixed up front — and `IndexedTxGraph::reindex` becomes a call to it. `KeychainTxOutIndex` overrides it and looks repeatedly, stopping when a pass leaves its revealed frontier unmoved.

  Whether to look more than once belongs to the indexer, because the indexer is the only thing that knows whether its recognition set can still grow. Two things follow from putting it there rather than in `IndexedTxGraph`:

  - **An indexer that does not widen what it matches is walked exactly once**, as today. `SpkTxOutIndex` is unaffected. There is no convergence requirement imposed on implementors, and no way for a third-party indexer to be spun forever by a loop it never asked for.
  - **`KeychainTxOutIndex` can key the loop on its own frontier** rather than on whether a changeset came back empty. That matters: the changeset also carries staged spk cache entries, which move *without* the frontier moving. A `changeset.is_empty()` loop therefore spends an extra full walk on the ordinary restore path. I measured this — with `persist_spks = true`, restoring via `from_changeset` with a correct frontier takes **1 pass** keyed on the frontier versus **2** keyed on changeset emptiness.

  ### Notes to the reviewers

  **On the test shape.** The regression test uses **one** transaction with two of our outputs rather than two transactions. A two-transaction test would depend on graph walk order — the very thing that is unreliable — and so would pass a single-pass implementation about half the time, which is a test that fails to fail. `index_tx` walks `tx.output`, a `Vec`, in vout order, so a single tx paying a far index at vout 0 and a near one at vout 1, against an empty frontier, misses the far output on every pass on every run. It is deterministic by construction; I verified it fails against the old implementation on five consecutive runs (`left: Some(9)`, `right: Some(15)`).

  **On cost.** `KeychainTxOutIndex::rescan` re-walks the whole graph per look, so it is O(looks × txs), with looks bounded by the number of distinct frontier advances plus one. Measured: the settled/restore case is 1 look; the recovery case in the test (empty frontier, far output only reachable after the near one lands) is 3. Making a later look re-examine only the outputs that did not already match would need the indexer to say *what* to re-offer rather than just *whether*, which is a bigger change than this fix.

  **Trait change.** `rescan` is a defaulted method, so existing `Indexer` implementations keep compiling unchanged. It is generic over the anchor `A`, which makes `Indexer` no longer object-safe — nothing in the workspace uses `dyn Indexer`.

  **Known adjacent gap, not addressed here.** `index_tx_graph_changeset` — used by `insert_tx`, `insert_txout`, `apply_update` and `apply_block_relevant` — is still a single pass and has the same order dependence. Feeding the one-transaction case above through `insert_tx` yields `last_revealed = Some(9)` and only one of the two outpoints, so a live wallet can drop the far UTXO until the next restart re-runs `reindex`. It needs the incremental paths to go through the same mechanism rather than their own copy; I kept this PR to `reindex` to stay reviewable, and am happy to follow up.

  ### Changelog notice

  - Added: `Indexer::rescan`, which indexes an entire `TxGraph` and lets an implementation decide how many looks that takes. Defaulted, so existing implementations are unaffected.
  - Fixed: `IndexedTxGraph::reindex` no longer depends on `HashMap` iteration order. It previously made a single pass and could miss outputs at derivation indices that only came into range after another output in the same walk advanced the lookahead; `KeychainTxOutIndex` now looks until its revealed frontier stops moving.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [x] I've added tests to reproduce the issue which are now passing

ACKs for top commit:
  evanlinjin:
    ACK f08eeac1547e4a4585933989573ede4db4dcb9ba

Tree-SHA512: 0c5496805655a2c0f060e26bacd05a48adb2866961a9b785b53b444572a5b2e7f78ad0ddbcb737a308be5e1958bb0c49c09ed1e56fb30d029e06661f9e10a953

6 weeks agofix(chain): let the indexer decide how far to rescan
LLFourn [Wed, 19 Aug 2026 05:15:39 +0000 (15:15 +1000)]
fix(chain): let the indexer decide how far to rescan

`IndexedTxGraph::reindex` was a single pass over `TxGraph::full_txs`,
which iterates a `HashMap`, so the same graph reindexed twice could
produce two different `last_revealed` results. Each match inside
`KeychainTxOutIndex::_index_txout` bumps `last_revealed` and replenishes
the lookahead, widening the derived window the remaining outputs are
judged against. An output far enough out to need that widening was
skipped whenever it happened to be visited first, and no pass revisited
it.

This is not merely misuse of the API. The lookahead exists precisely to
catch indices the persisted frontier does not know about -- a restored
wallet, another signer on the same descriptor, an externally built PSBT
paying one of our far indices -- so whenever it does its job the frontier
moves mid-walk, and the order dependence is present in the intended use.

Add `Indexer::rescan`, which is handed the whole graph and returns what
indexing it produced. The default implementation offers every full
transaction and floating output once, which is all an indexer needs when
what it recognizes is fixed up front; `reindex` becomes a call to it.
`KeychainTxOutIndex` overrides it and looks repeatedly, stopping when a
pass leaves its revealed frontier unmoved.

Putting the loop behind the trait rather than in `IndexedTxGraph` keeps
the decision where the knowledge is. An indexer that does not widen
what it matches is walked exactly once, as before, so the looping
cannot leak into implementations that have no use for it. And
`KeychainTxOutIndex` can key the loop on its own frontier instead of on
whether a changeset came back empty: the changeset also carries staged
spk cache entries, which move without the frontier moving, so an
emptiness test spends an extra full walk on the ordinary restore path.

The regression test uses a single transaction with both outputs rather
than two transactions, because the graph's walk order is a `HashMap`
order: a two-transaction test would pass a single-pass implementation
about half the time. `index_tx` walks `tx.output` in vout order, so the
far index at vout 0 is always judged against the initial window and
always missed until the near index at vout 1 lifts the frontier.

2 months agoMerge bitcoindevkit/bdk#2257: fix: fix command order for importing GPG key
merge-script [Mon, 17 Aug 2026 07:49:39 +0000 (07:49 +0000)]
Merge bitcoindevkit/bdk#2257: fix: fix command order for importing GPG key

3474e0c6d02e527c1f8a38660191e243026dac0d fix: fix command order for importing GPG key (Carlos Marques)

Pull request description:

  ### Fix

  Options order wrong. `--recv-keys` eat everything after as key IDs.
  Put `--keyserver` first.

ACKs for top commit:
  thunderbiscuit:
    ACK 3474e0c6d02e527c1f8a38660191e243026dac0d. Tested and our current line is indeed incorrect; proposed fix works.
  luisschwab:
    ACK 3474e0c6d02e527c1f8a38660191e243026dac0d
  evanlinjin:
    ACK 3474e0c6d02e527c1f8a38660191e243026dac0d

Tree-SHA512: 6f31c18a21507260fd207757bf06fb4660459a35002f3020ac1b1651c4441389495564a84317a25046d49f5af8458a56e9874a0b1d32b729b791c18ab138b4ce

2 months agochore(ci): add stale issue triage workflow
Rishit Modi [Fri, 14 Aug 2026 22:28:02 +0000 (03:58 +0530)]
chore(ci): add stale issue triage workflow

2 months agofix: fix command order for importing GPG key
Carlos Marques [Tue, 11 Aug 2026 13:54:19 +0000 (14:54 +0100)]
fix: fix command order for importing GPG key

2 months agoMerge bitcoindevkit/bdk#2255: Add GH security advisory link on SECURITY.md
merge-script [Wed, 5 Aug 2026 22:02:30 +0000 (19:02 -0300)]
Merge bitcoindevkit/bdk#2255: Add GH security advisory link on SECURITY.md

a98ab189340c6ed2a2fbc91ee6d8178950e09b6b chore(doc): add GH security advisory link on SECURITY.md (Luis Schwab)

Pull request description:

  Link to GitHub's security advisory page on `SECURITY.md`

ACKs for top commit:
  oleonardolima:
    ACK a98ab189340c6ed2a2fbc91ee6d8178950e09b6b

Tree-SHA512: 2e25062934d717f05c6a567d33ca27445594eafbb6aeefc648c5108a0d7631f4cac30861e857f28d88fb501ac72ef24f4fef6814144e659c60edbde7657108ec

2 months agochore(doc): add GH security advisory link on SECURITY.md
Luis Schwab [Wed, 5 Aug 2026 20:03:54 +0000 (17:03 -0300)]
chore(doc): add GH security advisory link on SECURITY.md

2 months agoMerge bitcoindevkit/bdk#2240: fix(chain): skip evicted txs in leftover canonicalization
merge-script [Fri, 24 Jul 2026 10:10:25 +0000 (10:10 +0000)]
Merge bitcoindevkit/bdk#2240: fix(chain): skip evicted txs in leftover canonicalization

2a8512b15706e3f05a4a506af0293c2d33d12581 fix(chain): skip evicted leftover transactions (Noah Joeris)
9e404720bbc0ba462a661141c01325815aa6c180 feat(chain): add TxNode::is_evicted (Noah Joeris)

Pull request description:

  ### Description

  `CanonicalStage::LeftOverTxs` doesn't filter evicted txs and can mark them canonical. So a stale-anchored tx that was evicted/replaced could still show as unconfirmed.

  Example: a tx confirms, then a reorg leaves only a stale anchor. Leftover treats it as unconfirmed. If it's later RBF'd or missing from the mempool, leftover still marks it unconfirmed.

  ### Changelog notice
  - Add `TxNode::is_evicted()` for determining mempool eviction state.
  - Skip evicted transactions during leftover canonicalization.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [x] I've added tests to reproduce the issue which are now passing
  * [ ] I'm linking the issue being fixed by this PR

ACKs for top commit:
  evanlinjin:
    ACK 2a8512b15706e3f05a4a506af0293c2d33d12581

Tree-SHA512: 615a46406600115210a272668b01e9a85a6d6fbbcc9d055cf83617fcc5c6e742f845b57e34277e26ecdffe41f39b01576ae038cd7eed5ce916fcb505b58fc8e8

2 months agofix(chain): skip evicted leftover transactions
Noah Joeris [Wed, 22 Jul 2026 19:23:11 +0000 (22:23 +0300)]
fix(chain): skip evicted leftover transactions

2 months agofeat(chain): add TxNode::is_evicted
Noah Joeris [Wed, 22 Jul 2026 19:22:54 +0000 (22:22 +0300)]
feat(chain): add TxNode::is_evicted

3 months agoMerge bitcoindevkit/bdk#2209: ci: individual package tests
merge-script [Mon, 13 Jul 2026 21:08:22 +0000 (17:08 -0400)]
Merge bitcoindevkit/bdk#2209: ci: individual package tests

2185dcbb31d512d708e8c117c5b1a429ff52ce81 ci: Update cont_integration.yml (valued mammal)

Pull request description:

  Close #1944

  ### Description

  Replaces the monolithic `build-test` matrix job (which ran a 2-toolchain × 2-feature-set matrix across the whole workspace) with dedicated per-crate build jobs. Each crate uses named steps for every meaningful feature combination, making it easy to identify which specific crate and feature set broke in CI.

  **Changes:**

  - **Per-crate jobs**: `build-core`, `build-chain`, `build-bitcoind-rpc`, `build-electrum`, `build-esplora`, `build-file-store`, `build-testenv` — each with per-feature `cargo build` steps and a `cargo test --all-features` step
  - **`msrv` job**: Simplified from a matrix to a single flat job targeting 1.85.0; runs `cargo build --workspace --all-targets` and `cargo test --workspace --lib --tests --all-features`
  - **`check-no-std`**: Updated to check `bdk_core` and `bdk_chain` with no default features + with `hashbrown` and `miniscript`; cross-compilation against `thumbv6m-none-eabi` is stubbed out as usual to be addressed in a separate PR

Top commit has no ACKs.

Tree-SHA512: d912a967ded7eb23bb25dbe6c59f11361b34e2cd77d1c6f3971b02261b4169ec96ab69bf68e6b667530120d8f75610bd85bfbc7da47d9d8cdf79d54ba4b1cb01

3 months agoci: Update cont_integration.yml
valued mammal [Fri, 15 May 2026 21:47:34 +0000 (17:47 -0400)]
ci: Update cont_integration.yml

Added workspace level MSRV job.

Added per-crate jobs to build meaningful feature
combinations and test all features.

Removed build-test job.

3 months agoMerge bitcoindevkit/bdk#2210: docs(rpc): add Emitter::next_block and Emitter::mempool...
merge-script [Sat, 11 Jul 2026 22:50:43 +0000 (18:50 -0400)]
Merge bitcoindevkit/bdk#2210: docs(rpc): add Emitter::next_block and Emitter::mempool examples

30fa17955b16d6a5a435bea8e2b9728e9e0b3701 docs(rpc): add Emitter::next_block and Emitter::mempool examples (Kyle 🐆)

Pull request description:

  Adds `# Example` rustdoc to `Emitter::next_block` and `Emitter::mempool`, which were flagged as missing in the #2006 review.

  Originally this PR also removed `example_bitcoind_rpc_polling`, but #2006 has since merged and removed all example crates, so that part is dropped after rebase. Only the doc additions remain.

  Part of the #2006 follow-up to document the components previously covered by the `examples/` crates.

ACKs for top commit:
  ValuedMammal:
    ACK 30fa17955b16d6a5a435bea8e2b9728e9e0b3701

Tree-SHA512: 28ade24c33f9714f94282a4594024bff9ed304c461b477b7ba5e95de64fbc5951c23a6a93ee623cc764e1e31971aaccac3aaebe4f615637d5b2de6d56bc32b56

3 months agodocs(rpc): add Emitter::next_block and Emitter::mempool examples
Kyle 🐆 [Sat, 23 May 2026 18:59:47 +0000 (14:59 -0400)]
docs(rpc): add Emitter::next_block and Emitter::mempool examples

4 months agoMerge bitcoindevkit/bdk#2203: ci: automated update to rustc 1.96.0
merge-script [Mon, 15 Jun 2026 17:49:37 +0000 (14:49 -0300)]
Merge bitcoindevkit/bdk#2203: ci: automated update to rustc 1.96.0

c1cffa3fd6b55f2fc0c862fd059ca1fa427a025c ci: automated update to rustc 1.96.0 (Github Action)

Pull request description:

  Automated update to Github CI workflow `cont_integration.yml` by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action

ACKs for top commit:
  oleonardolima:
    ACK c1cffa3fd6b55f2fc0c862fd059ca1fa427a025c

Tree-SHA512: 28f681fe56bfee891c06c8d324cc89b6af9fb40af976ad5eebfe89e8ee0c2ea7d5ad0c9ad1d2849d77c4fc202474b8032285bf4b42a5f8f544a86e5e6a115c71

4 months agoci: automated update to rustc 1.96.0
Github Action [Mon, 15 Jun 2026 03:43:59 +0000 (03:43 +0000)]
ci: automated update to rustc 1.96.0

4 months agoMerge bitcoindevkit/bdk#2038: refactor(chain,core)!: replace `CanonicalIter` with...
merge-script [Mon, 15 Jun 2026 12:42:14 +0000 (09:42 -0300)]
Merge bitcoindevkit/bdk#2038: refactor(chain,core)!: replace `CanonicalIter` with sans-IO `CanonicalTask` + `ChainQuery` trait

555f774169ea934dbb2934f2fbe6763908e86670 fix(chain): position resolution for assumed txs (Leonardo Lima)
4f10cdef4d55139ee7f608d9cf90ff8d9c0b3037 chore(chain,example)!: remove `ChainOracle`, update docs (志宇)
a3c73e4685f3381b5b1ae799c2279009b6f7d1ea refactor(chain)!: split `canonical_view` into `canonical`,`canonical_view_task` (志宇)
22b04c33dc0b75e5a247f99087ad36f41e99a0ed refactor(chain)!: split canonicalization into two tasks with generic `Canonical<A, P>` (志宇)
a9f5ca4415af36b42cca43551414ad0644d627b8 refactor(chain)!: remove `CanonicalIter` APIs (Leonardo Lima)
bb83da8260fb167fa548f31c12710153095e9064 chore(workspace): use new `LocalChain::canonical_view` API (Leonardo Lima)
987da73fce7b7f4e015aa05cc8fa4e36deebf4df feat(core,chain): introduce `CanonicalizationTask` and `ChainQuery` (Leonardo Lima)

Pull request description:

  fixes #1816

  ### Description

  Replaces the iterator-based `CanonicalIter` with a two-phase sans-IO canonicalization pipeline, and introduces a generic `ChainQuery` trait in `bdk_core` to decouple canonicalization from chain sources.

  **Old API:**
  ```rust
  // Direct coupling between canonicalization logic and ChainOracle
  let view = tx_graph.canonical_view(&chain, chain_tip, params)?;
  ```

  **New API:**
  ```rust
  // Option A: Two-phase (full control)
  let canonical_txs = chain.canonicalize(tx_graph.canonical_task(tip, params));
  let view = chain.canonicalize(canonical_txs.view_task(&tx_graph));

  // Option B: Convenience method
  let view = chain.canonical_view(&tx_graph, tip, params);
  ```

  #### Phase 1: `CanonicalTask`
  Determines which transactions are canonical by processing them in stages:
  1. **Assumed txs** — transactions assumed canonical via `CanonicalParams`
  2. **Anchored txs** — transactions anchored in the best chain (descending height)
  3. **Seen txs** — unconfirmed transactions by descending last-seen time
  4. **Remaining txs** — leftover anchored transactions not in the best chain

  Produces a `CanonicalTxs<A>` containing each canonical transaction with its `CanonicalReason`.

  #### Phase 2: `CanonicalViewTask`
  Resolves `CanonicalReason`s into concrete `ChainPosition`s (confirmed height or unconfirmed with last-seen), producing the final `CanonicalView<A>`.

  Both phases implement the `ChainQuery` trait, so any chain source can drive them via the same `next_query`/`resolve_query` loop.

  #### Key structural changes

  - **`ChainQuery` trait** added to `bdk_core` — a generic sans-IO interface (`next_query` → `resolve_query` → `finish`) for any algorithm that needs to verify blocks against a chain source.
  - **`ChainOracle` trait removed** — replaced by `ChainQuery`. `LocalChain::canonicalize()` now drives any `ChainQuery` implementor.
  - **`Canonical<A, P>` generic container** — `CanonicalTxs<A>` (phase 1 output) and `CanonicalView<A>` (phase 2 output) are type aliases over `Canonical<A, P>`.
  - **Module split** — `canonical_view.rs` split into `canonical.rs` (types: `Canonical`, `CanonicalTx`, `CanonicalTxOut`) and `canonical_view_task.rs` (phase 2 task). `canonical_iter.rs` replaced by `canonical_task.rs`.

  ### Notes to the reviewers

  The changes are split into multiple commits for easier review. Also depends on #2029.

  ### Changelog notice

  ```
    ### Added
    - `bdk_core::ChainQuery` trait — generic sans-IO interface for chain verification queries
    - `bdk_core::ChainRequest` / `ChainResponse` type aliases
    - `CanonicalTask` — phase 1 sans-IO canonicalization (determines canonical txs)
    - `CanonicalViewTask` — phase 2 sans-IO canonicalization (resolves chain positions)
    - `Canonical<A, P>` generic container with `CanonicalTxs<A>` and `CanonicalView<A>` aliases
    - `LocalChain::canonicalize()` — drives any `ChainQuery` implementor
    - `LocalChain::canonical_view()` — convenience method for full two-phase canonicalization

    ### Changed
    - **Breaking:** Replace `TxGraph::canonical_iter()` / `TxGraph::canonical_view()` with `TxGraph::canonical_task()`
    - **Breaking:** Canonicalization now uses a two-phase sans-IO process via `ChainQuery`
    - **Breaking:** `ChainQuery`, `ChainRequest`, `ChainResponse` have no generics (use `BlockId` directly)
    - **Breaking:** Chain tip moved from `ChainRequest` to `ChainQuery::tip()`

    ### Removed
    - **Breaking:** `ChainOracle` trait and all implementations
    - **Breaking:** `CanonicalIter` type and `canonical_iter` module
    - **Breaking:** `TxGraph::try_canonical_view()` and `TxGraph::canonical_view()` methods
    - **Breaking:** `CanonicalView::new()` public constructor
  ```

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

ACKs for top commit:
  evanlinjin:
    ACK 555f774169ea934dbb2934f2fbe6763908e86670

Tree-SHA512: ce466a623a1f8df20dedd3e4e68460892bd369567db44e6ba391c3d9ae1d10bca6204ebdcdb7b1376a9c97f3155b89e991846af4122e200d1fa15d998deb7830

4 months agofix(chain): position resolution for assumed txs
Leonardo Lima [Mon, 16 Mar 2026 23:01:22 +0000 (20:01 -0300)]
fix(chain): position resolution for assumed txs

- add new `test_canonical_view_task.rs` to handle different scenarios
  of chain position resolution.
- fixes the assumed canonical txs chain position resolution, especially for transitively
  assumed canonical transactions, where there's an anchored/confirmed descendant.

4 months agochore(chain,example)!: remove `ChainOracle`, update docs
志宇 [Sun, 8 Mar 2026 23:50:34 +0000 (23:50 +0000)]
chore(chain,example)!: remove `ChainOracle`, update docs

4 months agorefactor(chain)!: split `canonical_view` into `canonical`,`canonical_view_task`
志宇 [Fri, 13 Feb 2026 14:43:13 +0000 (14:43 +0000)]
refactor(chain)!: split `canonical_view` into `canonical`,`canonical_view_task`

It moves the shared types: `CanonicalTx`, `Canonical`, `CanonicalView`,
`CanonicalTxs` and other convenience methods into `canonical.rs`.

Keep the phase-2 task (`CanonicalViewTask` in `canonical_view_task.rs`.

Also, rename `FullTxOut` to `CanonicalTxOut`, and move it to
`canonical.rs`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4 months agorefactor(chain)!: split canonicalization into two tasks with generic `Canonical<A...
志宇 [Fri, 13 Feb 2026 12:57:39 +0000 (12:57 +0000)]
refactor(chain)!: split canonicalization into two tasks with generic `Canonical<A, P>`

Separate concerns by splitting `CanonicalizationTask` into two phases:

1. `CanonicalTask` determines which transactions are canonical and why
   (`CanonicalReason`), outputting `CanonicalTxs<A>`.
2. `CanonicalViewTask` resolves reasons into `ChainPosition`s (confirmed
   vs unconfirmed), outputting `CanonicalView<A>`.

Make `Canonical<A, P>`, `CanonicalTx<P>`, and `FullTxOut<P>` generic over
the position type so the same structs serve both phases. Add
`LocalChain::canonical_view()` convenience method for the common two-step
pipeline.

Renames:

- `CanonicalizationTask` -> `CanonicalTask`
- `CanonicalizationParams` -> `CanonicalParams`
- `canonicalization_task()` -> `canonical_task()`
- `FullTxOut::chain_position` -> `FullTxOut::pos`

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4 months agorefactor(chain)!: remove `CanonicalIter` APIs
Leonardo Lima [Tue, 19 May 2026 19:32:39 +0000 (16:32 -0300)]
refactor(chain)!: remove `CanonicalIter` APIs

The codebase has been updated to the new `LocalChain::canonical_view`
method. It's now safe to remove the `CanonicalIter` it's the old APIs
relying on it, eg. `try_canonical_view`.

4 months agochore(workspace): use new `LocalChain::canonical_view` API
Leonardo Lima [Tue, 19 May 2026 18:56:26 +0000 (15:56 -0300)]
chore(workspace): use new `LocalChain::canonical_view` API

Updates the codebase to use the new convenience
`LocalChain::canonical_view` method in order to generate the
`CanonicalView`. Internally the convenience method follows the `sans-IO` approach,
separating the canonicalization algorithm from i/o operations, and it's
used as follows:

1. Create a new `CanonicalizationTask` with a `TxGraph`, by calling:
   `graph.canonicalization_task(params)`
2. Execute the canonicalization process with a chain oracle (e.g
   `LocalChain`, which implements `ChainOracle` trait), by calling:
   `chain.canonicalize(task, chain_tip)`

4 months agofeat(core,chain): introduce `CanonicalizationTask` and `ChainQuery`
Leonardo Lima [Tue, 19 May 2026 18:53:10 +0000 (15:53 -0300)]
feat(core,chain): introduce `CanonicalizationTask` and `ChainQuery`

It introduces the new `CanonicalizationTask` that's implements the
canonicalization algorithm through a request/response pattern.

Also, it introduces the new `ChainQuery` trait in `bdk_core`, which
provides an interface for blockchain source/oracle query-based operations.
Allowing sans-IO patterns for algorithm that needs a blockchain oracle,
without the need for directly implement/handle I/O.

Adds new API methods into `LocalChain`: `canonicalize` and `canonical_view`,
adding same features as the existing `CanonicalIter` and it's APIs.

Co-Authored-By: Claude <noreply@anthropic.com>
4 months agoMerge bitcoindevkit/bdk#2006: refactor: replace examples with focused rustdoc examples
merge-script [Mon, 25 May 2026 14:55:22 +0000 (10:55 -0400)]
Merge bitcoindevkit/bdk#2006: refactor: replace examples with focused rustdoc examples

522e441a1a4511860a90c43c3932cd45a8797c15 chore: Remove example_cli (Kyle 🐆)

Pull request description:

  Closes #1973

  The PR is now scoped to the first step of the work discussed in #1973:

  - Remove the unmaintained example binaries in examples/
  - Remove the related workspace members from Cargo.toml
  - Clean up docs and CI references that pointed to the removed examples

ACKs for top commit:
  ValuedMammal:
    ACK 522e441a1a4511860a90c43c3932cd45a8797c15

Tree-SHA512: 217bbe23c0798903046eaf3f8638099d721c59bc26902fa5d9c3a0e3c3bcc6738b9a7e9b6e6ca82e3c1758aaffd69bec3e91cf00e5a6bf7e1da7c53cf23f1288

4 months agochore: Remove example_cli
Kyle 🐆 [Mon, 25 May 2026 00:22:17 +0000 (20:22 -0400)]
chore: Remove example_cli

Remove the standalone example crates under examples/ and clean up
workspace, docs, and CI references that depended on them.

This commit only does the removal and cleanup. Adding targeted
rustdoc examples is deferred to follow-up PRs.

5 months agoMerge bitcoindevkit/bdk#2048: feat(core): add skiplist to CheckPoint for faster traversal
merge-script [Fri, 15 May 2026 19:55:48 +0000 (19:55 +0000)]
Merge bitcoindevkit/bdk#2048: feat(core): add skiplist to CheckPoint for faster traversal

e095266afa2d80be0c6d2ef9d140ba2c67078a1d feat(core): Optimize `CheckPointIter` using pskip (志宇)
0257c549d0602bfcf016be713f281443a0d85a78 feat(core)!: Remove unused methods of `CheckPointEntry` (志宇)
86c052e5cb1096cb282b78b72e4df8fa9d76b4c6 test(core): add random-access skiplist benchmark (志宇)
d9ce149db413c44c31d8719580876f7bbc31d277 feat(core): add skiplist to CheckPoint for faster traversal (志宇)
08d1bdee2fae549610abed2db705586305231e0c feat(chain,electrum): Make clippy happy (志宇)

Pull request description:

  ### Description

  #### Summary

  Add a skiplist to `CheckPoint` using Bitcoin Core's `CBlockIndex::pskip` pattern, adapted to operate on checkpoint indices rather than block heights. Result: O(log n) lookups for `get`, `floor_at`, and `range`, with no tuning constant. Per-node memory is unchanged — `Option<Arc<CPInner>>` is niche-optimized to 8 bytes whether or not it's populated.

  The pskip approach was suggested by @ValuedMammal in https://github.com/bitcoindevkit/bdk/pull/2048#issuecomment-4328710770 and prototyped in https://github.com/ValuedMammal/block-graph/blob/master/block-graph/src/checkpoint.rs. Credit for the core idea goes there. This PR takes a different implementation path; the rationale is below.

  #### Design

  Every `CheckPoint` node carries one Arc skip pointer to a deterministically chosen ancestor at index `skip_index(i)`. The chosen targets give skip distances that grow exponentially as you walk back, yielding O(log n) traversal — ~17 hops at 1M blocks vs ~1M for a linear walk.

  `get`, `floor_at`, and `range` all reduce to "walk back to the highest checkpoint at or below `target_height`", factored into one private `walk_to_floor` helper. Each public method is a thin wrapper.

  #### Bench numbers (criterion)

  Absolute pskip times, with a linear-walk baseline at 1M blocks for scale:

  ```
  get_1000_middle             227 ns
  get_10000_near_end          397 ns
  floor_at_10000              565 ns
  random_access_skiplist_1m   1.36 µs   (vs 3.84 ms linear walk over the same chain)
  ```

  #### Caveats

  - Insert is ~50 ns slower per push than a non-skiplist `CheckPoint` because every push wires its skip pointer via an O(log n) `ancestor_by_index` walk. For real-world per-block chain extension this is negligible (well under 1 µs/block), but bulk-rebuild paths (`insert_sparse_1000` → ~3.5 µs for 1000 sequential pushes) pay it linearly.

  ### Notes to the reviewers

  #### History

  This PR went through three design iterations. Including the rationale here so reviewers don't have to reconstruct it from the commit graph:

  **1. Fixed-stride skiplist (initial proposal).** First version added a single skip pointer every `CHECKPOINT_SKIP_INTERVAL` indices, each pointing exactly that many positions back. `INTERVAL` was originally `100`, then bumped to `1000` after benchmarking — at mainnet-scale chains (~1M blocks) the cost-optimal stride for an O(n/k + k) walk is `k ≈ √n ≈ 1000`. This delivered ~50–80× speedups over linear walks but still left lookups at O(√n) (~2,000 hops at 1M blocks) and forced a tuning constant.

  **2. Bitcoin Core's `pskip` (current).** @ValuedMammal pointed out that Bitcoin Core's `CBlockIndex::pskip` pattern achieves O(log n) lookups with no tuning constant by giving every node a single skip pointer to a *deterministically chosen* ancestor (computed from the index's bit pattern). Crucially, this comes at no extra per-node memory cost: `Option<Arc<CPInner>>` was already 8 bytes per node thanks to niche-optimization, so populating the field on every node uses the same space as populating it on every 1000th. Switched to this approach; the synthetic skiplist benches went from ~80% improvement to ~94% over linear, and the tuning constant disappeared.

  **3. Reuse the pskip walker.** Once `ancestor_by_index` (the O(log n) walk underlying pskip) existed, it became natural to plug it into anywhere `CheckPoint`/`CheckPointIter` was advancing through the chain by a known amount. `get`, `range`, `floor_at`, `floor_below`, and `Iterator::nth` / `last` all use it now; `count` and `size_hint` derive from the `index` counter directly. Unused traversal methods on `CheckPointEntry` were removed in the same pass since they were duplicating `CheckPoint`'s own surface and had no callers in the workspace.

  #### Diffs from @ValuedMammal's prototype

  The [block-graph prototype](https://github.com/ValuedMammal/block-graph/blob/master/block-graph/src/checkpoint.rs) gets the high-level idea right (every node carries one deterministic skip pointer; index-based formula so sparse chains work) but has a few choices I wanted to address differently:

  **1. `n & (n - 1)` underflow.** The prototype handles it by converting `u32 → i32 → u32` with `try_into().expect(...)`, relying on signed two's-complement to give `0 & -1 == 0`. This PR uses `n & n.wrapping_sub(1)` directly on `u32`, which produces the same `0 & u32::MAX == 0` result without the round-trip conversion.

  **2. Traversal heuristic.** The prototype's `get` uses simple greedy ("take skip if it doesn't undershoot, else prev"). That's correct but **not genuinely O(log n)** — for targets near genesis on a long chain, it degrades to many small geometric descents stacked end-to-end. This PR uses Bitcoin Core's `GetAncestor` heuristic verbatim: take skip unless the *predecessor's* skip would have been a strictly bigger jump that still doesn't undershoot.

  **3. Single internal helper for `get`/`range`/`floor_at`.** The public surface (`get`, `range`, `floor_at`) all reduces to "walk back to the highest checkpoint at or below `target_height`". This PR factors that into one private `walk_to_floor` function; each public method becomes a thin wrapper. The prototype duplicates the skip-walk logic.

  **4. `Drop` cleanup.** With every node holding a skip Arc, the manual unwind loop in `Drop for CPInner` (existing fix for #1634) is extended with `node.skip.take()` so skip-pointer drops happen on the manual loop rather than triggering ancestor drops via the implicit recursive path.

  ### Changelog

  ```md
  Added:
  - `O(log n)` skiplist on `CheckPoint` (Bitcoin Core's pskip pattern). Speeds up `get`, `floor_at`, `range`, and full-chain iteration.
  - `Iterator::nth` / `last` / `count` / `size_hint` overrides on `CheckPointIter` and `CheckPointEntryIter`, plus `ExactSizeIterator` impls. `nth` and `last` are now `O(log n)`; `count` and `size_hint` are `O(1)`.

  Removed:
  - Unused traversal methods on `CheckPointEntry`: `iter`, `get`, `range`, `floor_at`, `floor_below`. They had no callers in the workspace and duplicated `CheckPoint`'s own surface.
  ```

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

ACKs for top commit:
  nymius:
    ACK e095266afa2d80be0c6d2ef9d140ba2c67078a1d

Tree-SHA512: 15d71abe5959d4928d44bd60cbd323f24197b3fb8de36f774f4f45e517fe67b470df7193f69b7d95e3854bf921c55659236009fd3569289e0063b227e66b0cbf

5 months agofeat(core): Optimize `CheckPointIter` using pskip
志宇 [Fri, 15 May 2026 18:22:26 +0000 (18:22 +0000)]
feat(core): Optimize `CheckPointIter` using pskip

5 months agofeat(core)!: Remove unused methods of `CheckPointEntry`
志宇 [Mon, 4 May 2026 09:08:23 +0000 (09:08 +0000)]
feat(core)!: Remove unused methods of `CheckPointEntry`

5 months agotest(core): add random-access skiplist benchmark
志宇 [Thu, 23 Apr 2026 16:19:24 +0000 (16:19 +0000)]
test(core): add random-access skiplist benchmark

Addresses review feedback from @nymius: existing benches use fixed
targets, which can land favorably or unfavorably relative to skip
pointer positions and don't reflect real query patterns. The new
bench draws 256 targets from a deterministic xorshift sequence and
runs both a skiplist-enhanced get() and a plain linear walk over
a 100k-node chain, so the same query stream exercises both paths.

Also changed benchmarks to reflect checkpoint full chains and removed
unhelpful benchmarks.

5 months agofeat(core): add skiplist to CheckPoint for faster traversal
志宇 [Thu, 25 Sep 2025 07:20:43 +0000 (07:20 +0000)]
feat(core): add skiplist to CheckPoint for faster traversal

Adds a skip pointer and an index field to CheckPoint to accelerate
get(), floor_at(), and range(). push() and insert() maintain the
index/skip invariants on the rebuilt chain.

The skip pointer follows Bitcoin Core's `CBlockIndex::pskip` pattern,
adapted to operate on checkpoint indices (not heights) so it works on
sparse chains. Skip distances grow exponentially as you walk back,
yielding O(log n) traversal.

5 months agofeat(chain,electrum): Make clippy happy
志宇 [Fri, 1 May 2026 10:33:08 +0000 (10:33 +0000)]
feat(chain,electrum): Make clippy happy

5 months agoMerge bitcoindevkit/bdk#2144: ci: switch from coveralls to codecov for coverage reporting
merge-script [Fri, 8 May 2026 19:13:25 +0000 (16:13 -0300)]
Merge bitcoindevkit/bdk#2144: ci: switch from coveralls to codecov for coverage reporting

2a42f2517036325588c7681b5efc90b3c725a119 ci(coverage): use `codecov` instead of `coveralls` (Shubham Shinde)

Pull request description:

  closes #2093

  ### Description

  Migrates code coverage reporting from Coveralls to Codecov.

  - **Replaced coverage service**: Switched from `coverallsapp/github-action` to `codecov/codecov-action@v5.5.2` (pinned commit SHA for security)
  - **Added permissions**: Added `contents: read` and `pull-requests: write` to enable Codecov PR comments
  - **Codecov configuration**: Added `flags: rust` and `name: codecov-bdk` for better organization

  ### Changelog notice

  ```
  ### Changed
  - chore(ci): switch from coveralls to codecov
  ```
  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

ACKs for top commit:
  oleonardolima:
    self-ACK 2a42f2517036325588c7681b5efc90b3c725a119
  notmandatory:
    ACK 2a42f2517036325588c7681b5efc90b3c725a119

Tree-SHA512: 53dd898fefb973fc4a884fbfb34335d5f03992d30c676841b66db8dcc5a4d3a8e5178d5886cb91308df873f25fe6703457dc27aa5b45451f828bfcded612cace

5 months agoMerge bitcoindevkit/bdk#2195: fix(electrum): do not pick unindexed outputs for histor...
merge-script [Fri, 8 May 2026 18:49:43 +0000 (15:49 -0300)]
Merge bitcoindevkit/bdk#2195: fix(electrum): do not pick unindexed outputs for history lookup

2e3d52e80fdc504c718426b9255a30fc24b7ceda fix(electrum): do not pick unindexed outputs for history lookup (Zoe Faltibà)

Pull request description:

  ### Description

  `BdkElectrumClient::populate_with_txids` queries each transaction's confirmation status by calling `script_get_history` on the script of one of its outputs. It currently picks the first output unconditionally. This breaks for transactions which first output is an `OP_RETURN`, because Electrum servers don't index `OP_RETURN` scripts and will return an empty history. This is a real-world scenario: protocols like RGB place an `OP_RETURN` commitment as the first output of every transaction.

  ### Notes to the reviewers

  The fix selects the first output which script is not `OP_RETURN` or a `OP_FALSE OP_RETURN`. If a transaction has only `OP_RETURN`/`OP_FALSE OP_RETURN` outputs, we fall back to the script of any input's previous output to query history. The only case still skipped is a coinbase with all unindexed outputs, since coinbases have no parent to fall back on.

  ### Changelog notice

  ```
  Fixed:
  - `BdkElectrumClient::sync` now correctly retrieves confirmation status for transactions which first output is an `OP_RETURN` or `OP_FALSE OP_RETURN`
  ```

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

ACKs for top commit:
  oleonardolima:
    ACK 2e3d52e80fdc504c718426b9255a30fc24b7ceda
  evanlinjin:
    ACK 2e3d52e80fdc504c718426b9255a30fc24b7ceda

Tree-SHA512: d87a0580c0db4e86ee6fce84a82ac56ef70378b46405bb54ca33f1a2a643feba6fa769c0ba2a9e01c77cc6308c777ccc34f89d548e46a1bbf036e31d8c3ddcd0

5 months agoci(coverage): use `codecov` instead of `coveralls`
Shubham Shinde [Mon, 9 Mar 2026 22:13:37 +0000 (03:43 +0530)]
ci(coverage): use `codecov` instead of `coveralls`

- update the `code_coverage.yml` CI job to use codecov instead of
  coveralls, copied from `bdk_wallet` repository.

5 months agoMerge bitcoindevkit/bdk#2171: Add `SECURITY.md`
merge-script [Tue, 5 May 2026 14:02:57 +0000 (11:02 -0300)]
Merge bitcoindevkit/bdk#2171: Add `SECURITY.md`

122207f099bd1c7085701dd0d905b3ccd8c46bad feat(doc): add `SECURITY.md` (Luis Schwab)

Pull request description:

  Closes #2168

  ## Changelog
  ```
  - Add `SECURITY.md` listing the PGP key to be used for vulnerability disclosures
  ```

ACKs for top commit:
  oleonardolima:
    ACK 122207f099bd1c7085701dd0d905b3ccd8c46bad
  notmandatory:
    ACK 122207f099bd1c7085701dd0d905b3ccd8c46bad

Tree-SHA512: 22e5d05ee4497a1c4e40e1aedb25739c8e2ab954e876f4305175697ee366b17d18d44aeb22ef81558e0515cc056bd828358116c6af0567f9ca17d4183b21bf3c

5 months agoMerge bitcoindevkit/bdk#2193: [testenv] Bump `electrsd` to v0.38.0
merge-script [Fri, 1 May 2026 14:07:51 +0000 (11:07 -0300)]
Merge bitcoindevkit/bdk#2193: [testenv] Bump `electrsd` to v0.38.0

7ba31dd8ecdfa72732654a74d144fa4a2d9c18d0 chore(deps)!: bump `electrsd` to v0.38.0 (Luis Schwab)

Pull request description:

  Closes #2183

  ## Changelog
  ```
  ### Changed
  Bump `electrsd` to v0.38.0
  ```

ACKs for top commit:
  oleonardolima:
    ACK 7ba31dd8ecdfa72732654a74d144fa4a2d9c18d0
  tvpeter:
    tACK 7ba31dd8ecdfa72732654a74d144fa4a2d9c18d0
  ValuedMammal:
    ACK 7ba31dd8ecdfa72732654a74d144fa4a2d9c18d0

Tree-SHA512: 53a9800e74dc63ff532a13fc7eabe123dd1a60cc6b9d81109e5bac63f85d0babf308b2dd9fb0021abde956a4e7aa567f42cec20b626e4e6a360a14cc7139d7f9

5 months agofix(electrum): do not pick unindexed outputs for history lookup
Zoe Faltibà [Wed, 29 Apr 2026 09:50:23 +0000 (11:50 +0200)]
fix(electrum): do not pick unindexed outputs for history lookup

5 months agochore(deps)!: bump `electrsd` to v0.38.0
Luis Schwab [Wed, 29 Apr 2026 01:31:25 +0000 (22:31 -0300)]
chore(deps)!: bump `electrsd` to v0.38.0

5 months agoMerge bitcoindevkit/bdk#2190: Remove unused `start-core.sh` script
merge-script [Wed, 29 Apr 2026 11:16:43 +0000 (11:16 +0000)]
Merge bitcoindevkit/bdk#2190: Remove unused `start-core.sh` script

ae0e826c378377e589f0f606c215cb61976f0d0d chore: remove unused `start-core.sh` script (Luis Schwab)

Pull request description:

  ## Changelog
  ```
  - Remove the unused `ci/start-core.sh` script
  ```

ACKs for top commit:
  oleonardolima:
    ACK ae0e826c378377e589f0f606c215cb61976f0d0d
  evanlinjin:
    ACK ae0e826c378377e589f0f606c215cb61976f0d0d

Tree-SHA512: af9205cfb0de497f16fa10d49afd2f87291f79e19157a784b7e97ec8cf75bdffc1c4c5a71f755f0c16074b2afa96560001951551554749ff5ae57e3898202908

5 months agoMerge bitcoindevkit/bdk#2194: ci: Fix pin-msrv
merge-script [Wed, 29 Apr 2026 10:43:47 +0000 (10:43 +0000)]
Merge bitcoindevkit/bdk#2194: ci: Fix pin-msrv

24e23e3a7d1f9a64dcf499f894c93af104a98bb9 ci: Fix pin-msrv (志宇)

Pull request description:

  ### Description

  MSRV broke again.

  ### Checklists

  #### All Submissions:

  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)

ACKs for top commit:
  LLFourn:
    ACK 24e23e3a7d1f9a64dcf499f894c93af104a98bb9

Tree-SHA512: 8e94f2eddabc1a403d227a4466bf94a55ee2fe06c1f97d581fe6a7a1039b4bfa156e08559e1b2263ee2cf205352eb1fe2ec819a76d7b081d2995f2804511f2db

5 months agoci: Fix pin-msrv
志宇 [Tue, 28 Apr 2026 15:31:00 +0000 (15:31 +0000)]
ci: Fix pin-msrv