-
Notifications
You must be signed in to change notification settings - Fork 405
refactor(chain)!: replace CanonicalIter
with sans-io CanonicalizationTask
#2038
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
refactor(chain)!: replace CanonicalIter
with sans-io CanonicalizationTask
#2038
Conversation
d851ba6
to
c02636d
Compare
c02636d
to
78c0538
Compare
677e25a
to
9e27ab1
Compare
/// after completing the canonicalization process. It takes the processed transaction | ||
/// data including the canonical ordering, transaction map with chain positions, and | ||
/// spend information. | ||
pub(crate) fn new( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I think we can remove this and make all fields pub(crate)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work.
This is my initial round of reviews.
Are you planning to introduce topological ordering in a separate PR?
/// | ||
/// This method processes the response to a previous query request and updates | ||
/// the internal state accordingly. | ||
fn resolve_query(&mut self, response: ChainResponse<B>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably mention that:
- Queries need to be resolved in order.
- The same
ChainRequest
will be returned fromnext_query
if it's not resolved.
let chain_tip = chain.tip().block_id(); | ||
let task = graph.canonicalization_task(chain_tip, Default::default()); | ||
let canonical_view = chain.canonicalize(task); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about the following naming:
CanonicalizationTask
->CanonicalResolver
.TxGraph::canonicalization_task
->TxGraph::resolver
.LocalChain::canonicalize
->LocalChain::resolve
.
crates/chain/src/canonical_task.rs
Outdated
canonical: CanonicalMap<A>, | ||
not_canonical: NotCanonicalSet, | ||
|
||
pending_anchor_checks: VecDeque<(Txid, Arc<Transaction>, Vec<A>)>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: What do you think about renaming this to pending_anchor_queries
? This makes it clearer that we are checking against the remote.
crates/chain/src/canonical_task.rs
Outdated
} | ||
|
||
fn is_finished(&mut self) -> bool { | ||
self.pending_anchor_checks.is_empty() && self.unprocessed_anchored_txs.size_hint().0 == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking throught the docs for .size_hint
, I don't think we should use this here - it seems unreliable.
Additionally, Edit: Turns out process_anchored_txs()
just moves items from unprocessed_anchored_txs
to pending_anchor_checks
one at a time, skipping already canonicalized txs. How about we just collect everything upfront? This way, we will know exactly how many is left by looking at that field and not relying on .size_hint
.pending_anchor_checks
is also populated in .mark_canonical
for marking a transitively-canonical tx.
crates/chain/src/local_chain.rs
Outdated
/// ``` | ||
pub fn canonicalize<A: Anchor>( | ||
&self, | ||
mut task: CanonicalizationTask<'_, A>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trait here!
crates/chain/src/canonical_task.rs
Outdated
for txid in undo_not_canonical { | ||
self.not_canonical.remove(&txid); | ||
} | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Have the detected_self_double_spend
early return instead of having the else
branch.
Rationale: Early return is easier to read and results in less nesting.
} | ||
} | ||
None => { | ||
self.unprocessed_leftover_txs.push_back(( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to hit this branch if it's a transitively-canonical tx.
…ionTask` introduces `CanonicalizationTask` that implements canonicalization using a request/response pattern, removing direct dependency on `ChainOracle`. - add `CanonicalizationTask` with request/response pattern for chain queries - track confirmed anchors to eliminate redundant queries - handle direct vs transitive anchor determination - return complete `CanonicalView` with correct chain positions - add `LocalChain::handle_canonicalization_request` helper - export `CanonicalizationTask`, `CanonicalizationRequest`, `CanonicalizationResponse` BREAKING CHANGE: replaces direct `ChainOracle` querying in canonical iteration with a new request/response pattern through `CanonicalizationTask`.
Changes `CanonicalizationRequest` to a struct and `CanonicalizationResponse` to `Option<A>` to process all anchors for a transaction in a single request. - convert `CanonicalizationRequest` from enum to struct with anchors vector - change `CanonicalizationResponse` to `Option<A>` returning best confirmed anchor - batch all anchors for a transaction in one request instead of one-by-one - simplify `process_anchored_txs` to queue all anchors at once - add transitive anchor checking back in `mark_canonical()` This reduces round trips between `CanonicalizationTask` and `ChainOracle` while maintaining the same functionality. The API is cleaner with a struct-based request that mirrors how `scan_anchors` worked in the original `CanonicalIter`. BREAKING CHANGE: `CanonicalizationRequest` and `CanonicalizationResponse` types have changed from enums to struct/type alias respectively.
- Replace `CanonicalView::new()` constructor with internal `CanonicalView::new()` for use by `CanonicalizationTask` - Remove `TxGraph::try_canonical_view()` and `TxGraph::canonical_view()` methods - Add `TxGraph::canonicalization_task()` method to create canonicalization tasks - Add `LocalChain::canonicalize()` method to process tasks and return `CanonicalView`'s - Update `IndexedTxGraph` to delegate canonicalization to underlying `TxGraph` The new API separates canonicalization logic from I/O operations: - Create canonicalization task: `graph.canonicalization_task(params)` - Execute canonicalization: `chain.canonicalize(task, chain_tip)` BREAKING CHANGE: Remove `CanonicalView::new()` and `TxGraph::canonical_view()` methods in favor of task-based approach
- Delete entire `canonical_iter.rs` file and its module declaration - Move `CanonicalReason`, `ObservedIn`, and `CanonicalizationParams` to `canonical_task.rs` - Update module exports to use `pub use canonical_task::*` instead of selective exports BREAKING CHANGE: `CanonicalIter` and all its exports are removed
…icalizationTask` Introduce a new `ChainQuery` trait in `bdk_core` that provides an interface for query-based operations against blockchain data. This trait enables sans-IO patterns for algorithms that need to interact with blockchain oracles without directly performing I/O. The `CanonicalizationTask` now implements this trait, making it more composable and allowing the query pattern to be reused for other blockchain query operations. - Add `ChainQuery` trait with associated types for Request, Response, Context, and Result - Implement `ChainQuery` for `CanonicalizationTask` with `BlockId` as context BREAKING CHANGE: `CanonicalizationTask::finish()` now requires a `BlockId` parameter Co-Authored-By: Claude <[email protected]>
Make `ChainRequest`/`ChainResponse` generic over block identifier types to enable reuse beyond BlockId. Move `chain_tip` into `ChainRequest` for better encapsulation and simpler API. - Make `ChainRequest` and `ChainResponse` generic types with `BlockId` as default - Add `chain_tip` field to `ChainRequest` to make it self-contained - Change `ChainQuery` trait to use generic parameter `B` for block identifier type - Remove `chain_tip` parameter from `LocalChain::canonicalize()` method - Rename `ChainQuery::Result` to `ChainQuery::Output` for clarity BREAKING CHANGE: - `ChainRequest` now has a `chain_tip` field and is generic over block identifier type - `ChainResponse` is now generic with default type parameter `BlockId` - `ChainQuery` trait now takes a generic parameter `B = BlockId` - `LocalChain::canonicalize()` no longer takes a `chain_tip` parameter Co-authored-by: Claude <[email protected]>
9e27ab1
to
f6c8b02
Compare
…zation - convert `unprocessed_anchored_txs` from iterator to `VecDeque` - remove `pending_anchor_checks` queue entirely - collect anchored transactions upfront instead of lazy iteration - make `LocalChain::canonicalize()` generic over `ChainQuery` trait
…nQuery` Allow any type implementing `ChainQuery` trait instead of requiring `CanonicalizationTask` specifically.
fixes #1816
Description
It completes a refactoring of the canonicalization API in
bdk_chain
, migrating from an iterator-based approachCanonicalIter
to a sans-IO/task-based patternCanonicalizationTask
. This change improves the separation of concerns between canonicalization logic and I/O operations, making the code more testable and flexible.Old API:
New API:
The new flow works as follows:
CanonicalizationTask
encapsulates all canonicalization logic without performing any chain queries.CanonicalizationRequests
for anchor verification as needed, allowing theChainOracle
to batch or optimize these queries.ChainOracle
(e.g., LocalChain) processes requests and returnsCanonicalizationResponse
's indicating which anchors are the best in chain.CanonicalView
containing all canonical transactions with their chain positions.This sans-IO pattern enables:
Notes to the reviewers
The changes are splitted in multiple commits, as I think it could help reviewing it. Also it depends on #2029 PR, as it's built on top of it.
Changelog notice
Checklists
All Submissions:
New Features:
Bugfixes: