Skip to content

Commit 1adcb62

Browse files
committed
feat(chain)!: TxGraph::apply_update auto-adds seen_at for unanchored
Change `apply_update` to use the current timestamp as `seen_at` for unanchored transactions of the update. This makes `apply_update` only avaliable with the "std" feature. Introduce `apply_update_at` which includes an optional `seen_at` input. This is the no-std version of `apply_update`. Also update docs.
1 parent 9d47ad1 commit 1adcb62

File tree

9 files changed

+100
-58
lines changed

9 files changed

+100
-58
lines changed

crates/chain/src/indexed_tx_graph.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,38 @@ where
9090

9191
/// Apply an `update` directly.
9292
///
93-
/// `update` is a [`TxGraph<A>`] and the resultant changes is returned as [`ChangeSet`].
93+
/// `update` is a [`tx_graph::Update<A>`] and the resultant changes is returned as [`ChangeSet`].
94+
#[cfg(feature = "std")]
95+
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
9496
pub fn apply_update(&mut self, update: tx_graph::Update<A>) -> ChangeSet<A, I::ChangeSet> {
9597
let tx_graph = self.graph.apply_update(update);
9698
let indexer = self.index_tx_graph_changeset(&tx_graph);
9799
ChangeSet { tx_graph, indexer }
98100
}
99101

102+
/// Apply the given `update` with an optional `seen_at` timestamp.
103+
///
104+
/// `seen_at` represents when the update is seen (in unix seconds). It is used to determine the
105+
/// `last_seen`s for all transactions in the update which have no corresponding anchor(s). The
106+
/// `last_seen` value is used internally to determine precedence of conflicting unconfirmed
107+
/// transactions (where the transaction with the lower `last_seen` value is omitted from the
108+
/// canonical history).
109+
///
110+
/// Not setting a `seen_at` value means unconfirmed transactions introduced by this update will
111+
/// not be part of the canonical history of transactions.
112+
///
113+
/// Use [`apply_update`](IndexedTxGraph::apply_update) to have the `seen_at` value automatically
114+
/// set to the current time.
115+
pub fn apply_update_at(
116+
&mut self,
117+
update: tx_graph::Update<A>,
118+
seen_at: Option<u64>,
119+
) -> ChangeSet<A, I::ChangeSet> {
120+
let tx_graph = self.graph.apply_update_at(update, seen_at);
121+
let indexer = self.index_tx_graph_changeset(&tx_graph);
122+
ChangeSet { tx_graph, indexer }
123+
}
124+
100125
/// Insert a floating `txout` of given `outpoint`.
101126
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A, I::ChangeSet> {
102127
let graph = self.graph.insert_txout(outpoint, txout);

crates/chain/src/tx_graph.rs

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -146,29 +146,12 @@ impl<A> From<TxGraph<A>> for Update<A> {
146146
impl<A: Ord + Clone> From<Update<A>> for TxGraph<A> {
147147
fn from(update: Update<A>) -> Self {
148148
let mut graph = TxGraph::<A>::default();
149-
let _ = graph.apply_update(update);
149+
let _ = graph.apply_update_at(update, None);
150150
graph
151151
}
152152
}
153153

154154
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-
172155
/// Extend this update with `other`.
173156
pub fn extend(&mut self, other: Update<A>) {
174157
self.txs.extend(other.txs);
@@ -762,25 +745,56 @@ impl<A: Clone + Ord> TxGraph<A> {
762745
changeset
763746
}
764747

765-
/// Extends this graph with another so that `self` becomes the union of the two sets of
766-
/// transactions.
748+
/// Extends this graph with the given `update`.
767749
///
768750
/// The returned [`ChangeSet`] is the set difference between `update` and `self` (transactions that
769751
/// exist in `update` but not in `self`).
752+
#[cfg(feature = "std")]
753+
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
770754
pub fn apply_update(&mut self, update: Update<A>) -> ChangeSet<A> {
755+
use std::time::*;
756+
let now = SystemTime::now()
757+
.duration_since(UNIX_EPOCH)
758+
.expect("current time must be greater than epoch anchor");
759+
self.apply_update_at(update, Some(now.as_secs()))
760+
}
761+
762+
/// Extends this graph with the given `update` alongside an optional `seen_at` timestamp.
763+
///
764+
/// `seen_at` represents when the update is seen (in unix seconds). It is used to determine the
765+
/// `last_seen`s for all transactions in the update which have no corresponding anchor(s). The
766+
/// `last_seen` value is used internally to determine precedence of conflicting unconfirmed
767+
/// transactions (where the transaction with the lower `last_seen` value is omitted from the
768+
/// canonical history).
769+
///
770+
/// Not setting a `seen_at` value means unconfirmed transactions introduced by this update will
771+
/// not be part of the canonical history of transactions.
772+
///
773+
/// Use [`apply_update`](TxGraph::apply_update) to have the `seen_at` value automatically set
774+
/// to the current time.
775+
pub fn apply_update_at(&mut self, update: Update<A>, seen_at: Option<u64>) -> ChangeSet<A> {
771776
let mut changeset = ChangeSet::<A>::default();
777+
let mut unanchored_txs = HashSet::<Txid>::new();
772778
for tx in update.txs {
773-
changeset.merge(self.insert_tx(tx));
779+
if unanchored_txs.insert(tx.compute_txid()) {
780+
changeset.merge(self.insert_tx(tx));
781+
}
774782
}
775783
for (outpoint, txout) in update.txouts {
776784
changeset.merge(self.insert_txout(outpoint, txout));
777785
}
778786
for (anchor, txid) in update.anchors {
787+
unanchored_txs.remove(&txid);
779788
changeset.merge(self.insert_anchor(txid, anchor));
780789
}
781790
for (txid, seen_at) in update.seen_ats {
782791
changeset.merge(self.insert_seen_at(txid, seen_at));
783792
}
793+
if let Some(seen_at) = seen_at {
794+
for txid in unanchored_txs {
795+
changeset.merge(self.insert_seen_at(txid, seen_at));
796+
}
797+
}
784798
changeset
785799
}
786800

crates/electrum/tests/test_electrum.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,12 @@ where
3838
Spks: IntoIterator<Item = ScriptBuf>,
3939
Spks::IntoIter: ExactSizeIterator + Send + 'static,
4040
{
41-
let mut update = client.sync(
41+
let update = client.sync(
4242
SyncRequest::builder().chain_tip(chain.tip()).spks(spks),
4343
BATCH_SIZE,
4444
true,
4545
)?;
4646

47-
// Update `last_seen` to be able to calculate balance for unconfirmed transactions.
48-
let now = std::time::UNIX_EPOCH
49-
.elapsed()
50-
.expect("must get time")
51-
.as_secs();
52-
update.graph_update.update_last_seen_unconfirmed(now);
53-
5447
if let Some(chain_update) = update.chain_update.clone() {
5548
let _ = chain
5649
.apply_update(chain_update)

crates/wallet/src/wallet/mod.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2277,7 +2277,34 @@ impl Wallet {
22772277
/// to persist staged wallet changes see [`Wallet::reveal_next_address`]. `
22782278
///
22792279
/// [`commit`]: Self::commit
2280+
#[cfg(feature = "std")]
2281+
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
22802282
pub fn apply_update(&mut self, update: impl Into<Update>) -> Result<(), CannotConnectError> {
2283+
use std::time::*;
2284+
let now = SystemTime::now()
2285+
.duration_since(UNIX_EPOCH)
2286+
.expect("time now must surpass epoch anchor");
2287+
self.apply_update_at(update, Some(now.as_secs()))
2288+
}
2289+
2290+
/// Applies an `update` alongside an optional `seen_at` timestamp and stages the changes.
2291+
///
2292+
/// `seen_at` represents when the update is seen (in unix seconds). It is used to determine the
2293+
/// `last_seen`s for all transactions in the update which have no corresponding anchor(s). The
2294+
/// `last_seen` value is used internally to determine precedence of conflicting unconfirmed
2295+
/// transactions (where the transaction with the lower `last_seen` value is omitted from the
2296+
/// canonical history).
2297+
///
2298+
/// Not setting a `seen_at` value means unconfirmed transactions introduced by this update will
2299+
/// not be part of the canonical history of transactions.
2300+
///
2301+
/// Use [`apply_update`](Wallet::apply_update) to have the `seen_at` value automatically set to
2302+
/// the current time.
2303+
pub fn apply_update_at(
2304+
&mut self,
2305+
update: impl Into<Update>,
2306+
seen_at: Option<u64>,
2307+
) -> Result<(), CannotConnectError> {
22812308
let update = update.into();
22822309
let mut changeset = match update.chain {
22832310
Some(chain_update) => ChangeSet::from(self.chain.apply_update(chain_update)?),
@@ -2289,7 +2316,11 @@ impl Wallet {
22892316
.index
22902317
.reveal_to_target_multi(&update.last_active_indices);
22912318
changeset.merge(index_changeset.into());
2292-
changeset.merge(self.indexed_graph.apply_update(update.graph).into());
2319+
changeset.merge(
2320+
self.indexed_graph
2321+
.apply_update_at(update.graph, seen_at)
2322+
.into(),
2323+
);
22932324
self.stage.merge(changeset);
22942325
Ok(())
22952326
}

example-crates/example_electrum/src/main.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ fn main() -> anyhow::Result<()> {
129129
// Tell the electrum client about the txs we've already got locally so it doesn't re-download them
130130
client.populate_tx_cache(&*graph.lock().unwrap());
131131

132-
let (chain_update, mut graph_update, keychain_update) = match electrum_cmd.clone() {
132+
let (chain_update, graph_update, keychain_update) = match electrum_cmd.clone() {
133133
ElectrumCommands::Scan {
134134
stop_gap,
135135
scan_options,
@@ -248,12 +248,6 @@ fn main() -> anyhow::Result<()> {
248248
}
249249
};
250250

251-
let now = std::time::UNIX_EPOCH
252-
.elapsed()
253-
.expect("must get time")
254-
.as_secs();
255-
graph_update.update_last_seen_unconfirmed(now);
256-
257251
let db_changeset = {
258252
let mut chain = chain.lock().unwrap();
259253
let mut graph = graph.lock().unwrap();

example-crates/example_esplora/src/main.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,10 @@ fn main() -> anyhow::Result<()> {
166166
// is reached. It returns a `TxGraph` update (`graph_update`) and a structure that
167167
// represents the last active spk derivation indices of keychains
168168
// (`keychain_indices_update`).
169-
let mut update = client
169+
let update = client
170170
.full_scan(request, *stop_gap, scan_options.parallel_requests)
171171
.context("scanning for transactions")?;
172172

173-
// We want to keep track of the latest time a transaction was seen unconfirmed.
174-
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
175-
update.graph_update.update_last_seen_unconfirmed(now);
176-
177173
let mut graph = graph.lock().expect("mutex must not be poisoned");
178174
let mut chain = chain.lock().expect("mutex must not be poisoned");
179175
// Because we did a stop gap based scan we are likely to have some updates to our
@@ -265,11 +261,7 @@ fn main() -> anyhow::Result<()> {
265261
}
266262
}
267263

268-
let mut update = client.sync(request, scan_options.parallel_requests)?;
269-
270-
// Update last seen unconfirmed
271-
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
272-
update.graph_update.update_last_seen_unconfirmed(now);
264+
let update = client.sync(request, scan_options.parallel_requests)?;
273265

274266
(
275267
chain

example-crates/wallet_electrum/src/main.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,7 @@ fn main() -> Result<(), anyhow::Error> {
6464
}
6565
});
6666

67-
let mut update = client.full_scan(request, STOP_GAP, BATCH_SIZE, false)?;
68-
69-
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
70-
update.graph_update.update_last_seen_unconfirmed(now);
67+
let update = client.full_scan(request, STOP_GAP, BATCH_SIZE, false)?;
7168

7269
println!();
7370

example-crates/wallet_esplora_async/src/main.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,9 @@ async fn main() -> Result<(), anyhow::Error> {
5757
}
5858
});
5959

60-
let mut update = client
60+
let update = client
6161
.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)
6262
.await?;
63-
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
64-
update.graph_update.update_last_seen_unconfirmed(now);
6563

6664
wallet.apply_update(update)?;
6765
wallet.persist(&mut conn)?;

example-crates/wallet_esplora_blocking/src/main.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@ fn main() -> Result<(), anyhow::Error> {
5959
}
6060
});
6161

62-
let mut update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?;
63-
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
64-
update.graph_update.update_last_seen_unconfirmed(now);
62+
let update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?;
6563

6664
wallet.apply_update(update)?;
6765
if let Some(changeset) = wallet.take_staged() {

0 commit comments

Comments
 (0)