Skip to content

Commit 5150801

Browse files
feat!: introduce tx_graph::Update
Instead of updating a `TxGraph` with a `TxGraph`, we introduce a dedicated data object (`tx_graph::Update`). This brings us closer to completing #1543. Co-authored-by: Wei Chen <[email protected]>
1 parent 71a3e0e commit 5150801

File tree

20 files changed

+439
-342
lines changed

20 files changed

+439
-342
lines changed

crates/chain/src/indexed_tx_graph.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,10 @@ where
9191
/// Apply an `update` directly.
9292
///
9393
/// `update` is a [`TxGraph<A>`] and the resultant changes is returned as [`ChangeSet`].
94-
pub fn apply_update(&mut self, update: TxGraph<A>) -> ChangeSet<A, I::ChangeSet> {
95-
let graph = self.graph.apply_update(update);
96-
let indexer = self.index_tx_graph_changeset(&graph);
97-
ChangeSet {
98-
tx_graph: graph,
99-
indexer,
100-
}
94+
pub fn apply_update(&mut self, update: tx_graph::Update<A>) -> ChangeSet<A, I::ChangeSet> {
95+
let tx_graph = self.graph.apply_update(update);
96+
let indexer = self.index_tx_graph_changeset(&tx_graph);
97+
ChangeSet { tx_graph, indexer }
10198
}
10299

103100
/// Insert a floating `txout` of given `outpoint`.

crates/chain/src/spk_client.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
alloc::{boxed::Box, collections::VecDeque, vec::Vec},
44
collections::BTreeMap,
55
local_chain::CheckPoint,
6-
ConfirmationBlockTime, Indexed, TxGraph,
6+
ConfirmationBlockTime, Indexed,
77
};
88
use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
99

