Skip to content

Commit

Permalink
WIP: rm TxGraph::anchors
Browse files Browse the repository at this point in the history
  • Loading branch information
evanlinjin committed Jul 18, 2024
1 parent afe5511 commit 63d9e6a
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 52 deletions.
22 changes: 12 additions & 10 deletions crates/chain/src/chain_data.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid};

use crate::{Anchor, BlockTime, COINBASE_MATURITY};
use crate::{BlockTime, COINBASE_MATURITY};

/// Represents the observed position of some chain data.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
pub enum ChainPosition<AM> {
/// The chain data is seen as confirmed, and in anchored by `Anchor`.
Confirmed((Anchor, AM)),
Confirmed(BlockId, AM),
/// The chain data is not confirmed and last seen in the mempool at this timestamp.
Unconfirmed(u64),
}

impl<AM> ChainPosition<AM> {
/// Returns whether [`ChainPosition`] is confirmed or not.
pub fn is_confirmed(&self) -> bool {
matches!(self, Self::Confirmed(_))
matches!(self, Self::Confirmed(_, _))
}
}

impl<AM: Clone> ChainPosition<AM> {
/// Maps a [`ChainPosition`] into a [`ChainPosition`] by cloning the contents.
pub fn cloned(self) -> ChainPosition<AM> {
match self {
ChainPosition::Confirmed(a) => ChainPosition::Confirmed(a),
ChainPosition::Confirmed(bid, anchor_meta) => {
ChainPosition::Confirmed(bid, anchor_meta.clone())
}
ChainPosition::Unconfirmed(last_seen) => ChainPosition::Unconfirmed(last_seen),
}
}
Expand Down Expand Up @@ -65,8 +67,8 @@ impl ConfirmationTime {
impl From<ChainPosition<BlockTime>> for ConfirmationTime {
fn from(observed_as: ChainPosition<BlockTime>) -> Self {
match observed_as {
ChainPosition::Confirmed(((_txid, blockid), anchor_meta)) => Self::Confirmed {
height: blockid.height,
ChainPosition::Confirmed(bid, anchor_meta) => Self::Confirmed {
height: bid.height,
time: *anchor_meta.as_ref() as u64,
},
ChainPosition::Unconfirmed(last_seen) => Self::Unconfirmed { last_seen },
Expand Down Expand Up @@ -147,7 +149,7 @@ impl<AM> FullTxOut<AM> {
pub fn is_mature(&self, tip: u32) -> bool {
if self.is_on_coinbase {
let tx_height = match &self.chain_position {
ChainPosition::Confirmed(((_, blockid), _)) => blockid.height,
ChainPosition::Confirmed(bid, _) => bid.height,
ChainPosition::Unconfirmed(_) => {
debug_assert!(false, "coinbase tx can never be unconfirmed");
return false;
Expand Down Expand Up @@ -177,16 +179,16 @@ impl<AM> FullTxOut<AM> {
}

let confirmation_height = match &self.chain_position {
ChainPosition::Confirmed(((_, blockid), _)) => blockid.height,
ChainPosition::Confirmed(bid, _) => bid.height,
ChainPosition::Unconfirmed(_) => return false,
};
if confirmation_height > tip {
return false;
}

// if the spending tx is confirmed within tip height, the txout is no longer spendable
if let Some((ChainPosition::Confirmed(((_, spending_blockid), _)), _)) = &self.spent_by {
if spending_blockid.height <= tip {
if let Some((ChainPosition::Confirmed(spending_bid, _), _)) = &self.spent_by {
if spending_bid.height <= tip {
return false;
}
}
Expand Down
82 changes: 43 additions & 39 deletions crates/chain/src/tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,8 @@ use core::{
#[derive(Clone, Debug, PartialEq)]
pub struct TxGraph<AM = BlockTime> {
// all transactions that the graph is aware of in format: `(tx_node, tx_anchors)`
txs: HashMap<Txid, (TxNodeInternal, BTreeMap<Anchor, AM>)>,
txs: HashMap<Txid, (TxNodeInternal, BTreeMap<BlockId, AM>)>,
spends: BTreeMap<OutPoint, HashSet<Txid>>,
anchors: BTreeMap<Anchor, AM>,
last_seen: HashMap<Txid, u64>,

// This atrocity exists so that `TxGraph::outspends()` can return a reference.
Expand All @@ -125,7 +124,6 @@ impl<AM> Default for TxGraph<AM> {
Self {
txs: Default::default(),
spends: Default::default(),
anchors: Default::default(),
last_seen: Default::default(),
empty_outspends: Default::default(),
}
Expand All @@ -140,7 +138,7 @@ pub struct TxNode<'a, T, AM> {
/// A partial or full representation of the transaction.
pub tx: T,
/// The blocks that the transaction is "anchored" in.
pub anchors: &'a BTreeMap<Anchor, AM>,
pub anchors: &'a BTreeMap<BlockId, AM>,
/// The last-seen unix timestamp of the transaction as unconfirmed.
pub last_seen_unconfirmed: Option<u64>,
}
Expand Down Expand Up @@ -470,8 +468,12 @@ impl<AM> TxGraph<AM> {
}

/// Get all transaction anchors known by [`TxGraph`].
pub fn all_anchors(&self) -> &BTreeMap<Anchor, AM> {
&self.anchors
pub fn all_anchors(&self) -> impl Iterator<Item = (Anchor, &AM)> {
self.txs.iter().flat_map(|(&txid, (_, blocks))| {
blocks
.iter()
.map(move |(&bid, anchor_data)| ((txid, bid), anchor_data))
})
}

/// Whether the graph has any transactions or outputs in it.
Expand Down Expand Up @@ -547,7 +549,11 @@ impl<AM: Clone + Ord> TxGraph<AM> {
/// `anchor`.
pub fn insert_anchor(&mut self, anchor: Anchor, anchor_meta: AM) -> ChangeSet<AM> {
let mut update = Self::default();
update.anchors.insert(anchor, anchor_meta);
let (txid, bid) = anchor;
update.txs.insert(
txid,
(TxNodeInternal::default(), [(bid, anchor_meta)].into()),
);
self.apply_update(update)
}

Expand Down Expand Up @@ -684,15 +690,12 @@ impl<AM: Clone + Ord> TxGraph<AM> {
}
}

for ((txid, blockid), anchor_meta) in changeset.anchors {
if self
.anchors
.insert((txid, blockid), anchor_meta.clone())
.is_none()
{
let (_, anchors) = self.txs.entry(txid).or_default();
anchors.insert((txid, blockid), anchor_meta);
}
for ((txid, bid), anchor_data) in changeset.anchors {
let (_, blocks) = self
.txs
.entry(txid)
.or_insert((TxNodeInternal::default(), BTreeMap::new()));
blocks.insert(bid, anchor_data);
}

for (txid, new_last_seen) in changeset.last_seen {
Expand Down Expand Up @@ -740,22 +743,28 @@ impl<AM: Clone + Ord> TxGraph<AM> {
}
}

changeset.anchors = update
.all_anchors()
.filter(|&(anchor, anchor_data)| self.anchor_data(anchor) != Some(anchor_data))
.map(|(anchor, anchor_data)| (anchor, anchor_data.clone()))
.collect();

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);
}
}

changeset.anchors = update
.anchors
.iter()
.filter(|(k, _)| !self.anchors.contains_key(k))
.map(|(k, v)| (*k, v.clone()))
.collect::<BTreeMap<_, _>>();

changeset
}

/// Get data for a given `anchor`.
pub fn anchor_data(&self, anchor: Anchor) -> Option<&AM> {
let (txid, bid) = anchor;
let (_, blocks) = self.txs.get(&txid)?;
blocks.get(&bid)
}
}

impl<AM: Ord + Clone> TxGraph<AM> {
Expand Down Expand Up @@ -788,19 +797,14 @@ impl<AM: Ord + Clone> TxGraph<AM> {
chain_tip: BlockId,
txid: Txid,
) -> Result<Option<ChainPosition<AM>>, C::Error> {
let (tx_node, anchors) = match self.txs.get(&txid) {
let (tx_node, blocks) = match self.txs.get(&txid) {
Some(v) => v,
None => return Ok(None),
};

for (anchor, anchor_meta) in anchors {
match chain.is_block_in_chain(anchor.1, chain_tip)? {
Some(true) => {
return Ok(Some(ChainPosition::Confirmed((
*anchor,
anchor_meta.clone(),
))))
}
for (&bid, anchor_meta) in blocks {
match chain.is_block_in_chain(bid, chain_tip)? {
Some(true) => return Ok(Some(ChainPosition::Confirmed(bid, anchor_meta.clone()))),
_ => continue,
}
}
Expand Down Expand Up @@ -843,8 +847,8 @@ impl<AM: Ord + Clone> TxGraph<AM> {
let tx_node = self.get_tx_node(ancestor_tx.as_ref().compute_txid())?;
// We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
// the best chain)
for (_, block) in tx_node.anchors.keys() {
match chain.is_block_in_chain(*block, chain_tip) {
for bid in tx_node.anchors.keys() {
match chain.is_block_in_chain(*bid, chain_tip) {
Ok(Some(true)) => return None,
Err(e) => return Some(Err(e)),
_ => continue,
Expand All @@ -863,8 +867,8 @@ impl<AM: Ord + Clone> TxGraph<AM> {
let tx_node = self.get_tx_node(descendant_txid)?;
// We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
// the best chain)
for (_, block) in tx_node.anchors.keys() {
match chain.is_block_in_chain(*block, chain_tip) {
for bid in tx_node.anchors.keys() {
match chain.is_block_in_chain(*bid, chain_tip) {
Ok(Some(true)) => return None,
Err(e) => return Some(Err(e)),
_ => continue,
Expand All @@ -890,8 +894,8 @@ impl<AM: Ord + Clone> TxGraph<AM> {
// If a conflicting tx is in the best chain, or has `last_seen` higher than this ancestor, then
// this tx cannot exist in the best chain
for conflicting_tx in conflicting_txs {
for (_, block) in conflicting_tx.anchors.keys() {
if chain.is_block_in_chain(*block, chain_tip)? == Some(true) {
for bid in conflicting_tx.anchors.keys() {
if chain.is_block_in_chain(*bid, chain_tip)? == Some(true) {
return Ok(None);
}
}
Expand Down Expand Up @@ -1174,7 +1178,7 @@ impl<AM: Ord + Clone> TxGraph<AM> {
let (spk_i, txout) = res?;

match &txout.chain_position {
ChainPosition::Confirmed(_) => {
ChainPosition::Confirmed(_, _) => {
if txout.is_confirmed_and_spendable(chain_tip.height) {
confirmed += txout.txout.value;
} else if !txout.is_mature(chain_tip.height) {
Expand Down
2 changes: 1 addition & 1 deletion crates/wallet/src/wallet/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ impl FullyNodedExport {
let blockheight = if include_blockheight {
wallet.transactions().next().map_or(0, |canonical_tx| {
match canonical_tx.chain_position {
bdk_chain::ChainPosition::Confirmed(((_, blockid), _)) => blockid.height,
bdk_chain::ChainPosition::Confirmed(bid, _) => bid.height,
bdk_chain::ChainPosition::Unconfirmed(_) => 0,
}
})
Expand Down
4 changes: 2 additions & 2 deletions crates/wallet/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1547,7 +1547,7 @@ impl Wallet {
let pos = graph
.get_chain_position(&self.chain, chain_tip, txid)
.ok_or(BuildFeeBumpError::TransactionNotFound(txid))?;
if let ChainPosition::Confirmed(_) = pos {
if let ChainPosition::Confirmed(_, _) = pos {
return Err(BuildFeeBumpError::TransactionConfirmed(txid));
}

Expand Down Expand Up @@ -1799,7 +1799,7 @@ impl Wallet {
.graph()
.get_chain_position(&self.chain, chain_tip, input.previous_output.txid)
.map(|chain_position| match chain_position {
ChainPosition::Confirmed(((_, blockid), _)) => blockid.height,
ChainPosition::Confirmed(bid, _) => bid.height,
ChainPosition::Unconfirmed(_) => u32::MAX,
});
let current_height = sign_options
Expand Down

0 comments on commit 63d9e6a

Please sign in to comment.