66//! ## Example
77//!
88//! ```
9- //! # use bdk_chain::{TxGraph, CanonicalParams, CanonicalTask, local_chain::LocalChain};
9+ //! # use bdk_chain::{TxGraph, CanonicalParams, local_chain::LocalChain};
1010//! # use bdk_core::BlockId;
1111//! # use bitcoin::hashes::Hash;
1212//! # let tx_graph = TxGraph::<BlockId>::default();
1313//! # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
1414//! let chain_tip = chain.tip().block_id();
1515//! let params = CanonicalParams::default();
16- //! let task = CanonicalTask::new(&tx_graph, chain_tip, params);
17- //! let view = chain.canonicalize(task);
16+ //! let view = chain.canonical_view(&tx_graph, chain_tip, params);
1817//!
1918//! // Iterate over canonical transactions
2019//! for tx in view.txs() {
@@ -27,13 +26,21 @@ use alloc::sync::Arc;
2726use alloc:: vec:: Vec ;
2827use core:: { fmt, ops:: RangeBounds } ;
2928
30- use bdk_core:: BlockId ;
29+ use bdk_core:: { BlockId , BlockQueries } ;
3130use bitcoin:: {
3231 constants:: COINBASE_MATURITY , Amount , OutPoint , ScriptBuf , Transaction , TxOut , Txid ,
3332} ;
3433
3534use crate :: { spk_txout:: SpkTxOutIndex , Anchor , Balance , CanonicalViewTask , ChainPosition , TxGraph } ;
3635
36+ /// Internal per-transaction entry in [`Canonical`].
37+ #[ derive( Clone , Debug ) ]
38+ pub ( crate ) struct CanonicalEntry < P > {
39+ pub ( crate ) tx : Arc < Transaction > ,
40+ pub ( crate ) pos : P ,
41+ pub ( crate ) mtp : Option < u32 > ,
42+ }
43+
3744/// A single canonical transaction with its position.
3845///
3946/// This struct represents a transaction that has been determined to be canonical (not
@@ -52,6 +59,11 @@ pub struct CanonicalTx<P> {
5259 pub txid : Txid ,
5360 /// The full transaction.
5461 pub tx : Arc < Transaction > ,
62+ /// The median-time-past at the confirmation height, if computed.
63+ ///
64+ /// This is `Some` only when the transaction is confirmed and MTP computation was
65+ /// enabled via [`CanonicalViewTask::with_mtp`](crate::CanonicalViewTask::with_mtp).
66+ pub mtp : Option < u32 > ,
5567}
5668
5769impl < P : Ord > Ord for CanonicalTx < P > {
@@ -86,6 +98,11 @@ pub struct CanonicalTxOut<P> {
8698 pub spent_by : Option < ( P , Txid ) > ,
8799 /// Whether this output is on a coinbase transaction.
88100 pub is_on_coinbase : bool ,
101+ /// The median-time-past at the confirmation height, if computed.
102+ ///
103+ /// This is `Some` only when the output's transaction is confirmed and MTP computation
104+ /// was enabled via [`CanonicalViewTask::with_mtp`](crate::CanonicalViewTask::with_mtp).
105+ pub mtp : Option < u32 > ,
89106}
90107
91108impl < P : Ord > Ord for CanonicalTxOut < P > {
@@ -186,12 +203,14 @@ impl<A: Anchor> CanonicalTxOut<ChainPosition<A>> {
186203pub struct Canonical < A , P > {
187204 /// Ordered list of transaction IDs in topological-spending order.
188205 pub ( crate ) order : Vec < Txid > ,
189- /// Map of transaction IDs to their transaction data and position .
190- pub ( crate ) txs : HashMap < Txid , ( Arc < Transaction > , P ) > ,
206+ /// Map of transaction IDs to their transaction data, position, and MTP .
207+ pub ( crate ) txs : HashMap < Txid , CanonicalEntry < P > > ,
191208 /// Map of outpoints to the transaction ID that spends them.
192209 pub ( crate ) spends : HashMap < OutPoint , Txid > ,
193210 /// The chain tip at the time this view was created.
194211 pub ( crate ) tip : BlockId ,
212+ /// Median-time-past at the chain tip height.
213+ pub ( crate ) tip_mtp : Option < u32 > ,
195214 /// Marker for the anchor type.
196215 pub ( crate ) _anchor : core:: marker:: PhantomData < A > ,
197216}
@@ -213,14 +232,16 @@ impl<A, P: Clone> Canonical<A, P> {
213232 pub ( crate ) fn new (
214233 tip : BlockId ,
215234 order : Vec < Txid > ,
216- txs : HashMap < Txid , ( Arc < Transaction > , P ) > ,
235+ txs : HashMap < Txid , CanonicalEntry < P > > ,
217236 spends : HashMap < OutPoint , Txid > ,
237+ tip_mtp : Option < u32 > ,
218238 ) -> Self {
219239 Self {
220240 tip,
221241 order,
222242 txs,
223243 spends,
244+ tip_mtp,
224245 _anchor : core:: marker:: PhantomData ,
225246 }
226247 }
@@ -230,15 +251,25 @@ impl<A, P: Clone> Canonical<A, P> {
230251 self . tip
231252 }
232253
254+ /// Get the MTP at the chain tip height.
255+ ///
256+ /// Returns `None` if MTP was not computed.
257+ pub fn tip_mtp ( & self ) -> Option < u32 > {
258+ self . tip_mtp
259+ }
260+
233261 /// Get a single canonical transaction by its transaction ID.
234262 ///
235263 /// Returns `Some(CanonicalTx)` if the transaction exists in the canonical set,
236264 /// or `None` if the transaction doesn't exist or was excluded due to conflicts.
237265 pub fn tx ( & self , txid : Txid ) -> Option < CanonicalTx < P > > {
238- self . txs
239- . get ( & txid)
240- . cloned ( )
241- . map ( |( tx, pos) | CanonicalTx { pos, txid, tx } )
266+ let entry = self . txs . get ( & txid) ?;
267+ Some ( CanonicalTx {
268+ pos : entry. pos . clone ( ) ,
269+ txid,
270+ tx : entry. tx . clone ( ) ,
271+ mtp : entry. mtp ,
272+ } )
242273 }
243274
244275 /// Get a single canonical transaction output.
@@ -251,19 +282,20 @@ impl<A, P: Clone> Canonical<A, P> {
251282 /// - The output index is out of bounds
252283 /// - The transaction was excluded due to conflicts
253284 pub fn txout ( & self , op : OutPoint ) -> Option < CanonicalTxOut < P > > {
254- let ( tx , pos ) = self . txs . get ( & op. txid ) ?;
285+ let entry = self . txs . get ( & op. txid ) ?;
255286 let vout: usize = op. vout . try_into ( ) . ok ( ) ?;
256- let txout = tx. output . get ( vout) ?;
287+ let txout = entry . tx . output . get ( vout) ?;
257288 let spent_by = self . spends . get ( & op) . map ( |spent_by_txid| {
258- let ( _ , spent_by_pos ) = & self . txs [ spent_by_txid] ;
259- ( spent_by_pos . clone ( ) , * spent_by_txid)
289+ let spent_by_entry = & self . txs [ spent_by_txid] ;
290+ ( spent_by_entry . pos . clone ( ) , * spent_by_txid)
260291 } ) ;
261292 Some ( CanonicalTxOut {
262- pos : pos. clone ( ) ,
293+ pos : entry . pos . clone ( ) ,
263294 outpoint : op,
264295 txout : txout. clone ( ) ,
265296 spent_by,
266- is_on_coinbase : tx. is_coinbase ( ) ,
297+ is_on_coinbase : entry. tx . is_coinbase ( ) ,
298+ mtp : entry. mtp ,
267299 } )
268300 }
269301
@@ -275,14 +307,13 @@ impl<A, P: Clone> Canonical<A, P> {
275307 /// # Example
276308 ///
277309 /// ```
278- /// # use bdk_chain::{TxGraph, CanonicalTask, local_chain::LocalChain};
310+ /// # use bdk_chain::{TxGraph, local_chain::LocalChain};
279311 /// # use bdk_core::BlockId;
280312 /// # use bitcoin::hashes::Hash;
281313 /// # let tx_graph = TxGraph::<BlockId>::default();
282314 /// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
283315 /// # let chain_tip = chain.tip().block_id();
284- /// # let task = CanonicalTask::new(&tx_graph, chain_tip, Default::default());
285- /// # let view = chain.canonicalize(task);
316+ /// # let view = chain.canonical_view(&tx_graph, chain_tip, Default::default());
286317 /// // Iterate over all canonical transactions
287318 /// for tx in view.txs() {
288319 /// println!("TX {}: {:?}", tx.txid, tx.pos);
@@ -293,8 +324,13 @@ impl<A, P: Clone> Canonical<A, P> {
293324 /// ```
294325 pub fn txs ( & self ) -> impl ExactSizeIterator < Item = CanonicalTx < P > > + DoubleEndedIterator + ' _ {
295326 self . order . iter ( ) . map ( |& txid| {
296- let ( tx, pos) = self . txs [ & txid] . clone ( ) ;
297- CanonicalTx { pos, txid, tx }
327+ let entry = & self . txs [ & txid] ;
328+ CanonicalTx {
329+ pos : entry. pos . clone ( ) ,
330+ txid,
331+ tx : entry. tx . clone ( ) ,
332+ mtp : entry. mtp ,
333+ }
298334 } )
299335 }
300336
@@ -310,14 +346,13 @@ impl<A, P: Clone> Canonical<A, P> {
310346 /// # Example
311347 ///
312348 /// ```
313- /// # use bdk_chain::{TxGraph, CanonicalTask, local_chain::LocalChain, keychain_txout::KeychainTxOutIndex};
349+ /// # use bdk_chain::{TxGraph, local_chain::LocalChain, keychain_txout::KeychainTxOutIndex};
314350 /// # use bdk_core::BlockId;
315351 /// # use bitcoin::hashes::Hash;
316352 /// # let tx_graph = TxGraph::<BlockId>::default();
317353 /// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
318354 /// # let chain_tip = chain.tip().block_id();
319- /// # let task = CanonicalTask::new(&tx_graph, chain_tip, Default::default());
320- /// # let view = chain.canonicalize(task);
355+ /// # let view = chain.canonical_view(&tx_graph, chain_tip, Default::default());
321356 /// # let indexer = KeychainTxOutIndex::<&str>::default();
322357 /// // Get all outputs from an indexer
323358 /// for (keychain, txout) in view.filter_outpoints(indexer.outpoints().clone()) {
@@ -341,14 +376,13 @@ impl<A, P: Clone> Canonical<A, P> {
341376 /// # Example
342377 ///
343378 /// ```
344- /// # use bdk_chain::{TxGraph, CanonicalTask, local_chain::LocalChain, keychain_txout::KeychainTxOutIndex};
379+ /// # use bdk_chain::{TxGraph, local_chain::LocalChain, keychain_txout::KeychainTxOutIndex};
345380 /// # use bdk_core::BlockId;
346381 /// # use bitcoin::hashes::Hash;
347382 /// # let tx_graph = TxGraph::<BlockId>::default();
348383 /// # let chain = LocalChain::from_blocks([(0, bitcoin::BlockHash::all_zeros())].into_iter().collect()).unwrap();
349384 /// # let chain_tip = chain.tip().block_id();
350- /// # let task = CanonicalTask::new(&tx_graph, chain_tip, Default::default());
351- /// # let view = chain.canonicalize(task);
385+ /// # let view = chain.canonical_view(&tx_graph, chain_tip, Default::default());
352386 /// # let indexer = KeychainTxOutIndex::<&str>::default();
353387 /// // Get unspent outputs (UTXOs) from an indexer
354388 /// for (keychain, utxo) in view.filter_unspent_outpoints(indexer.outpoints().clone()) {
@@ -488,13 +522,22 @@ impl<A: Anchor> CanonicalView<A> {
488522}
489523
490524impl < A : Anchor > CanonicalTxs < A > {
491- /// Creates a [`CanonicalViewTask`] that resolves [`CanonicalReason`](crate::CanonicalReason)s
492- /// into [`ChainPosition`]s.
493- ///
494- /// This is the second phase of the canonicalization pipeline. The resulting task
495- /// queries the chain to verify anchors for transitively anchored transactions and
496- /// produces a [`CanonicalView`] with resolved chain positions.
497- pub fn view_task < ' g > ( self , tx_graph : & ' g TxGraph < A > ) -> CanonicalViewTask < ' g , A > {
498- CanonicalViewTask :: new ( tx_graph, self . tip , self . order , self . txs , self . spends )
525+ /// Creates a [`CanonicalViewTask`] that resolves [`CanonicalReason`]s into [`ChainPosition`]s.
526+ ///
527+ /// This is the second phase of the canonicalization pipeline. Blocks fetched during
528+ /// phase 1 are passed through so they can be reused without redundant queries.
529+ pub fn view_task < ' g , B > (
530+ self ,
531+ tx_graph : & ' g TxGraph < A > ,
532+ queries : BlockQueries < B > ,
533+ ) -> CanonicalViewTask < ' g , A , B > {
534+ CanonicalViewTask :: new (
535+ tx_graph,
536+ self . tip ,
537+ self . order ,
538+ self . txs ,
539+ self . spends ,
540+ queries,
541+ )
499542 }
500543}
0 commit comments