chain: add batch-insert methods for IndexedTxGraph
This commit is contained in:
parent
43bc813c64
commit
240657b167
@ -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,27 +141,149 @@ 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)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
|
@ -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 =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user