/// [module-level documentation]: crate::tx_graph
#[derive(Clone, Debug, PartialEq)]
pub struct TxGraph<A = ()> {
- // all transactions that the graph is aware of in format: `(tx_node, tx_anchors, tx_last_seen)`
- txs: HashMap<Txid, (TxNodeInternal, BTreeSet<A>, Option<u64>)>,
+ // all transactions that the graph is aware of in format: `(tx_node, tx_anchors)`
+ txs: HashMap<Txid, (TxNodeInternal, BTreeSet<A>)>,
spends: BTreeMap<OutPoint, HashSet<Txid>>,
anchors: BTreeSet<(A, Txid)>,
+ last_seen: HashMap<Txid, u64>,
// This atrocity exists so that `TxGraph::outspends()` can return a reference.
// FIXME: This can be removed once `HashSet::new` is a const fn.
txs: Default::default(),
spends: Default::default(),
anchors: Default::default(),
+ last_seen: Default::default(),
empty_outspends: Default::default(),
}
}
///
/// This includes txouts of both full transactions as well as floating transactions.
pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
- self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
+ self.txs.iter().flat_map(|(txid, (tx, _))| match tx {
TxNodeInternal::Whole(tx) => tx
.as_ref()
.output
pub fn floating_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
self.txs
.iter()
- .filter_map(|(txid, (tx_node, _, _))| match tx_node {
+ .filter_map(|(txid, (tx_node, _))| match tx_node {
TxNodeInternal::Whole(_) => None,
TxNodeInternal::Partial(txouts) => Some(
txouts
pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Arc<Transaction>, A>> {
self.txs
.iter()
- .filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
+ .filter_map(|(&txid, (tx, anchors))| match tx {
TxNodeInternal::Whole(tx) => Some(TxNode {
txid,
tx: tx.clone(),
anchors,
- last_seen_unconfirmed: *last_seen,
+ last_seen_unconfirmed: self.last_seen.get(&txid).copied(),
}),
TxNodeInternal::Partial(_) => None,
})
/// Get a transaction node by txid. This only returns `Some` for full transactions.
pub fn get_tx_node(&self, txid: Txid) -> Option<TxNode<'_, Arc<Transaction>, A>> {
match &self.txs.get(&txid)? {
- (TxNodeInternal::Whole(tx), anchors, last_seen) => Some(TxNode {
+ (TxNodeInternal::Whole(tx), anchors) => Some(TxNode {
txid,
tx: tx.clone(),
anchors,
- last_seen_unconfirmed: *last_seen,
+ last_seen_unconfirmed: self.last_seen.get(&txid).copied(),
}),
_ => None,
}
(
TxNodeInternal::Partial([(outpoint.vout, txout)].into()),
BTreeSet::new(),
- None,
),
);
self.apply_update(update)
let mut update = Self::default();
update.txs.insert(
tx.compute_txid(),
- (TxNodeInternal::Whole(tx), BTreeSet::new(), None),
+ (TxNodeInternal::Whole(tx), BTreeSet::new()),
);
self.apply_update(update)
}
/// [`update_last_seen_unconfirmed`]: Self::update_last_seen_unconfirmed
pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
let mut update = Self::default();
- let (_, _, update_last_seen) = update.txs.entry(txid).or_default();
- *update_last_seen = Some(seen_at);
+ update.last_seen.insert(txid, seen_at);
self.apply_update(update)
}
.txs
.iter()
.filter_map(
- |(&txid, (_, anchors, _))| {
+ |(&txid, (_, anchors))| {
if anchors.is_empty() {
Some(txid)
} else {
});
match self.txs.get_mut(&txid) {
- Some((tx_node @ TxNodeInternal::Partial(_), _, _)) => {
+ Some((tx_node @ TxNodeInternal::Partial(_), _)) => {
*tx_node = TxNodeInternal::Whole(wrapped_tx.clone());
}
- Some((TxNodeInternal::Whole(tx), _, _)) => {
+ Some((TxNodeInternal::Whole(tx), _)) => {
debug_assert_eq!(
tx.as_ref().compute_txid(),
txid,
);
}
None => {
- self.txs.insert(
- txid,
- (TxNodeInternal::Whole(wrapped_tx), BTreeSet::new(), None),
- );
+ self.txs
+ .insert(txid, (TxNodeInternal::Whole(wrapped_tx), BTreeSet::new()));
}
}
}
let tx_entry = self.txs.entry(outpoint.txid).or_default();
match tx_entry {
- (TxNodeInternal::Whole(_), _, _) => { /* do nothing since we already have full tx */
- }
- (TxNodeInternal::Partial(txouts), _, _) => {
+ (TxNodeInternal::Whole(_), _) => { /* do nothing since we already have full tx */ }
+ (TxNodeInternal::Partial(txouts), _) => {
txouts.insert(outpoint.vout, txout);
}
}
for (anchor, txid) in changeset.anchors {
if self.anchors.insert((anchor.clone(), txid)) {
- let (_, anchors, _) = self.txs.entry(txid).or_default();
+ let (_, anchors) = self.txs.entry(txid).or_default();
anchors.insert(anchor);
}
}
for (txid, new_last_seen) in changeset.last_seen {
- let (_, _, last_seen) = self.txs.entry(txid).or_default();
- if Some(new_last_seen) > *last_seen {
- *last_seen = Some(new_last_seen);
+ let last_seen = self.last_seen.entry(txid).or_default();
+ if new_last_seen > *last_seen {
+ *last_seen = new_last_seen;
}
}
}
pub(crate) fn determine_changeset(&self, update: TxGraph<A>) -> ChangeSet<A> {
let mut changeset = ChangeSet::<A>::default();
- for (&txid, (update_tx_node, _, update_last_seen)) in &update.txs {
- let prev_last_seen = match (self.txs.get(&txid), update_tx_node) {
+ for (&txid, (update_tx_node, _)) in &update.txs {
+ match (self.txs.get(&txid), update_tx_node) {
(None, TxNodeInternal::Whole(update_tx)) => {
changeset.txs.insert(update_tx.clone());
- None
}
(None, TxNodeInternal::Partial(update_txos)) => {
changeset.txouts.extend(
.iter()
.map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
);
- None
}
- (Some((TxNodeInternal::Whole(_), _, last_seen)), _) => *last_seen,
- (
- Some((TxNodeInternal::Partial(_), _, last_seen)),
- TxNodeInternal::Whole(update_tx),
- ) => {
+ (Some((TxNodeInternal::Whole(_), _)), _) => {}
+ (Some((TxNodeInternal::Partial(_), _)), TxNodeInternal::Whole(update_tx)) => {
changeset.txs.insert(update_tx.clone());
- *last_seen
}
(
- Some((TxNodeInternal::Partial(txos), _, last_seen)),
+ Some((TxNodeInternal::Partial(txos), _)),
TxNodeInternal::Partial(update_txos),
) => {
changeset.txouts.extend(
.filter(|(vout, _)| !txos.contains_key(*vout))
.map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
);
- *last_seen
}
- };
+ }
+ }
- if *update_last_seen > prev_last_seen {
- changeset.last_seen.insert(
- txid,
- update_last_seen.expect("checked is greater, so we must have a last_seen"),
- );
+ for (txid, update_last_seen) in update.last_seen {
+ let prev_last_seen = self.last_seen.get(&txid).copied();
+ if Some(update_last_seen) > prev_last_seen {
+ changeset.last_seen.insert(txid, update_last_seen);
}
}
chain_tip: BlockId,
txid: Txid,
) -> Result<Option<ChainPosition<&A>>, C::Error> {
- let (tx_node, anchors, last_seen) = match self.txs.get(&txid) {
+ let (tx_node, anchors) = match self.txs.get(&txid) {
Some(v) => v,
None => return Ok(None),
};
// If no anchors are in best chain and we don't have a last_seen, we can return
// early because by definition the tx doesn't have a chain position.
- let last_seen = match last_seen {
+ let last_seen = match self.last_seen.get(&txid) {
Some(t) => *t,
None => return Ok(None),
};