Skip to content

Commit 737b19b

Browse files
committed
wip
1 parent 16354d7 commit 737b19b

File tree

6 files changed

+80
-207
lines changed

6 files changed

+80
-207
lines changed

crates/chain/src/canonical_task.rs

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ use bitcoin::{Transaction, Txid};
1414
pub struct CanonicalizationRequest<A> {
1515
/// The anchors to check.
1616
pub anchors: Vec<A>,
17-
/// The chain tip to check against.
18-
pub chain_tip: BlockId,
1917
}
2018

2119
/// Response containing the best confirmed anchor, if any.
@@ -30,7 +28,6 @@ type NotCanonicalSet = HashSet<Txid>;
3028
/// Manages the canonicalization process without direct I/O operations.
3129
pub struct CanonicalizationTask<'g, A> {
3230
tx_graph: &'g TxGraph<A>,
33-
chain_tip: BlockId,
3431

3532
unprocessed_assumed_txs: Box<dyn Iterator<Item = (Txid, Arc<Transaction>)> + 'g>,
3633
unprocessed_anchored_txs:
@@ -52,13 +49,10 @@ pub struct CanonicalizationTask<'g, A> {
5249

5350
impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
5451
/// Creates a new canonicalization task.
55-
///
56-
/// Returns the task and an optional initial request.
5752
pub fn new(
5853
tx_graph: &'g TxGraph<A>,
59-
chain_tip: BlockId,
6054
params: CanonicalizationParams,
61-
) -> (Self, Option<CanonicalizationRequest<A>>) {
55+
) -> Self {
6256
let anchors = tx_graph.all_anchors();
6357
let unprocessed_assumed_txs = Box::new(
6458
params
@@ -80,7 +74,6 @@ impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
8074

8175
let mut task = Self {
8276
tx_graph,
83-
chain_tip,
8477

8578
unprocessed_assumed_txs,
8679
unprocessed_anchored_txs,
@@ -96,22 +89,18 @@ impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
9689
confirmed_anchors: HashMap::new(),
9790
};
9891

99-
// Process assumed transactions first (they don't need queries)
92+
// process assumed transactions first (they don't need queries)
10093
task.process_assumed_txs();
10194

102-
// Process anchored transactions and get the first request if needed
103-
let initial_request = task.process_anchored_txs();
104-
105-
(task, initial_request)
95+
task
10696
}
10797

10898
/// Returns the next query needed, if any.
10999
pub fn next_query(&mut self) -> Option<CanonicalizationRequest<A>> {
110100
// Check if we have pending anchor checks
111101
if let Some((_, _, anchors)) = self.pending_anchor_checks.front() {
112102
return Some(CanonicalizationRequest {
113-
anchors: anchors.clone(),
114-
chain_tip: self.chain_tip,
103+
anchors: anchors.clone()
115104
});
116105
}
117106

@@ -152,7 +141,7 @@ impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
152141
}
153142

154143
/// Completes the canonicalization and returns a CanonicalView.
155-
pub fn finish(mut self) -> CanonicalView<A> {
144+
pub fn finish(mut self, chain_tip: BlockId) -> CanonicalView<A> {
156145
// Process remaining transactions (seen and leftover)
157146
self.process_seen_txs();
158147
self.process_leftover_txs();
@@ -232,7 +221,7 @@ impl<'g, A: Anchor> CanonicalizationTask<'g, A> {
232221
}
233222
}
234223

235-
CanonicalView::from_parts(self.chain_tip, view_order, view_txs, view_spends)
224+
CanonicalView::new(chain_tip, view_order, view_txs, view_spends)
236225
}
237226

238227
fn is_canonicalized(&self, txid: Txid) -> bool {
@@ -426,23 +415,10 @@ mod tests {
426415
};
427416
let _ = tx_graph.insert_anchor(txid, anchor);
428417

429-
// Create canonicalization task
418+
// Create canonicalization task and canonicalize using the chain
430419
let params = CanonicalizationParams::default();
431-
let (mut task, initial_request) = CanonicalizationTask::new(&tx_graph, chain_tip, params);
432-
433-
// Process requests
434-
if let Some(request) = initial_request {
435-
let response = chain.handle_canonicalization_request(&request).unwrap();
436-
task.resolve_query(response);
437-
}
438-
439-
while let Some(request) = task.next_query() {
440-
let response = chain.handle_canonicalization_request(&request).unwrap();
441-
task.resolve_query(response);
442-
}
443-
444-
// Get canonical view
445-
let canonical_view = task.finish();
420+
let task = CanonicalizationTask::new(&tx_graph, params);
421+
let canonical_view = chain.canonicalize(task).unwrap();
446422

447423
// Should have one canonical transaction
448424
assert_eq!(canonical_view.txs().len(), 1);

crates/chain/src/canonical_view.rs

Lines changed: 7 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@ use bdk_core::BlockId;
3131
use bitcoin::{Amount, OutPoint, ScriptBuf, Transaction, Txid};
3232

3333
use crate::{
34-
local_chain::LocalChain, spk_txout::SpkTxOutIndex, Anchor, Balance, CanonicalizationParams,
35-
CanonicalizationTask, ChainOracle, ChainPosition, FullTxOut, TxGraph,
34+
canonical_iter::{CanonicalIter, CanonicalReason, ObservedIn},
35+
spk_txout::SpkTxOutIndex,
36+
tx_graph::TxNode,
37+
Anchor, Balance, CanonicalizationParams, ChainOracle, ChainPosition,
38+
FullTxOut, TxGraph,
3639
};
3740

3841
/// A single canonical transaction with its chain position.
@@ -76,9 +79,10 @@ pub struct CanonicalView<A> {
7679
}
7780

7881
impl<A: Anchor> CanonicalView<A> {
82+
// TODO: @(oleonardolima) update the documentation
7983
/// Creates a CanonicalView from its constituent parts.
8084
/// This is used by CanonicalizationTask to build the view.
81-
pub(crate) fn from_parts(
85+
pub(crate) fn new(
8286
tip: BlockId,
8387
order: Vec<Txid>,
8488
txs: HashMap<Txid, (Arc<Transaction>, ChainPosition<A>)>,
@@ -92,84 +96,6 @@ impl<A: Anchor> CanonicalView<A> {
9296
}
9397
}
9498

95-
/// Create a new canonical view from a transaction graph.
96-
///
97-
/// This constructor analyzes the given [`TxGraph`] and creates a canonical view of all
98-
/// transactions, resolving conflicts and ordering them according to their chain position.
99-
///
100-
/// # Returns
101-
///
102-
/// Returns `Ok(CanonicalView)` on success, or an error if the chain oracle fails.
103-
pub fn new<'g, C>(
104-
tx_graph: &'g TxGraph<A>,
105-
chain: &'g C,
106-
chain_tip: BlockId,
107-
params: CanonicalizationParams,
108-
) -> Result<Self, C::Error>
109-
where
110-
C: ChainOracle,
111-
{
112-
let (mut task, initial_request) = CanonicalizationTask::new(tx_graph, chain_tip, params);
113-
114-
// Process the initial request if present
115-
if let Some(request) = initial_request {
116-
// Check each anchor and return the first confirmed one
117-
let mut best_anchor = None;
118-
for anchor in &request.anchors {
119-
if chain.is_block_in_chain(anchor.anchor_block(), request.chain_tip)? == Some(true)
120-
{
121-
best_anchor = Some(anchor.clone());
122-
break;
123-
}
124-
}
125-
task.resolve_query(best_anchor);
126-
}
127-
128-
// Process all subsequent requests
129-
while let Some(request) = task.next_query() {
130-
// Check each anchor and return the first confirmed one
131-
let mut best_anchor = None;
132-
for anchor in &request.anchors {
133-
if chain.is_block_in_chain(anchor.anchor_block(), request.chain_tip)? == Some(true)
134-
{
135-
best_anchor = Some(anchor.clone());
136-
break;
137-
}
138-
}
139-
task.resolve_query(best_anchor);
140-
}
141-
142-
// Return the finished canonical view
143-
Ok(task.finish())
144-
}
145-
146-
/// Create a new canonical view from a transaction graph using a LocalChain.
147-
///
148-
/// This is a convenience method for working with [`LocalChain`] specifically.
149-
pub fn new_with_local_chain<'g>(
150-
tx_graph: &'g TxGraph<A>,
151-
chain: &'g LocalChain,
152-
chain_tip: BlockId,
153-
params: CanonicalizationParams,
154-
) -> Result<Self, Infallible> {
155-
let (mut task, initial_request) = CanonicalizationTask::new(tx_graph, chain_tip, params);
156-
157-
// Process the initial request if present
158-
if let Some(request) = initial_request {
159-
let response = chain.handle_canonicalization_request(&request)?;
160-
task.resolve_query(response);
161-
}
162-
163-
// Process all subsequent requests
164-
while let Some(request) = task.next_query() {
165-
let response = chain.handle_canonicalization_request(&request)?;
166-
task.resolve_query(response);
167-
}
168-
169-
// Return the finished canonical view
170-
Ok(task.finish())
171-
}
172-
17399
/// Get a single canonical transaction by its transaction ID.
174100
///
175101
/// Returns `Some(CanonicalTx)` if the transaction exists in the canonical view,

crates/chain/src/indexed_tx_graph.rs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -423,33 +423,6 @@ where
423423
}
424424
}
425425

