chain: add batch-insert methods for IndexedTxGraph

This commit is contained in:
志宇 2023-10-04 17:10:46 +08:00
parent 43bc813c64
commit 240657b167
No known key found for this signature in database
GPG Key ID: F6345C9837C2BDE8
2 changed files with 149 additions and 32 deletions

View File

@ -3,12 +3,12 @@
//! This is essentially a [`TxGraph`] combined with an indexer. //! This is essentially a [`TxGraph`] combined with an indexer.
use alloc::vec::Vec; use alloc::vec::Vec;
use bitcoin::{OutPoint, Transaction, TxOut}; use bitcoin::{Block, OutPoint, Transaction, TxOut};
use crate::{ use crate::{
keychain, keychain,
tx_graph::{self, TxGraph}, tx_graph::{self, TxGraph},
Anchor, Append, Anchor, AnchorFromBlockPosition, Append, BlockId,
}; };
/// A struct that combines [`TxGraph`] and an [`Indexer`] implementation. /// A struct that combines [`TxGraph`] and an [`Indexer`] implementation.
@ -126,17 +126,13 @@ where
self.apply_update(update) self.apply_update(update)
} }
/// Insert relevant transactions from the given `txs` iterator. /// Batch insert transactions, filtering out those that are irrelevant.
/// ///
/// Relevancy is determined by the [`Indexer::is_tx_relevant`] implementation of `I`. Irrelevant /// Relevancy is determined by the [`Indexer::is_tx_relevant`] implementation of `I`. Irrelevant
/// transactions in `txs` will be ignored. `txs` do not need to be in topological order. /// transactions in `txs` will be ignored. `txs` do not need to be in topological order.
/// pub fn batch_insert_relevant<'t>(
/// `anchors` can be provided to anchor the transactions to blocks. `seen_at` is a unix
/// timestamp of when the transactions are last seen.
pub fn insert_relevant_txs<'t>(
&mut self, &mut self,
txs: impl IntoIterator<Item = (&'t Transaction, impl IntoIterator<Item = A>)>, txs: impl IntoIterator<Item = InsertTxItem<'t, impl IntoIterator<Item = A>>>,
seen_at: Option<u64>,
) -> ChangeSet<A, I::ChangeSet> { ) -> ChangeSet<A, I::ChangeSet> {
// The algorithm below allows for non-topologically ordered transactions by using two loops. // The algorithm below allows for non-topologically ordered transactions by using two loops.
// This is achieved by: // This is achieved by:
@ -145,26 +141,148 @@ where
// 2. decide whether to insert them into the graph depending on whether `is_tx_relevant` // 2. decide whether to insert them into the graph depending on whether `is_tx_relevant`
// returns true or not. (in a second loop). // returns true or not. (in a second loop).
let mut changeset = ChangeSet::<A, I::ChangeSet>::default(); let mut changeset = ChangeSet::<A, I::ChangeSet>::default();
let mut transactions = Vec::new();
for (tx, anchors) in txs.into_iter() { let txs = txs
changeset.indexer.append(self.index.index_tx(tx));
transactions.push((tx, anchors));
}
changeset.append(
transactions
.into_iter() .into_iter()
.filter_map(|(tx, anchors)| match self.index.is_tx_relevant(tx) { .inspect(|(tx, _, _)| changeset.indexer.append(self.index.index_tx(tx)))
true => Some(self.insert_tx(tx, anchors, seen_at)), .collect::<Vec<_>>();
false => None,
}) for (tx, anchors, seen_at) in txs {
.fold(Default::default(), |mut acc, other| { if self.index.is_tx_relevant(tx) {
acc.append(other); changeset.append(self.insert_tx(tx, anchors, seen_at));
acc }
}), }
);
changeset changeset
} }
/// Batch insert transactions.
///
/// All transactions in `txs` will be inserted. To filter out irrelevant transactions, use
/// [`batch_insert_relevant`] instead.
///
/// [`batch_insert_relevant`]: IndexedTxGraph::batch_insert_relevant
pub fn batch_insert<'t>(
&mut self,
txs: impl IntoIterator<Item = InsertTxItem<'t, impl IntoIterator<Item = A>>>,
) -> ChangeSet<A, I::ChangeSet> {
let mut changeset = ChangeSet::<A, I::ChangeSet>::default();
for (tx, anchors, seen_at) in txs {
changeset.indexer.append(self.index.index_tx(tx));
changeset.append(self.insert_tx(tx, anchors, seen_at));
} }
changeset
}
/// Batch insert unconfirmed transactions, filtering out those that are irrelevant.
///
/// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`.
/// Irrelevant tansactions in `txs` will be ignored.
///
/// Items of `txs` are tuples containing the transaction and an optional *last seen* timestamp.
/// The *last seen* communicates when the transaction is last seen in the mempool which is used
/// for conflict-resolution in [`TxGraph`] (refer to [`TxGraph::insert_seen_at`] for details).
pub fn batch_insert_relevant_unconfirmed<'t>(
&mut self,
unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, Option<u64>)>,
) -> ChangeSet<A, I::ChangeSet> {
self.batch_insert_relevant(
unconfirmed_txs
.into_iter()
.map(|(tx, last_seen)| (tx, core::iter::empty(), last_seen)),
)
}
/// Batch insert unconfirmed transactions.
///
/// Items of `txs` are tuples containing the transaction and an optional *last seen* timestamp.
/// The *last seen* communicates when the transaction is last seen in the mempool which is used
/// for conflict-resolution in [`TxGraph`] (refer to [`TxGraph::insert_seen_at`] for details).
///
/// To filter out irrelevant transactions, use [`batch_insert_relevant_unconfirmed`] instead.
///
/// [`batch_insert_relevant_unconfirmed`]: IndexedTxGraph::batch_insert_relevant_unconfirmed
pub fn batch_insert_unconfirmed<'t>(
&mut self,
unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, Option<u64>)>,
) -> ChangeSet<A, I::ChangeSet> {
self.batch_insert(
unconfirmed_txs
.into_iter()
.map(|(tx, last_seen)| (tx, core::iter::empty(), last_seen)),
)
}
}
/// Methods are available if the anchor (`A`) implements [`AnchorFromBlockPosition`].
impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I>
where
I::ChangeSet: Default + Append,
A: AnchorFromBlockPosition,
{
/// Batch insert all transactions of the given `block` of `height`, filtering out those that are
/// irrelevant.
///
/// Each inserted transaction's anchor will be constructed from
/// [`AnchorFromBlockPosition::from_block_position`].
///
/// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`.
/// Irrelevant tansactions in `txs` will be ignored.
pub fn apply_block_relevant(
&mut self,
block: Block,
height: u32,
) -> ChangeSet<A, I::ChangeSet> {
let block_id = BlockId {
hash: block.block_hash(),
height,
};
let txs = block.txdata.iter().enumerate().map(|(tx_pos, tx)| {
(
tx,
core::iter::once(A::from_block_position(&block, block_id, tx_pos)),
None,
)
});
self.batch_insert_relevant(txs)
}
/// Batch insert all transactions of the given `block` of `height`.
///
/// Each inserted transaction's anchor will be constructed from
/// [`AnchorFromBlockPosition::from_block_position`].
///
/// To only insert relevant transactions, use [`apply_block_relevant`] instead.
///
/// [`apply_block_relevant`]: IndexedTxGraph::apply_block_relevant
pub fn apply_block(&mut self, block: Block, height: u32) -> ChangeSet<A, I::ChangeSet> {
let block_id = BlockId {
hash: block.block_hash(),
height,
};
let txs = block.txdata.iter().enumerate().map(|(tx_pos, tx)| {
(
tx,
core::iter::once(A::from_block_position(&block, block_id, tx_pos)),
None,
)
});
self.batch_insert(txs)
}
}
/// A tuple of a transaction, and associated metadata, that are to be inserted into [`IndexedTxGraph`].
///
/// This tuple contains fields in the following order:
/// * A reference to the transaction.
/// * A collection of [`Anchor`]s.
/// * An optional last-seen timestamp.
///
/// This is used as a input item of [`batch_insert_relevant`] and [`batch_insert`].
///
/// [`batch_insert_relevant`]: IndexedTxGraph::batch_insert_relevant
/// [`batch_insert`]: IndexedTxGraph::batch_insert
pub type InsertTxItem<'t, A> = (&'t Transaction, A, Option<u64>);
/// A structure that represents changes to an [`IndexedTxGraph`]. /// A structure that represents changes to an [`IndexedTxGraph`].
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]

View File

@ -74,7 +74,7 @@ fn insert_relevant_txs() {
}; };
assert_eq!( assert_eq!(
graph.insert_relevant_txs(txs.iter().map(|tx| (tx, None)), None), graph.batch_insert_relevant(txs.iter().map(|tx| (tx, None, None))),
changeset, changeset,
); );
@ -211,8 +211,8 @@ fn test_list_owned_txouts() {
// Insert transactions into graph with respective anchors // Insert transactions into graph with respective anchors
// For unconfirmed txs we pass in `None`. // For unconfirmed txs we pass in `None`.
let _ = graph.insert_relevant_txs( let _ =
[&tx1, &tx2, &tx3, &tx6].iter().enumerate().map(|(i, tx)| { graph.batch_insert_relevant([&tx1, &tx2, &tx3, &tx6].iter().enumerate().map(|(i, tx)| {
let height = i as u32; let height = i as u32;
( (
*tx, *tx,
@ -225,12 +225,11 @@ fn test_list_owned_txouts() {
anchor_block, anchor_block,
confirmation_height: anchor_block.height, confirmation_height: anchor_block.height,
}), }),
)
}),
None, None,
); )
}));
let _ = graph.insert_relevant_txs([&tx4, &tx5].iter().map(|tx| (*tx, None)), Some(100)); let _ = graph.batch_insert_relevant([&tx4, &tx5].iter().map(|tx| (*tx, None, Some(100))));
// A helper lambda to extract and filter data from the graph. // A helper lambda to extract and filter data from the graph.
let fetch = let fetch =