Skip to content

fix(bdk_electrum_streaming): Keep the highest last active index per update - #21

Open
evanlinjin wants to merge 2 commits into
mainfrom
claude/github-issue-3-status-0ewl7a
Open

fix(bdk_electrum_streaming): Keep the highest last active index per update#21
evanlinjin wants to merge 2 commits into
mainfrom
claude/github-issue-3-status-0ewl7a

Conversation

@evanlinjin

Copy link
Copy Markdown
Owner

Context

Follow-up on #3 ("Fix off-by-one error in last_active_index emission").

The off-by-one from #3 is already fixed. At the time the issue was filed, handle_script_status returned this_index + 1 and that value was emitted directly as the keychain's last active index. Commit 4447f17 ("fix: Off-by-one last_active_indices update", May 2025) changed it to return this_index, and every emission site on main today reports the index of the spk that actually has history. The first commit here adds a state-level regression test that pins that behaviour so it cannot silently regress.

While covering it, I found a second way the same field goes wrong — not an off-by-one, but the same consequence (an active spk never gets revealed).

Changes

test(bdk_electrum_streaming): Cover last_active_indices emission

Drives a full sync of a descriptor whose only history sits at derivation index 3 and asserts the emitted last_active_indices is exactly {"external": 3} — not 4. Passes on main.

To let the test server answer a script status, Server now holds the set of script hashes its tx pays to along with the status to answer their subscriptions with, instead of a single hash that always answers null.

fix(bdk_electrum_streaming): Keep the highest last active index per update

Spk jobs of the same keychain share requests, so one response can finish several of them at once — a tx paying two of our spks is enough. advance_spk_jobs folded each finished job into the update with:

update.last_active_indices.extend(self.spk_tracker.index_of_spk_hash(spk_hash));

BTreeMap::extend overwrites, and the jobs are iterated in ElectrumScriptHash order (BTreeSet<JobId>), which is unrelated to derivation order. So the last job iterated wins, and the update can report a lower index than the highest spk that actually has history.

That index is what reveal_to_target_multi reveals to, so the higher spk stays unrevealed and the wallet does not recognise its txouts as its own.

The fix merges on the maximum instead. Added test: one tx pays derivation indices 3 and 4 (index 4's script hash sorts below index 3's, so the higher index is reached first and then clobbered). It fails on main with left: Some(3), right: Some(4) and passes with the fix.

Testing

cargo fmt --check, cargo clippy --lib -D warnings, and cargo test (lib + tests/state.rs) all pass.

tests/env.rs could not be run here: its bdk_testenv dev-dependency builds bitcoind, whose build script downloads Bitcoin Core from bitcoincore.org, which this sandbox's egress proxy blocks. That file is untouched by this PR, but it is worth a CI run.


Generated by Claude Code

claude added 2 commits August 25, 2026 01:17
`last_active_indices` must name the derivation index of the spk that has
history, not the index after it. Emitting `index + 1` reveals one spk too
many on every sync, permanently skipping an unused address.

Pin the emitted value with a state-level test that drives a full sync of a
descriptor whose only history sits at a non-zero derivation index.

To let a test server answer a script status, `Server` now holds the set of
script hashes its tx pays to along with the status to answer their
subscriptions with.

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

Spk jobs of the same keychain share requests, so a single response can
finish several of them at once — a tx paying two of our spks is enough.
`advance_spk_jobs` folded each finished job into the update's
`last_active_indices` with `extend`, which overwrites rather than keeps the
larger index, and jobs are iterated in script hash order, not derivation
order. The last job iterated therefore won, so the update could report a
lower index than the highest spk that actually has history.

That index is what `reveal_to_target_multi` reveals to, so the higher spk
stays unrevealed and the wallet does not recognise its txouts as its own.

Merge on the maximum instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fiXt5nTpVEys3D7NNAtAC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants