[bdk_chain_redesign] Simplify TxIndex

This commit is contained in:
志宇 2023-03-31 12:39:00 +08:00
parent a1172def7d
commit a63ffe9739
No known key found for this signature in database
GPG Key ID: F6345C9837C2BDE8
5 changed files with 86 additions and 124 deletions

View File

@ -1,12 +1,12 @@
use core::convert::Infallible; use core::{convert::Infallible, ops::AddAssign};
use bitcoin::{OutPoint, Transaction, TxOut}; use bitcoin::{OutPoint, Script, Transaction, TxOut};
use crate::{ use crate::{
keychain::Balance, keychain::Balance,
sparse_chain::ChainPosition, sparse_chain::ChainPosition,
tx_graph::{Additions, TxGraph, TxNode}, tx_graph::{Additions, TxGraph, TxNode},
BlockAnchor, ChainOracle, FullTxOut, ObservedIn, TxIndex, TxIndexAdditions, BlockAnchor, ChainOracle, FullTxOut, ObservedIn, TxIndex,
}; };
/// An outwards-facing view of a transaction that is part of the *best chain*'s history. /// An outwards-facing view of a transaction that is part of the *best chain*'s history.
@ -18,46 +18,37 @@ pub struct TxInChain<'a, T, A> {
pub tx: TxNode<'a, T, A>, pub tx: TxNode<'a, T, A>,
} }
/// An outwards-facing view of a relevant txout that is part of the *best chain*'s history.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct TxOutInChain<'a, I, A> {
/// The custom index of the txout's script pubkey.
pub spk_index: &'a I,
/// The full txout.
pub txout: FullTxOut<ObservedIn<&'a A>>,
}
/// A structure that represents changes to an [`IndexedTxGraph`]. /// A structure that represents changes to an [`IndexedTxGraph`].
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[must_use] #[must_use]
pub struct IndexedAdditions<A, D> { pub struct IndexedAdditions<A, IA> {
/// [`TxGraph`] additions. /// [`TxGraph`] additions.
pub graph_additions: Additions<A>, pub graph_additions: Additions<A>,
/// [`TxIndex`] additions. /// [`TxIndex`] additions.
pub index_delta: D, pub index_additions: IA,
/// Last block height witnessed (if any). /// Last block height witnessed (if any).
pub last_height: Option<u32>, pub last_height: Option<u32>,
} }
impl<A, D: Default> Default for IndexedAdditions<A, D> { impl<A, IA: Default> Default for IndexedAdditions<A, IA> {
fn default() -> Self { fn default() -> Self {
Self { Self {
graph_additions: Default::default(), graph_additions: Default::default(),
index_delta: Default::default(), index_additions: Default::default(),
last_height: None, last_height: None,
} }
} }
} }
impl<A: BlockAnchor, D: TxIndexAdditions> TxIndexAdditions for IndexedAdditions<A, D> { impl<A: BlockAnchor, IA: AddAssign> AddAssign for IndexedAdditions<A, IA> {
fn append_additions(&mut self, other: Self) { fn add_assign(&mut self, rhs: Self) {
let Self { let Self {
graph_additions, graph_additions,
index_delta, index_additions: index_delta,
last_height, last_height,
} = other; } = rhs;
self.graph_additions.append(graph_additions); self.graph_additions.append(graph_additions);
self.index_delta.append_additions(index_delta); self.index_additions += index_delta;
if self.last_height < last_height { if self.last_height < last_height {
let last_height = let last_height =
last_height.expect("must exist as it is larger than self.last_height"); last_height.expect("must exist as it is larger than self.last_height");
@ -102,11 +93,11 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
pub fn apply_additions(&mut self, additions: IndexedAdditions<A, I::Additions>) { pub fn apply_additions(&mut self, additions: IndexedAdditions<A, I::Additions>) {
let IndexedAdditions { let IndexedAdditions {
graph_additions, graph_additions,
index_delta, index_additions,
last_height, last_height,
} = additions; } = additions;
self.index.apply_additions(index_delta); self.index.apply_additions(index_additions);
for tx in &graph_additions.tx { for tx in &graph_additions.tx {
self.index.index_tx(tx); self.index.index_tx(tx);
@ -122,16 +113,23 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
} }
} }
/// Insert a block height that the chain source has scanned up to. fn insert_height_internal(&mut self, tip: u32) -> Option<u32> {
pub fn insert_height(&mut self, tip: u32) -> IndexedAdditions<A, I::Additions> {
if self.last_height < tip { if self.last_height < tip {
self.last_height = tip; self.last_height = tip;
IndexedAdditions { Some(tip)
last_height: Some(tip),
..Default::default()
}
} else { } else {
IndexedAdditions::default() None
}
}
/// Insert a block height that the chain source has scanned up to.
pub fn insert_height(&mut self, tip: u32) -> IndexedAdditions<A, I::Additions>
where
I::Additions: Default,
{
IndexedAdditions {
last_height: self.insert_height_internal(tip),
..Default::default()
} }
} }
@ -142,12 +140,12 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
txout: &TxOut, txout: &TxOut,
observation: ObservedIn<A>, observation: ObservedIn<A>,
) -> IndexedAdditions<A, I::Additions> { ) -> IndexedAdditions<A, I::Additions> {
let mut additions = match &observation { let last_height = match &observation {
ObservedIn::Block(anchor) => self.insert_height(anchor.anchor_block().height), ObservedIn::Block(anchor) => self.insert_height_internal(anchor.anchor_block().height),
ObservedIn::Mempool(_) => IndexedAdditions::default(), ObservedIn::Mempool(_) => None,
}; };
additions.append_additions(IndexedAdditions { IndexedAdditions {
graph_additions: { graph_additions: {
let mut graph_additions = self.graph.insert_txout(outpoint, txout.clone()); let mut graph_additions = self.graph.insert_txout(outpoint, txout.clone());
graph_additions.append(match observation { graph_additions.append(match observation {
@ -158,11 +156,9 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
}); });
graph_additions graph_additions
}, },
index_delta: <I as TxIndex>::index_txout(&mut self.index, outpoint, txout), index_additions: <I as TxIndex>::index_txout(&mut self.index, outpoint, txout),
last_height: None, last_height,
}); }
additions
} }
pub fn insert_tx( pub fn insert_tx(
@ -172,12 +168,12 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
) -> IndexedAdditions<A, I::Additions> { ) -> IndexedAdditions<A, I::Additions> {
let txid = tx.txid(); let txid = tx.txid();
let mut additions = match &observation { let last_height = match &observation {
ObservedIn::Block(anchor) => self.insert_height(anchor.anchor_block().height), ObservedIn::Block(anchor) => self.insert_height_internal(anchor.anchor_block().height),
ObservedIn::Mempool(_) => IndexedAdditions::default(), ObservedIn::Mempool(_) => None,
}; };
additions.append_additions(IndexedAdditions { IndexedAdditions {
graph_additions: { graph_additions: {
let mut graph_additions = self.graph.insert_tx(tx.clone()); let mut graph_additions = self.graph.insert_tx(tx.clone());
graph_additions.append(match observation { graph_additions.append(match observation {
@ -186,11 +182,9 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
}); });
graph_additions graph_additions
}, },
index_delta: <I as TxIndex>::index_tx(&mut self.index, tx), index_additions: <I as TxIndex>::index_tx(&mut self.index, tx),
last_height: None, last_height,
}); }
additions
} }
pub fn filter_and_insert_txs<'t, T>( pub fn filter_and_insert_txs<'t, T>(
@ -200,6 +194,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
) -> IndexedAdditions<A, I::Additions> ) -> IndexedAdditions<A, I::Additions>
where where
T: Iterator<Item = &'t Transaction>, T: Iterator<Item = &'t Transaction>,
I::Additions: Default + AddAssign,
{ {
txs.filter_map(|tx| { txs.filter_map(|tx| {
if self.index.is_tx_relevant(tx) { if self.index.is_tx_relevant(tx) {
@ -209,7 +204,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
} }
}) })
.fold(IndexedAdditions::default(), |mut acc, other| { .fold(IndexedAdditions::default(), |mut acc, other| {
acc.append_additions(other); acc += other;
acc acc
}) })
} }
@ -252,50 +247,47 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
pub fn try_list_chain_txouts<'a, C>( pub fn try_list_chain_txouts<'a, C>(
&'a self, &'a self,
chain: C, chain: C,
) -> impl Iterator<Item = Result<TxOutInChain<'a, I::SpkIndex, A>, C::Error>> ) -> impl Iterator<Item = Result<FullTxOut<ObservedIn<A>>, C::Error>> + 'a
where where
C: ChainOracle + 'a, C: ChainOracle + 'a,
ObservedIn<A>: ChainPosition, ObservedIn<A>: ChainPosition,
{ {
self.index.relevant_txouts().iter().filter_map( self.graph
move |(op, (spk_i, txout))| -> Option<Result<_, C::Error>> { .all_txouts()
.filter(|(_, txo)| self.index.is_spk_owned(&txo.script_pubkey))
.filter_map(move |(op, txout)| -> Option<Result<_, C::Error>> {
let graph_tx = self.graph.get_tx(op.txid)?; let graph_tx = self.graph.get_tx(op.txid)?;
let is_on_coinbase = graph_tx.is_coin_base(); let is_on_coinbase = graph_tx.is_coin_base();
let chain_position = match self.graph.try_get_chain_position(&chain, op.txid) { let chain_position = match self.graph.try_get_chain_position(&chain, op.txid) {
Ok(Some(observed_at)) => observed_at, Ok(Some(observed_at)) => observed_at.into_owned(),
Ok(None) => return None, Ok(None) => return None,
Err(err) => return Some(Err(err)), Err(err) => return Some(Err(err)),
}; };
let spent_by = match self.graph.try_get_spend_in_chain(&chain, *op) { let spent_by = match self.graph.try_get_spend_in_chain(&chain, op) {
Ok(spent_by) => spent_by, Ok(Some((obs, txid))) => Some((obs.into_owned(), txid)),
Ok(None) => None,
Err(err) => return Some(Err(err)), Err(err) => return Some(Err(err)),
}; };
let full_txout = FullTxOut { let full_txout = FullTxOut {
outpoint: *op, outpoint: op,
txout: txout.clone(), txout: txout.clone(),
chain_position, chain_position,
spent_by, spent_by,
is_on_coinbase, is_on_coinbase,
}; };
let txout_in_chain = TxOutInChain { Some(Ok(full_txout))
spk_index: spk_i, })
txout: full_txout,
};
Some(Ok(txout_in_chain))
},
)
} }
pub fn list_chain_txouts<'a, C>( pub fn list_chain_txouts<'a, C>(
&'a self, &'a self,
chain: C, chain: C,
) -> impl Iterator<Item = TxOutInChain<'a, I::SpkIndex, A>> ) -> impl Iterator<Item = FullTxOut<ObservedIn<A>>> + 'a
where where
C: ChainOracle<Error = Infallible> + 'a, C: ChainOracle<Error = Infallible> + 'a,
ObservedIn<A>: ChainPosition, ObservedIn<A>: ChainPosition,
@ -308,19 +300,19 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
pub fn try_list_chain_utxos<'a, C>( pub fn try_list_chain_utxos<'a, C>(
&'a self, &'a self,
chain: C, chain: C,
) -> impl Iterator<Item = Result<TxOutInChain<'a, I::SpkIndex, A>, C::Error>> ) -> impl Iterator<Item = Result<FullTxOut<ObservedIn<A>>, C::Error>> + 'a
where where
C: ChainOracle + 'a, C: ChainOracle + 'a,
ObservedIn<A>: ChainPosition, ObservedIn<A>: ChainPosition,
{ {
self.try_list_chain_txouts(chain) self.try_list_chain_txouts(chain)
.filter(|r| !matches!(r, Ok(txo) if txo.txout.spent_by.is_none())) .filter(|r| !matches!(r, Ok(txo) if txo.spent_by.is_none()))
} }
pub fn list_chain_utxos<'a, C>( pub fn list_chain_utxos<'a, C>(
&'a self, &'a self,
chain: C, chain: C,
) -> impl Iterator<Item = TxOutInChain<'a, I::SpkIndex, A>> ) -> impl Iterator<Item = FullTxOut<ObservedIn<A>>> + 'a
where where
C: ChainOracle<Error = Infallible> + 'a, C: ChainOracle<Error = Infallible> + 'a,
ObservedIn<A>: ChainPosition, ObservedIn<A>: ChainPosition,
@ -338,7 +330,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
where where
C: ChainOracle, C: ChainOracle,
ObservedIn<A>: ChainPosition + Clone, ObservedIn<A>: ChainPosition + Clone,
F: FnMut(&I::SpkIndex) -> bool, F: FnMut(&Script) -> bool,
{ {
let mut immature = 0; let mut immature = 0;
let mut trusted_pending = 0; let mut trusted_pending = 0;
@ -346,8 +338,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
let mut confirmed = 0; let mut confirmed = 0;
for res in self.try_list_chain_txouts(&chain) { for res in self.try_list_chain_txouts(&chain) {
let TxOutInChain { spk_index, txout } = res?; let txout = res?;
let txout = txout.into_owned();
match &txout.chain_position { match &txout.chain_position {
ObservedIn::Block(_) => { ObservedIn::Block(_) => {
@ -360,7 +351,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
} }
} }
ObservedIn::Mempool(_) => { ObservedIn::Mempool(_) => {
if should_trust(spk_index) { if should_trust(&txout.txout.script_pubkey) {
trusted_pending += txout.txout.value; trusted_pending += txout.txout.value;
} else { } else {
untrusted_pending += txout.txout.value; untrusted_pending += txout.txout.value;
@ -381,7 +372,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
where where
C: ChainOracle<Error = Infallible>, C: ChainOracle<Error = Infallible>,
ObservedIn<A>: ChainPosition + Clone, ObservedIn<A>: ChainPosition + Clone,
F: FnMut(&I::SpkIndex) -> bool, F: FnMut(&Script) -> bool,
{ {
self.try_balance(chain, tip, should_trust) self.try_balance(chain, tip, should_trust)
.expect("error is infallible") .expect("error is infallible")
@ -393,8 +384,8 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
ObservedIn<A>: ChainPosition + Clone, ObservedIn<A>: ChainPosition + Clone,
{ {
let mut sum = 0; let mut sum = 0;
for res in self.try_list_chain_txouts(chain) { for txo_res in self.try_list_chain_txouts(chain) {
let txo = res?.txout.into_owned(); let txo = txo_res?;
if txo.is_spendable_at(height) { if txo.is_spendable_at(height) {
sum += txo.txout.value; sum += txo.txout.value;
} }

View File

@ -14,12 +14,14 @@
//! [`KeychainChangeSet`]s. //! [`KeychainChangeSet`]s.
//! //!
//! [`SpkTxOutIndex`]: crate::SpkTxOutIndex //! [`SpkTxOutIndex`]: crate::SpkTxOutIndex
use core::ops::AddAssign;
use crate::{ use crate::{
chain_graph::{self, ChainGraph}, chain_graph::{self, ChainGraph},
collections::BTreeMap, collections::BTreeMap,
sparse_chain::ChainPosition, sparse_chain::ChainPosition,
tx_graph::TxGraph, tx_graph::TxGraph,
ForEachTxOut, TxIndexAdditions, ForEachTxOut,
}; };
#[cfg(feature = "miniscript")] #[cfg(feature = "miniscript")]
@ -85,9 +87,9 @@ impl<K: Ord> DerivationAdditions<K> {
} }
} }
impl<K: Ord> TxIndexAdditions for DerivationAdditions<K> { impl<K: Ord> AddAssign for DerivationAdditions<K> {
fn append_additions(&mut self, other: Self) { fn add_assign(&mut self, rhs: Self) {
self.append(other) self.append(rhs)
} }
} }

View File

@ -91,8 +91,6 @@ impl<K> Deref for KeychainTxOutIndex<K> {
impl<K: Clone + Ord + Debug + 'static> TxIndex for KeychainTxOutIndex<K> { impl<K: Clone + Ord + Debug + 'static> TxIndex for KeychainTxOutIndex<K> {
type Additions = DerivationAdditions<K>; type Additions = DerivationAdditions<K>;
type SpkIndex = (K, u32);
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions { fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
self.scan_txout(outpoint, txout) self.scan_txout(outpoint, txout)
} }
@ -109,8 +107,8 @@ impl<K: Clone + Ord + Debug + 'static> TxIndex for KeychainTxOutIndex<K> {
self.is_relevant(tx) self.is_relevant(tx)
} }
fn relevant_txouts(&self) -> &BTreeMap<OutPoint, (Self::SpkIndex, TxOut)> { fn is_spk_owned(&self, spk: &Script) -> bool {
self.inner.relevant_txouts() self.index_of_spk(spk).is_some()
} }
} }

View File

@ -53,19 +53,16 @@ impl<I> Default for SpkTxOutIndex<I> {
} }
impl<I: Clone + Ord + 'static> TxIndex for SpkTxOutIndex<I> { impl<I: Clone + Ord + 'static> TxIndex for SpkTxOutIndex<I> {
type Additions = BTreeSet<I>; type Additions = ();
type SpkIndex = I;
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions { fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
self.scan_txout(outpoint, txout) self.scan_txout(outpoint, txout);
.cloned() Default::default()
.into_iter()
.collect()
} }
fn index_tx(&mut self, tx: &Transaction) -> Self::Additions { fn index_tx(&mut self, tx: &Transaction) -> Self::Additions {
self.scan(tx) self.scan(tx);
Default::default()
} }
fn apply_additions(&mut self, _additions: Self::Additions) { fn apply_additions(&mut self, _additions: Self::Additions) {
@ -76,8 +73,8 @@ impl<I: Clone + Ord + 'static> TxIndex for SpkTxOutIndex<I> {
self.is_relevant(tx) self.is_relevant(tx)
} }
fn relevant_txouts(&self) -> &BTreeMap<OutPoint, (Self::SpkIndex, TxOut)> { fn is_spk_owned(&self, spk: &Script) -> bool {
&self.txouts self.index_of_spk(spk).is_some()
} }
} }

View File

@ -1,5 +1,4 @@
use alloc::collections::{BTreeMap, BTreeSet}; use bitcoin::{Block, BlockHash, OutPoint, Script, Transaction, TxOut};
use bitcoin::{Block, BlockHash, OutPoint, Transaction, TxOut};
use crate::BlockId; use crate::BlockId;
@ -89,41 +88,16 @@ impl<C: ChainOracle> ChainOracle for &C {
} }
} }
/// Represents changes to a [`TxIndex`] implementation.
pub trait TxIndexAdditions: Default {
/// Append `other` on top of `self`.
fn append_additions(&mut self, other: Self);
}
impl<I: Ord> TxIndexAdditions for BTreeSet<I> {
fn append_additions(&mut self, mut other: Self) {
self.append(&mut other);
}
}
/// Represents an index of transaction data. /// Represents an index of transaction data.
pub trait TxIndex { pub trait TxIndex {
/// The resultant "additions" when new transaction data is indexed. /// The resultant "additions" when new transaction data is indexed.
type Additions: TxIndexAdditions; type Additions;
type SpkIndex: Ord;
/// Scan and index the given `outpoint` and `txout`. /// Scan and index the given `outpoint` and `txout`.
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions; fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions;
/// Scan and index the given transaction. /// Scan and index the given transaction.
fn index_tx(&mut self, tx: &Transaction) -> Self::Additions { fn index_tx(&mut self, tx: &Transaction) -> Self::Additions;
let txid = tx.txid();
tx.output
.iter()
.enumerate()
.map(|(vout, txout)| self.index_txout(OutPoint::new(txid, vout as _), txout))
.reduce(|mut acc, other| {
acc.append_additions(other);
acc
})
.unwrap_or_default()
}
/// Apply additions to itself. /// Apply additions to itself.
fn apply_additions(&mut self, additions: Self::Additions); fn apply_additions(&mut self, additions: Self::Additions);
@ -132,6 +106,6 @@ pub trait TxIndex {
/// spends an already-indexed outpoint that we have previously indexed. /// spends an already-indexed outpoint that we have previously indexed.
fn is_tx_relevant(&self, tx: &Transaction) -> bool; fn is_tx_relevant(&self, tx: &Transaction) -> bool;
/// Lists all relevant txouts known by the index. /// Returns whether the script pubkey is owned by us.
fn relevant_txouts(&self) -> &BTreeMap<OutPoint, (Self::SpkIndex, TxOut)>; fn is_spk_owned(&self, spk: &Script) -> bool;
} }