Skip to content

Commit

Permalink
Merge bitcoindevkit#1325: Add map_anchors for TxGraph
Browse files Browse the repository at this point in the history
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 bitcoindevkit#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
  • Loading branch information
evanlinjin committed Feb 16, 2024
2 parents 50c549b + 5489f90 commit 2c324d3
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 6 deletions.
35 changes: 35 additions & 0 deletions crates/chain/src/tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,21 @@ impl<A> TxGraph<A> {
}
}

impl<A: Clone + Ord> TxGraph<A> {
/// 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<A2: Clone + Ord, F>(self, f: F) -> TxGraph<A2>
where
F: FnMut(A) -> A2,
{
let mut new_graph = TxGraph::<A2>::default();
new_graph.apply_changeset(self.initial_changeset().map_anchors(f));
new_graph
}
}

impl<A: Clone + Ord> TxGraph<A> {
/// Construct a new [`TxGraph`] from a list of transactions.
pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
Expand Down Expand Up @@ -1294,6 +1309,26 @@ impl<A: Ord> Append for ChangeSet<A> {
}
}

impl<A: Ord> ChangeSet<A> {
/// 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<A2: Ord, F>(self, mut f: F) -> ChangeSet<A2>
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<A> AsRef<TxGraph<A>> for TxGraph<A> {
fn as_ref(&self) -> &TxGraph<A> {
self
Expand Down
12 changes: 6 additions & 6 deletions crates/chain/tests/common/tx_template.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use rand::distributions::{Alphanumeric, DistString};
use std::collections::HashMap;

use bdk_chain::{tx_graph::TxGraph, BlockId, SpkTxOutIndex};
use bdk_chain::{tx_graph::TxGraph, Anchor, SpkTxOutIndex};
use bitcoin::{
locktime::absolute::LockTime, secp256k1::Secp256k1, OutPoint, ScriptBuf, Sequence, Transaction,
TxIn, TxOut, Txid, Witness,
Expand Down Expand Up @@ -49,11 +49,11 @@ impl TxOutTemplate {
}

#[allow(dead_code)]
pub fn init_graph<'a>(
tx_templates: impl IntoIterator<Item = &'a TxTemplate<'a, BlockId>>,
) -> (TxGraph<BlockId>, SpkTxOutIndex<u32>, HashMap<&'a str, Txid>) {
pub fn init_graph<'a, A: Anchor + Clone + 'a>(
tx_templates: impl IntoIterator<Item = &'a TxTemplate<'a, A>>,
) -> (TxGraph<A>, SpkTxOutIndex<u32>, HashMap<&'a str, Txid>) {
let (descriptor, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap();
let mut graph = TxGraph::<BlockId>::default();
let mut graph = TxGraph::<A>::default();
let mut spk_index = SpkTxOutIndex::default();
(0..10).for_each(|index| {
spk_index.insert_spk(
Expand Down Expand Up @@ -126,7 +126,7 @@ pub fn init_graph<'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);
Expand Down
85 changes: 85 additions & 0 deletions crates/chain/tests/test_tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -1178,3 +1180,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"),
]
);
}

0 comments on commit 2c324d3

Please sign in to comment.