Skip to content

Commit 5489f90

Browse files
committed
feat(chain): add map_anchors for TxGraph and ChangeSet
1 parent 022d5a2 commit 5489f90

File tree

3 files changed

+122
-2
lines changed

3 files changed

+122
-2
lines changed

crates/chain/src/tx_graph.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,21 @@ impl<A> TxGraph<A> {
451451
}
452452
}
453453

454+
impl<A: Clone + Ord> TxGraph<A> {
455+
/// Transform the [`TxGraph`] to have [`Anchor`]s of another type.
456+
///
457+
/// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to
458+
/// transform it.
459+
pub fn map_anchors<A2: Clone + Ord, F>(self, f: F) -> TxGraph<A2>
460+
where
461+
F: FnMut(A) -> A2,
462+
{
463+
let mut new_graph = TxGraph::<A2>::default();
464+
new_graph.apply_changeset(self.initial_changeset().map_anchors(f));
465+
new_graph
466+
}
467+
}
468+
454469
impl<A: Clone + Ord> TxGraph<A> {
455470
/// Construct a new [`TxGraph`] from a list of transactions.
456471
pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
@@ -1296,6 +1311,26 @@ impl<A: Ord> Append for ChangeSet<A> {
12961311
}
12971312
}
12981313

1314+
impl<A: Ord> ChangeSet<A> {
1315+
/// Transform the [`ChangeSet`] to have [`Anchor`]s of another type.
1316+
///
1317+
/// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to
1318+
/// transform it.
1319+
pub fn map_anchors<A2: Ord, F>(self, mut f: F) -> ChangeSet<A2>
1320+
where
1321+
F: FnMut(A) -> A2,
1322+
{
1323+
ChangeSet {
1324+
txs: self.txs,
1325+
txouts: self.txouts,
1326+
anchors: BTreeSet::<(A2, Txid)>::from_iter(
1327+
self.anchors.into_iter().map(|(a, txid)| (f(a), txid)),
1328+
),
1329+
last_seen: self.last_seen,
1330+
}
1331+
}
1332+
}
1333+
12991334
impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
13001335
fn as_ref(&self) -> &TxGraph<A> {
13011336
self

crates/chain/tests/common/tx_template.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ impl TxOutTemplate {
4949
}
5050

5151
#[allow(dead_code)]
52-
pub fn init_graph<'a, A: Anchor + Copy + 'a>(
52+
pub fn init_graph<'a, A: Anchor + Clone + 'a>(
5353
tx_templates: impl IntoIterator<Item = &'a TxTemplate<'a, A>>,
5454
) -> (TxGraph<A>, SpkTxOutIndex<u32>, HashMap<&'a str, Txid>) {
5555
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>(
126126
spk_index.scan(&tx);
127127
let _ = graph.insert_tx(tx.clone());
128128
for anchor in tx_tmp.anchors.iter() {
129-
let _ = graph.insert_anchor(tx.txid(), *anchor);
129+
let _ = graph.insert_anchor(tx.txid(), anchor.clone());
130130
}
131131
if let Some(seen_at) = tx_tmp.last_seen {
132132
let _ = graph.insert_seen_at(tx.txid(), seen_at);

crates/chain/tests/test_tx_graph.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ use bdk_chain::{
1010
use bitcoin::{
1111
absolute, hashes::Hash, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid,
1212
};
13+
use common::*;
1314
use core::iter;
15+
use rand::RngCore;
1416
use std::vec;
1517

1618
#[test]
@@ -1172,3 +1174,86 @@ fn test_missing_blocks() {
11721174
),
11731175
]);
11741176
}
1177+
1178+
#[test]
1179+
/// The `map_anchors` allow a caller to pass a function to reconstruct the [`TxGraph`] with any [`Anchor`],
1180+
/// even though the function is non-deterministic.
1181+
fn call_map_anchors_with_non_deterministic_anchor() {
1182+
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
1183+
/// A non-deterministic anchor
1184+
pub struct NonDeterministicAnchor {
1185+
pub anchor_block: BlockId,
1186+
pub non_deterministic_field: u32,
1187+
}
1188+
1189+
let template = [
1190+
TxTemplate {
1191+
tx_name: "tx1",
1192+
inputs: &[TxInTemplate::Bogus],
1193+
outputs: &[TxOutTemplate::new(10000, Some(1))],
1194+
anchors: &[block_id!(1, "A")],
1195+
last_seen: None,
1196+
},
1197+
TxTemplate {
1198+
tx_name: "tx2",
1199+
inputs: &[TxInTemplate::PrevTx("tx1", 0)],
1200+
outputs: &[TxOutTemplate::new(20000, Some(2))],
1201+
anchors: &[block_id!(2, "B")],
1202+
..Default::default()
1203+
},
1204+
TxTemplate {
1205+
tx_name: "tx3",
1206+
inputs: &[TxInTemplate::PrevTx("tx2", 0)],
1207+
outputs: &[TxOutTemplate::new(30000, Some(3))],
1208+
anchors: &[block_id!(3, "C"), block_id!(4, "D")],
1209+
..Default::default()
1210+
},
1211+
];
1212+
let (graph, _, _) = init_graph(&template);
1213+
let new_graph = graph.clone().map_anchors(|a| NonDeterministicAnchor {
1214+
anchor_block: a,
1215+
// A non-deterministic value
1216+
non_deterministic_field: rand::thread_rng().next_u32(),
1217+
});
1218+
1219+
// Check all the details in new_graph reconstruct as well
1220+
1221+
let mut full_txs_vec: Vec<_> = graph.full_txs().collect();
1222+
full_txs_vec.sort();
1223+
let mut new_txs_vec: Vec<_> = new_graph.full_txs().collect();
1224+
new_txs_vec.sort();
1225+
let mut new_txs = new_txs_vec.iter();
1226+
1227+
for tx_node in full_txs_vec.iter() {
1228+
let new_txnode = new_txs.next().unwrap();
1229+
assert_eq!(new_txnode.txid, tx_node.txid);
1230+
assert_eq!(new_txnode.tx, tx_node.tx);
1231+
assert_eq!(
1232+
new_txnode.last_seen_unconfirmed,
1233+
tx_node.last_seen_unconfirmed
1234+
);
1235+
assert_eq!(new_txnode.anchors.len(), tx_node.anchors.len());
1236+
1237+
let mut new_anchors: Vec<_> = new_txnode.anchors.iter().map(|a| a.anchor_block).collect();
1238+
new_anchors.sort();
1239+
let mut old_anchors: Vec<_> = tx_node.anchors.iter().copied().collect();
1240+
old_anchors.sort();
1241+
assert_eq!(new_anchors, old_anchors);
1242+
}
1243+
assert!(new_txs.next().is_none());
1244+
1245+
let new_graph_anchors: Vec<_> = new_graph
1246+
.all_anchors()
1247+
.iter()
1248+
.map(|i| i.0.anchor_block)
1249+
.collect();
1250+
assert_eq!(
1251+
new_graph_anchors,
1252+
vec![
1253+
block_id!(1, "A"),
1254+
block_id!(2, "B"),
1255+
block_id!(3, "C"),
1256+
block_id!(4, "D"),
1257+
]
1258+
);
1259+
}

0 commit comments

Comments
 (0)