-
Notifications
You must be signed in to change notification settings - Fork 406
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
.
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.
I've been thinking about this, I agree with the CanonicalResolver
, though the TxGraph::resolver
and LocalChain::resolve
seems a bit off.
What do you think about (?):
- CanonicalResolver
- TxGraph::canonical_resolver
- LocalChain::canonical_resolve
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.
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.
I should've addressed this, let me double check.
…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]>
9e27ab1
to
f6c8b02
Compare
#[derive(Debug, Clone, PartialEq, Eq)] | ||
pub struct ChainRequest<B = BlockId> { | ||
/// The chain tip to use as reference for the query. | ||
pub chain_tip: 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.
I'm wondering whether it will be better to remove this from ChainRequest
and have a new method on ChainQuery
called something like query_tip(&self) -> BlockId
.
Rationale: I'm assuming that we always want this tip to be consistent across the lifetime of the ChainQuery
impl.
Counter-argument: We may want to have a ChainQuery
impl which queries across conflicting chains - what would this be used for though?
WDYT
/// Returns `Some(B)` if at least one of the requested blocks | ||
/// is confirmed in the chain, or `None` if none are confirmed. | ||
/// The generic parameter `B` represents the block identifier type, which defaults to `BlockId`. | ||
pub type ChainResponse<B = BlockId> = Option<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.
I think it's better API to have:
pub type ChainResponse<B = BlockId> = Vec<(B, bool)>;
Rationale: We only care about whether a block is in the chain or not. However, with Option<B>
, we now need to worry about the order the data is coming in.
Counter-argument: This increasing internal complexity.
Counter-counter-argument: No, this decreases internal complexity - allowing us to represent things in the most simplistic manner internally - whether something exists or not.
/// We don't have it yet. This is our `ChainRequest`
pending_blocks: BTreeSet<BlockId>,
/// We have it!
blocks: HashMap<BlockId, bool>,
At the anchored-tx/transitive-tx stages, we try to advance with the blocks
we have. If we are missing anything, just populate pending_blocks
then try again later. Very simple.
b7f8fba
to
4dd96e8
Compare
Yes, it would be best to add it after finalizing the API, as it can be done internally. |
4dd96e8
to
e3282d0
Compare
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]>
e3282d0
to
065a8d1
Compare
- 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 Signed-off-by: Leonardo Lima <[email protected]>
…nQuery` Allow any type implementing `ChainQuery` trait instead of requiring `CanonicalizationTask` specifically. Signed-off-by: Leonardo Lima <[email protected]>
- add private `CanonicalStage` enum to track processing phases - add private `try_advance()` for automatic stage progression - add `is_transitive()` helper to `CanonicalReason` - rename internal `confirmed_anchors` to `direct_anchors` for clarity - update `resolve_query()` with stage-specific logic Co-authored-by: Claude <[email protected]> Signed-off-by: Leonardo Lima <[email protected]>
065a8d1
to
88a99ba
Compare
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: