/// Apply an `update` directly.
///
- /// `update` is a [`TxGraph<A>`] and the resultant changes is returned as [`ChangeSet`].
+ /// `update` is a [`tx_graph::Update<A>`] and the resultant changes is returned as [`ChangeSet`].
+ #[cfg(feature = "std")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
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 }
}
+ /// Apply the given `update` with an optional `seen_at` timestamp.
+ ///
+ /// `seen_at` represents when the update is seen (in unix seconds). It is used to determine the
+ /// `last_seen`s for all transactions in the update which have no corresponding anchor(s). The
+ /// `last_seen` value is used internally to determine precedence of conflicting unconfirmed
+ /// transactions (where the transaction with the lower `last_seen` value is omitted from the
+ /// canonical history).
+ ///
+ /// Not setting a `seen_at` value means unconfirmed transactions introduced by this update will
+ /// not be part of the canonical history of transactions.
+ ///
+ /// Use [`apply_update`](IndexedTxGraph::apply_update) to have the `seen_at` value automatically
+ /// set to the current time.
+ pub fn apply_update_at(
+ &mut self,
+ update: tx_graph::Update<A>,
+ seen_at: Option<u64>,
+ ) -> ChangeSet<A, I::ChangeSet> {
+ let tx_graph = self.graph.apply_update_at(update, seen_at);
+ let indexer = self.index_tx_graph_changeset(&tx_graph);
+ ChangeSet { tx_graph, indexer }
+ }
+
/// Insert a floating `txout` of given `outpoint`.
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A, I::ChangeSet> {
let graph = self.graph.insert_txout(outpoint, txout);
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);
+ let _ = graph.apply_update_at(update, None);
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);
changeset
}
- /// Extends this graph with another so that `self` becomes the union of the two sets of
- /// transactions.
+ /// Extends this graph with the given `update`.
///
/// The returned [`ChangeSet`] is the set difference between `update` and `self` (transactions that
/// exist in `update` but not in `self`).
+ #[cfg(feature = "std")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn apply_update(&mut self, update: Update<A>) -> ChangeSet<A> {
+ use std::time::*;
+ let now = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("current time must be greater than epoch anchor");
+ self.apply_update_at(update, Some(now.as_secs()))
+ }
+
+ /// Extends this graph with the given `update` alongside an optional `seen_at` timestamp.
+ ///
+ /// `seen_at` represents when the update is seen (in unix seconds). It is used to determine the
+ /// `last_seen`s for all transactions in the update which have no corresponding anchor(s). The
+ /// `last_seen` value is used internally to determine precedence of conflicting unconfirmed
+ /// transactions (where the transaction with the lower `last_seen` value is omitted from the
+ /// canonical history).
+ ///
+ /// Not setting a `seen_at` value means unconfirmed transactions introduced by this update will
+ /// not be part of the canonical history of transactions.
+ ///
+ /// Use [`apply_update`](TxGraph::apply_update) to have the `seen_at` value automatically set
+ /// to the current time.
+ pub fn apply_update_at(&mut self, update: Update<A>, seen_at: Option<u64>) -> ChangeSet<A> {
let mut changeset = ChangeSet::<A>::default();
+ let mut unanchored_txs = HashSet::<Txid>::new();
for tx in update.txs {
- changeset.merge(self.insert_tx(tx));
+ if unanchored_txs.insert(tx.compute_txid()) {
+ changeset.merge(self.insert_tx(tx));
+ }
}
for (outpoint, txout) in update.txouts {
changeset.merge(self.insert_txout(outpoint, txout));
}
for (anchor, txid) in update.anchors {
+ unanchored_txs.remove(&txid);
changeset.merge(self.insert_anchor(txid, anchor));
}
for (txid, seen_at) in update.seen_ats {
changeset.merge(self.insert_seen_at(txid, seen_at));
}
+ if let Some(seen_at) = seen_at {
+ for txid in unanchored_txs {
+ changeset.merge(self.insert_seen_at(txid, seen_at));
+ }
+ }
changeset
}
Spks: IntoIterator<Item = ScriptBuf>,
Spks::IntoIter: ExactSizeIterator + Send + 'static,
{
- let mut update = client.sync(
+ let update = client.sync(
SyncRequest::builder().chain_tip(chain.tip()).spks(spks),
BATCH_SIZE,
true,
)?;
- // Update `last_seen` to be able to calculate balance for unconfirmed transactions.
- let now = std::time::UNIX_EPOCH
- .elapsed()
- .expect("must get time")
- .as_secs();
- update.graph_update.update_last_seen_unconfirmed(now);
-
if let Some(chain_update) = update.chain_update.clone() {
let _ = chain
.apply_update(chain_update)
/// to persist staged wallet changes see [`Wallet::reveal_next_address`]. `
///
/// [`commit`]: Self::commit
+ #[cfg(feature = "std")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn apply_update(&mut self, update: impl Into<Update>) -> Result<(), CannotConnectError> {
+ use std::time::*;
+ let now = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("time now must surpass epoch anchor");
+ self.apply_update_at(update, Some(now.as_secs()))
+ }
+
+ /// Applies an `update` alongside an optional `seen_at` timestamp and stages the changes.
+ ///
+ /// `seen_at` represents when the update is seen (in unix seconds). It is used to determine the
+ /// `last_seen`s for all transactions in the update which have no corresponding anchor(s). The
+ /// `last_seen` value is used internally to determine precedence of conflicting unconfirmed
+ /// transactions (where the transaction with the lower `last_seen` value is omitted from the
+ /// canonical history).
+ ///
+ /// Not setting a `seen_at` value means unconfirmed transactions introduced by this update will
+ /// not be part of the canonical history of transactions.
+ ///
+ /// Use [`apply_update`](Wallet::apply_update) to have the `seen_at` value automatically set to
+ /// the current time.
+ pub fn apply_update_at(
+ &mut self,
+ update: impl Into<Update>,
+ seen_at: Option<u64>,
+ ) -> Result<(), CannotConnectError> {
let update = update.into();
let mut changeset = match update.chain {
Some(chain_update) => ChangeSet::from(self.chain.apply_update(chain_update)?),
.index
.reveal_to_target_multi(&update.last_active_indices);
changeset.merge(index_changeset.into());
- changeset.merge(self.indexed_graph.apply_update(update.graph).into());
+ changeset.merge(
+ self.indexed_graph
+ .apply_update_at(update.graph, seen_at)
+ .into(),
+ );
self.stage.merge(changeset);
Ok(())
}
// Tell the electrum client about the txs we've already got locally so it doesn't re-download them
client.populate_tx_cache(&*graph.lock().unwrap());
- let (chain_update, mut graph_update, keychain_update) = match electrum_cmd.clone() {
+ let (chain_update, graph_update, keychain_update) = match electrum_cmd.clone() {
ElectrumCommands::Scan {
stop_gap,
scan_options,
}
};
- let now = std::time::UNIX_EPOCH
- .elapsed()
- .expect("must get time")
- .as_secs();
- graph_update.update_last_seen_unconfirmed(now);
-
let db_changeset = {
let mut chain = chain.lock().unwrap();
let mut graph = graph.lock().unwrap();
// is reached. It returns a `TxGraph` update (`graph_update`) and a structure that
// represents the last active spk derivation indices of keychains
// (`keychain_indices_update`).
- let mut update = client
+ let update = client
.full_scan(request, *stop_gap, scan_options.parallel_requests)
.context("scanning for transactions")?;
- // We want to keep track of the latest time a transaction was seen unconfirmed.
- let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
- 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");
// Because we did a stop gap based scan we are likely to have some updates to our
}
}
- let mut update = client.sync(request, scan_options.parallel_requests)?;
-
- // Update last seen unconfirmed
- let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
- update.graph_update.update_last_seen_unconfirmed(now);
+ let update = client.sync(request, scan_options.parallel_requests)?;
(
chain
}
});
- let mut update = client.full_scan(request, STOP_GAP, BATCH_SIZE, false)?;
-
- let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
- update.graph_update.update_last_seen_unconfirmed(now);
+ let update = client.full_scan(request, STOP_GAP, BATCH_SIZE, false)?;
println!();
}
});
- let mut update = client
+ let update = client
.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)
.await?;
- let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
- 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();
- update.graph_update.update_last_seen_unconfirmed(now);
+ let update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?;
wallet.apply_update(update)?;
if let Some(changeset) = wallet.take_staged() {