Skip to content

Commit

Permalink
[bdk_chain_redesign] Introduce ChainOracle and TxIndex traits
Browse files Browse the repository at this point in the history
The chain oracle keeps track of the best chain, while the transaction
index indexes transaction data in relation to script pubkeys.

This commit also includes initial work on `IndexedTxGraph`.
  • Loading branch information
evanlinjin committed Mar 25, 2023
1 parent 5ae5fe3 commit 56bb46e
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 5 deletions.
9 changes: 9 additions & 0 deletions crates/chain/src/chain_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ use crate::{
BlockAnchor, COINBASE_MATURITY,
};

/// Represents an observation of some chain data.
#[derive(Debug, Clone, Copy)]
pub enum Observation<A> {
/// The chain data is seen in a block identified by `A`.
InBlock(A),
/// The chain data is seen at this given unix timestamp.
SeenAt(u64),
}

/// Represents the height at which a transaction is confirmed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
Expand Down
8 changes: 7 additions & 1 deletion crates/chain/src/keychain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
collections::BTreeMap,
sparse_chain::ChainPosition,
tx_graph::TxGraph,
ForEachTxOut,
ForEachTxOut, TxIndexAdditions,
};

#[cfg(feature = "miniscript")]
Expand Down Expand Up @@ -85,6 +85,12 @@ impl<K: Ord> DerivationAdditions<K> {
}
}

impl<K: Ord> TxIndexAdditions for DerivationAdditions<K> {
fn append_additions(&mut self, other: Self) {
self.append(other)
}
}

impl<K> Default for DerivationAdditions<K> {
fn default() -> Self {
Self(Default::default())
Expand Down
18 changes: 17 additions & 1 deletion crates/chain/src/keychain/txout_index.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
collections::*,
miniscript::{Descriptor, DescriptorPublicKey},
ForEachTxOut, SpkTxOutIndex,
ForEachTxOut, SpkTxOutIndex, TxIndex,
};
use alloc::{borrow::Cow, vec::Vec};
use bitcoin::{secp256k1::Secp256k1, OutPoint, Script, TxOut};
Expand Down Expand Up @@ -88,6 +88,22 @@ impl<K> Deref for KeychainTxOutIndex<K> {
}
}

impl<K: Clone + Ord + Debug> TxIndex for KeychainTxOutIndex<K> {
type Additions = DerivationAdditions<K>;

fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
self.scan_txout(outpoint, txout)
}

fn index_tx(&mut self, tx: &bitcoin::Transaction) -> Self::Additions {
self.scan(tx)
}

fn is_tx_relevant(&self, tx: &bitcoin::Transaction) -> bool {
self.is_relevant(tx)
}
}

impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
/// Scans an object for relevant outpoints, which are stored and indexed internally.
///
Expand Down
10 changes: 9 additions & 1 deletion crates/chain/src/sparse_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ use core::{
ops::{Bound, RangeBounds},
};

use crate::{collections::*, tx_graph::TxGraph, BlockId, FullTxOut, TxHeight};
use crate::{collections::*, tx_graph::TxGraph, BlockId, ChainOracle, FullTxOut, TxHeight};
use bitcoin::{hashes::Hash, BlockHash, OutPoint, Txid};

/// This is a non-monotone structure that tracks relevant [`Txid`]s that are ordered by chain
Expand Down Expand Up @@ -456,6 +456,14 @@ impl<P: core::fmt::Debug> core::fmt::Display for UpdateError<P> {
#[cfg(feature = "std")]
impl<P: core::fmt::Debug> std::error::Error for UpdateError<P> {}

impl<P: ChainPosition> ChainOracle for SparseChain<P> {
type Error = ();

fn get_block_in_best_chain(&self, height: u32) -> Result<Option<BlockHash>, Self::Error> {
Ok(self.checkpoint_at(height).map(|b| b.hash))
}
}

impl<P: ChainPosition> SparseChain<P> {
/// Creates a new chain from a list of block hashes and heights. The caller must guarantee they
/// are in the same chain.
Expand Down
21 changes: 20 additions & 1 deletion crates/chain/src/spk_txout_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use core::ops::RangeBounds;

use crate::{
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
ForEachTxOut,
ForEachTxOut, TxIndex,
};
use bitcoin::{self, OutPoint, Script, Transaction, TxOut, Txid};

Expand Down Expand Up @@ -52,6 +52,25 @@ impl<I> Default for SpkTxOutIndex<I> {
}
}

impl<I: Clone + Ord> TxIndex for SpkTxOutIndex<I> {
type Additions = BTreeSet<I>;

fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
self.scan_txout(outpoint, txout)
.cloned()
.into_iter()
.collect()
}

fn index_tx(&mut self, tx: &Transaction) -> Self::Additions {
self.scan(tx)
}

fn is_tx_relevant(&self, tx: &Transaction) -> bool {
self.is_relevant(tx)
}
}

/// This macro is used instead of a member function of `SpkTxOutIndex`, which would result in a
/// compiler error[E0521]: "borrowed data escapes out of closure" when we attempt to take a
/// reference out of the `ForEachTxOut` closure during scanning.
Expand Down
72 changes: 72 additions & 0 deletions crates/chain/src/tx_data_traits.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use alloc::collections::BTreeSet;
use bitcoin::{Block, BlockHash, OutPoint, Transaction, TxOut};

use crate::BlockId;
Expand Down Expand Up @@ -44,8 +45,79 @@ pub trait BlockAnchor:
fn anchor_block(&self) -> BlockId;
}

impl<A: BlockAnchor> BlockAnchor for &'static A {
fn anchor_block(&self) -> BlockId {
<A as BlockAnchor>::anchor_block(self)
}
}

impl BlockAnchor for (u32, BlockHash) {
fn anchor_block(&self) -> BlockId {
(*self).into()
}
}

/// Represents a service that tracks the best chain history.
pub trait ChainOracle {
/// Error type.
type Error: core::fmt::Debug;

/// Returns the block hash (if any) of the given `height`.
fn get_block_in_best_chain(&self, height: u32) -> Result<Option<BlockHash>, Self::Error>;

/// Determines whether the block of [`BlockId`] exists in the best chain.
fn is_block_in_best_chain(&self, block_id: BlockId) -> Result<bool, Self::Error> {
Ok(matches!(self.get_block_in_best_chain(block_id.height)?, Some(h) if h == block_id.hash))
}
}

impl<C: ChainOracle> ChainOracle for &C {
type Error = C::Error;

fn get_block_in_best_chain(&self, height: u32) -> Result<Option<BlockHash>, Self::Error> {
<C as ChainOracle>::get_block_in_best_chain(self, height)
}

fn is_block_in_best_chain(&self, block_id: BlockId) -> Result<bool, Self::Error> {
<C as ChainOracle>::is_block_in_best_chain(self, block_id)
}
}

/// 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.
pub trait TxIndex {
/// The resultant "additions" when new transaction data is indexed.
type Additions: TxIndexAdditions;

/// Scan and index the given `outpoint` and `txout`.
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions;

/// Scan and index the given transaction.
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()
}

/// A transaction is relevant if it contains a txout with a script_pubkey that we own, or if it
/// spends an already-indexed outpoint that we have previously indexed.
fn is_tx_relevant(&self, tx: &Transaction) -> bool;
}
Loading

0 comments on commit 56bb46e

Please sign in to comment.