426-
impl<A, X> IndexedTxGraph<A, X>
427-
where
428-
A: Anchor,
429-
{
430-
/// Returns a [`CanonicalView`].
431-
pub fn try_canonical_view<'a, C: ChainOracle>(
432-
&'a self,
433-
chain: &'a C,
434-
chain_tip: BlockId,
435-
params: CanonicalizationParams,
436-
) -> Result<CanonicalView<A>, C::Error> {
437-
self.graph.try_canonical_view(chain, chain_tip, params)
438-
}
439-
440-
/// Returns a [`CanonicalView`].
441-
///
442-
/// This is the infallible version of [`try_canonical_view`](Self::try_canonical_view).
443-
pub fn canonical_view<'a, C: ChainOracle<Error = Infallible>>(
444-
&'a self,
445-
chain: &'a C,
446-
chain_tip: BlockId,
447-
params: CanonicalizationParams,
448-
) -> CanonicalView<A> {
449-
self.graph.canonical_view(chain, chain_tip, params)
450-
}
451-
}
452-
453426
impl<A, I> AsRef<TxGraph<A>> for IndexedTxGraph<A, I> {
454427
fn as_ref(&self) -> &TxGraph<A> {
455428
&self.graph

crates/chain/src/local_chain.rs

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use core::convert::Infallible;
44
use core::fmt;
55
use core::ops::RangeBounds;
66

7-
use crate::canonical_task::{CanonicalizationRequest, CanonicalizationResponse};
7+
use crate::canonical_task::CanonicalizationTask;
88
use crate::collections::BTreeMap;
9-
use crate::{Anchor, BlockId, ChainOracle, Merge};
9+
use crate::{Anchor, BlockId, CanonicalView, ChainOracle, Merge};
1010
use bdk_core::ToBlockHash;
1111
pub use bdk_core::{CheckPoint, CheckPointIter};
1212
use bitcoin::block::Header;
@@ -70,50 +70,75 @@ impl<D> PartialEq for LocalChain<D> {
7070
}
7171
}
7272

73-
impl<D> ChainOracle for LocalChain<D> {
74-
type Error = Infallible;
75-
76-
fn is_block_in_chain(
77-
&self,
78-
block: BlockId,
79-
chain_tip: BlockId,
80-
) -> Result<Option<bool>, Self::Error> {
73+
// Methods for `LocalChain<BlockHash>`
74+
impl LocalChain<BlockHash> {
75+
/// Check if a block is in the chain.
76+
///
77+
/// # Arguments
78+
/// * `block` - The block to check
79+
/// * `chain_tip` - The chain tip to check against
80+
///
81+
/// # Returns
82+
/// * `Some(true)` if the block is in the chain
83+
/// * `Some(false)` if the block is not in the chain
84+
/// * `None` if it cannot be determined
85+
pub fn is_block_in_chain(&self, block: BlockId, chain_tip: BlockId) -> Option<bool> {
8186
let chain_tip_cp = match self.tip.get(chain_tip.height) {
8287
// we can only determine whether `block` is in chain of `chain_tip` if `chain_tip` can
8388
// be identified in chain
8489
Some(cp) if cp.hash() == chain_tip.hash => cp,
85-
_ => return Ok(None),
90+
_ => return None,
8691
};
87-
match chain_tip_cp.get(block.height) {
88-
Some(cp) => Ok(Some(cp.hash() == block.hash)),
89-
None => Ok(None),
90-
}
92+
chain_tip_cp
93+
.get(block.height)
94+
.map(|cp| cp.hash() == block.hash)
9195
}
9296

93-
fn get_chain_tip(&self) -> Result<BlockId, Self::Error> {
94-
Ok(self.tip.block_id())
97+
/// Get the chain tip.
98+
///
99+
/// # Returns
100+
/// The [`BlockId`] of the chain tip.
101+
pub fn chain_tip(&self) -> BlockId {
102+
self.tip.block_id()
95103
}
96-
}
97104

98-
// Methods for `LocalChain<BlockHash>`
99-
impl LocalChain<BlockHash> {
100-
/// Handle a canonicalization request.
105+
/// Canonicalize a transaction graph using this chain.
106+
///
107+
/// This method processes a [`CanonicalizationTask`], handling all its requests
108+
/// to determine which transactions are canonical, and returns a [`CanonicalView`].
101109
///
102-
/// This method processes requests from [`CanonicalizationTask`] to check if blocks
103-
/// are in the chain.
110+
/// # Example
104111
///
105-
/// [`CanonicalizationTask`]: crate::canonical_task::CanonicalizationTask
106-
pub fn handle_canonicalization_request<A: Anchor>(
112+
/// ```
113+
/// # use bdk_chain::{CanonicalizationTask, CanonicalizationParams, TxGraph, local_chain::LocalChain};
114+
/// # use bdk_core::BlockId;
115+
/// # use bitcoin::hashes::Hash;
116+
/// # let tx_graph: TxGraph<BlockId> = TxGraph::default();
117+
/// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
118+
/// let task = CanonicalizationTask::new(&tx_graph, chain.tip().block_id(), CanonicalizationParams::default());
119+
/// let view = chain.canonicalize(task).unwrap();
120+
/// ```
121+
pub fn canonicalize<A: Anchor>(
107122
&self,
108-
request: &CanonicalizationRequest<A>,
109-
) -> Result<CanonicalizationResponse<A>, Infallible> {
110-
// Check each anchor and return the first confirmed one
111-
for anchor in &request.anchors {
112-
if self.is_block_in_chain(anchor.anchor_block(), request.chain_tip)? == Some(true) {
113-
return Ok(Some(anchor.clone()));
123+
mut task: CanonicalizationTask<'_, A>,
124+
) -> Result<CanonicalView<A>, Infallible> {
125+
// Process all requests from the task
126+
let chain_tip = self.chain_tip();
127+
128+
while let Some(request) = task.next_query() {
129+
// Check each anchor and return the first confirmed one
130+
let mut best_anchor = None;
131+
for anchor in &request.anchors {
132+
if self.is_block_in_chain(anchor.anchor_block(), chain_tip) == Some(true) {
133+
best_anchor = Some(anchor.clone());
134+
break;
135+
}
114136
}
137+
task.resolve_query(best_anchor);
115138
}
116-
Ok(None)
139+
140+
// Return the finished canonical view
141+
Ok(task.finish(chain_tip))
117142
}
118143

119144
/// Update the chain with a given [`Header`] at `height` which you claim is connected to a

0 commit comments

Comments
 (0)