Refactor ChainQuery to be more flexible #2139
Conversation
6faf825 to
c1ad161
Compare
bdd7608 to
45bf03a
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2139 +/- ##
==========================================
+ Coverage 78.65% 78.71% +0.05%
==========================================
Files 30 31 +1
Lines 5909 6107 +198
Branches 279 279
==========================================
+ Hits 4648 4807 +159
- Misses 1185 1227 +42
+ Partials 76 73 -3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@evanlinjin the PR dependency is solved, #2038 landed on master! 🚀 🚀 |
c037f35 to
dfe4f4e
Compare
Exposing median-time-past (MTP) on canonical views needs block
timestamps, but the `ChainQuery` interface can't carry them: its
response is `ChainResponse = Option<BlockId>` — only a hash and height.
Change the interface so the driver returns block *data* by height,
generic over a block type `B` (default `BlockHash`, usable as a full
`Header`). The driving call also moves from `next_query() -> Option<…>`
to `poll() -> TaskProgress`, whose enum expresses states `next_query`
couldn't: incremental progress (`Advanced`) and a `Blocked` state that
leaves room for a future streaming driver. This keeps the multi-stage
canonicalization a clean poll loop rather than an internal one.
With block data flowing back, `CanonicalView` gains per-transaction and
tip MTP, computed optionally via `CanonicalViewTask::with_mtp()`.
Mechanically: rename `ChainQuery` to `ChainTask`; remove
`ChainRequest`/`ChainResponse`; add a `BlockQueries` helper for
request/resolve tracking; add `LocalChain::run_task` and the
`canonicalize{,_with_mtp}()` convenience methods.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both canonicalization phases checked a tx's anchors against fetched blocks with identical logic: scan for a resolved, matching block; else request the missing heights; else wait on in-flight queries; else give up. The two copies had already drifted (the phase-2 copy still carried the old `has_unresolved_heights` shape with a latent empty-`Query` edge). Extract that logic as `BlockQueries::resolve_candidates`, returning a `BlockCandidateResolution` enum (`Confirmed` / `Query` / `Awaiting` / `NotConfirmed`). It lives on `BlockQueries` since it is an operation on that type, and is decoupled from `Anchor`: callers pass the candidates as `(payload, BlockId)` pairs, so it needs only a `BlockId` per candidate rather than the `Anchor` trait. Each call site now maps the four outcomes onto its own queue handling and `TaskProgress`. Adds unit tests covering all four outcomes, including the `Awaiting` branch that the synchronous driver never hits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ked` `Blocked` reads more cleanly at call sites and pairs naturally with the streaming-driver framing: `Query` says "launch I/O", `Blocked` says "block until some lands". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers the previously-untested median-time-past path: a `LocalChain<Header>` whose block times equal their heights (so the MTP of an 11-block window is the middle height), with a tx confirmed at a known height. Asserts both the tip MTP and the per-tx MTP. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both `CheckPoint::median_time_past` and the canonical view task's MTP computation implemented the same BIP-113 median (11-block window, higher-middle value). Extract a `bdk_core::median_time_past(height, block_time)` helper — abstracting the timestamp source via a closure — so the rule lives in one place; both sites now delegate to it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`CanonicalTxs::view_task` is the entry point for building a view, but MTP computation is opt-in via `CanonicalViewTask::with_mtp`. Cross-reference it so it is discoverable from the view-building method. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2f5d193 to
5dc4b51
Compare
|
@oleonardolima We should have done this in one go in #2038. Now reviewers need to review 2 large refactor PRs. 😢 |
Oh, agree! Though #2038 was long overdue and getting too convoluted, I think having this follow-up won't be that bad. I'll make this review my top priority, let me know when it's ready. |
Description
This PR introduces median-time-past (MTP) on canonical views. MTP needs block timestamps, and the existing task (
bdk_core::ChainQuery) interface cannot expose them.This PR changes the task (
bdk_core::ChainQuery) interface to request block data by height, with the response now a genericB. A driver such asLocalChaincan returnHeaders (which carry timestamps), and it resolves every requested height rather than collapsing the request to a single answer (as MTP needs all the blocks in a window, not just one). Previously the request wasVec<BlockId>and the response a singleChainResponse = Option<BlockId>: only the first in-chain block among the candidates.The new response shape could have been done by keeping
next_query() -> Option<…>, but the multi-stage canonicalization is much cleaner when the driving call returns a richerTaskProgressenum. This PR reshapes the protocol to a poll-basedChainTaskwhoseTaskProgressexpresses statesnext_querycouldn't:TaskProgress::Blockedsignals the task has issued every request it can but still needs outstanding data before it can finish. This is the hook a future streaming (non-blocking) driver would use to know when to wait.TaskProgress::Advancedlets a task writepollas one unit of work per call instead of wrapping its whole implementation in an internal loop. It also lets a driver observe progress in finer-grained increments.Canonical views can then compute MTP per transaction (
CanonicalTx::mtp) and for the tip (Canonical::tip_mtp), opted into viawith_mtp()so callers who don't need it pay nothing.Notes to the reviewers
master,LocalChain::canonicalizeis the generic task runner andLocalChain::canonical_viewis the high-level helper. This naming is confusing. In this PR, the runner becomesrun_taskand the helper becomescanonicalize.CheckPoint::median_time_past. This does not replace that method.CheckPoint::median_time_pastcomputes MTP for a height directly from the in-memoryLocalChaincheckpoint you already hold. Canonical-view computes MTP by sourcing its blocks throughChainTask/BlockQueriesso it is driver-agnostic (works for any chain source, not justLocalChain<Header>) and attaches the result to eachCanonicalTxand the tip, rather than making the caller walk checkpoints per height.TaskProgress::Blockedhas no in-tree consumer yet — this is intentional. It exists for a future streaming driver (e.g. a Bitcoin Core RPC driver) that issues fetches without blocking:Querysays "launch I/O",Blockedsays "block until some lands". The only current driver (run_task) is synchronous and treatsBlockedasunreachable!(). A comment inCanonicalTaskdocuments the intended "anchored-leaves" batching optimization for the future driver.BlockQueries::resolve_candidates(returningBlockCandidateResolution), decoupled fromAnchorby taking the candidates as(payload, BlockId)pairs.Changelog notice
bdk_coreChainQuerytrait is reshaped into a poll-basedChainTasktrait with aTaskProgressenum (Advanced/Query/Blocked/Done), generic over block typeB(defaultBlockHash). Drivers now fetch block data by height instead of resolving candidateBlockIds.BlockQueries, a request/resolve tracker, withresolve_candidatesand theBlockCandidateResolutionenum.median_time_past(height, block_time), a free helper computing the BIP-113 median-time-past with the timestamp source supplied by closure.CheckPoint::median_time_pastand the canonical-view MTP computation both delegate to it.ChainRequest/ChainResponsetype aliases.bdk_chainCanonicalTx::mtpandCanonical::tip_mtp, opted into viaCanonicalViewTask::with_mtp()andLocalChain::canonicalize_with_mtp().CanonicalTaskandCanonicalViewTasknow implement the poll-basedChainTask(previouslyChainQuery) and take a generic block-type parameterB.LocalChain::canonicalize(the generic task runner) is renamed toLocalChain::run_task, andLocalChain::canonical_view(the high-level helper) is renamed toLocalChain::canonicalize.Checklists
All Submissions:
New Features:
Bugfixes: