feat(bdk_electrum_streaming)!: Report sync progress - #31
Draft
evanlinjin wants to merge 1 commit into
Draft
evanlinjin wants to merge 1 commit into
evanlinjin wants to merge 1 commit into
Conversation
`State::poll` now returns a `Progress` alongside the update, and `State::progress` returns the same snapshot at any time. It reports: - `local_tip` vs `remote_tip`, the tip the server last announced on this connection. - `chain_synced`: the local chain is at the remote tip and the confirmation job finished its pass against it, so every anchor is proven. - `spk_jobs_completed` vs `spk_jobs_pending`. - `txs_remaining`: distinct transactions requested and not yet received. - `headers_fetched` vs `headers_remaining`, and `anchors_fetched` vs `anchors_remaining`, for the confirmation job's current pass. `Progress::is_synced` combines these, and `Progress::work` returns rough `(done, remaining)` units for a progress bar, with `remaining == 0` exactly when synced. Building a `Progress` is O(1), since `poll` builds one per message. Header and anchor counts live in the `FetchBlocks`/`FetchAnchors` stages, so a restarted or abandoned pass takes them with it, and `txs_remaining` is a counter of in-flight `GetTx` requests in `ReqCoord`. Abandoning a pass on inconsistent headers now goes to a new `ConfirmationStage::Abandoned`, so `Waiting` means finished. `start()` forgets the remote tip. `run_async` and `run_blocking` take a `progress_tx` channel that is sent to whenever progress changes; a dropped receiver does not stop them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GU8CAerKAhq93c6H7qerQT
evanlinjin
force-pushed
the
feat/progress-updates
branch
from
September 16, 2026 12:16
66dc3b4 to
98d9479
Compare
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.
Description
Lets callers show sync progress.
State::pollnow returns(Option<Update<K>>, Progress), andState::progress()returns the same snapshot at any time.Progresscontains:local_tip/remote_tipNoneuntil it announces one)chain_syncedspk_jobs_completed/spk_jobs_pendingtxs_remainingheaders_fetched/headers_remaininganchors_fetched/anchors_remainingMethods:
is_synced():chain_synced, with no spk jobs pending and no transactions remaining.work(): rough(done, remaining)units for a progress bar. Every header, anchor, spk job and transaction counts as one unit. The bar can move backwards when new work arrives.remainingis zero exactly whenis_synced()is true, so the bar never shows full early. When nothing is counted but we're not synced (before the server announces its tip, or after the confirmation job gave up), it reports one unit remaining.Keeping it accurate and cheap
pollbuilds aProgressafter every server message, so building one takes constant time:ConfirmationStage::FetchBlocksandFetchAnchorsvariants. They are updated as headers arrive (resolve_blocks) and as the anchor pass runs. When a pass restarts or is abandoned, the stage is replaced and its counts go with it, so they can't go stale.txs_remainingis a counter inReqCoordofGetTxrequests still waiting for a response.ReqCoordalready deduplicates requests, so a transaction that several jobs need is counted once.ConfirmationStage::Abandonedinstead ofWaiting. That keepsWaitingmeaning "finished", whichchain_syncedrelies on.start()clears the remote tip, since the new connection may be to a server that hasn't announced its tip yet.Runners
run_asyncandrun_blockingtake a newprogress_txchannel. It gets a message only whenProgresschanges. Progress is informational, so if the receiver is dropped, the state machine keeps running.Breaking changes
State::pollreturns(Option<Update<K>>, Progress).run_asyncandrun_blockingtake an extraprogress_txargument.ConfirmationStagehas a newAbandonedvariant, and itsFetchBlocksandFetchAnchorsvariants have a newremainingfield.Notes for reviewers
0/0once their pass finishes, so the UI never sees a final "N/N anchors". Useis_synced()for the finished state.txs_remaininghas no matching fetched count. Nothing keeps a record of what has been fetched: a job hands over its transactions and is removed when it finishes.txs_remainingalso counts transactions requested by an spk job that was later cancelled, until the server answers.FetchBlocksstage now checks itsremainingcounter to decide whether it can move on, instead of scanning every height each poll. Adebug_assertchecks that the counter matches a full scan.Tests
progress_counts_down_to_synceda_failed_proof_is_not_syncedrestarting_forgets_the_remote_tipwork()shows remaining work exactly when not synced.cargo testpasses: 34 state tests, 13 unit tests and 6 env tests against regtest.cargo clippy --all-targetsis clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01GU8CAerKAhq93c6H7qerQT