Skip to content

Commit

Permalink
Merge branch 'bitcoindevkit:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
jonmarrs authored May 18, 2023
2 parents b11ae9f + 725eee8 commit b75c68b
Show file tree
Hide file tree
Showing 13 changed files with 765 additions and 313 deletions.
134 changes: 14 additions & 120 deletions crates/chain/src/indexed_tx_graph.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
use core::convert::Infallible;

use alloc::vec::Vec;
use bitcoin::{OutPoint, Script, Transaction, TxOut};
use bitcoin::{OutPoint, Transaction, TxOut};

use crate::{
keychain::Balance,
tx_graph::{Additions, TxGraph},
Anchor, Append, BlockId, ChainOracle, FullTxOut, ObservedAs,
Anchor, Append,
};

/// A struct that combines [`TxGraph`] and an [`Indexer`] implementation.
Expand All @@ -29,6 +26,14 @@ impl<A, I: Default> Default for IndexedTxGraph<A, I> {
}

impl<A, I> IndexedTxGraph<A, I> {
/// Construct a new [`IndexedTxGraph`] with a given `index`.
pub fn new(index: I) -> Self {
Self {
index,
graph: TxGraph::default(),
}
}

/// Get a reference of the internal transaction graph.
pub fn graph(&self) -> &TxGraph<A> {
&self.graph
Expand Down Expand Up @@ -157,115 +162,6 @@ where
}
}

impl<A: Anchor, I: OwnedIndexer> IndexedTxGraph<A, I> {
pub fn try_list_owned_txouts<'a, C: ChainOracle + 'a>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a {
self.graph()
.try_list_chain_txouts(chain, chain_tip)
.filter(|r| {
if let Ok(full_txout) = r {
if !self.index.is_spk_owned(&full_txout.txout.script_pubkey) {
return false;
}
}
true
})
}

pub fn list_owned_txouts<'a, C: ChainOracle<Error = Infallible> + 'a>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a {
self.try_list_owned_txouts(chain, chain_tip)
.map(|r| r.expect("oracle is infallible"))
}

pub fn try_list_owned_unspents<'a, C: ChainOracle + 'a>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a {
self.graph()
.try_list_chain_unspents(chain, chain_tip)
.filter(|r| {
if let Ok(full_txout) = r {
if !self.index.is_spk_owned(&full_txout.txout.script_pubkey) {
return false;
}
}
true
})
}

pub fn list_owned_unspents<'a, C: ChainOracle<Error = Infallible> + 'a>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a {
self.try_list_owned_unspents(chain, chain_tip)
.map(|r| r.expect("oracle is infallible"))
}

pub fn try_balance<C, F>(
&self,
chain: &C,
chain_tip: BlockId,
mut should_trust: F,
) -> Result<Balance, C::Error>
where
C: ChainOracle,
F: FnMut(&Script) -> bool,
{
let tip_height = chain_tip.height;

let mut immature = 0;
let mut trusted_pending = 0;
let mut untrusted_pending = 0;
let mut confirmed = 0;

for res in self.try_list_owned_unspents(chain, chain_tip) {
let txout = res?;

match &txout.chain_position {
ObservedAs::Confirmed(_) => {
if txout.is_confirmed_and_spendable(tip_height) {
confirmed += txout.txout.value;
} else if !txout.is_mature(tip_height) {
immature += txout.txout.value;
}
}
ObservedAs::Unconfirmed(_) => {
if should_trust(&txout.txout.script_pubkey) {
trusted_pending += txout.txout.value;
} else {
untrusted_pending += txout.txout.value;
}
}
}
}

Ok(Balance {
immature,
trusted_pending,
untrusted_pending,
confirmed,
})
}

pub fn balance<C, F>(&self, chain: &C, chain_tip: BlockId, should_trust: F) -> Balance
where
C: ChainOracle<Error = Infallible>,
F: FnMut(&Script) -> bool,
{
self.try_balance(chain, chain_tip, should_trust)
.expect("error is infallible")
}
}

/// A structure that represents changes to an [`IndexedTxGraph`].
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
Expand Down Expand Up @@ -301,6 +197,10 @@ impl<A: Anchor, IA: Append> Append for IndexedAdditions<A, IA> {
self.graph_additions.append(other.graph_additions);
self.index_additions.append(other.index_additions);
}

fn is_empty(&self) -> bool {
self.graph_additions.is_empty() && self.index_additions.is_empty()
}
}

/// Represents a structure that can index transaction data.
Expand All @@ -320,9 +220,3 @@ pub trait Indexer {
/// Determines whether the transaction should be included in the index.
fn is_tx_relevant(&self, tx: &Transaction) -> bool;
}

/// A trait that extends [`Indexer`] to also index "owned" script pubkeys.
pub trait OwnedIndexer: Indexer {
/// Determines whether a given script pubkey (`spk`) is owned.
fn is_spk_owned(&self, spk: &Script) -> bool;
}
4 changes: 4 additions & 0 deletions crates/chain/src/keychain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ impl<K: Ord> Append for DerivationAdditions<K> {

self.0.append(&mut other.0);
}

fn is_empty(&self) -> bool {
self.0.is_empty()
}
}

