diff --git a/crates/chain/src/indexed_tx_graph.rs b/crates/chain/src/indexed_tx_graph.rs
index 92a08a9ef..d24b1b307 100644
--- a/crates/chain/src/indexed_tx_graph.rs
+++ b/crates/chain/src/indexed_tx_graph.rs
@@ -91,13 +91,10 @@ where
/// Apply an `update` directly.
///
/// `update` is a [`TxGraph`] and the resultant changes is returned as [`ChangeSet`].
- pub fn apply_update(&mut self, update: TxGraph) -> ChangeSet {
- let graph = self.graph.apply_update(update);
- let indexer = self.index_tx_graph_changeset(&graph);
- ChangeSet {
- tx_graph: graph,
- indexer,
- }
+ pub fn apply_update(&mut self, update: tx_graph::Update) -> ChangeSet {
+ let tx_graph = self.graph.apply_update(update);
+ let indexer = self.index_tx_graph_changeset(&tx_graph);
+ ChangeSet { tx_graph, indexer }
}
/// Insert a floating `txout` of given `outpoint`.
diff --git a/crates/chain/src/spk_client.rs b/crates/chain/src/spk_client.rs
index 567a8f0a9..e31b431dd 100644
--- a/crates/chain/src/spk_client.rs
+++ b/crates/chain/src/spk_client.rs
@@ -3,7 +3,7 @@ use crate::{
alloc::{boxed::Box, collections::VecDeque, vec::Vec},
collections::BTreeMap,
local_chain::CheckPoint,
- ConfirmationBlockTime, Indexed, TxGraph,
+ ConfirmationBlockTime, Indexed,
};
use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
@@ -345,8 +345,8 @@ impl SyncRequest {
#[must_use]
#[derive(Debug)]
pub struct SyncResult {
- /// The update to apply to the receiving [`TxGraph`].
- pub graph_update: TxGraph,
+ /// The update to apply to the receiving [`TxGraph`](crate::tx_graph::TxGraph).
+ pub graph_update: crate::tx_graph::Update,
/// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain).
pub chain_update: Option,
}
@@ -497,8 +497,8 @@ impl FullScanRequest {
#[derive(Debug)]
pub struct FullScanResult {
/// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain).
- pub graph_update: TxGraph,
- /// The update to apply to the receiving [`TxGraph`].
+ pub graph_update: crate::tx_graph::Update,
+ /// The update to apply to the receiving [`TxGraph`](crate::tx_graph::TxGraph).
pub chain_update: Option,
/// Last active indices for the corresponding keychains (`K`).
pub last_active_indices: BTreeMap,
diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs
index 9ab1268b3..ba894fa93 100644
--- a/crates/chain/src/tx_graph.rs
+++ b/crates/chain/src/tx_graph.rs
@@ -70,13 +70,17 @@
//!
//! ```
//! # use bdk_chain::{Merge, BlockId};
-//! # use bdk_chain::tx_graph::TxGraph;
+//! # use bdk_chain::tx_graph::{self, TxGraph};
//! # use bdk_chain::example_utils::*;
//! # use bitcoin::Transaction;
+//! # use std::sync::Arc;
//! # let tx_a = tx_from_hex(RAW_TX_1);
//! # let tx_b = tx_from_hex(RAW_TX_2);
//! let mut graph: TxGraph = TxGraph::default();
-//! let update = TxGraph::new(vec![tx_a, tx_b]);
+//!
+//! let mut update = tx_graph::Update::default();
+//! update.txs.push(Arc::new(tx_a));
+//! update.txs.push(Arc::new(tx_b));
//!
//! // apply the update graph
//! let changeset = graph.apply_update(update.clone());
@@ -101,6 +105,79 @@ use core::{
ops::{Deref, RangeInclusive},
};
+/// Data object used to update the [`TxGraph`] with.
+#[derive(Debug, Clone)]
+pub struct Update {
+ /// Full transactions.
+ pub txs: Vec>,
+ /// Floating txouts.
+ pub txouts: BTreeMap,
+ /// Transaction anchors.
+ pub anchors: BTreeSet<(A, Txid)>,
+ /// Seen at times for transactions.
+ pub seen_ats: HashMap,
+}
+
+impl Default for Update {
+ fn default() -> Self {
+ Self {
+ txs: Default::default(),
+ txouts: Default::default(),
+ anchors: Default::default(),
+ seen_ats: Default::default(),
+ }
+ }
+}
+
+impl From> for Update {
+ fn from(graph: TxGraph) -> Self {
+ Self {
+ txs: graph.full_txs().map(|tx_node| tx_node.tx).collect(),
+ txouts: graph
+ .floating_txouts()
+ .map(|(op, txo)| (op, txo.clone()))
+ .collect(),
+ anchors: graph.anchors,
+ seen_ats: graph.last_seen.into_iter().collect(),
+ }
+ }
+}
+
+impl From> for TxGraph {
+ fn from(update: Update) -> Self {
+ let mut graph = TxGraph::::default();
+ let _ = graph.apply_update(update);
+ graph
+ }
+}
+
+impl Update {
+ /// Update the [`seen_ats`](Self::seen_ats) for all unanchored transactions.
+ pub fn update_last_seen_unconfirmed(&mut self, seen_at: u64) {
+ let seen_ats = &mut self.seen_ats;
+ let anchors = &self.anchors;
+ let unanchored_txids = self.txs.iter().map(|tx| tx.compute_txid()).filter(|txid| {
+ for (_, anchor_txid) in anchors {
+ if txid == anchor_txid {
+ return false;
+ }
+ }
+ true
+ });
+ for txid in unanchored_txids {
+ seen_ats.insert(txid, seen_at);
+ }
+ }
+
+ /// Extend this update with `other`.
+ pub fn extend(&mut self, other: Update) {
+ self.txs.extend(other.txs);
+ self.txouts.extend(other.txouts);
+ self.anchors.extend(other.anchors);
+ self.seen_ats.extend(other.seen_ats);
+ }
+}
+
/// A graph of transactions and spends.
///
/// See the [module-level documentation] for more.
@@ -690,19 +767,19 @@ impl TxGraph {
///
/// The returned [`ChangeSet`] is the set difference between `update` and `self` (transactions that
/// exist in `update` but not in `self`).
- pub fn apply_update(&mut self, update: TxGraph) -> ChangeSet {
+ pub fn apply_update(&mut self, update: Update) -> ChangeSet {
let mut changeset = ChangeSet::::default();
- for tx_node in update.full_txs() {
- changeset.merge(self.insert_tx(tx_node.tx));
+ for tx in update.txs {
+ changeset.merge(self.insert_tx(tx));
}
- for (outpoint, txout) in update.floating_txouts() {
- changeset.merge(self.insert_txout(outpoint, txout.clone()));
+ for (outpoint, txout) in update.txouts {
+ changeset.merge(self.insert_txout(outpoint, txout));
}
- for (anchor, txid) in &update.anchors {
- changeset.merge(self.insert_anchor(*txid, anchor.clone()));
+ for (anchor, txid) in update.anchors {
+ changeset.merge(self.insert_anchor(txid, anchor));
}
- for (&txid, &last_seen) in &update.last_seen {
- changeset.merge(self.insert_seen_at(txid, last_seen));
+ for (txid, seen_at) in update.seen_ats {
+ changeset.merge(self.insert_seen_at(txid, seen_at));
}
changeset
}
diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs
index 3ffa82439..c6399f53b 100644
--- a/crates/chain/tests/test_tx_graph.rs
+++ b/crates/chain/tests/test_tx_graph.rs
@@ -2,7 +2,7 @@
#[macro_use]
mod common;
-use bdk_chain::tx_graph::CalculateFeeError;
+use bdk_chain::tx_graph::{self, CalculateFeeError};
use bdk_chain::{
collections::*,
local_chain::LocalChain,
@@ -49,7 +49,7 @@ fn insert_txouts() {
)];
// One full transaction to be included in the update
- let update_txs = Transaction {
+ let update_tx = Transaction {
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
@@ -63,17 +63,17 @@ fn insert_txouts() {
};
// Conf anchor used to mark the full transaction as confirmed.
- let conf_anchor = ChainPosition::Confirmed(BlockId {
+ let conf_anchor = BlockId {
height: 100,
hash: h!("random blockhash"),
- });
+ };
- // Unconfirmed anchor to mark the partial transactions as unconfirmed
- let unconf_anchor = ChainPosition::::Unconfirmed(1000000);
+ // Unconfirmed seen_at timestamp to mark the partial transactions as unconfirmed.
+ let unconf_seen_at = 1000000_u64;
// Make the original graph
let mut graph = {
- let mut graph = TxGraph::>::default();
+ let mut graph = TxGraph::::default();
for (outpoint, txout) in &original_ops {
assert_eq!(
graph.insert_txout(*outpoint, txout.clone()),
@@ -88,57 +88,21 @@ fn insert_txouts() {
// Make the update graph
let update = {
- let mut graph = TxGraph::default();
+ let mut update = tx_graph::Update::default();
for (outpoint, txout) in &update_ops {
- // Insert partials transactions
- assert_eq!(
- graph.insert_txout(*outpoint, txout.clone()),
- ChangeSet {
- txouts: [(*outpoint, txout.clone())].into(),
- ..Default::default()
- }
- );
+ // Insert partials transactions.
+ update.txouts.insert(*outpoint, txout.clone());
// Mark them unconfirmed.
- assert_eq!(
- graph.insert_anchor(outpoint.txid, unconf_anchor),
- ChangeSet {
- txs: [].into(),
- txouts: [].into(),
- anchors: [(unconf_anchor, outpoint.txid)].into(),
- last_seen: [].into()
- }
- );
- // Mark them last seen at.
- assert_eq!(
- graph.insert_seen_at(outpoint.txid, 1000000),
- ChangeSet {
- txs: [].into(),
- txouts: [].into(),
- anchors: [].into(),
- last_seen: [(outpoint.txid, 1000000)].into()
- }
- );
+ update.seen_ats.insert(outpoint.txid, unconf_seen_at);
}
- // Insert the full transaction
- assert_eq!(
- graph.insert_tx(update_txs.clone()),
- ChangeSet {
- txs: [Arc::new(update_txs.clone())].into(),
- ..Default::default()
- }
- );
+ // Insert the full transaction.
+ update.txs.push(update_tx.clone().into());
// Mark it as confirmed.
- assert_eq!(
- graph.insert_anchor(update_txs.compute_txid(), conf_anchor),
- ChangeSet {
- txs: [].into(),
- txouts: [].into(),
- anchors: [(conf_anchor, update_txs.compute_txid())].into(),
- last_seen: [].into()
- }
- );
- graph
+ update
+ .anchors
+ .insert((conf_anchor, update_tx.compute_txid()));
+ update
};
// Check the resulting addition.
@@ -147,13 +111,9 @@ fn insert_txouts() {
assert_eq!(
changeset,
ChangeSet {
- txs: [Arc::new(update_txs.clone())].into(),
+ txs: [Arc::new(update_tx.clone())].into(),
txouts: update_ops.clone().into(),
- anchors: [
- (conf_anchor, update_txs.compute_txid()),
- (unconf_anchor, h!("tx2"))
- ]
- .into(),
+ anchors: [(conf_anchor, update_tx.compute_txid()),].into(),
last_seen: [(h!("tx2"), 1000000)].into()
}
);
@@ -188,7 +148,7 @@ fn insert_txouts() {
assert_eq!(
graph
- .tx_outputs(update_txs.compute_txid())
+ .tx_outputs(update_tx.compute_txid())
.expect("should exists"),
[(
0u32,
@@ -204,13 +164,9 @@ fn insert_txouts() {
assert_eq!(
graph.initial_changeset(),
ChangeSet {
- txs: [Arc::new(update_txs.clone())].into(),
+ txs: [Arc::new(update_tx.clone())].into(),
txouts: update_ops.into_iter().chain(original_ops).collect(),
- anchors: [
- (conf_anchor, update_txs.compute_txid()),
- (unconf_anchor, h!("tx2"))
- ]
- .into(),
+ anchors: [(conf_anchor, update_tx.compute_txid()),].into(),
last_seen: [(h!("tx2"), 1000000)].into()
}
);
diff --git a/crates/electrum/src/bdk_electrum_client.rs b/crates/electrum/src/bdk_electrum_client.rs
index 1458e2bd9..571660756 100644
--- a/crates/electrum/src/bdk_electrum_client.rs
+++ b/crates/electrum/src/bdk_electrum_client.rs
@@ -3,12 +3,12 @@ use bdk_chain::{
collections::{BTreeMap, HashMap},
local_chain::CheckPoint,
spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult},
- tx_graph::TxGraph,
+ tx_graph::{self, TxGraph},
Anchor, BlockId, ConfirmationBlockTime,
};
use electrum_client::{ElectrumApi, Error, HeaderNotification};
use std::{
- collections::BTreeSet,
+ collections::HashSet,
sync::{Arc, Mutex},
};
@@ -138,7 +138,7 @@ impl BdkElectrumClient {
None => None,
};
- let mut graph_update = TxGraph::::default();
+ let mut graph_update = tx_graph::Update::::default();
let mut last_active_indices = BTreeMap::::default();
for keychain in request.keychains() {
let spks = request.iter_spks(keychain.clone());
@@ -158,7 +158,7 @@ impl BdkElectrumClient {
Some((chain_tip, latest_blocks)) => Some(chain_update(
chain_tip,
&latest_blocks,
- graph_update.all_anchors(),
+ graph_update.anchors.iter().cloned(),
)?),
_ => None,
};
@@ -205,7 +205,7 @@ impl BdkElectrumClient {
None => None,
};
- let mut graph_update = TxGraph::::default();
+ let mut graph_update = tx_graph::Update::::default();
self.populate_with_spks(
&mut graph_update,
request
@@ -227,7 +227,7 @@ impl BdkElectrumClient {
Some((chain_tip, latest_blocks)) => Some(chain_update(
chain_tip,
&latest_blocks,
- graph_update.all_anchors(),
+ graph_update.anchors.iter().cloned(),
)?),
None => None,
};
@@ -245,7 +245,7 @@ impl BdkElectrumClient {
/// also included.
fn populate_with_spks(
&self,
- graph_update: &mut TxGraph,
+ graph_update: &mut tx_graph::Update,
mut spks: impl Iterator- ,
stop_gap: usize,
batch_size: usize,
@@ -278,7 +278,7 @@ impl BdkElectrumClient {
}
for tx_res in spk_history {
- let _ = graph_update.insert_tx(self.fetch_tx(tx_res.tx_hash)?);
+ graph_update.txs.push(self.fetch_tx(tx_res.tx_hash)?);
self.validate_merkle_for_anchor(graph_update, tx_res.tx_hash, tx_res.height)?;
}
}
@@ -291,7 +291,7 @@ impl BdkElectrumClient {
/// included. Anchors of the aforementioned transactions are included.
fn populate_with_outpoints(
&self,
- graph_update: &mut TxGraph,
+ graph_update: &mut tx_graph::Update,
outpoints: impl IntoIterator
- ,
) -> Result<(), Error> {
for outpoint in outpoints {
@@ -314,7 +314,7 @@ impl BdkElectrumClient {
if !has_residing && res.tx_hash == op_txid {
has_residing = true;
- let _ = graph_update.insert_tx(Arc::clone(&op_tx));
+ graph_update.txs.push(Arc::clone(&op_tx));
self.validate_merkle_for_anchor(graph_update, res.tx_hash, res.height)?;
}
@@ -328,7 +328,7 @@ impl BdkElectrumClient {
if !has_spending {
continue;
}
- let _ = graph_update.insert_tx(Arc::clone(&res_tx));
+ graph_update.txs.push(Arc::clone(&res_tx));
self.validate_merkle_for_anchor(graph_update, res.tx_hash, res.height)?;
}
}
@@ -339,7 +339,7 @@ impl BdkElectrumClient {
/// Populate the `graph_update` with transactions/anchors of the provided `txids`.
fn populate_with_txids(
&self,
- graph_update: &mut TxGraph,
+ graph_update: &mut tx_graph::Update,
txids: impl IntoIterator
- ,
) -> Result<(), Error> {
for txid in txids {
@@ -366,7 +366,7 @@ impl BdkElectrumClient {
self.validate_merkle_for_anchor(graph_update, txid, r.height)?;
}
- let _ = graph_update.insert_tx(tx);
+ graph_update.txs.push(tx);
}
Ok(())
}
@@ -375,7 +375,7 @@ impl BdkElectrumClient {
// An anchor is inserted if the transaction is validated to be in a confirmed block.
fn validate_merkle_for_anchor(
&self,
- graph_update: &mut TxGraph,
+ graph_update: &mut tx_graph::Update,
txid: Txid,
confirmation_height: i32,
) -> Result<(), Error> {
@@ -402,8 +402,7 @@ impl BdkElectrumClient {
}
if is_confirmed_tx {
- let _ = graph_update.insert_anchor(
- txid,
+ graph_update.anchors.insert((
ConfirmationBlockTime {
confirmation_time: header.time as u64,
block_id: BlockId {
@@ -411,7 +410,8 @@ impl BdkElectrumClient {
hash: header.block_hash(),
},
},
- );
+ txid,
+ ));
}
}
Ok(())
@@ -421,17 +421,18 @@ impl BdkElectrumClient {
// which we do not have by default. This data is needed to calculate the transaction fee.
fn fetch_prev_txout(
&self,
- graph_update: &mut TxGraph,
+ graph_update: &mut tx_graph::Update,
) -> Result<(), Error> {
- let full_txs: Vec> =
- graph_update.full_txs().map(|tx_node| tx_node.tx).collect();
- for tx in full_txs {
- for vin in &tx.input {
- let outpoint = vin.previous_output;
- let vout = outpoint.vout;
- let prev_tx = self.fetch_tx(outpoint.txid)?;
- let txout = prev_tx.output[vout as usize].clone();
- let _ = graph_update.insert_txout(outpoint, txout);
+ let mut no_dup = HashSet::::new();
+ for tx in &graph_update.txs {
+ if no_dup.insert(tx.compute_txid()) {
+ for vin in &tx.input {
+ let outpoint = vin.previous_output;
+ let vout = outpoint.vout;
+ let prev_tx = self.fetch_tx(outpoint.txid)?;
+ let txout = prev_tx.output[vout as usize].clone();
+ let _ = graph_update.txouts.insert(outpoint, txout);
+ }
}
}
Ok(())
@@ -516,7 +517,7 @@ fn fetch_tip_and_latest_blocks(
fn chain_update(
mut tip: CheckPoint,
latest_blocks: &BTreeMap,
- anchors: &BTreeSet<(A, Txid)>,
+ anchors: impl Iterator
- ,
) -> Result {
for anchor in anchors {
let height = anchor.0.anchor_block().height;
diff --git a/crates/electrum/tests/test_electrum.rs b/crates/electrum/tests/test_electrum.rs
index 63e91081b..e8b054d33 100644
--- a/crates/electrum/tests/test_electrum.rs
+++ b/crates/electrum/tests/test_electrum.rs
@@ -1,9 +1,9 @@
use bdk_chain::{
- bitcoin::{hashes::Hash, Address, Amount, ScriptBuf, Txid, WScriptHash},
+ bitcoin::{hashes::Hash, Address, Amount, ScriptBuf, WScriptHash},
local_chain::LocalChain,
spk_client::{FullScanRequest, SyncRequest, SyncResult},
spk_txout::SpkTxOutIndex,
- Balance, ConfirmationBlockTime, IndexedTxGraph, Indexer, Merge,
+ Balance, ConfirmationBlockTime, IndexedTxGraph, Indexer, Merge, TxGraph,
};
use bdk_electrum::BdkElectrumClient;
use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, TestEnv};
@@ -49,7 +49,7 @@ where
.elapsed()
.expect("must get time")
.as_secs();
- let _ = update.graph_update.update_last_seen_unconfirmed(now);
+ update.graph_update.update_last_seen_unconfirmed(now);
if let Some(chain_update) = update.chain_update.clone() {
let _ = chain
@@ -128,18 +128,23 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
);
let graph_update = sync_update.graph_update;
+ let updated_graph = {
+ let mut graph = TxGraph::::default();
+ let _ = graph.apply_update(graph_update.clone());
+ graph
+ };
// Check to see if we have the floating txouts available from our two created transactions'
// previous outputs in order to calculate transaction fees.
- for tx in graph_update.full_txs() {
+ for tx in &graph_update.txs {
// Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the
// floating txouts available from the transactions' previous outputs.
- let fee = graph_update.calculate_fee(&tx.tx).expect("Fee must exist");
+ let fee = updated_graph.calculate_fee(tx).expect("Fee must exist");
// Retrieve the fee in the transaction data from `bitcoind`.
let tx_fee = env
.bitcoind
.client
- .get_transaction(&tx.txid, None)
+ .get_transaction(&tx.compute_txid(), None)
.expect("Tx must exist")
.fee
.expect("Fee must exist")
@@ -151,12 +156,15 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
assert_eq!(fee, tx_fee);
}
- let mut graph_update_txids: Vec = graph_update.full_txs().map(|tx| tx.txid).collect();
- graph_update_txids.sort();
- let mut expected_txids = vec![txid1, txid2];
- expected_txids.sort();
- assert_eq!(graph_update_txids, expected_txids);
-
+ assert_eq!(
+ graph_update
+ .txs
+ .iter()
+ .map(|tx| tx.compute_txid())
+ .collect::>(),
+ [txid1, txid2].into(),
+ "update must include all expected transactions",
+ );
Ok(())
}
@@ -216,7 +224,7 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.spks_for_keychain(0, spks.clone());
client.full_scan(request, 3, 1, false)?
};
- assert!(full_scan_update.graph_update.full_txs().next().is_none());
+ assert!(full_scan_update.graph_update.txs.is_empty());
assert!(full_scan_update.last_active_indices.is_empty());
let full_scan_update = {
let request = FullScanRequest::builder()
@@ -227,10 +235,10 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
assert_eq!(
full_scan_update
.graph_update
- .full_txs()
- .next()
+ .txs
+ .first()
.unwrap()
- .txid,
+ .compute_txid(),
txid_4th_addr
);
assert_eq!(full_scan_update.last_active_indices[&0], 3);
@@ -259,8 +267,9 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
};
let txs: HashSet<_> = full_scan_update
.graph_update
- .full_txs()
- .map(|tx| tx.txid)
+ .txs
+ .iter()
+ .map(|tx| tx.compute_txid())
.collect();
assert_eq!(txs.len(), 1);
assert!(txs.contains(&txid_4th_addr));
@@ -273,8 +282,9 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
};
let txs: HashSet<_> = full_scan_update
.graph_update
- .full_txs()
- .map(|tx| tx.txid)
+ .txs
+ .iter()
+ .map(|tx| tx.compute_txid())
.collect();
assert_eq!(txs.len(), 2);
assert!(txs.contains(&txid_4th_addr) && txs.contains(&txid_last_addr));
@@ -475,13 +485,12 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> {
)?;
// Retain a snapshot of all anchors before reorg process.
- let initial_anchors = update.graph_update.all_anchors();
- let anchors: Vec<_> = initial_anchors.iter().cloned().collect();
- assert_eq!(anchors.len(), REORG_COUNT);
+ let initial_anchors = update.graph_update.anchors.clone();
+ assert_eq!(initial_anchors.len(), REORG_COUNT);
for i in 0..REORG_COUNT {
- let (anchor, txid) = anchors[i];
+ let (anchor, txid) = initial_anchors.iter().nth(i).unwrap();
assert_eq!(anchor.block_id.hash, hashes[i]);
- assert_eq!(txid, txids[i]);
+ assert_eq!(*txid, txids[i]);
}
// Check if initial balance is correct.
@@ -507,7 +516,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> {
)?;
// Check that no new anchors are added during current reorg.
- assert!(initial_anchors.is_superset(update.graph_update.all_anchors()));
+ assert!(initial_anchors.is_superset(&update.graph_update.anchors));
assert_eq!(
get_balance(&recv_chain, &recv_graph)?,
diff --git a/crates/esplora/src/async_ext.rs b/crates/esplora/src/async_ext.rs
index 066b91e17..f3c8e966a 100644
--- a/crates/esplora/src/async_ext.rs
+++ b/crates/esplora/src/async_ext.rs
@@ -1,4 +1,4 @@
-use std::collections::BTreeSet;
+use std::collections::{BTreeSet, HashSet};
use async_trait::async_trait;
use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult};
@@ -6,10 +6,9 @@ use bdk_chain::{
bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid},
collections::BTreeMap,
local_chain::CheckPoint,
- BlockId, ConfirmationBlockTime, TxGraph,
+ BlockId, ConfirmationBlockTime,
};
-use bdk_chain::{Anchor, Indexed};
-use esplora_client::{Tx, TxStatus};
+use bdk_chain::{tx_graph, Anchor, Indexed};
use futures::{stream::FuturesOrdered, TryStreamExt};
use crate::{insert_anchor_from_status, insert_prevouts};
@@ -72,23 +71,29 @@ impl EsploraAsyncExt for esplora_client::AsyncClient {
None
};
- let mut graph_update = TxGraph::default();
+ let mut graph_update = tx_graph::Update::::default();
+ let mut inserted_txs = HashSet::::new();
let mut last_active_indices = BTreeMap::::new();
for keychain in keychains {
let keychain_spks = request.iter_spks(keychain.clone());
- let (tx_graph, last_active_index) =
- fetch_txs_with_keychain_spks(self, keychain_spks, stop_gap, parallel_requests)
- .await?;
- let _ = graph_update.apply_update(tx_graph);
+ let (update, last_active_index) = fetch_txs_with_keychain_spks(
+ self,
+ &mut inserted_txs,
+ keychain_spks,
+ stop_gap,
+ parallel_requests,
+ )
+ .await?;
+ graph_update.extend(update);
if let Some(last_active_index) = last_active_index {
last_active_indices.insert(keychain, last_active_index);
}
}
let chain_update = match (chain_tip, latest_blocks) {
- (Some(chain_tip), Some(latest_blocks)) => Some(
- chain_update(self, &latest_blocks, &chain_tip, graph_update.all_anchors()).await?,
- ),
+ (Some(chain_tip), Some(latest_blocks)) => {
+ Some(chain_update(self, &latest_blocks, &chain_tip, &graph_update.anchors).await?)
+ }
_ => None,
};
@@ -113,20 +118,40 @@ impl EsploraAsyncExt for esplora_client::AsyncClient {
None
};
- let mut graph_update = TxGraph::::default();
- let _ = graph_update
- .apply_update(fetch_txs_with_spks(self, request.iter_spks(), parallel_requests).await?);
- let _ = graph_update.apply_update(
- fetch_txs_with_txids(self, request.iter_txids(), parallel_requests).await?,
+ let mut graph_update = tx_graph::Update::::default();
+ let mut inserted_txs = HashSet::::new();
+ graph_update.extend(
+ fetch_txs_with_spks(
+ self,
+ &mut inserted_txs,
+ request.iter_spks(),
+ parallel_requests,
+ )
+ .await?,
+ );
+ graph_update.extend(
+ fetch_txs_with_txids(
+ self,
+ &mut inserted_txs,
+ request.iter_txids(),
+ parallel_requests,
+ )
+ .await?,
);
- let _ = graph_update.apply_update(
- fetch_txs_with_outpoints(self, request.iter_outpoints(), parallel_requests).await?,
+ graph_update.extend(
+ fetch_txs_with_outpoints(
+ self,
+ &mut inserted_txs,
+ request.iter_outpoints(),
+ parallel_requests,
+ )
+ .await?,
);
let chain_update = match (chain_tip, latest_blocks) {
- (Some(chain_tip), Some(latest_blocks)) => Some(
- chain_update(self, &latest_blocks, &chain_tip, graph_update.all_anchors()).await?,
- ),
+ (Some(chain_tip), Some(latest_blocks)) => {
+ Some(chain_update(self, &latest_blocks, &chain_tip, &graph_update.anchors).await?)
+ }
_ => None,
};
@@ -252,13 +277,14 @@ async fn chain_update(
/// Refer to [crate-level docs](crate) for more.
async fn fetch_txs_with_keychain_spks> + Send>(
client: &esplora_client::AsyncClient,
+ inserted_txs: &mut HashSet,
mut keychain_spks: I,
stop_gap: usize,
parallel_requests: usize,
-) -> Result<(TxGraph, Option), Error> {
+) -> Result<(tx_graph::Update, Option), Error> {
type TxsOfSpkIndex = (u32, Vec);
- let mut tx_graph = TxGraph::default();
+ let mut update = tx_graph::Update::::default();
let mut last_index = Option::::None;
let mut last_active_index = Option::::None;
@@ -294,9 +320,11 @@ async fn fetch_txs_with_keychain_spks> + S
last_active_index = Some(index);
}
for tx in txs {
- let _ = tx_graph.insert_tx(tx.to_tx());
- insert_anchor_from_status(&mut tx_graph, tx.txid, tx.status);
- insert_prevouts(&mut tx_graph, tx.vin);
+ if inserted_txs.insert(tx.txid) {
+ update.txs.push(tx.to_tx().into());
+ }
+ insert_anchor_from_status(&mut update, tx.txid, tx.status);
+ insert_prevouts(&mut update, tx.vin);
}
}
@@ -311,7 +339,7 @@ async fn fetch_txs_with_keychain_spks> + S
}
}
- Ok((tx_graph, last_active_index))
+ Ok((update, last_active_index))
}
/// Fetch transactions and associated [`ConfirmationBlockTime`]s by scanning `spks`
@@ -324,20 +352,22 @@ async fn fetch_txs_with_keychain_spks> + S
/// Refer to [crate-level docs](crate) for more.
async fn fetch_txs_with_spks + Send>(
client: &esplora_client::AsyncClient,
+ inserted_txs: &mut HashSet,
spks: I,
parallel_requests: usize,
-) -> Result, Error>
+) -> Result, Error>
where
I::IntoIter: Send,
{
fetch_txs_with_keychain_spks(
client,
+ inserted_txs,
spks.into_iter().enumerate().map(|(i, spk)| (i as u32, spk)),
usize::MAX,
parallel_requests,
)
.await
- .map(|(tx_graph, _)| tx_graph)
+ .map(|(update, _)| update)
}
/// Fetch transactions and associated [`ConfirmationBlockTime`]s by scanning `txids`
@@ -348,39 +378,27 @@ where
/// Refer to [crate-level docs](crate) for more.
async fn fetch_txs_with_txids + Send>(
client: &esplora_client::AsyncClient,
+ inserted_txs: &mut HashSet,
txids: I,
parallel_requests: usize,
-) -> Result, Error>
+) -> Result, Error>
where
I::IntoIter: Send,
{
- enum EsploraResp {
- TxStatus(TxStatus),
- Tx(Option),
- }
-
- let mut tx_graph = TxGraph::default();
- let mut txids = txids.into_iter();
+ let mut update = tx_graph::Update::::default();
+ // Only fetch for non-inserted txs.
+ let mut txids = txids
+ .into_iter()
+ .filter(|txid| !inserted_txs.contains(txid))
+ .collect::>()
+ .into_iter();
loop {
let handles = txids
.by_ref()
.take(parallel_requests)
.map(|txid| {
let client = client.clone();
- let tx_already_exists = tx_graph.get_tx(txid).is_some();
- async move {
- if tx_already_exists {
- client
- .get_tx_status(&txid)
- .await
- .map(|s| (txid, EsploraResp::TxStatus(s)))
- } else {
- client
- .get_tx_info(&txid)
- .await
- .map(|t| (txid, EsploraResp::Tx(t)))
- }
- }
+ async move { client.get_tx_info(&txid).await.map(|t| (txid, t)) }
})
.collect::>();
@@ -388,21 +406,17 @@ where
break;
}
- for (txid, resp) in handles.try_collect::>().await? {
- match resp {
- EsploraResp::TxStatus(status) => {
- insert_anchor_from_status(&mut tx_graph, txid, status);
- }
- EsploraResp::Tx(Some(tx_info)) => {
- let _ = tx_graph.insert_tx(tx_info.to_tx());
- insert_anchor_from_status(&mut tx_graph, txid, tx_info.status);
- insert_prevouts(&mut tx_graph, tx_info.vin);
+ for (txid, tx_info) in handles.try_collect::>().await? {
+ if let Some(tx_info) = tx_info {
+ if inserted_txs.insert(txid) {
+ update.txs.push(tx_info.to_tx().into());
}
- _ => continue,
+ insert_anchor_from_status(&mut update, txid, tx_info.status);
+ insert_prevouts(&mut update, tx_info.vin);
}
}
}
- Ok(tx_graph)
+ Ok(update)
}
/// Fetch transactions and [`ConfirmationBlockTime`]s that contain and spend the provided
@@ -413,22 +427,27 @@ where
/// Refer to [crate-level docs](crate) for more.
async fn fetch_txs_with_outpoints + Send>(
client: &esplora_client::AsyncClient,
+ inserted_txs: &mut HashSet,
outpoints: I,
parallel_requests: usize,
-) -> Result, Error>
+) -> Result, Error>
where
I::IntoIter: Send,
{
let outpoints = outpoints.into_iter().collect::>();
+ let mut update = tx_graph::Update::::default();
// make sure txs exists in graph and tx statuses are updated
// TODO: We should maintain a tx cache (like we do with Electrum).
- let mut tx_graph = fetch_txs_with_txids(
- client,
- outpoints.iter().copied().map(|op| op.txid),
- parallel_requests,
- )
- .await?;
+ update.extend(
+ fetch_txs_with_txids(
+ client,
+ inserted_txs,
+ outpoints.iter().copied().map(|op| op.txid),
+ parallel_requests,
+ )
+ .await?,
+ );
// get outpoint spend-statuses
let mut outpoints = outpoints.into_iter();
@@ -452,18 +471,18 @@ where
Some(txid) => txid,
None => continue,
};
- if tx_graph.get_tx(spend_txid).is_none() {
+ if !inserted_txs.contains(&spend_txid) {
missing_txs.push(spend_txid);
}
if let Some(spend_status) = op_status.status {
- insert_anchor_from_status(&mut tx_graph, spend_txid, spend_status);
+ insert_anchor_from_status(&mut update, spend_txid, spend_status);
}
}
}
- let _ =
- tx_graph.apply_update(fetch_txs_with_txids(client, missing_txs, parallel_requests).await?);
- Ok(tx_graph)
+ update
+ .extend(fetch_txs_with_txids(client, inserted_txs, missing_txs, parallel_requests).await?);
+ Ok(update)
}
#[cfg(test)]
diff --git a/crates/esplora/src/blocking_ext.rs b/crates/esplora/src/blocking_ext.rs
index 6e3e25afe..62f0d351e 100644
--- a/crates/esplora/src/blocking_ext.rs
+++ b/crates/esplora/src/blocking_ext.rs
@@ -1,4 +1,4 @@
-use std::collections::BTreeSet;
+use std::collections::{BTreeSet, HashSet};
use std::thread::JoinHandle;
use bdk_chain::collections::BTreeMap;
@@ -6,10 +6,10 @@ use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncRe
use bdk_chain::{
bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid},
local_chain::CheckPoint,
- BlockId, ConfirmationBlockTime, TxGraph,
+ BlockId, ConfirmationBlockTime,
};
-use bdk_chain::{Anchor, Indexed};
-use esplora_client::{OutputStatus, Tx, TxStatus};
+use bdk_chain::{tx_graph, Anchor, Indexed};
+use esplora_client::{OutputStatus, Tx};
use crate::{insert_anchor_from_status, insert_prevouts};
@@ -66,13 +66,19 @@ impl EsploraExt for esplora_client::BlockingClient {
None
};
- let mut graph_update = TxGraph::default();
+ let mut graph_update = tx_graph::Update::default();
+ let mut inserted_txs = HashSet::::new();
let mut last_active_indices = BTreeMap::::new();
for keychain in request.keychains() {
let keychain_spks = request.iter_spks(keychain.clone());
- let (tx_graph, last_active_index) =
- fetch_txs_with_keychain_spks(self, keychain_spks, stop_gap, parallel_requests)?;
- let _ = graph_update.apply_update(tx_graph);
+ let (update, last_active_index) = fetch_txs_with_keychain_spks(
+ self,
+ &mut inserted_txs,
+ keychain_spks,
+ stop_gap,
+ parallel_requests,
+ )?;
+ graph_update.extend(update);
if let Some(last_active_index) = last_active_index {
last_active_indices.insert(keychain, last_active_index);
}
@@ -83,7 +89,7 @@ impl EsploraExt for esplora_client::BlockingClient {
self,
&latest_blocks,
&chain_tip,
- graph_update.all_anchors(),
+ &graph_update.anchors,
)?),
_ => None,
};
@@ -109,19 +115,23 @@ impl EsploraExt for esplora_client::BlockingClient {
None
};
- let mut graph_update = TxGraph::default();
- let _ = graph_update.apply_update(fetch_txs_with_spks(
+ let mut graph_update = tx_graph::Update::::default();
+ let mut inserted_txs = HashSet::::new();
+ graph_update.extend(fetch_txs_with_spks(
self,
+ &mut inserted_txs,
request.iter_spks(),
parallel_requests,
)?);
- let _ = graph_update.apply_update(fetch_txs_with_txids(
+ graph_update.extend(fetch_txs_with_txids(
self,
+ &mut inserted_txs,
request.iter_txids(),
parallel_requests,
)?);
- let _ = graph_update.apply_update(fetch_txs_with_outpoints(
+ graph_update.extend(fetch_txs_with_outpoints(
self,
+ &mut inserted_txs,
request.iter_outpoints(),
parallel_requests,
)?);
@@ -131,7 +141,7 @@ impl EsploraExt for esplora_client::BlockingClient {
self,
&latest_blocks,
&chain_tip,
- graph_update.all_anchors(),
+ &graph_update.anchors,
)?),
_ => None,
};
@@ -244,13 +254,14 @@ fn chain_update(
fn fetch_txs_with_keychain_spks>>(
client: &esplora_client::BlockingClient,
+ inserted_txs: &mut HashSet,
mut keychain_spks: I,
stop_gap: usize,
parallel_requests: usize,
-) -> Result<(TxGraph, Option), Error> {
+) -> Result<(tx_graph::Update, Option), Error> {
type TxsOfSpkIndex = (u32, Vec);
- let mut tx_graph = TxGraph::default();
+ let mut update = tx_graph::Update::::default();
let mut last_index = Option::::None;
let mut last_active_index = Option::::None;
@@ -289,9 +300,11 @@ fn fetch_txs_with_keychain_spks>>(
last_active_index = Some(index);
}
for tx in txs {
- let _ = tx_graph.insert_tx(tx.to_tx());
- insert_anchor_from_status(&mut tx_graph, tx.txid, tx.status);
- insert_prevouts(&mut tx_graph, tx.vin);
+ if inserted_txs.insert(tx.txid) {
+ update.txs.push(tx.to_tx().into());
+ }
+ insert_anchor_from_status(&mut update, tx.txid, tx.status);
+ insert_prevouts(&mut update, tx.vin);
}
}
@@ -306,7 +319,7 @@ fn fetch_txs_with_keychain_spks>>(
}
}
- Ok((tx_graph, last_active_index))
+ Ok((update, last_active_index))
}
/// Fetch transactions and associated [`ConfirmationBlockTime`]s by scanning `spks`
@@ -319,16 +332,18 @@ fn fetch_txs_with_keychain_spks>>(
/// Refer to [crate-level docs](crate) for more.
fn fetch_txs_with_spks>(
client: &esplora_client::BlockingClient,
+ inserted_txs: &mut HashSet,
spks: I,
parallel_requests: usize,
-) -> Result, Error> {
+) -> Result, Error> {
fetch_txs_with_keychain_spks(
client,
+ inserted_txs,
spks.into_iter().enumerate().map(|(i, spk)| (i as u32, spk)),
usize::MAX,
parallel_requests,
)
- .map(|(tx_graph, _)| tx_graph)
+ .map(|(update, _)| update)
}
/// Fetch transactions and associated [`ConfirmationBlockTime`]s by scanning `txids`
@@ -339,59 +354,48 @@ fn fetch_txs_with_spks>(
/// Refer to [crate-level docs](crate) for more.
fn fetch_txs_with_txids>(
client: &esplora_client::BlockingClient,
+ inserted_txs: &mut HashSet,
txids: I,
parallel_requests: usize,
-) -> Result, Error> {
- enum EsploraResp {
- TxStatus(TxStatus),
- Tx(Option),
- }
-
- let mut tx_graph = TxGraph::default();
- let mut txids = txids.into_iter();
+) -> Result, Error> {
+ let mut update = tx_graph::Update::::default();
+ // Only fetch for non-inserted txs.
+ let mut txids = txids
+ .into_iter()
+ .filter(|txid| !inserted_txs.contains(txid))
+ .collect::>()
+ .into_iter();
loop {
let handles = txids
.by_ref()
.take(parallel_requests)
.map(|txid| {
let client = client.clone();
- let tx_already_exists = tx_graph.get_tx(txid).is_some();
std::thread::spawn(move || {
- if tx_already_exists {
- client
- .get_tx_status(&txid)
- .map_err(Box::new)
- .map(|s| (txid, EsploraResp::TxStatus(s)))
- } else {
- client
- .get_tx_info(&txid)
- .map_err(Box::new)
- .map(|t| (txid, EsploraResp::Tx(t)))
- }
+ client
+ .get_tx_info(&txid)
+ .map_err(Box::new)
+ .map(|t| (txid, t))
})
})
- .collect::>>>();
+ .collect::), Error>>>>();
if handles.is_empty() {
break;
}
for handle in handles {
- let (txid, resp) = handle.join().expect("thread must not panic")?;
- match resp {
- EsploraResp::TxStatus(status) => {
- insert_anchor_from_status(&mut tx_graph, txid, status);
+ let (txid, tx_info) = handle.join().expect("thread must not panic")?;
+ if let Some(tx_info) = tx_info {
+ if inserted_txs.insert(txid) {
+ update.txs.push(tx_info.to_tx().into());
}
- EsploraResp::Tx(Some(tx_info)) => {
- let _ = tx_graph.insert_tx(tx_info.to_tx());
- insert_anchor_from_status(&mut tx_graph, txid, tx_info.status);
- insert_prevouts(&mut tx_graph, tx_info.vin);
- }
- _ => continue,
+ insert_anchor_from_status(&mut update, txid, tx_info.status);
+ insert_prevouts(&mut update, tx_info.vin);
}
}
}
- Ok(tx_graph)
+ Ok(update)
}
/// Fetch transactions and [`ConfirmationBlockTime`]s that contain and spend the provided
@@ -402,18 +406,21 @@ fn fetch_txs_with_txids>(
/// Refer to [crate-level docs](crate) for more.
fn fetch_txs_with_outpoints>(
client: &esplora_client::BlockingClient,
+ inserted_txs: &mut HashSet,
outpoints: I,
parallel_requests: usize,
-) -> Result, Error> {
+) -> Result, Error> {
let outpoints = outpoints.into_iter().collect::>();
+ let mut update = tx_graph::Update::::default();
// make sure txs exists in graph and tx statuses are updated
// TODO: We should maintain a tx cache (like we do with Electrum).
- let mut tx_graph = fetch_txs_with_txids(
+ update.extend(fetch_txs_with_txids(
client,
+ inserted_txs,
outpoints.iter().map(|op| op.txid),
parallel_requests,
- )?;
+ )?);
// get outpoint spend-statuses
let mut outpoints = outpoints.into_iter();
@@ -442,22 +449,23 @@ fn fetch_txs_with_outpoints>(
Some(txid) => txid,
None => continue,
};
- if tx_graph.get_tx(spend_txid).is_none() {
+ if !inserted_txs.contains(&spend_txid) {
missing_txs.push(spend_txid);
}
if let Some(spend_status) = op_status.status {
- insert_anchor_from_status(&mut tx_graph, spend_txid, spend_status);
+ insert_anchor_from_status(&mut update, spend_txid, spend_status);
}
}
}
}
- let _ = tx_graph.apply_update(fetch_txs_with_txids(
+ update.extend(fetch_txs_with_txids(
client,
+ inserted_txs,
missing_txs,
parallel_requests,
)?);
- Ok(tx_graph)
+ Ok(update)
}
#[cfg(test)]
diff --git a/crates/esplora/src/lib.rs b/crates/esplora/src/lib.rs
index 7db6967b6..9a6e8f1df 100644
--- a/crates/esplora/src/lib.rs
+++ b/crates/esplora/src/lib.rs
@@ -26,7 +26,7 @@
//! [`example_esplora`]: https://github.com/bitcoindevkit/bdk/tree/master/example-crates/example_esplora
use bdk_chain::bitcoin::{Amount, OutPoint, TxOut, Txid};
-use bdk_chain::{BlockId, ConfirmationBlockTime, TxGraph};
+use bdk_chain::{tx_graph, BlockId, ConfirmationBlockTime};
use esplora_client::TxStatus;
pub use esplora_client;
@@ -42,7 +42,7 @@ mod async_ext;
pub use async_ext::*;
fn insert_anchor_from_status(
- tx_graph: &mut TxGraph,
+ update: &mut tx_graph::Update,
txid: Txid,
status: TxStatus,
) {
@@ -57,21 +57,21 @@ fn insert_anchor_from_status(
block_id: BlockId { height, hash },
confirmation_time: time,
};
- let _ = tx_graph.insert_anchor(txid, anchor);
+ update.anchors.insert((anchor, txid));
}
}
/// Inserts floating txouts into `tx_graph` using [`Vin`](esplora_client::api::Vin)s returned by
/// Esplora.
fn insert_prevouts(
- tx_graph: &mut TxGraph,
+ update: &mut tx_graph::Update,
esplora_inputs: impl IntoIterator
- ,
) {
let prevouts = esplora_inputs
.into_iter()
.filter_map(|vin| Some((vin.txid, vin.vout, vin.prevout?)));
for (prev_txid, prev_vout, prev_txout) in prevouts {
- let _ = tx_graph.insert_txout(
+ update.txouts.insert(
OutPoint::new(prev_txid, prev_vout),
TxOut {
script_pubkey: prev_txout.scriptpubkey,
diff --git a/crates/esplora/tests/async_ext.rs b/crates/esplora/tests/async_ext.rs
index 70d464194..7b0ef7fa6 100644
--- a/crates/esplora/tests/async_ext.rs
+++ b/crates/esplora/tests/async_ext.rs
@@ -1,4 +1,5 @@
use bdk_chain::spk_client::{FullScanRequest, SyncRequest};
+use bdk_chain::{ConfirmationBlockTime, TxGraph};
use bdk_esplora::EsploraAsyncExt;
use esplora_client::{self, Builder};
use std::collections::{BTreeSet, HashSet};
@@ -6,7 +7,7 @@ use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;
-use bdk_chain::bitcoin::{Address, Amount, Txid};
+use bdk_chain::bitcoin::{Address, Amount};
use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, TestEnv};
#[tokio::test]
@@ -78,18 +79,23 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
);
let graph_update = sync_update.graph_update;
+ let updated_graph = {
+ let mut graph = TxGraph::::default();
+ let _ = graph.apply_update(graph_update.clone());
+ graph
+ };
// Check to see if we have the floating txouts available from our two created transactions'
// previous outputs in order to calculate transaction fees.
- for tx in graph_update.full_txs() {
+ for tx in &graph_update.txs {
// Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the
// floating txouts available from the transactions' previous outputs.
- let fee = graph_update.calculate_fee(&tx.tx).expect("Fee must exist");
+ let fee = updated_graph.calculate_fee(tx).expect("Fee must exist");
// Retrieve the fee in the transaction data from `bitcoind`.
let tx_fee = env
.bitcoind
.client
- .get_transaction(&tx.txid, None)
+ .get_transaction(&tx.compute_txid(), None)
.expect("Tx must exist")
.fee
.expect("Fee must exist")
@@ -101,11 +107,15 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
assert_eq!(fee, tx_fee);
}
- let mut graph_update_txids: Vec = graph_update.full_txs().map(|tx| tx.txid).collect();
- graph_update_txids.sort();
- let mut expected_txids = vec![txid1, txid2];
- expected_txids.sort();
- assert_eq!(graph_update_txids, expected_txids);
+ assert_eq!(
+ graph_update
+ .txs
+ .iter()
+ .map(|tx| tx.compute_txid())
+ .collect::>(),
+ [txid1, txid2].into(),
+ "update must include all expected transactions"
+ );
Ok(())
}
@@ -167,7 +177,7 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.spks_for_keychain(0, spks.clone());
client.full_scan(request, 3, 1).await?
};
- assert!(full_scan_update.graph_update.full_txs().next().is_none());
+ assert!(full_scan_update.graph_update.txs.is_empty());
assert!(full_scan_update.last_active_indices.is_empty());
let full_scan_update = {
let request = FullScanRequest::builder()
@@ -178,10 +188,10 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
assert_eq!(
full_scan_update
.graph_update
- .full_txs()
- .next()
+ .txs
+ .first()
.unwrap()
- .txid,
+ .compute_txid(),
txid_4th_addr
);
assert_eq!(full_scan_update.last_active_indices[&0], 3);
@@ -212,8 +222,9 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
};
let txs: HashSet<_> = full_scan_update
.graph_update
- .full_txs()
- .map(|tx| tx.txid)
+ .txs
+ .iter()
+ .map(|tx| tx.compute_txid())
.collect();
assert_eq!(txs.len(), 1);
assert!(txs.contains(&txid_4th_addr));
@@ -226,8 +237,9 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
};
let txs: HashSet<_> = full_scan_update
.graph_update
- .full_txs()
- .map(|tx| tx.txid)
+ .txs
+ .iter()
+ .map(|tx| tx.compute_txid())
.collect();
assert_eq!(txs.len(), 2);
assert!(txs.contains(&txid_4th_addr) && txs.contains(&txid_last_addr));
diff --git a/crates/esplora/tests/blocking_ext.rs b/crates/esplora/tests/blocking_ext.rs
index 818f1f5fb..b3833b899 100644
--- a/crates/esplora/tests/blocking_ext.rs
+++ b/crates/esplora/tests/blocking_ext.rs
@@ -1,4 +1,5 @@
use bdk_chain::spk_client::{FullScanRequest, SyncRequest};
+use bdk_chain::{ConfirmationBlockTime, TxGraph};
use bdk_esplora::EsploraExt;
use esplora_client::{self, Builder};
use std::collections::{BTreeSet, HashSet};
@@ -6,7 +7,7 @@ use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;
-use bdk_chain::bitcoin::{Address, Amount, Txid};
+use bdk_chain::bitcoin::{Address, Amount};
use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, TestEnv};
#[test]
@@ -78,18 +79,23 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
);
let graph_update = sync_update.graph_update;
+ let updated_graph = {
+ let mut graph = TxGraph::::default();
+ let _ = graph.apply_update(graph_update.clone());
+ graph
+ };
// Check to see if we have the floating txouts available from our two created transactions'
// previous outputs in order to calculate transaction fees.
- for tx in graph_update.full_txs() {
+ for tx in &graph_update.txs {
// Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the
// floating txouts available from the transactions' previous outputs.
- let fee = graph_update.calculate_fee(&tx.tx).expect("Fee must exist");
+ let fee = updated_graph.calculate_fee(tx).expect("Fee must exist");
// Retrieve the fee in the transaction data from `bitcoind`.
let tx_fee = env
.bitcoind
.client
- .get_transaction(&tx.txid, None)
+ .get_transaction(&tx.compute_txid(), None)
.expect("Tx must exist")
.fee
.expect("Fee must exist")
@@ -101,12 +107,15 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
assert_eq!(fee, tx_fee);
}
- let mut graph_update_txids: Vec = graph_update.full_txs().map(|tx| tx.txid).collect();
- graph_update_txids.sort();
- let mut expected_txids = vec![txid1, txid2];
- expected_txids.sort();
- assert_eq!(graph_update_txids, expected_txids);
-
+ assert_eq!(
+ graph_update
+ .txs
+ .iter()
+ .map(|tx| tx.compute_txid())
+ .collect::>(),
+ [txid1, txid2].into(),
+ "update must include all expected transactions"
+ );
Ok(())
}
@@ -168,7 +177,7 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.spks_for_keychain(0, spks.clone());
client.full_scan(request, 3, 1)?
};
- assert!(full_scan_update.graph_update.full_txs().next().is_none());
+ assert!(full_scan_update.graph_update.txs.is_empty());
assert!(full_scan_update.last_active_indices.is_empty());
let full_scan_update = {
let request = FullScanRequest::builder()
@@ -179,10 +188,10 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
assert_eq!(
full_scan_update
.graph_update
- .full_txs()
- .next()
+ .txs
+ .first()
.unwrap()
- .txid,
+ .compute_txid(),
txid_4th_addr
);
assert_eq!(full_scan_update.last_active_indices[&0], 3);
@@ -213,8 +222,9 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
};
let txs: HashSet<_> = full_scan_update
.graph_update
- .full_txs()
- .map(|tx| tx.txid)
+ .txs
+ .iter()
+ .map(|tx| tx.compute_txid())
.collect();
assert_eq!(txs.len(), 1);
assert!(txs.contains(&txid_4th_addr));
@@ -227,8 +237,9 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
};
let txs: HashSet<_> = full_scan_update
.graph_update
- .full_txs()
- .map(|tx| tx.txid)
+ .txs
+ .iter()
+ .map(|tx| tx.compute_txid())
.collect();
assert_eq!(txs.len(), 2);
assert!(txs.contains(&txid_4th_addr) && txs.contains(&txid_last_addr));
diff --git a/crates/wallet/src/wallet/export.rs b/crates/wallet/src/wallet/export.rs
index 6dce7503b..386d9d4e3 100644
--- a/crates/wallet/src/wallet/export.rs
+++ b/crates/wallet/src/wallet/export.rs
@@ -219,13 +219,13 @@ mod test {
use bdk_chain::{BlockId, ConfirmationBlockTime};
use bitcoin::hashes::Hash;
use bitcoin::{transaction, BlockHash, Network, Transaction};
+ use chain::tx_graph;
use super::*;
use crate::Wallet;
fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet {
use crate::wallet::Update;
- use bdk_chain::TxGraph;
let mut wallet = Wallet::create(descriptor.to_string(), change_descriptor.to_string())
.network(network)
.create_wallet_no_persist()
@@ -253,11 +253,12 @@ mod test {
confirmation_time: 0,
block_id,
};
- let mut graph = TxGraph::default();
- let _ = graph.insert_anchor(txid, anchor);
wallet
.apply_update(Update {
- graph,
+ graph: tx_graph::Update {
+ anchors: [(anchor, txid)].into_iter().collect(),
+ ..Default::default()
+ },
..Default::default()
})
.unwrap();
diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs
index 47e440c71..0d6cdf184 100644
--- a/crates/wallet/src/wallet/mod.rs
+++ b/crates/wallet/src/wallet/mod.rs
@@ -132,7 +132,7 @@ pub struct Update {
pub last_active_indices: BTreeMap,
/// Update for the wallet's internal [`TxGraph`].
- pub graph: TxGraph,
+ pub graph: chain::tx_graph::Update,
/// Update for the wallet's internal [`LocalChain`].
///
@@ -2562,7 +2562,7 @@ macro_rules! floating_rate {
macro_rules! doctest_wallet {
() => {{
use $crate::bitcoin::{BlockHash, Transaction, absolute, TxOut, Network, hashes::Hash};
- use $crate::chain::{ConfirmationBlockTime, BlockId, TxGraph};
+ use $crate::chain::{ConfirmationBlockTime, BlockId, TxGraph, tx_graph};
use $crate::{Update, KeychainKind, Wallet};
let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
@@ -2590,9 +2590,13 @@ macro_rules! doctest_wallet {
confirmation_time: 50_000,
block_id,
};
- let mut graph = TxGraph::default();
- let _ = graph.insert_anchor(txid, anchor);
- let update = Update { graph, ..Default::default() };
+ let update = Update {
+ graph: tx_graph::Update {
+ anchors: [(anchor, txid)].into_iter().collect(),
+ ..Default::default()
+ },
+ ..Default::default()
+ };
wallet.apply_update(update).unwrap();
wallet
}}
diff --git a/crates/wallet/tests/common.rs b/crates/wallet/tests/common.rs
index 288560b0a..561a9a5fb 100644
--- a/crates/wallet/tests/common.rs
+++ b/crates/wallet/tests/common.rs
@@ -1,5 +1,5 @@
#![allow(unused)]
-use bdk_chain::{BlockId, ConfirmationBlockTime, ConfirmationTime, TxGraph};
+use bdk_chain::{tx_graph, BlockId, ConfirmationBlockTime, ConfirmationTime, TxGraph};
use bdk_wallet::{CreateParams, KeychainKind, LocalOutput, Update, Wallet};
use bitcoin::{
hashes::Hash, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, Transaction,
@@ -218,11 +218,12 @@ pub fn insert_anchor_from_conf(wallet: &mut Wallet, txid: Txid, position: Confir
})
.expect("confirmation height cannot be greater than tip");
- let mut graph = TxGraph::default();
- let _ = graph.insert_anchor(txid, anchor);
wallet
.apply_update(Update {
- graph,
+ graph: tx_graph::Update {
+ anchors: [(anchor, txid)].into(),
+ ..Default::default()
+ },
..Default::default()
})
.unwrap();
diff --git a/crates/wallet/tests/wallet.rs b/crates/wallet/tests/wallet.rs
index c530e779c..243161658 100644
--- a/crates/wallet/tests/wallet.rs
+++ b/crates/wallet/tests/wallet.rs
@@ -5,7 +5,7 @@ use std::str::FromStr;
use anyhow::Context;
use assert_matches::assert_matches;
-use bdk_chain::COINBASE_MATURITY;
+use bdk_chain::{tx_graph, COINBASE_MATURITY};
use bdk_chain::{BlockId, ConfirmationTime};
use bdk_wallet::coin_selection::{self, LargestFirstCoinSelection};
use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor};
@@ -81,11 +81,12 @@ fn receive_output_in_latest_block(wallet: &mut Wallet, value: u64) -> OutPoint {
fn insert_seen_at(wallet: &mut Wallet, txid: Txid, seen_at: u64) {
use bdk_wallet::Update;
- let mut graph = bdk_chain::TxGraph::default();
- let _ = graph.insert_seen_at(txid, seen_at);
wallet
.apply_update(Update {
- graph,
+ graph: tx_graph::Update {
+ seen_ats: [(txid, seen_at)].into_iter().collect(),
+ ..Default::default()
+ },
..Default::default()
})
.unwrap();
diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs
index 49608fbf1..7212547d6 100644
--- a/example-crates/example_electrum/src/main.rs
+++ b/example-crates/example_electrum/src/main.rs
@@ -252,7 +252,7 @@ fn main() -> anyhow::Result<()> {
.elapsed()
.expect("must get time")
.as_secs();
- let _ = graph_update.update_last_seen_unconfirmed(now);
+ graph_update.update_last_seen_unconfirmed(now);
let db_changeset = {
let mut chain = chain.lock().unwrap();
diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs
index b07a6697d..d188eab76 100644
--- a/example-crates/example_esplora/src/main.rs
+++ b/example-crates/example_esplora/src/main.rs
@@ -172,7 +172,7 @@ fn main() -> anyhow::Result<()> {
// We want to keep track of the latest time a transaction was seen unconfirmed.
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
- let _ = update.graph_update.update_last_seen_unconfirmed(now);
+ update.graph_update.update_last_seen_unconfirmed(now);
let mut graph = graph.lock().expect("mutex must not be poisoned");
let mut chain = chain.lock().expect("mutex must not be poisoned");
@@ -269,7 +269,7 @@ fn main() -> anyhow::Result<()> {
// Update last seen unconfirmed
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
- let _ = update.graph_update.update_last_seen_unconfirmed(now);
+ update.graph_update.update_last_seen_unconfirmed(now);
(
chain
diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs
index f4596ce18..4cc698a00 100644
--- a/example-crates/wallet_electrum/src/main.rs
+++ b/example-crates/wallet_electrum/src/main.rs
@@ -67,7 +67,7 @@ fn main() -> Result<(), anyhow::Error> {
let mut update = client.full_scan(request, STOP_GAP, BATCH_SIZE, false)?;
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
- let _ = update.graph_update.update_last_seen_unconfirmed(now);
+ update.graph_update.update_last_seen_unconfirmed(now);
println!();
diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs
index f81f8101c..d4dae1f3f 100644
--- a/example-crates/wallet_esplora_async/src/main.rs
+++ b/example-crates/wallet_esplora_async/src/main.rs
@@ -61,7 +61,7 @@ async fn main() -> Result<(), anyhow::Error> {
.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)
.await?;
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
- let _ = update.graph_update.update_last_seen_unconfirmed(now);
+ update.graph_update.update_last_seen_unconfirmed(now);
wallet.apply_update(update)?;
wallet.persist(&mut conn)?;
diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs
index bec395611..9f79d6bf6 100644
--- a/example-crates/wallet_esplora_blocking/src/main.rs
+++ b/example-crates/wallet_esplora_blocking/src/main.rs
@@ -61,7 +61,7 @@ fn main() -> Result<(), anyhow::Error> {
let mut update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?;
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
- let _ = update.graph_update.update_last_seen_unconfirmed(now);
+ update.graph_update.update_last_seen_unconfirmed(now);
wallet.apply_update(update)?;
if let Some(changeset) = wallet.take_staged() {