fix(bdk_electrum_streaming): Keep the highest last active index per update - #21
Open
evanlinjin wants to merge 2 commits into
Open
fix(bdk_electrum_streaming): Keep the highest last active index per update#21evanlinjin wants to merge 2 commits into
evanlinjin wants to merge 2 commits into
Conversation
`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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_statusreturnedthis_index + 1and that value was emitted directly as the keychain's last active index. Commit 4447f17 ("fix: Off-by-onelast_active_indicesupdate", May 2025) changed it to returnthis_index, and every emission site onmaintoday 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 emissionDrives a full sync of a descriptor whose only history sits at derivation index 3 and asserts the emitted
last_active_indicesis exactly{"external": 3}— not4. Passes onmain.To let the test server answer a script status,
Servernow 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 answersnull.fix(bdk_electrum_streaming): Keep the highest last active index per updateSpk 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_jobsfolded each finished job into the update with:BTreeMap::extendoverwrites, and the jobs are iterated inElectrumScriptHashorder (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_multireveals 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
mainwithleft: Some(3), right: Some(4)and passes with the fix.Testing
cargo fmt --check,cargo clippy --lib -D warnings, andcargo test(lib +tests/state.rs) all pass.tests/env.rscould not be run here: itsbdk_testenvdev-dependency buildsbitcoind, whose build script downloads Bitcoin Core frombitcoincore.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