Skip to content

Commit 2c324d3

Browse files
committed
Merge #1325: Add map_anchors for TxGraph
5489f90 feat(chain): add `map_anchors` for `TxGraph` and `ChangeSet` (Antonio Yang) 022d5a2 test(chain) use `Anchor` generic on `init_graph` (Antonio Yang) Pull request description: ### Description Fix #1295 ### Checklists #### All Submissions: * [X] I've signed all my commits * [X] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [X] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [ ] I've added tests for the new feature * [ ] I've added docs for the new feature #### Bugfixes: * [ ] This pull request breaks the existing API * [ ] I've added tests to reproduce the issue which are now passing * [ ] I'm linking the issue being fixed by this PR ACKs for top commit: evanlinjin: ACK 5489f90 LLFourn: ACK 5489f90 Tree-SHA512: c8327f2e7035a46208eb32c6da1f9f0bc3e8625168450c5b0b39f695268e42b0b9053b6eb97805b116328195d77af7ca9edb1f12206c50513fbe295dded542e7
2 parents 50c549b + 5489f90 commit 2c324d3

File tree

3 files changed

+126
-6
lines changed

3 files changed

+126
-6
lines changed

crates/chain/src/tx_graph.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,21 @@ impl<A> TxGraph<A> {
454454
}
455455
}
456456

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

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

crates/chain/tests/common/tx_template.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use rand::distributions::{Alphanumeric, DistString};
22
use std::collections::HashMap;
33

4-
use bdk_chain::{tx_graph::TxGraph, BlockId, SpkTxOutIndex};
4+
use bdk_chain::{tx_graph::TxGraph, Anchor, SpkTxOutIndex};
55
use bitcoin::{
66
locktime::absolute::LockTime, secp256k1::Secp256k1, OutPoint, ScriptBuf, Sequence, Transaction,
77
TxIn, TxOut, Txid, Witness,
@@ -49,11 +49,11 @@ impl TxOutTemplate {
4949
}
5050

5151
#[allow(dead_code)]
52-
pub fn init_graph<'a>(
53-
tx_templates: impl IntoIterator<Item = &'a TxTemplate<'a, BlockId>>,
54-
) -> (TxGraph<BlockId>, SpkTxOutIndex<u32>, HashMap<&'a str, Txid>) {
52+
pub fn init_graph<'a, A: Anchor + Clone + 'a>(
53+
tx_templates: impl IntoIterator<Item = &'a TxTemplate<'a, A>>,
54+
) -> (TxGraph<A>, SpkTxOutIndex<u32>, HashMap<&'a str, Txid>) {
5555
let (descriptor, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap();
56-
let mut graph = TxGraph::<BlockId>::default();
56+
let mut graph = TxGraph::<A>::default();
5757
let mut spk_index = SpkTxOutIndex::default();
5858
(0..10).for_each(|index| {
5959
spk_index.insert_spk(
@@ -126,7 +126,7 @@ pub fn init_graph<'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]
@@ -1178,3 +1180,86 @@ fn test_missing_blocks() {
11781180
),
11791181
]);
11801182
}
1183+
1184+
#[test]
1185+
/// The `map_anchors` allow a caller to pass a function to reconstruct the [`TxGraph`] with any [`Anchor`],
1186+
/// even though the function is non-deterministic.
1187+
fn call_map_anchors_with_non_deterministic_anchor() {
1188+
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
1189+
/// A non-deterministic anchor
1190+
pub struct NonDeterministicAnchor {
1191+
pub anchor_block: BlockId,
1192+
pub non_deterministic_field: u32,
1193+
}
1194+
1195+
let template = [
1196+
TxTemplate {
1197+
tx_name: "tx1",
1198+
inputs: &[TxInTemplate::Bogus],
1199+
outputs: &[TxOutTemplate::new(10000, Some(1))],
1200+
anchors: &[block_id!(1, "A")],
1201+
last_seen: None,
1202+
},
1203+
TxTemplate {
1204+
tx_name: "tx2",
1205+
inputs: &[TxInTemplate::PrevTx("tx1", 0)],
1206+
outputs: &[TxOutTemplate::new(20000, Some(2))],
1207+
anchors: &[block_id!(2, "B")],
1208+
..Default::default()
1209+
},
1210+
TxTemplate {
1211+
tx_name: "tx3",
1212+
inputs: &[TxInTemplate::PrevTx("tx2", 0)],
1213+
outputs: &[TxOutTemplate::new(30000, Some(3))],
1214+
anchors: &[block_id!(3, "C"), block_id!(4, "D")],
1215+
..Default::default()
1216+
},
1217+
];
1218+
let (graph, _, _) = init_graph(&template);
1219+
let new_graph = graph.clone().map_anchors(|a| NonDeterministicAnchor {
1220+
anchor_block: a,
1221+
// A non-deterministic value
1222+
non_deterministic_field: rand::thread_rng().next_u32(),
1223+
});
1224+
1225+
// Check all the details in new_graph reconstruct as well
1226+
1227+
let mut full_txs_vec: Vec<_> = graph.full_txs().collect();
1228+
full_txs_vec.sort();
1229+
let mut new_txs_vec: Vec<_> = new_graph.full_txs().collect();
1230+
new_txs_vec.sort();
1231+
let mut new_txs = new_txs_vec.iter();
1232+
1233+
for tx_node in full_txs_vec.iter() {
1234+
let new_txnode = new_txs.next().unwrap();
1235+
assert_eq!(new_txnode.txid, tx_node.txid);
1236+
assert_eq!(new_txnode.tx, tx_node.tx);
1237+
assert_eq!(
1238+
new_txnode.last_seen_unconfirmed,
1239+
tx_node.last_seen_unconfirmed
1240+
);
1241+
assert_eq!(new_txnode.anchors.len(), tx_node.anchors.len());
1242+
1243+
let mut new_anchors: Vec<_> = new_txnode.anchors.iter().map(|a| a.anchor_block).collect();
1244+
new_anchors.sort();
1245+
let mut old_anchors: Vec<_> = tx_node.anchors.iter().copied().collect();
1246+
old_anchors.sort();
1247+
assert_eq!(new_anchors, old_anchors);
1248+
}
1249+
assert!(new_txs.next().is_none());
1250+
1251+
let new_graph_anchors: Vec<_> = new_graph
1252+
.all_anchors()
1253+
.iter()
1254+
.map(|i| i.0.anchor_block)
1255+
.collect();
1256+
assert_eq!(
1257+
new_graph_anchors,
1258+
vec![
1259+
block_id!(1, "A"),
1260+
block_id!(2, "B"),
1261+
block_id!(3, "C"),
1262+
block_id!(4, "D"),
1263+
]
1264+
);
1265+
}

0 commit comments

Comments
 (0)