Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion crates/chain/src/chain_graph.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Module for structures that combine the features of [`sparse_chain`] and [`tx_graph`].
use crate::{
collections::HashSet,
collections::{BTreeSet, HashSet},
sparse_chain::{self, ChainPosition, SparseChain},
tx_graph::{self, TxGraph},
BlockId, ForEachTxOut, FullTxOut, TxHeight,
Expand Down Expand Up @@ -291,6 +291,41 @@ where
Ok(changeset)
}

/// Calculates the difference between self and `update` in the form of a [`ChangeSet`],
/// filtering the irrelevant transactions
pub fn determine_relevant_changeset<F>(
&self,
update: ChainGraph<P>,
is_relevant: F,
) -> Result<ChangeSet<P>, UpdateError<P>>
where
F: Fn(&Transaction) -> bool,
{
let graph_additions = self
.graph
.determine_relevant_additions(update.graph, is_relevant);

let relevant_txids = graph_additions
.clone()
.tx
.into_iter()
.map(|t| t.txid())
.collect::<BTreeSet<Txid>>();

let chain_changeset = self
.chain
.determine_relevant_changeset(update.chain, &relevant_txids)
.map_err(UpdateError::Chain)?;

let mut changeset = ChangeSet {
chain: chain_changeset,
graph: graph_additions,
};

self.fix_conflicts(&mut changeset)?;
Ok(changeset)
}

/// Calculates the difference between self and `update` in the form of a [`ChangeSet`].
pub fn determine_changeset(
&self,
Expand Down
48 changes: 43 additions & 5 deletions crates/chain/src/keychain/tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,33 @@ where
&self,
scan: &KeychainScan<K, P>,
) -> Result<KeychainChangeSet<K, P>, chain_graph::UpdateError<P>> {
// TODO: `KeychainTxOutIndex::determine_additions`
Ok(KeychainChangeSet {
derivation_indices: self.determine_additions(scan),
chain_graph: self.chain_graph.determine_changeset(&scan.update)?,
})
}

/// Determines the resultant [`KeychainChangeSet`] if the relevant part of a [`KeychainScan`] is applied.
///
/// Internally, we call [`ChainGraph::determine_relevant_changeset`] and also determine the additions of
/// [`KeychainTxOutIndex`].
pub fn determine_relevant_changeset(
&self,
scan: KeychainScan<K, P>,
) -> Result<KeychainChangeSet<K, P>, chain_graph::UpdateError<P>> {
Ok(KeychainChangeSet {
derivation_indices: self.determine_additions(&scan),
chain_graph: self
.chain_graph
.determine_relevant_changeset(scan.update, |tx| self.txout_index.is_relevant(tx))?,
})
}

/// Determines the changes ([`DerivationAdditions`]) in the local keychain
/// derivation if the given [`KeychainScan`] is applied
// Note: we could accept `last_active_indices: BTreeMap<K, u32>` here, but we're accepting a whole
// KeychainScan for clarity
pub fn determine_additions(&self, scan: &KeychainScan<K, P>) -> DerivationAdditions<K> {
let mut derivation_indices = scan.last_active_indices.clone();
derivation_indices.retain(|keychain, index| {
match self.txout_index.last_revealed_index(keychain) {
Expand All @@ -75,10 +101,7 @@ where
}
});

Ok(KeychainChangeSet {
derivation_indices: DerivationAdditions(derivation_indices),
chain_graph: self.chain_graph.determine_changeset(&scan.update)?,
})
DerivationAdditions(derivation_indices)
}

/// Directly applies a [`KeychainScan`] on [`KeychainTracker`].
Expand All @@ -96,6 +119,21 @@ where
Ok(changeset)
}

/// Directly applies the relevant part of a [`KeychainScan`] on [`KeychainTracker`].
///
/// This is equivilant to calling [`determine_relevant_changeset`] and [`apply_changeset`] in sequence.
///
/// [`determine_relevant_changeset`]: Self::determine_relevant_changeset
/// [`apply_changeset`]: Self::apply_changeset
pub fn apply_update_relevant(
&mut self,
scan: KeychainScan<K, P>,
) -> Result<KeychainChangeSet<K, P>, chain_graph::UpdateError<P>> {
let changeset = self.determine_relevant_changeset(scan)?;
self.apply_changeset(changeset.clone());
Ok(changeset)
}

/// Applies the changes in `changeset` to [`KeychainTracker`].
///
/// Internally, this calls [`KeychainTxOutIndex::apply_additions`] and
Expand Down
18 changes: 18 additions & 0 deletions crates/chain/src/sparse_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,24 @@ impl<P: ChainPosition> SparseChain<P> {
.map(|(&height, &hash)| BlockId { height, hash })
}

