diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs
index 66d1e9502..735df62bd 100644
--- a/crates/chain/src/tx_graph.rs
+++ b/crates/chain/src/tx_graph.rs
@@ -451,6 +451,21 @@ impl TxGraph {
}
}
+impl TxGraph {
+ /// Transform the [`TxGraph`] to have [`Anchor`]s of another type.
+ ///
+ /// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to
+ /// transform it.
+ pub fn map_anchors(self, f: F) -> TxGraph
+ where
+ F: FnMut(A) -> A2,
+ {
+ let mut new_graph = TxGraph::::default();
+ new_graph.apply_changeset(self.initial_changeset().map_anchors(f));
+ new_graph
+ }
+}
+
impl TxGraph {
/// Construct a new [`TxGraph`] from a list of transactions.
pub fn new(txs: impl IntoIterator- ) -> Self {
@@ -1296,6 +1311,26 @@ impl Append for ChangeSet {
}
}
+impl ChangeSet {
+ /// Transform the [`ChangeSet`] to have [`Anchor`]s of another type.
+ ///
+ /// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to
+ /// transform it.
+ pub fn map_anchors(self, mut f: F) -> ChangeSet
+ where
+ F: FnMut(A) -> A2,
+ {
+ ChangeSet {
+ txs: self.txs,
+ txouts: self.txouts,
+ anchors: BTreeSet::<(A2, Txid)>::from_iter(
+ self.anchors.into_iter().map(|(a, txid)| (f(a), txid)),
+ ),
+ last_seen: self.last_seen,
+ }
+ }
+}
+
impl AsRef> for TxGraph {
fn as_ref(&self) -> &TxGraph {
self
diff --git a/crates/chain/tests/common/tx_template.rs b/crates/chain/tests/common/tx_template.rs
index cd96fa189..ec2eb1159 100644
--- a/crates/chain/tests/common/tx_template.rs
+++ b/crates/chain/tests/common/tx_template.rs
@@ -49,7 +49,7 @@ impl TxOutTemplate {
}
#[allow(dead_code)]
-pub fn init_graph<'a, A: Anchor + Copy + 'a>(
+pub fn init_graph<'a, A: Anchor + Clone + 'a>(
tx_templates: impl IntoIterator
- >,
) -> (TxGraph, SpkTxOutIndex, HashMap<&'a str, Txid>) {
let (descriptor, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap();
@@ -126,7 +126,7 @@ pub fn init_graph<'a, A: Anchor + Copy + 'a>(
spk_index.scan(&tx);
let _ = graph.insert_tx(tx.clone());
for anchor in tx_tmp.anchors.iter() {
- let _ = graph.insert_anchor(tx.txid(), *anchor);
+ let _ = graph.insert_anchor(tx.txid(), anchor.clone());
}
if let Some(seen_at) = tx_tmp.last_seen {
let _ = graph.insert_seen_at(tx.txid(), seen_at);
diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs
index a71e24f99..af1b222d9 100644
--- a/crates/chain/tests/test_tx_graph.rs
+++ b/crates/chain/tests/test_tx_graph.rs
@@ -10,7 +10,9 @@ use bdk_chain::{
use bitcoin::{
absolute, hashes::Hash, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid,
};
+use common::*;
use core::iter;
+use rand::RngCore;
use std::vec;
#[test]
@@ -1172,3 +1174,86 @@ fn test_missing_blocks() {
),
]);
}
+
+#[test]
+/// The `map_anchors` allow a caller to pass a function to reconstruct the [`TxGraph`] with any [`Anchor`],
+/// even though the function is non-deterministic.
+fn call_map_anchors_with_non_deterministic_anchor() {
+ #[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
+ /// A non-deterministic anchor
+ pub struct NonDeterministicAnchor {
+ pub anchor_block: BlockId,
+ pub non_deterministic_field: u32,
+ }
+
+ let template = [
+ TxTemplate {
+ tx_name: "tx1",
+ inputs: &[TxInTemplate::Bogus],
+ outputs: &[TxOutTemplate::new(10000, Some(1))],
+ anchors: &[block_id!(1, "A")],
+ last_seen: None,
+ },
+ TxTemplate {
+ tx_name: "tx2",
+ inputs: &[TxInTemplate::PrevTx("tx1", 0)],
+ outputs: &[TxOutTemplate::new(20000, Some(2))],
+ anchors: &[block_id!(2, "B")],
+ ..Default::default()
+ },
+ TxTemplate {
+ tx_name: "tx3",
+ inputs: &[TxInTemplate::PrevTx("tx2", 0)],
+ outputs: &[TxOutTemplate::new(30000, Some(3))],
+ anchors: &[block_id!(3, "C"), block_id!(4, "D")],
+ ..Default::default()
+ },
+ ];
+ let (graph, _, _) = init_graph(&template);
+ let new_graph = graph.clone().map_anchors(|a| NonDeterministicAnchor {
+ anchor_block: a,
+ // A non-deterministic value
+ non_deterministic_field: rand::thread_rng().next_u32(),
+ });
+
+ // Check all the details in new_graph reconstruct as well
+
+ let mut full_txs_vec: Vec<_> = graph.full_txs().collect();
+ full_txs_vec.sort();
+ let mut new_txs_vec: Vec<_> = new_graph.full_txs().collect();
+ new_txs_vec.sort();
+ let mut new_txs = new_txs_vec.iter();
+
+ for tx_node in full_txs_vec.iter() {
+ let new_txnode = new_txs.next().unwrap();
+ assert_eq!(new_txnode.txid, tx_node.txid);
+ assert_eq!(new_txnode.tx, tx_node.tx);
+ assert_eq!(
+ new_txnode.last_seen_unconfirmed,
+ tx_node.last_seen_unconfirmed
+ );
+ assert_eq!(new_txnode.anchors.len(), tx_node.anchors.len());
+
+ let mut new_anchors: Vec<_> = new_txnode.anchors.iter().map(|a| a.anchor_block).collect();
+ new_anchors.sort();
+ let mut old_anchors: Vec<_> = tx_node.anchors.iter().copied().collect();
+ old_anchors.sort();
+ assert_eq!(new_anchors, old_anchors);
+ }
+ assert!(new_txs.next().is_none());
+
+ let new_graph_anchors: Vec<_> = new_graph
+ .all_anchors()
+ .iter()
+ .map(|i| i.0.anchor_block)
+ .collect();
+ assert_eq!(
+ new_graph_anchors,
+ vec![
+ block_id!(1, "A"),
+ block_id!(2, "B"),
+ block_id!(3, "C"),
+ block_id!(4, "D"),
+ ]
+ );
+}