@@ -345,8 +345,8 @@ impl<I> SyncRequest<I> {
345345
#[must_use]
346346
#[derive(Debug)]
347347
pub struct SyncResult<A = ConfirmationBlockTime> {
348-
/// The update to apply to the receiving [`TxGraph`].
349-
pub graph_update: TxGraph<A>,
348+
/// The update to apply to the receiving [`TxGraph`](crate::tx_graph::TxGraph).
349+
pub graph_update: crate::tx_graph::Update<A>,
350350
/// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain).
351351
pub chain_update: Option<CheckPoint>,
352352
}
@@ -497,8 +497,8 @@ impl<K: Ord + Clone> FullScanRequest<K> {
497497
#[derive(Debug)]
498498
pub struct FullScanResult<K, A = ConfirmationBlockTime> {
499499
/// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain).
500-
pub graph_update: TxGraph<A>,
501-
/// The update to apply to the receiving [`TxGraph`].
500+
pub graph_update: crate::tx_graph::Update<A>,
501+
/// The update to apply to the receiving [`TxGraph`](crate::tx_graph::TxGraph).
502502
pub chain_update: Option<CheckPoint>,
503503
/// Last active indices for the corresponding keychains (`K`).
504504
pub last_active_indices: BTreeMap<K, u32>,

crates/chain/src/tx_graph.rs

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,17 @@
7070
//!
7171
//! ```
7272
//! # use bdk_chain::{Merge, BlockId};
73-
//! # use bdk_chain::tx_graph::TxGraph;
73+
//! # use bdk_chain::tx_graph::{self, TxGraph};
7474
//! # use bdk_chain::example_utils::*;
7575
//! # use bitcoin::Transaction;
76+
//! # use std::sync::Arc;
7677
//! # let tx_a = tx_from_hex(RAW_TX_1);
7778
//! # let tx_b = tx_from_hex(RAW_TX_2);
7879
//! let mut graph: TxGraph = TxGraph::default();
79-
//! let update = TxGraph::new(vec![tx_a, tx_b]);
80+
//!
81+
//! let mut update = tx_graph::Update::default();
82+
//! update.txs.push(Arc::new(tx_a));
83+
//! update.txs.push(Arc::new(tx_b));
8084
//!
8185
//! // apply the update graph
8286
//! let changeset = graph.apply_update(update.clone());
@@ -101,6 +105,79 @@ use core::{
101105
ops::{Deref, RangeInclusive},
102106
};
103107

108+
/// Data object used to update the [`TxGraph`] with.
109+
#[derive(Debug, Clone)]
110+
pub struct Update<A = ()> {
111+
/// Full transactions.
112+
pub txs: Vec<Arc<Transaction>>,
113+
/// Floating txouts.
114+
pub txouts: BTreeMap<OutPoint, TxOut>,
115+
/// Transaction anchors.
116+
pub anchors: BTreeSet<(A, Txid)>,
117+
/// Seen at times for transactions.
118+
pub seen_ats: HashMap<Txid, u64>,
119+
}
120+
121+
impl<A> Default for Update<A> {
122+
fn default() -> Self {
123+
Self {
124+
txs: Default::default(),
125+
txouts: Default::default(),
126+
anchors: Default::default(),
127+
seen_ats: Default::default(),
128+
}
129+
}
130+
}
131+
132+
impl<A> From<TxGraph<A>> for Update<A> {
133+
fn from(graph: TxGraph<A>) -> Self {
134+
Self {
135+
txs: graph.full_txs().map(|tx_node| tx_node.tx).collect(),
136+
txouts: graph
137+
.floating_txouts()
138+
.map(|(op, txo)| (op, txo.clone()))
139+
.collect(),
140+
anchors: graph.anchors,
141+
seen_ats: graph.last_seen.into_iter().collect(),
142+
}
143+
}
144+
}
145+
146+
impl<A: Ord + Clone> From<Update<A>> for TxGraph<A> {
147+
fn from(update: Update<A>) -> Self {
148+
let mut graph = TxGraph::<A>::default();
149+
let _ = graph.apply_update(update);
150+
graph
151+
}
152+
}
153+
154+
impl<A: Ord> Update<A> {
155+
/// Update the [`seen_ats`](Self::seen_ats) for all unanchored transactions.
156+
pub fn update_last_seen_unconfirmed(&mut self, seen_at: u64) {
157+
let seen_ats = &mut self.seen_ats;
158+
let anchors = &self.anchors;
159+
let unanchored_txids = self.txs.iter().map(|tx| tx.compute_txid()).filter(|txid| {
160+
for (_, anchor_txid) in anchors {
161+
if txid == anchor_txid {
162+
return false;
163+
}
164+
}
165+
true
166+
});
167+
for txid in unanchored_txids {
168+
seen_ats.insert(txid, seen_at);
169+
}
170+
}
171+
172+
/// Extend this update with `other`.
173+
pub fn extend(&mut self, other: Update<A>) {
174+
self.txs.extend(other.txs);
175+
self.txouts.extend(other.txouts);
176+
self.anchors.extend(other.anchors);
177+
self.seen_ats.extend(other.seen_ats);
178+
}
179+
}
180+
104181
/// A graph of transactions and spends.
105182
///
106183
/// See the [module-level documentation] for more.
@@ -690,19 +767,19 @@ impl<A: Clone + Ord> TxGraph<A> {
690767
///
691768
/// The returned [`ChangeSet`] is the set difference between `update` and `self` (transactions that
692769
/// exist in `update` but not in `self`).
693-
pub fn apply_update(&mut self, update: TxGraph<A>) -> ChangeSet<A> {
770+
pub fn apply_update(&mut self, update: Update<A>) -> ChangeSet<A> {
694771
let mut changeset = ChangeSet::<A>::default();
695-
for tx_node in update.full_txs() {
696-
changeset.merge(self.insert_tx(tx_node.tx));
772+
for tx in update.txs {
773+
changeset.merge(self.insert_tx(tx));
697774
}
698-
for (outpoint, txout) in update.floating_txouts() {
699-
changeset.merge(self.insert_txout(outpoint, txout.clone()));
775+
for (outpoint, txout) in update.txouts {
776+
changeset.merge(self.insert_txout(outpoint, txout));
700777
}
701-
for (anchor, txid) in &update.anchors {
702-
changeset.merge(self.insert_anchor(*txid, anchor.clone()));
778+
for (anchor, txid) in update.anchors {
779+
changeset.merge(self.insert_anchor(txid, anchor));
703780
}
704-
for (&txid, &last_seen) in &update.last_seen {
705-
changeset.merge(self.insert_seen_at(txid, last_seen));
781+
for (txid, seen_at) in update.seen_ats {
782+
changeset.merge(self.insert_seen_at(txid, seen_at));
706783
}
707784
changeset
708785
}

crates/chain/tests/test_tx_graph.rs

Lines changed: 22 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#[macro_use]
44
mod common;
5-
use bdk_chain::tx_graph::CalculateFeeError;
5+
use bdk_chain::tx_graph::{self, CalculateFeeError};
66
use bdk_chain::{
77
collections::*,
88
local_chain::LocalChain,
@@ -49,7 +49,7 @@ fn insert_txouts() {
4949
)];
5050

5151
// One full transaction to be included in the update
52-
let update_txs = Transaction {
52+
let update_tx = Transaction {
5353
version: transaction::Version::ONE,
5454
lock_time: absolute::LockTime::ZERO,
5555
input: vec![TxIn {
@@ -63,17 +63,17 @@ fn insert_txouts() {
6363
};
6464

6565
// Conf anchor used to mark the full transaction as confirmed.
66-
let conf_anchor = ChainPosition::Confirmed(BlockId {
66+
let conf_anchor = BlockId {
6767
height: 100,
6868
hash: h!("random blockhash"),
69-
});
69+
};
7070

71-
// Unconfirmed anchor to mark the partial transactions as unconfirmed
72-
let unconf_anchor = ChainPosition::<BlockId>::Unconfirmed(1000000);
71+
// Unconfirmed seen_at timestamp to mark the partial transactions as unconfirmed.
72+
let unconf_seen_at = 1000000_u64;
7373

7474
// Make the original graph
7575
let mut graph = {
76-
let mut graph = TxGraph::<ChainPosition<BlockId>>::default();
76+
let mut graph = TxGraph::<BlockId>::default();
7777
for (outpoint, txout) in &original_ops {
7878
assert_eq!(
7979
graph.insert_txout(*outpoint, txout.clone()),
@@ -88,57 +88,21 @@ fn insert_txouts() {
8888

8989
// Make the update graph
9090
let update = {
91-
let mut graph = TxGraph::default();
91+
let mut update = tx_graph::Update::default();
9292
for (outpoint, txout) in &update_ops {
93-
// Insert partials transactions
94-
assert_eq!(
95-
graph.insert_txout(*outpoint, txout.clone()),
96-
ChangeSet {
97-
txouts: [(*outpoint, txout.clone())].into(),
98-
..Default::default()
99-
}
100-
);
93+
// Insert partials transactions.
94+
update.txouts.insert(*outpoint, txout.clone());
10195
// Mark them unconfirmed.
102-
assert_eq!(
103-
graph.insert_anchor(outpoint.txid, unconf_anchor),
104-
ChangeSet {
105-
txs: [].into(),
106-
txouts: [].into(),
107-
anchors: [(unconf_anchor, outpoint.txid)].into(),
108-
last_seen: [].into()
109-
}
110-
);
111-
// Mark them last seen at.
112-
assert_eq!(
113-
graph.insert_seen_at(outpoint.txid, 1000000),
114-
ChangeSet {
115-
txs: [].into(),
116-
txouts: [].into(),
117-
anchors: [].into(),
118-
last_seen: [(outpoint.txid, 1000000)].into()
119-
}
120-
);
96+
update.seen_ats.insert(outpoint.txid, unconf_seen_at);
12197
}
122-
// Insert the full transaction
123-
assert_eq!(
124-
graph.insert_tx(update_txs.clone()),
125-
ChangeSet {
126-
txs: [Arc::new(update_txs.clone())].into(),
127-
..Default::default()
128-
}
129-
);
13098

99+
// Insert the full transaction.
100+
update.txs.push(update_tx.clone().into());
131101
// Mark it as confirmed.
132-
assert_eq!(
133-
graph.insert_anchor(update_txs.compute_txid(), conf_anchor),
134-
ChangeSet {
135-
txs: [].into(),
136-
txouts: [].into(),
137-
anchors: [(conf_anchor, update_txs.compute_txid())].into(),
138-
last_seen: [].into()
139-
}
140-
);
141-
graph
102+
update
103+
.anchors
104+
.insert((conf_anchor, update_tx.compute_txid()));
105+
update
142106
};
143107

144108
// Check the resulting addition.
@@ -147,13 +111,9 @@ fn insert_txouts() {
147111
assert_eq!(
148112
changeset,
149113
ChangeSet {
150-
txs: [Arc::new(update_txs.clone())].into(),
114+
txs: [Arc::new(update_tx.clone())].into(),
151115
txouts: update_ops.clone().into(),
152-
anchors: [
153-
(conf_anchor, update_txs.compute_txid()),
154-
(unconf_anchor, h!("tx2"))
155-
]
156-
.into(),
116+
anchors: [(conf_anchor, update_tx.compute_txid()),].into(),
157117
last_seen: [(h!("tx2"), 1000000)].into()
158118
}
159119
);
@@ -188,7 +148,7 @@ fn insert_txouts() {
188148

189149
assert_eq!(
190150
graph
191-
.tx_outputs(update_txs.compute_txid())
151+
.tx_outputs(update_tx.compute_txid())
192152
.expect("should exists"),
193153
[(
194154
0u32,
@@ -204,13 +164,9 @@ fn insert_txouts() {
204164
assert_eq!(
205165
graph.initial_changeset(),
206166
ChangeSet {
207-
txs: [Arc::new(update_txs.clone())].into(),
167+
txs: [Arc::new(update_tx.clone())].into(),
208168
txouts: update_ops.into_iter().chain(original_ops).collect(),
209-
anchors: [
210-
(conf_anchor, update_txs.compute_txid()),
211-
(unconf_anchor, h!("tx2"))
212-
]
213-
.into(),
169+
anchors: [(conf_anchor, update_tx.compute_txid()),].into(),
214170
last_seen: [(h!("tx2"), 1000000)].into()
215171
}
216172
);

0 commit comments

Comments
 (0)