/// Preview changes of updating [`Self`] with another chain that connects to it.
/// All the non-relevant transactions will be excluded.
///
/// Refer to `determine_changeset` for more.
pub fn determine_relevant_changeset(
&self,
mut update: Self,
relevant_txids: &BTreeSet<Txid>,
) -> Result<ChangeSet<P>, UpdateError<P>> {
update
.ordered_txids
.retain(|(_, txid)| relevant_txids.contains(txid));
update
.txid_to_pos
.retain(|txid, _| relevant_txids.contains(txid));
self.determine_changeset(&update)
}

/// Preview changes of updating [`Self`] with another chain that connects to it.
///
/// If the `update` wishes to introduce confirmed transactions, it must contain a checkpoint
Expand Down
17 changes: 17 additions & 0 deletions crates/chain/src/tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,23 @@ impl TxGraph {
additions
}

/// Previews the resultant relevant [`Additions`] when [`Self`] is updated against the `update` graph.
///
/// The [`Additions`] would be the set difference of the relevant `update` and `self` (transactions that
/// exist in `update` and are relevant, but not in `self`).
pub fn determine_relevant_additions<F>(&self, mut update: TxGraph, is_relevant: F) -> Additions
where
F: Fn(&Transaction) -> bool,
{
// TODO: this always considers partial txs as relevant
update.txs.retain(|_, tx_node| match tx_node {
TxNode::Whole(tx) => is_relevant(tx),
_ => true,
});

self.determine_additions(&update)
}

/// Returns the resultant [`Additions`] if the given transaction is inserted. Does not actually
/// mutate [`Self`].
///
Expand Down
61 changes: 61 additions & 0 deletions crates/chain/tests/test_chain_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ use bitcoin::{OutPoint, PackedLockTime, Script, Sequence, Transaction, TxIn, TxO

#[test]
fn test_spent_by() {
// Creating three transactions:
// tx1
// tx2: spends from `op` (tx1.txid():0)
// tx3: spends from `op` (tx1.txid():0)
let tx1 = Transaction {
version: 0x01,
lock_time: PackedLockTime(0),
Expand Down Expand Up @@ -43,6 +47,9 @@ fn test_spent_by() {
output: vec![],
};

// We create two checkpoints:
// - cg1, containing tx1 and tx2
// - cg2, containing tx1 and tx3
let mut cg1 = ChainGraph::default();
let _ = cg1
.insert_tx(tx1, TxHeight::Unconfirmed)
Expand All @@ -55,6 +62,7 @@ fn test_spent_by() {
.insert_tx(tx3.clone(), TxHeight::Unconfirmed)
.expect("should insert");

// We query cg1 and cg2 to get the tx spending `op`
assert_eq!(cg1.spent_by(op), Some((&TxHeight::Unconfirmed, tx2.txid())));
assert_eq!(cg2.spent_by(op), Some((&TxHeight::Unconfirmed, tx3.txid())));
}
Expand Down Expand Up @@ -651,3 +659,56 @@ fn test_evict_descendants() {
.expect_err("must fail due to conflicts");
assert!(matches!(err, InsertTxError::UnresolvableConflict(_)));
}

#[test]
fn test_chain_graph_relevant_changeset() {
let tx_a = Transaction {
version: 0x01,
lock_time: PackedLockTime(0),
input: vec![],
output: vec![TxOut::default()],
};

let tx_b = Transaction {
version: 0x01,
lock_time: PackedLockTime(0),
input: vec![TxIn {
previous_output: OutPoint::new(tx_a.txid(), 0),
script_sig: Script::new(),
sequence: Sequence::default(),
witness: Witness::new(),
}],
output: vec![TxOut::default()],
};

let cg1 = ChainGraph::default();

// cg2 has two txs, but only tx_a is relevant
let cg2 = {
let mut cg = ChainGraph::default();
let _ = cg
.insert_tx(tx_a.clone(), TxHeight::Unconfirmed)
.expect("should insert tx");
let _ = cg
.insert_tx(tx_b, TxHeight::Unconfirmed)
.expect("should insert tx");
cg
};

let changeset = ChangeSet::<TxHeight> {
chain: sparse_chain::ChangeSet {
checkpoints: Default::default(),
txids: [(tx_a.txid(), Some(TxHeight::Unconfirmed))].into(),
},
graph: tx_graph::Additions {
tx: [tx_a.clone()].into(),
txout: [].into(),
},
};

assert_eq!(
cg1.determine_relevant_changeset(cg2, |tx: &Transaction| { tx.txid() == tx_a.txid() }),
Ok(changeset),
"tx_b shouldn't be in the txgraph",
);
}