/// Apply an `update` directly.
///
/// `update` is a [`TxGraph<A>`] and the resultant changes is returned as [`ChangeSet`].
- pub fn apply_update(&mut self, update: TxGraph<A>) -> ChangeSet<A, I::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<A>) -> ChangeSet<A, I::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`.
alloc::{boxed::Box, collections::VecDeque, vec::Vec},
collections::BTreeMap,
local_chain::CheckPoint,
- ConfirmationBlockTime, Indexed, TxGraph,
+ ConfirmationBlockTime, Indexed,
};
use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
#[must_use]
#[derive(Debug)]
pub struct SyncResult<A = ConfirmationBlockTime> {
- /// The update to apply to the receiving [`TxGraph`].
- pub graph_update: TxGraph<A>,
+ /// The update to apply to the receiving [`TxGraph`](crate::tx_graph::TxGraph).
+ pub graph_update: crate::tx_graph::Update<A>,
/// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain).
pub chain_update: Option<CheckPoint>,
}
#[derive(Debug)]
pub struct FullScanResult<K, A = ConfirmationBlockTime> {
/// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain).
- pub graph_update: TxGraph<A>,
- /// The update to apply to the receiving [`TxGraph`].
+ pub graph_update: crate::tx_graph::Update<A>,
+ /// The update to apply to the receiving [`TxGraph`](crate::tx_graph::TxGraph).
pub chain_update: Option<CheckPoint>,
/// Last active indices for the corresponding keychains (`K`).
pub last_active_indices: BTreeMap<K, u32>,
//!
//! ```
//! # 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());
ops::{Deref, RangeInclusive},
};
+/// Data object used to update the [`TxGraph`] with.
+#[derive(Debug, Clone)]
+pub struct Update<A = ()> {
+ /// Full transactions.
+ pub txs: Vec<Arc<Transaction>>,
+ /// Floating txouts.
+ pub txouts: BTreeMap<OutPoint, TxOut>,
+ /// Transaction anchors.
+ pub anchors: BTreeSet<(A, Txid)>,
+ /// Seen at times for transactions.
+ pub seen_ats: HashMap<Txid, u64>,
+}
+
+impl<A> Default for Update<A> {
+ fn default() -> Self {
+ Self {
+ txs: Default::default(),
+ txouts: Default::default(),
+ anchors: Default::default(),
+ seen_ats: Default::default(),
+ }
+ }
+}
+
+impl<A> From<TxGraph<A>> for Update<A> {
+ fn from(graph: TxGraph<A>) -> 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<A: Ord + Clone> From<Update<A>> for TxGraph<A> {
+ fn from(update: Update<A>) -> Self {
+ let mut graph = TxGraph::<A>::default();
+ let _ = graph.apply_update(update);
+ graph
+ }
+}
+
+impl<A: Ord> Update<A> {
+ /// 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<A>) {
+ 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.
///
/// 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<A>) -> ChangeSet<A> {
+ pub fn apply_update(&mut self, update: Update<A>) -> ChangeSet<A> {
let mut changeset = ChangeSet::<A>::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
}
#[macro_use]
mod common;
-use bdk_chain::tx_graph::CalculateFeeError;
+use bdk_chain::tx_graph::{self, CalculateFeeError};
use bdk_chain::{
collections::*,
local_chain::LocalChain,
)];
// 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 {
};
// 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::<BlockId>::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::<ChainPosition<BlockId>>::default();
+ let mut graph = TxGraph::<BlockId>::default();
for (outpoint, txout) in &original_ops {
assert_eq!(
graph.insert_txout(*outpoint, txout.clone()),
// 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.
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()
}
);
assert_eq!(
graph
- .tx_outputs(update_txs.compute_txid())
+ .tx_outputs(update_tx.compute_txid())
.expect("should exists"),
[(
0u32,
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()
}
);
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},
};
None => None,
};
- let mut graph_update = TxGraph::<ConfirmationBlockTime>::default();
+ let mut graph_update = tx_graph::Update::<ConfirmationBlockTime>::default();
let mut last_active_indices = BTreeMap::<K, u32>::default();
for keychain in request.keychains() {
let spks = request.iter_spks(keychain.clone());
Some((chain_tip, latest_blocks)) => Some(chain_update(
chain_tip,
&latest_blocks,
- graph_update.all_anchors(),
+ graph_update.anchors.iter().cloned(),
)?),
_ => None,
};
None => None,
};
- let mut graph_update = TxGraph::<ConfirmationBlockTime>::default();
+ let mut graph_update = tx_graph::Update::<ConfirmationBlockTime>::default();
self.populate_with_spks(
&mut graph_update,
request
Some((chain_tip, latest_blocks)) => Some(chain_update(
chain_tip,
&latest_blocks,
- graph_update.all_anchors(),
+ graph_update.anchors.iter().cloned(),
)?),
None => None,
};
/// also included.
fn populate_with_spks(
&self,
- graph_update: &mut TxGraph<ConfirmationBlockTime>,
+ graph_update: &mut tx_graph::Update<ConfirmationBlockTime>,
mut spks: impl Iterator<Item = (u32, ScriptBuf)>,
stop_gap: usize,
batch_size: usize,
}
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)?;
}
}
/// included. Anchors of the aforementioned transactions are included.
fn populate_with_outpoints(
&self,
- graph_update: &mut TxGraph<ConfirmationBlockTime>,
+ graph_update: &mut tx_graph::Update<ConfirmationBlockTime>,
outpoints: impl IntoIterator<Item = OutPoint>,
) -> Result<(), Error> {
for outpoint in outpoints {
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)?;
}
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)?;
}
}
/// Populate the `graph_update` with transactions/anchors of the provided `txids`.
fn populate_with_txids(
&self,
- graph_update: &mut TxGraph<ConfirmationBlockTime>,
+ graph_update: &mut tx_graph::Update<ConfirmationBlockTime>,
txids: impl IntoIterator<Item = Txid>,
) -> Result<(), Error> {
for txid in txids {
self.validate_merkle_for_anchor(graph_update, txid, r.height)?;
}
- let _ = graph_update.insert_tx(tx);
+ graph_update.txs.push(tx);
}
Ok(())
}
// 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<ConfirmationBlockTime>,
+ graph_update: &mut tx_graph::Update<ConfirmationBlockTime>,
txid: Txid,
confirmation_height: i32,
) -> Result<(), Error> {
}
if is_confirmed_tx {
- let _ = graph_update.insert_anchor(
- txid,
+ graph_update.anchors.insert((
ConfirmationBlockTime {
confirmation_time: header.time as u64,
block_id: BlockId {
hash: header.block_hash(),
},
},
- );
+ txid,
+ ));
}
}
Ok(())
// 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<ConfirmationBlockTime>,
+ graph_update: &mut tx_graph::Update<ConfirmationBlockTime>,
) -> Result<(), Error> {
- let full_txs: Vec<Arc<Transaction>> =
- 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::<Txid>::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(())
fn chain_update<A: Anchor>(
mut tip: CheckPoint,
latest_blocks: &BTreeMap<u32, BlockHash>,
- anchors: &BTreeSet<(A, Txid)>,
+ anchors: impl Iterator<Item = (A, Txid)>,
) -> Result<CheckPoint, Error> {
for anchor in anchors {
let height = anchor.0.anchor_block().height;
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};
.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
);
let graph_update = sync_update.graph_update;
+ let updated_graph = {
+ let mut graph = TxGraph::<ConfirmationBlockTime>::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")
assert_eq!(fee, tx_fee);
}
- let mut graph_update_txids: Vec<Txid> = 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::<BTreeSet<_>>(),
+ [txid1, txid2].into(),
+ "update must include all expected transactions",
+ );
Ok(())
}
.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()
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);
};
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));
};
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));
)?;
// 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.
)?;
// 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)?,
-use std::collections::BTreeSet;
+use std::collections::{BTreeSet, HashSet};
use async_trait::async_trait;
use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult};
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};
None
};
- let mut graph_update = TxGraph::default();
+ let mut graph_update = tx_graph::Update::<ConfirmationBlockTime>::default();
+ let mut inserted_txs = HashSet::<Txid>::new();
let mut last_active_indices = BTreeMap::<K, u32>::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,
};
None
};
- let mut graph_update = TxGraph::<ConfirmationBlockTime>::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::<ConfirmationBlockTime>::default();
+ let mut inserted_txs = HashSet::<Txid>::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,
};
/// Refer to [crate-level docs](crate) for more.
async fn fetch_txs_with_keychain_spks<I: Iterator<Item = Indexed<ScriptBuf>> + Send>(
client: &esplora_client::AsyncClient,
+ inserted_txs: &mut HashSet<Txid>,
mut keychain_spks: I,
stop_gap: usize,
parallel_requests: usize,
-) -> Result<(TxGraph<ConfirmationBlockTime>, Option<u32>), Error> {
+) -> Result<(tx_graph::Update<ConfirmationBlockTime>, Option<u32>), Error> {
type TxsOfSpkIndex = (u32, Vec<esplora_client::Tx>);
- let mut tx_graph = TxGraph::default();
+ let mut update = tx_graph::Update::<ConfirmationBlockTime>::default();
let mut last_index = Option::<u32>::None;
let mut last_active_index = Option::<u32>::None;
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);
}
}
}
}
- Ok((tx_graph, last_active_index))
+ Ok((update, last_active_index))
}
/// Fetch transactions and associated [`ConfirmationBlockTime`]s by scanning `spks`
/// Refer to [crate-level docs](crate) for more.
async fn fetch_txs_with_spks<I: IntoIterator<Item = ScriptBuf> + Send>(
client: &esplora_client::AsyncClient,
+ inserted_txs: &mut HashSet<Txid>,
spks: I,
parallel_requests: usize,
-) -> Result<TxGraph<ConfirmationBlockTime>, Error>
+) -> Result<tx_graph::Update<ConfirmationBlockTime>, 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`
/// Refer to [crate-level docs](crate) for more.
async fn fetch_txs_with_txids<I: IntoIterator<Item = Txid> + Send>(
client: &esplora_client::AsyncClient,
+ inserted_txs: &mut HashSet<Txid>,
txids: I,
parallel_requests: usize,
-) -> Result<TxGraph<ConfirmationBlockTime>, Error>
+) -> Result<tx_graph::Update<ConfirmationBlockTime>, Error>
where
I::IntoIter: Send,
{
- enum EsploraResp {
- TxStatus(TxStatus),
- Tx(Option<Tx>),
- }
-
- let mut tx_graph = TxGraph::default();
- let mut txids = txids.into_iter();
+ let mut update = tx_graph::Update::<ConfirmationBlockTime>::default();
+ // Only fetch for non-inserted txs.
+ let mut txids = txids
+ .into_iter()
+ .filter(|txid| !inserted_txs.contains(txid))
+ .collect::<Vec<Txid>>()
+ .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::<FuturesOrdered<_>>();
break;
}
- for (txid, resp) in handles.try_collect::<Vec<_>>().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::<Vec<_>>().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
/// Refer to [crate-level docs](crate) for more.
async fn fetch_txs_with_outpoints<I: IntoIterator<Item = OutPoint> + Send>(
client: &esplora_client::AsyncClient,
+ inserted_txs: &mut HashSet<Txid>,
outpoints: I,
parallel_requests: usize,
-) -> Result<TxGraph<ConfirmationBlockTime>, Error>
+) -> Result<tx_graph::Update<ConfirmationBlockTime>, Error>
where
I::IntoIter: Send,
{
let outpoints = outpoints.into_iter().collect::<Vec<_>>();
+ let mut update = tx_graph::Update::<ConfirmationBlockTime>::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();
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)]
-use std::collections::BTreeSet;
+use std::collections::{BTreeSet, HashSet};
use std::thread::JoinHandle;
use bdk_chain::collections::BTreeMap;
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};
None
};
- let mut graph_update = TxGraph::default();
+ let mut graph_update = tx_graph::Update::default();
+ let mut inserted_txs = HashSet::<Txid>::new();
let mut last_active_indices = BTreeMap::<K, u32>::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);
}
self,
&latest_blocks,
&chain_tip,
- graph_update.all_anchors(),
+ &graph_update.anchors,
)?),
_ => None,
};
None
};
- let mut graph_update = TxGraph::default();
- let _ = graph_update.apply_update(fetch_txs_with_spks(
+ let mut graph_update = tx_graph::Update::<ConfirmationBlockTime>::default();
+ let mut inserted_txs = HashSet::<Txid>::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,
)?);
self,
&latest_blocks,
&chain_tip,
- graph_update.all_anchors(),
+ &graph_update.anchors,
)?),
_ => None,
};
fn fetch_txs_with_keychain_spks<I: Iterator<Item = Indexed<ScriptBuf>>>(
client: &esplora_client::BlockingClient,
+ inserted_txs: &mut HashSet<Txid>,
mut keychain_spks: I,
stop_gap: usize,
parallel_requests: usize,
-) -> Result<(TxGraph<ConfirmationBlockTime>, Option<u32>), Error> {
+) -> Result<(tx_graph::Update<ConfirmationBlockTime>, Option<u32>), Error> {
type TxsOfSpkIndex = (u32, Vec<esplora_client::Tx>);
- let mut tx_graph = TxGraph::default();
+ let mut update = tx_graph::Update::<ConfirmationBlockTime>::default();
let mut last_index = Option::<u32>::None;
let mut last_active_index = Option::<u32>::None;
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);
}
}
}
}
- Ok((tx_graph, last_active_index))
+ Ok((update, last_active_index))
}
/// Fetch transactions and associated [`ConfirmationBlockTime`]s by scanning `spks`
/// Refer to [crate-level docs](crate) for more.
fn fetch_txs_with_spks<I: IntoIterator<Item = ScriptBuf>>(
client: &esplora_client::BlockingClient,
+ inserted_txs: &mut HashSet<Txid>,
spks: I,
parallel_requests: usize,
-) -> Result<TxGraph<ConfirmationBlockTime>, Error> {
+) -> Result<tx_graph::Update<ConfirmationBlockTime>, 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`
/// Refer to [crate-level docs](crate) for more.
fn fetch_txs_with_txids<I: IntoIterator<Item = Txid>>(
client: &esplora_client::BlockingClient,
+ inserted_txs: &mut HashSet<Txid>,
txids: I,
parallel_requests: usize,
-) -> Result<TxGraph<ConfirmationBlockTime>, Error> {
- enum EsploraResp {
- TxStatus(TxStatus),
- Tx(Option<Tx>),
- }
-
- let mut tx_graph = TxGraph::default();
- let mut txids = txids.into_iter();
+) -> Result<tx_graph::Update<ConfirmationBlockTime>, Error> {
+ let mut update = tx_graph::Update::<ConfirmationBlockTime>::default();
+ // Only fetch for non-inserted txs.
+ let mut txids = txids
+ .into_iter()
+ .filter(|txid| !inserted_txs.contains(txid))
+ .collect::<Vec<Txid>>()
+ .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::<Vec<JoinHandle<Result<(Txid, EsploraResp), Error>>>>();
+ .collect::<Vec<JoinHandle<Result<(Txid, Option<Tx>), 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
/// Refer to [crate-level docs](crate) for more.
fn fetch_txs_with_outpoints<I: IntoIterator<Item = OutPoint>>(
client: &esplora_client::BlockingClient,
+ inserted_txs: &mut HashSet<Txid>,
outpoints: I,
parallel_requests: usize,
-) -> Result<TxGraph<ConfirmationBlockTime>, Error> {
+) -> Result<tx_graph::Update<ConfirmationBlockTime>, Error> {
let outpoints = outpoints.into_iter().collect::<Vec<_>>();
+ let mut update = tx_graph::Update::<ConfirmationBlockTime>::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();
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)]
//! [`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;
pub use async_ext::*;
fn insert_anchor_from_status(
- tx_graph: &mut TxGraph<ConfirmationBlockTime>,
+ update: &mut tx_graph::Update<ConfirmationBlockTime>,
txid: Txid,
status: TxStatus,
) {
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<ConfirmationBlockTime>,
+ update: &mut tx_graph::Update<ConfirmationBlockTime>,
esplora_inputs: impl IntoIterator<Item = esplora_client::api::Vin>,
) {
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,
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};
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]
);
let graph_update = sync_update.graph_update;
+ let updated_graph = {
+ let mut graph = TxGraph::<ConfirmationBlockTime>::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")
assert_eq!(fee, tx_fee);
}
- let mut graph_update_txids: Vec<Txid> = 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::<BTreeSet<_>>(),
+ [txid1, txid2].into(),
+ "update must include all expected transactions"
+ );
Ok(())
}
.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()
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);
};
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));
};
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));
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};
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]
);
let graph_update = sync_update.graph_update;
+ let updated_graph = {
+ let mut graph = TxGraph::<ConfirmationBlockTime>::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")
assert_eq!(fee, tx_fee);
}
- let mut graph_update_txids: Vec<Txid> = 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::<BTreeSet<_>>(),
+ [txid1, txid2].into(),
+ "update must include all expected transactions"
+ );
Ok(())
}
.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()
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);
};
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));
};
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));
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()
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();
pub last_active_indices: BTreeMap<KeychainKind, u32>,
/// Update for the wallet's internal [`TxGraph`].
- pub graph: TxGraph<ConfirmationBlockTime>,
+ pub graph: chain::tx_graph::Update<ConfirmationBlockTime>,
/// Update for the wallet's internal [`LocalChain`].
///
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/*)";
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
}}
#![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,
})
.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();
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};
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();
.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();
// 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");
// 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
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!();
.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)?;
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() {