Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 57 additions & 54 deletions crates/chain/src/canonical_task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use alloc::boxed::Box;
use alloc::collections::BTreeSet;
use alloc::sync::Arc;
use alloc::vec::Vec;
use bdk_core::BlockId;
use bdk_core::{BlockId, ChainQuery};
use bitcoin::{Transaction, Txid};

type CanonicalMap<A> = HashMap<Txid, (Arc<Transaction>, CanonicalReason<A>)>;
Expand Down Expand Up @@ -53,53 +53,13 @@ pub struct CanonicalizationTask<'g, A> {
confirmed_anchors: HashMap<Txid, A>,
}

impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
/// Creates a new canonicalization task.
pub fn new(tx_graph: &'g TxGraph<A>, params: CanonicalizationParams) -> Self {
let anchors = tx_graph.all_anchors();
let unprocessed_assumed_txs = Box::new(
params
.assume_canonical
.into_iter()
.rev()
.filter_map(|txid| Some((txid, tx_graph.get_tx(txid)?))),
);
let unprocessed_anchored_txs = Box::new(
tx_graph
.txids_by_descending_anchor_height()
.filter_map(|(_, txid)| Some((txid, tx_graph.get_tx(txid)?, anchors.get(&txid)?))),
);
let unprocessed_seen_txs = Box::new(
tx_graph
.txids_by_descending_last_seen()
.filter_map(|(last_seen, txid)| Some((txid, tx_graph.get_tx(txid)?, last_seen))),
);
impl<'g, A: Anchor> ChainQuery for CanonicalizationTask<'g, A> {
type Request = CanonicalizationRequest<A>;
type Response = CanonicalizationResponse<A>;
type Context = BlockId;
type Result = CanonicalView<A>;

let mut task = Self {
tx_graph,

unprocessed_assumed_txs,
unprocessed_anchored_txs,
unprocessed_seen_txs,
unprocessed_leftover_txs: VecDeque::new(),

canonical: HashMap::new(),
not_canonical: HashSet::new(),

pending_anchor_checks: VecDeque::new(),

canonical_order: Vec::new(),
confirmed_anchors: HashMap::new(),
};

// process assumed transactions first (they don't need queries)
task.process_assumed_txs();

task
}

/// Returns the next query needed, if any.
pub fn next_query(&mut self) -> Option<CanonicalizationRequest<A>> {
fn next_query(&mut self) -> Option<Self::Request> {
// Check if we have pending anchor checks
if let Some((_, _, anchors)) = self.pending_anchor_checks.front() {
return Some(CanonicalizationRequest {
Expand All @@ -111,8 +71,7 @@ impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
self.process_anchored_txs()
}

/// Resolves a query with the given response.
pub fn resolve_query(&mut self, response: CanonicalizationResponse<A>) {
fn resolve_query(&mut self, response: Self::Response) {
if let Some((txid, tx, anchors)) = self.pending_anchor_checks.pop_front() {
match response {
Some(best_anchor) => {
Expand All @@ -138,13 +97,11 @@ impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
}
}

/// Returns true if the canonicalization process is complete.
pub fn is_finished(&self) -> bool {
fn is_finished(&mut self) -> bool {
self.pending_anchor_checks.is_empty() && self.unprocessed_anchored_txs.size_hint().0 == 0
}

/// Completes the canonicalization and returns a CanonicalView.
pub fn finish(mut self, chain_tip: BlockId) -> CanonicalView<A> {
fn finish(mut self, context: Self::Context) -> Self::Result {
// Process remaining transactions (seen and leftover)
self.process_seen_txs();
self.process_leftover_txs();
Expand Down Expand Up @@ -224,7 +181,53 @@ impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
}
}

CanonicalView::new(chain_tip, view_order, view_txs, view_spends)
CanonicalView::new(context, view_order, view_txs, view_spends)
}
}

impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
/// Creates a new canonicalization task.
pub fn new(tx_graph: &'g TxGraph<A>, params: CanonicalizationParams) -> Self {
let anchors = tx_graph.all_anchors();
let unprocessed_assumed_txs = Box::new(
params
.assume_canonical
.into_iter()
.rev()
.filter_map(|txid| Some((txid, tx_graph.get_tx(txid)?))),
);
let unprocessed_anchored_txs = Box::new(
tx_graph
.txids_by_descending_anchor_height()
.filter_map(|(_, txid)| Some((txid, tx_graph.get_tx(txid)?, anchors.get(&txid)?))),
);
let unprocessed_seen_txs = Box::new(
tx_graph
.txids_by_descending_last_seen()
.filter_map(|(last_seen, txid)| Some((txid, tx_graph.get_tx(txid)?, last_seen))),
);

let mut task = Self {
tx_graph,

unprocessed_assumed_txs,
unprocessed_anchored_txs,
unprocessed_seen_txs,
unprocessed_leftover_txs: VecDeque::new(),

canonical: HashMap::new(),
not_canonical: HashSet::new(),

pending_anchor_checks: VecDeque::new(),

canonical_order: Vec::new(),
confirmed_anchors: HashMap::new(),
};

// process assumed transactions first (they don't need queries)
task.process_assumed_txs();

task
}

fn is_canonicalized(&self, txid: Txid) -> bool {
Expand Down
2 changes: 1 addition & 1 deletion crates/chain/src/local_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use core::ops::RangeBounds;
use crate::canonical_task::CanonicalizationTask;
use crate::collections::BTreeMap;
use crate::{Anchor, BlockId, CanonicalView, ChainOracle, Merge};
use bdk_core::ToBlockHash;
use bdk_core::{ChainQuery, ToBlockHash};
pub use bdk_core::{CheckPoint, CheckPointIter};
use bitcoin::block::Header;
use bitcoin::BlockHash;
Expand Down
65 changes: 65 additions & 0 deletions crates/core/src/chain_query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Generic trait for query-based operations that require external blockchain data.
//!
//! The [`ChainQuery`] trait provides a standardized interface for implementing
//! algorithms that need to make queries to blockchain sources and process responses
//! in a sans-IO manner.

/// A trait for types that perform query-based operations against blockchain data.
///
/// This trait enables types to request blockchain information via queries and process
/// responses in a decoupled, sans-IO manner. It's particularly useful for algorithms
/// that need to interact with blockchain oracles, chain sources, or other blockchain
/// data providers without directly performing I/O.
///
/// # Type Parameters
///
/// * `Request` - The type of query request that can be made
/// * `Response` - The type of response expected for queries
/// * `Context` - The type of context needed for finalization (e.g., `BlockId` for chain tip)
/// * `Result` - The final result type produced when the query process is complete
pub trait ChainQuery {
/// The type of query request that can be made.
type Request;

/// The type of response expected for queries.
type Response;

/// The type of context needed for finalization.
///
/// This could be `BlockId` for algorithms needing chain tip information,
/// `()` for algorithms that don't need additional context, or any other
/// type specific to the implementation's needs.
type Context;

/// The final result type produced when the query process is complete.
type Result;

/// Returns the next query needed, if any.
///
/// This method should return `Some(request)` if more information is needed,
/// or `None` if no more queries are required.
fn next_query(&mut self) -> Option<Self::Request>;

/// Resolves a query with the given response.
///
/// This method processes the response to a previous query request and updates
/// the internal state accordingly.
fn resolve_query(&mut self, response: Self::Response);

/// Returns true if the query process is complete and ready to finish.
///
/// The default implementation returns `true` when there are no more queries needed.
/// Implementors can override this for more specific behavior if needed.
fn is_finished(&mut self) -> bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, this doesn't need to be mut.

self.next_query().is_none()
}

/// Completes the query process and returns the final result.
///
/// This method should be called when `is_finished` returns `true`.
/// It consumes `self` and produces the final result.
///
/// The `context` parameter provides implementation-specific context
/// needed for finalization.
fn finish(self, context: Self::Context) -> Self::Result;
}
3 changes: 3 additions & 0 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,6 @@ mod merge;
pub use merge::*;

pub mod spk_client;

mod chain_query;
pub use chain_query::*;