impl<K> Default for DerivationAdditions<K> {
Expand Down
13 changes: 6 additions & 7 deletions crates/chain/src/keychain/txout_index.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
collections::*,
indexed_tx_graph::{Indexer, OwnedIndexer},
indexed_tx_graph::Indexer,
miniscript::{Descriptor, DescriptorPublicKey},
spk_iter::BIP32_MAX_INDEX,
ForEachTxOut, SpkIterator, SpkTxOutIndex,
Expand Down Expand Up @@ -109,12 +109,6 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
}
}

impl<K: Clone + Ord + Debug> OwnedIndexer for KeychainTxOutIndex<K> {
fn is_spk_owned(&self, spk: &Script) -> bool {
self.index_of_spk(spk).is_some()
}
}

impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
/// Scans an object for relevant outpoints, which are stored and indexed internally.
///
Expand Down Expand Up @@ -153,6 +147,11 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
&self.inner
}

/// Get a reference to the set of indexed outpoints.
pub fn outpoints(&self) -> &BTreeSet<((K, u32), OutPoint)> {
self.inner.outpoints()
}

/// Return a reference to the internal map of the keychain to descriptors.
pub fn keychains(&self) -> &BTreeMap<K, Descriptor<DescriptorPublicKey>> {
&self.keychains
Expand Down
2 changes: 2 additions & 0 deletions crates/chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub mod tx_graph;
pub use tx_data_traits::*;
mod chain_oracle;
pub use chain_oracle::*;
mod persist;
pub use persist::*;

#[doc(hidden)]
pub mod example_utils;
Expand Down
97 changes: 97 additions & 0 deletions crates/chain/src/persist.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use core::convert::Infallible;

use crate::Append;

/// `Persist` wraps a [`PersistBackend`] (`B`) to create a convenient staging area for changes (`C`)
/// before they are persisted.
///
/// Not all changes to the in-memory representation needs to be written to disk right away, so
/// [`Persist::stage`] can be used to *stage* changes first and then [`Persist::commit`] can be used
/// to write changes to disk.
#[derive(Debug)]
pub struct Persist<B, C> {
backend: B,
stage: C,
}

impl<B, C> Persist<B, C>
where
B: PersistBackend<C>,
C: Default + Append,
{
/// Create a new [`Persist`] from [`PersistBackend`].
pub fn new(backend: B) -> Self {
Self {
backend,
stage: Default::default(),
}
}

/// Stage a `changeset` to be commited later with [`commit`].
///
/// [`commit`]: Self::commit
pub fn stage(&mut self, changeset: C) {
self.stage.append(changeset)
}

/// Get the changes that have not been commited yet.
pub fn staged(&self) -> &C {
&self.stage
}

/// Commit the staged changes to the underlying persistance backend.
///
/// Changes that are committed (if any) are returned.
///
/// # Error
///
/// Returns a backend-defined error if this fails.
pub fn commit(&mut self) -> Result<Option<C>, B::WriteError> {
if self.stage.is_empty() {
return Ok(None);
}
self.backend
.write_changes(&self.stage)
// if written successfully, take and return `self.stage`
.map(|_| Some(core::mem::take(&mut self.stage)))
}
}

/// A persistence backend for [`Persist`].
///
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
/// that are to be persisted, or retrieved from persistence.
pub trait PersistBackend<C> {
/// The error the backend returns when it fails to write.
type WriteError: core::fmt::Debug;

/// The error the backend returns when it fails to load changesets `C`.
type LoadError: core::fmt::Debug;

/// Writes a changeset to the persistence backend.
///
/// It is up to the backend what it does with this. It could store every changeset in a list or
/// it inserts the actual changes into a more structured database. All it needs to guarantee is
/// that [`load_from_persistence`] restores a keychain tracker to what it should be if all
/// changesets had been applied sequentially.
///
/// [`load_from_persistence`]: Self::load_from_persistence
fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError>;

/// Return the aggregate changeset `C` from persistence.
fn load_from_persistence(&mut self) -> Result<C, Self::LoadError>;
}

impl<C: Default> PersistBackend<C> for () {
type WriteError = Infallible;

type LoadError = Infallible;

fn write_changes(&mut self, _changeset: &C) -> Result<(), Self::WriteError> {
Ok(())
}

fn load_from_persistence(&mut self) -> Result<C, Self::LoadError> {
Ok(C::default())
}
}
13 changes: 6 additions & 7 deletions 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},
indexed_tx_graph::{Indexer, OwnedIndexer},
indexed_tx_graph::Indexer,
ForEachTxOut,
};
use bitcoin::{self, OutPoint, Script, Transaction, TxOut, Txid};
Expand Down Expand Up @@ -75,12 +75,6 @@ impl<I: Clone + Ord> Indexer for SpkTxOutIndex<I> {
}
}

impl<I: Clone + Ord + 'static> OwnedIndexer for SpkTxOutIndex<I> {
fn is_spk_owned(&self, spk: &Script) -> bool {
self.spk_indices.get(spk).is_some()
}
}

/// 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 Expand Up @@ -126,6 +120,11 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
scan_txout!(self, op, txout)
}

/// Get a reference to the set of indexed outpoints.
pub fn outpoints(&self) -> &BTreeSet<(I, OutPoint)> {
&self.spk_txouts
}

/// Iterate over all known txouts that spend to tracked script pubkeys.
pub fn txouts(
&self,
Expand Down
Loading

0 comments on commit b75c68b

Please sign in to comment.