Move bdk_chain into the bdk repo 🎉
Original repository: 250b4f1dcc/bdk_chain
Co-authored-by: Steve Myers <steve@notmandatory.org>
Co-authored-by: 志宇 <hello@evanlinjin.me>
Co-authored-by: LLFourn <lloyd.fourn@gmail.com>
Co-authored-by: rajarshimaitra <rajarshi149@gmail.com>
Co-authored-by: LagginTimes <wzc110@gmail.com>
Co-authored-by: Steve Myers <steve@notmandatory.org>
Co-authored-by: Vladimir Fomene <vladimirfomene@gmail.com>
This commit is contained in:
218
crates/chain/src/chain_data.rs
Normal file
218
crates/chain/src/chain_data.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid};
|
||||
|
||||
use crate::{
|
||||
sparse_chain::{self, ChainPosition},
|
||||
COINBASE_MATURITY,
|
||||
};
|
||||
|
||||
/// Represents the height in which a transaction is confirmed at.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(crate = "serde_crate")
|
||||
)]
|
||||
pub enum TxHeight {
|
||||
Confirmed(u32),
|
||||
Unconfirmed,
|
||||
}
|
||||
|
||||
impl Default for TxHeight {
|
||||
fn default() -> Self {
|
||||
Self::Unconfirmed
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for TxHeight {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::Confirmed(h) => core::write!(f, "confirmed_at({})", h),
|
||||
Self::Unconfirmed => core::write!(f, "unconfirmed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<u32>> for TxHeight {
|
||||
fn from(opt: Option<u32>) -> Self {
|
||||
match opt {
|
||||
Some(h) => Self::Confirmed(h),
|
||||
None => Self::Unconfirmed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TxHeight> for Option<u32> {
|
||||
fn from(height: TxHeight) -> Self {
|
||||
match height {
|
||||
TxHeight::Confirmed(h) => Some(h),
|
||||
TxHeight::Unconfirmed => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::sparse_chain::ChainPosition for TxHeight {
|
||||
fn height(&self) -> TxHeight {
|
||||
*self
|
||||
}
|
||||
|
||||
fn max_ord_of_height(height: TxHeight) -> Self {
|
||||
height
|
||||
}
|
||||
|
||||
fn min_ord_of_height(height: TxHeight) -> Self {
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
impl TxHeight {
|
||||
pub fn is_confirmed(&self) -> bool {
|
||||
matches!(self, Self::Confirmed(_))
|
||||
}
|
||||
}
|
||||
|
||||
/// Block height and timestamp in which a transaction is confirmed in.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(crate = "serde_crate")
|
||||
)]
|
||||
pub enum ConfirmationTime {
|
||||
Confirmed { height: u32, time: u64 },
|
||||
Unconfirmed,
|
||||
}
|
||||
|
||||
impl sparse_chain::ChainPosition for ConfirmationTime {
|
||||
fn height(&self) -> TxHeight {
|
||||
match self {
|
||||
ConfirmationTime::Confirmed { height, .. } => TxHeight::Confirmed(*height),
|
||||
ConfirmationTime::Unconfirmed => TxHeight::Unconfirmed,
|
||||
}
|
||||
}
|
||||
|
||||
fn max_ord_of_height(height: TxHeight) -> Self {
|
||||
match height {
|
||||
TxHeight::Confirmed(height) => Self::Confirmed {
|
||||
height,
|
||||
time: u64::MAX,
|
||||
},
|
||||
TxHeight::Unconfirmed => Self::Unconfirmed,
|
||||
}
|
||||
}
|
||||
|
||||
fn min_ord_of_height(height: TxHeight) -> Self {
|
||||
match height {
|
||||
TxHeight::Confirmed(height) => Self::Confirmed {
|
||||
height,
|
||||
time: u64::MIN,
|
||||
},
|
||||
TxHeight::Unconfirmed => Self::Unconfirmed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfirmationTime {
|
||||
pub fn is_confirmed(&self) -> bool {
|
||||
matches!(self, Self::Confirmed { .. })
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference to a block in the cannonical chain.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(crate = "serde_crate")
|
||||
)]
|
||||
pub struct BlockId {
|
||||
/// The height the block was confirmed at
|
||||
pub height: u32,
|
||||
/// The hash of the block
|
||||
pub hash: BlockHash,
|
||||
}
|
||||
|
||||
impl Default for BlockId {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
height: Default::default(),
|
||||
hash: BlockHash::from_inner([0u8; 32]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u32, BlockHash)> for BlockId {
|
||||
fn from((height, hash): (u32, BlockHash)) -> Self {
|
||||
Self { height, hash }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockId> for (u32, BlockHash) {
|
||||
fn from(block_id: BlockId) -> Self {
|
||||
(block_id.height, block_id.hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&u32, &BlockHash)> for BlockId {
|
||||
fn from((height, hash): (&u32, &BlockHash)) -> Self {
|
||||
Self {
|
||||
height: *height,
|
||||
hash: *hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `TxOut` with as much data as we can retreive about it
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FullTxOut<I> {
|
||||
/// The location of the `TxOut`
|
||||
pub outpoint: OutPoint,
|
||||
/// The `TxOut`
|
||||
pub txout: TxOut,
|
||||
/// The position of the transaction in `outpoint` in the overall chain.
|
||||
pub chain_position: I,
|
||||
/// The txid and chain position of the transaction (if any) that has spent this output.
|
||||
pub spent_by: Option<(I, Txid)>,
|
||||
/// Whether this output is on a coinbase transaction
|
||||
pub is_on_coinbase: bool,
|
||||
}
|
||||
|
||||
impl<I: ChainPosition> FullTxOut<I> {
|
||||
/// Whether the utxo is/was/will be spendable at `height`.
|
||||
///
|
||||
/// It is spendable if it is not an immature coinbase output and no spending tx has been
|
||||
/// confirmed by that heigt.
|
||||
pub fn is_spendable_at(&self, height: u32) -> bool {
|
||||
if !self.is_mature(height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.chain_position.height() > TxHeight::Confirmed(height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match &self.spent_by {
|
||||
Some((spending_height, _)) => spending_height.height() > TxHeight::Confirmed(height),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_mature(&self, height: u32) -> bool {
|
||||
if self.is_on_coinbase {
|
||||
let tx_height = match self.chain_position.height() {
|
||||
TxHeight::Confirmed(tx_height) => tx_height,
|
||||
TxHeight::Unconfirmed => {
|
||||
debug_assert!(false, "coinbase tx can never be unconfirmed");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let age = height.saturating_sub(tx_height);
|
||||
if age + 1 < COINBASE_MATURITY {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// TOOD: make test
|
||||
620
crates/chain/src/chain_graph.rs
Normal file
620
crates/chain/src/chain_graph.rs
Normal file
@@ -0,0 +1,620 @@
|
||||
//! Module for structures that combine the features of [`sparse_chain`] and [`tx_graph`].
|
||||
use crate::{
|
||||
collections::HashSet,
|
||||
sparse_chain::{self, ChainPosition, SparseChain},
|
||||
tx_graph::{self, TxGraph},
|
||||
AsTransaction, BlockId, ForEachTxOut, FullTxOut, IntoOwned, TxHeight,
|
||||
};
|
||||
use alloc::{borrow::Cow, string::ToString, vec::Vec};
|
||||
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
||||
use core::fmt::Debug;
|
||||
|
||||
/// A consistent combination of a [`SparseChain<P>`] and a [`TxGraph<T>`].
|
||||
///
|
||||
/// `SparseChain` only keeps track of transaction ids and their position in the chain but you often
|
||||
/// want to store the full transactions as well. Additionally you want to make sure that everything
|
||||
/// in the chain is consistent with the full transaction data. `ChainGraph` enforces these two
|
||||
/// invariants:
|
||||
///
|
||||
/// 1. Every transaction that is in the chain is also in the graph (you always have the full
|
||||
/// transaction).
|
||||
/// 2. No transactions in the chain conflict with each other i.e. they don't double spend each
|
||||
/// other or have ancestors that double spend each other.
|
||||
///
|
||||
/// Note that the `ChainGraph` guarantees a 1:1 mapping between transactions in the `chain` and
|
||||
/// `graph` but not the other way around. Transactions may fall out of the *chain* (via re-org or
|
||||
/// mempool eviction) but will remain in the *graph*.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ChainGraph<P = TxHeight, T = Transaction> {
|
||||
chain: SparseChain<P>,
|
||||
graph: TxGraph<T>,
|
||||
}
|
||||
|
||||
impl<P, T> Default for ChainGraph<P, T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
chain: Default::default(),
|
||||
graph: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> AsRef<SparseChain<P>> for ChainGraph<P, T> {
|
||||
fn as_ref(&self) -> &SparseChain<P> {
|
||||
&self.chain
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> AsRef<TxGraph<T>> for ChainGraph<P, T> {
|
||||
fn as_ref(&self) -> &TxGraph<T> {
|
||||
&self.graph
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> AsRef<ChainGraph<P, T>> for ChainGraph<P, T> {
|
||||
fn as_ref(&self) -> &ChainGraph<P, T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> ChainGraph<P, T> {
|
||||
/// Returns a reference to the internal [`SparseChain`].
|
||||
pub fn chain(&self) -> &SparseChain<P> {
|
||||
&self.chain
|
||||
}
|
||||
|
||||
/// Returns a reference to the internal [`TxGraph`].
|
||||
pub fn graph(&self) -> &TxGraph<T> {
|
||||
&self.graph
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> ChainGraph<P, T>
|
||||
where
|
||||
P: ChainPosition,
|
||||
T: AsTransaction + Clone + Ord,
|
||||
{
|
||||
/// Create a new chain graph from a `chain` and a `graph`.
|
||||
///
|
||||
/// There are two reasons this can return an `Err`:
|
||||
///
|
||||
/// 1. There is a transaction in the `chain` that does not have its corresponding full
|
||||
/// transaction in `graph`.
|
||||
/// 2. The `chain` has two transactions that allegedly in it but they conflict in the `graph`
|
||||
/// (so could not possibly be in the same chain).
|
||||
pub fn new(chain: SparseChain<P>, graph: TxGraph<T>) -> Result<Self, NewError<P>> {
|
||||
let mut missing = HashSet::default();
|
||||
for (pos, txid) in chain.txids() {
|
||||
if let Some(tx) = graph.get_tx(*txid) {
|
||||
let conflict = graph
|
||||
.walk_conflicts(tx.as_tx(), |_, txid| {
|
||||
Some((chain.tx_position(txid)?.clone(), txid))
|
||||
})
|
||||
.next();
|
||||
if let Some((conflict_pos, conflict)) = conflict {
|
||||
return Err(NewError::Conflict {
|
||||
a: (pos.clone(), *txid),
|
||||
b: (conflict_pos, conflict),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
missing.insert(*txid);
|
||||
}
|
||||
}
|
||||
|
||||
if !missing.is_empty() {
|
||||
return Err(NewError::Missing(missing));
|
||||
}
|
||||
|
||||
Ok(Self { chain, graph })
|
||||
}
|
||||
|
||||
/// Take an update in the form of a [`SparseChain<P>`][`SparseChain`] and attempt to turn it
|
||||
/// into a chain graph by filling in full transactions from `self` and from `new_txs`. This
|
||||
/// returns a `ChainGraph<P, Cow<T>>` where the [`Cow<'a, T>`] will borrow the transaction if it
|
||||
/// got it from `self`.
|
||||
///
|
||||
/// This is useful when interacting with services like an electrum server which returns a list
|
||||
/// of txids and heights when calling [`script_get_history`] which can easily be inserted into a
|
||||
/// [`SparseChain<TxHeight>`][`SparseChain`]. From there you need to figure out which full
|
||||
/// transactions you are missing in your chain graph and form `new_txs`. You then use
|
||||
/// `inflate_update` to turn this into an update `ChainGraph<P, Cow<Transaction>>` and finally
|
||||
/// use [`determine_changeset`] to generate the changeset from it.
|
||||
///
|
||||
/// [`SparseChain`]: crate::sparse_chain::SparseChain
|
||||
/// [`Cow<'a, T>`]: std::borrow::Cow
|
||||
/// [`script_get_history`]: https://docs.rs/electrum-client/latest/electrum_client/trait.ElectrumApi.html#tymethod.script_get_history
|
||||
/// [`determine_changeset`]: Self::determine_changeset
|
||||
pub fn inflate_update(
|
||||
&self,
|
||||
update: SparseChain<P>,
|
||||
new_txs: impl IntoIterator<Item = T>,
|
||||
) -> Result<ChainGraph<P, Cow<T>>, NewError<P>> {
|
||||
let mut inflated_graph = TxGraph::default();
|
||||
for (_, txid) in update.txids() {
|
||||
if let Some(tx) = self.graph.get_tx(*txid) {
|
||||
let _ = inflated_graph.insert_tx(Cow::Borrowed(tx));
|
||||
}
|
||||
}
|
||||
|
||||
for tx in new_txs {
|
||||
let _ = inflated_graph.insert_tx(Cow::Owned(tx));
|
||||
}
|
||||
|
||||
ChainGraph::new(update, inflated_graph)
|
||||
}
|
||||
|
||||
/// Sets the checkpoint limit.
|
||||
///
|
||||
/// Refer to [`SparseChain::checkpoint_limit`] for more.
|
||||
pub fn checkpoint_limit(&self) -> Option<usize> {
|
||||
self.chain.checkpoint_limit()
|
||||
}
|
||||
|
||||
/// Sets the checkpoint limit.
|
||||
///
|
||||
/// Refer to [`SparseChain::set_checkpoint_limit`] for more.
|
||||
pub fn set_checkpoint_limit(&mut self, limit: Option<usize>) {
|
||||
self.chain.set_checkpoint_limit(limit)
|
||||
}
|
||||
|
||||
/// Determines the changes required to invalidate checkpoints `from_height` (inclusive) and
|
||||
/// above. Displaced transactions will have their positions moved to [`TxHeight::Unconfirmed`].
|
||||
pub fn invalidate_checkpoints_preview(&self, from_height: u32) -> ChangeSet<P, T> {
|
||||
ChangeSet {
|
||||
chain: self.chain.invalidate_checkpoints_preview(from_height),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Invalidate checkpoints `from_height` (inclusive) and above. Displaced transactions will be
|
||||
/// re-positioned to [`TxHeight::Unconfirmed`].
|
||||
///
|
||||
/// This is equivalent to calling [`Self::invalidate_checkpoints_preview`] and
|
||||
/// [`Self::apply_changeset`] in sequence.
|
||||
pub fn invalidate_checkpoints(&mut self, from_height: u32) -> ChangeSet<P, T>
|
||||
where
|
||||
ChangeSet<P, T>: Clone,
|
||||
{
|
||||
let changeset = self.invalidate_checkpoints_preview(from_height);
|
||||
self.apply_changeset(changeset.clone());
|
||||
changeset
|
||||
}
|
||||
|
||||
/// Get a transaction that is currently in the underlying [`SparseChain`].
|
||||
///
|
||||
/// This does not necessarily mean that it is *confirmed* in the blockchain, it might just be in
|
||||
/// the unconfirmed transaction list within the [`SparseChain`].
|
||||
pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, &T)> {
|
||||
let position = self.chain.tx_position(txid)?;
|
||||
let full_tx = self.graph.get_tx(txid).expect("must exist");
|
||||
Some((position, full_tx))
|
||||
}
|
||||
|
||||
/// Determines the changes required to insert a transaction into the inner [`ChainGraph`] and
|
||||
/// [`SparseChain`] at the given `position`.
|
||||
///
|
||||
/// If inserting it into the chain `position` will result in conflicts, the returned
|
||||
/// [`ChangeSet`] should evict conflicting transactions.
|
||||
pub fn insert_tx_preview(&self, tx: T, pos: P) -> Result<ChangeSet<P, T>, InsertTxError<P>> {
|
||||
let mut changeset = ChangeSet {
|
||||
chain: self.chain.insert_tx_preview(tx.as_tx().txid(), pos)?,
|
||||
graph: self.graph.insert_tx_preview(tx),
|
||||
};
|
||||
self.fix_conflicts(&mut changeset)?;
|
||||
Ok(changeset)
|
||||
}
|
||||
|
||||
/// Inserts [`Transaction`] at given chain position.
|
||||
///
|
||||
/// This is equivalent to calling [`Self::insert_tx_preview`] and [`Self::apply_changeset`] in
|
||||
/// sequence.
|
||||
pub fn insert_tx(&mut self, tx: T, pos: P) -> Result<ChangeSet<P, T>, InsertTxError<P>> {
|
||||
let changeset = self.insert_tx_preview(tx, pos)?;
|
||||
self.apply_changeset(changeset.clone());
|
||||
Ok(changeset)
|
||||
}
|
||||
|
||||
/// Determines the changes required to insert a [`TxOut`] into the internal [`TxGraph`].
|
||||
pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<P, T> {
|
||||
ChangeSet {
|
||||
chain: Default::default(),
|
||||
graph: self.graph.insert_txout_preview(outpoint, txout),
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a [`TxOut`] into the internal [`TxGraph`].
|
||||
///
|
||||
/// This is equivalent to calling [`Self::insert_txout_preview`] and [`Self::apply_changeset`]
|
||||
/// in sequence.
|
||||
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<P, T> {
|
||||
let changeset = self.insert_txout_preview(outpoint, txout);
|
||||
self.apply_changeset(changeset.clone());
|
||||
changeset
|
||||
}
|
||||
|
||||
/// Determines the changes required to insert a `block_id` (a height and block hash) into the
|
||||
/// chain.
|
||||
///
|
||||
/// If a checkpoint already exists at that height with a different hash this will return
|
||||
/// an error.
|
||||
pub fn insert_checkpoint_preview(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
) -> Result<ChangeSet<P, T>, InsertCheckpointError> {
|
||||
self.chain
|
||||
.insert_checkpoint_preview(block_id)
|
||||
.map(|chain_changeset| ChangeSet {
|
||||
chain: chain_changeset,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Inserts checkpoint into [`Self`].
|
||||
///
|
||||
/// This is equivalent to calling [`Self::insert_checkpoint_preview`] and
|
||||
/// [`Self::apply_changeset`] in sequence.
|
||||
pub fn insert_checkpoint(
|
||||
&mut self,
|
||||
block_id: BlockId,
|
||||
) -> Result<ChangeSet<P, T>, InsertCheckpointError> {
|
||||
let changeset = self.insert_checkpoint_preview(block_id)?;
|
||||
self.apply_changeset(changeset.clone());
|
||||
Ok(changeset)
|
||||
}
|
||||
|
||||
/// Calculates the difference between self and `update` in the form of a [`ChangeSet`].
|
||||
pub fn determine_changeset<'a, T2>(
|
||||
&self,
|
||||
update: &ChainGraph<P, T2>,
|
||||
) -> Result<ChangeSet<P, T>, UpdateError<P>>
|
||||
where
|
||||
T2: IntoOwned<T> + Clone,
|
||||
{
|
||||
let chain_changeset = self
|
||||
.chain
|
||||
.determine_changeset(&update.chain)
|
||||
.map_err(UpdateError::Chain)?;
|
||||
|
||||
let mut changeset = ChangeSet {
|
||||
chain: chain_changeset,
|
||||
graph: self.graph.determine_additions(&update.graph),
|
||||
};
|
||||
|
||||
self.fix_conflicts(&mut changeset)?;
|
||||
Ok(changeset)
|
||||
}
|
||||
|
||||
/// Given a transaction, return an iterator of `txid`s that conflict with it (spends at least
|
||||
/// one of the same inputs). This includes all descendants of conflicting transactions.
|
||||
///
|
||||
/// This method only returns conflicts that exist in the [`SparseChain`] as transactions that
|
||||
/// are not included in [`SparseChain`] are already considered as evicted.
|
||||
pub fn tx_conflicts_in_chain<'a>(
|
||||
&'a self,
|
||||
tx: &'a Transaction,
|
||||
) -> impl Iterator<Item = (&'a P, Txid)> + 'a {
|
||||
self.graph.walk_conflicts(tx, |_, conflict_txid| {
|
||||
self.chain
|
||||
.tx_position(conflict_txid)
|
||||
.map(|conflict_pos| (conflict_pos, conflict_txid))
|
||||
})
|
||||
}
|
||||
|
||||
/// Fix changeset conflicts.
|
||||
///
|
||||
/// **WARNING:** If there are any missing full txs, conflict resolution will not be complete. In
|
||||
/// debug mode, this will result in panic.
|
||||
fn fix_conflicts(
|
||||
&self,
|
||||
changeset: &mut ChangeSet<P, T>,
|
||||
) -> Result<(), UnresolvableConflict<P>> {
|
||||
let chain_conflicts = changeset
|
||||
.chain
|
||||
.txids
|
||||
.iter()
|
||||
// we want to find new txid additions by the changeset (all txid entries in the
|
||||
// changeset with Some(position_change))
|
||||
.filter_map(|(&txid, pos_change)| pos_change.as_ref().map(|pos| (txid, pos)))
|
||||
// we don't care about txids that move, only newly added txids
|
||||
.filter(|&(txid, _)| self.chain.tx_position(txid).is_none())
|
||||
// full tx should exist (either in graph, or additions)
|
||||
.filter_map(|(txid, pos)| {
|
||||
let full_tx = self
|
||||
.graph
|
||||
.get_tx(txid)
|
||||
.or_else(|| {
|
||||
changeset
|
||||
.graph
|
||||
.tx
|
||||
.iter()
|
||||
.find(|tx| tx.as_tx().txid() == txid)
|
||||
})
|
||||
.map(|tx| (txid, tx, pos));
|
||||
debug_assert!(full_tx.is_some(), "should have full tx at this point");
|
||||
full_tx
|
||||
})
|
||||
.flat_map(|(new_txid, new_tx, new_pos)| {
|
||||
self.tx_conflicts_in_chain(new_tx.as_tx()).map(
|
||||
move |(conflict_pos, conflict_txid)| {
|
||||
(new_pos.clone(), new_txid, conflict_pos, conflict_txid)
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (update_pos, update_txid, conflicting_pos, conflicting_txid) in chain_conflicts {
|
||||
// We have found a tx that conflicts with our update txid. Only allow this when the
|
||||
// conflicting tx will be positioned as "unconfirmed" after the update is applied.
|
||||
// If so, we will modify the changeset to evict the conflicting txid.
|
||||
|
||||
// determine the position of the conflicting txid after current changeset is applied
|
||||
let conflicting_new_pos = changeset
|
||||
.chain
|
||||
.txids
|
||||
.get(&conflicting_txid)
|
||||
.map(Option::as_ref)
|
||||
.unwrap_or(Some(conflicting_pos));
|
||||
|
||||
match conflicting_new_pos {
|
||||
None => {
|
||||
// conflicting txid will be deleted, can ignore
|
||||
}
|
||||
Some(existing_new_pos) => match existing_new_pos.height() {
|
||||
TxHeight::Confirmed(_) => {
|
||||
// the new postion of the conflicting tx is "confirmed", therefore cannot be
|
||||
// evicted, return error
|
||||
return Err(UnresolvableConflict {
|
||||
already_confirmed_tx: (conflicting_pos.clone(), conflicting_txid),
|
||||
update_tx: (update_pos.clone(), update_txid),
|
||||
});
|
||||
}
|
||||
TxHeight::Unconfirmed => {
|
||||
// the new position of the conflicting tx is "unconfirmed", therefore it can
|
||||
// be evicted
|
||||
changeset.chain.txids.insert(conflicting_txid, None);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Applies `changeset` to `self`.
|
||||
///
|
||||
/// **Warning** this method assumes the changeset is assumed to be correctly formed. If it isn't
|
||||
/// then the chain graph may not behave correctly in the future and may panic unexpectedly.
|
||||
pub fn apply_changeset(&mut self, changeset: ChangeSet<P, T>) {
|
||||
self.chain.apply_changeset(changeset.chain);
|
||||
self.graph.apply_additions(changeset.graph);
|
||||
}
|
||||
|
||||
/// Applies the `update` chain graph. Note this is shorthand for calling
|
||||
/// [`Self::determine_changeset()`] and [`Self::apply_changeset()`] in sequence.
|
||||
pub fn apply_update<T2: IntoOwned<T> + Clone>(
|
||||
&mut self,
|
||||
update: ChainGraph<P, T2>,
|
||||
) -> Result<ChangeSet<P, T>, UpdateError<P>> {
|
||||
let changeset = self.determine_changeset(&update)?;
|
||||
self.apply_changeset(changeset.clone());
|
||||
Ok(changeset)
|
||||
}
|
||||
|
||||
/// Get the full transaction output at an outpoint if it exists in the chain and the graph.
|
||||
pub fn full_txout(&self, outpoint: OutPoint) -> Option<FullTxOut<P>> {
|
||||
self.chain.full_txout(&self.graph, outpoint)
|
||||
}
|
||||
|
||||
/// Iterate over the full transactions and their position in the chain ordered by their position
|
||||
/// in ascending order.
|
||||
pub fn transactions_in_chain(&self) -> impl DoubleEndedIterator<Item = (&P, &T)> {
|
||||
self.chain
|
||||
.txids()
|
||||
.map(|(pos, txid)| (pos, self.graph.get_tx(*txid).expect("must exist")))
|
||||
}
|
||||
|
||||
/// Finds the transaction in the chain that spends `outpoint` given the input/output
|
||||
/// relationships in `graph`. Note that the transaction including `outpoint` does not need to be
|
||||
/// in the `graph` or the `chain` for this to return `Some(_)`.
|
||||
pub fn spent_by(&self, outpoint: OutPoint) -> Option<(&P, Txid)> {
|
||||
self.chain.spent_by(&self.graph, outpoint)
|
||||
}
|
||||
|
||||
/// Whether the chain graph contains any data whatsoever.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.chain.is_empty() && self.graph.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents changes to [`ChainGraph`].
|
||||
///
|
||||
/// This is essentially a combination of [`sparse_chain::ChangeSet`] and [`tx_graph::Additions`].
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(
|
||||
crate = "serde_crate",
|
||||
bound(
|
||||
deserialize = "P: serde::Deserialize<'de>, T: Ord + serde::Deserialize<'de>",
|
||||
serialize = "P: serde::Serialize, T: Ord + serde::Serialize"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[must_use]
|
||||
pub struct ChangeSet<P, T> {
|
||||
pub chain: sparse_chain::ChangeSet<P>,
|
||||
pub graph: tx_graph::Additions<T>,
|
||||
}
|
||||
|
||||
impl<P, T> ChangeSet<P, T> {
|
||||
/// Returns `true` if this [`ChangeSet`] records no changes.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.chain.is_empty() && self.graph.is_empty()
|
||||
}
|
||||
|
||||
/// Returns `true` if this [`ChangeSet`] contains transaction evictions.
|
||||
pub fn contains_eviction(&self) -> bool {
|
||||
self.chain
|
||||
.txids
|
||||
.iter()
|
||||
.any(|(_, new_pos)| new_pos.is_none())
|
||||
}
|
||||
|
||||
/// Appends the changes in `other` into self such that applying `self` afterwards has the same
|
||||
/// effect as sequentially applying the original `self` and `other`.
|
||||
pub fn append(&mut self, other: ChangeSet<P, T>)
|
||||
where
|
||||
P: ChainPosition,
|
||||
T: Ord,
|
||||
{
|
||||
self.chain.append(other.chain);
|
||||
self.graph.append(other.graph);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> Default for ChangeSet<P, T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
chain: Default::default(),
|
||||
graph: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T: AsTransaction> ForEachTxOut for ChainGraph<P, T> {
|
||||
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
|
||||
self.graph.for_each_txout(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T: AsTransaction> ForEachTxOut for ChangeSet<P, T> {
|
||||
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
|
||||
self.graph.for_each_txout(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that may occur when calling [`ChainGraph::new`].
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum NewError<P> {
|
||||
/// Two transactions within the sparse chain conflicted with each other
|
||||
Conflict { a: (P, Txid), b: (P, Txid) },
|
||||
/// One or more transactions in the chain were not in the graph
|
||||
Missing(HashSet<Txid>),
|
||||
}
|
||||
|
||||
impl<P: core::fmt::Debug> core::fmt::Display for NewError<P> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
NewError::Conflict { a, b } => write!(
|
||||
f,
|
||||
"Unable to inflate sparse chain to chain graph since transactions {:?} and {:?}",
|
||||
a, b
|
||||
),
|
||||
NewError::Missing(missing) => write!(
|
||||
f,
|
||||
"missing full transactions for {}",
|
||||
missing
|
||||
.into_iter()
|
||||
.map(|txid| txid.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<P: core::fmt::Debug> std::error::Error for NewError<P> {}
|
||||
|
||||
/// Error that may occur when inserting a transaction.
|
||||
///
|
||||
/// Refer to [`ChainGraph::insert_tx_preview`] and [`ChainGraph::insert_tx`].
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum InsertTxError<P> {
|
||||
Chain(sparse_chain::InsertTxError<P>),
|
||||
UnresolvableConflict(UnresolvableConflict<P>),
|
||||
}
|
||||
|
||||
impl<P: core::fmt::Debug> core::fmt::Display for InsertTxError<P> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
InsertTxError::Chain(inner) => core::fmt::Display::fmt(inner, f),
|
||||
InsertTxError::UnresolvableConflict(inner) => core::fmt::Display::fmt(inner, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> From<sparse_chain::InsertTxError<P>> for InsertTxError<P> {
|
||||
fn from(inner: sparse_chain::InsertTxError<P>) -> Self {
|
||||
Self::Chain(inner)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<P: core::fmt::Debug> std::error::Error for InsertTxError<P> {}
|
||||
|
||||
/// A nice alias of [`sparse_chain::InsertCheckpointError`].
|
||||
pub type InsertCheckpointError = sparse_chain::InsertCheckpointError;
|
||||
|
||||
/// Represents an update failure.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum UpdateError<P> {
|
||||
/// The update chain was inconsistent with the existing chain
|
||||
Chain(sparse_chain::UpdateError<P>),
|
||||
/// A transaction in the update spent the same input as an already confirmed transaction
|
||||
UnresolvableConflict(UnresolvableConflict<P>),
|
||||
}
|
||||
|
||||
impl<P: core::fmt::Debug> core::fmt::Display for UpdateError<P> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
UpdateError::Chain(inner) => core::fmt::Display::fmt(inner, f),
|
||||
UpdateError::UnresolvableConflict(inner) => core::fmt::Display::fmt(inner, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> From<sparse_chain::UpdateError<P>> for UpdateError<P> {
|
||||
fn from(inner: sparse_chain::UpdateError<P>) -> Self {
|
||||
Self::Chain(inner)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<P: core::fmt::Debug> std::error::Error for UpdateError<P> {}
|
||||
|
||||
/// Represents an unresolvable conflict between an update's transaction and an
|
||||
/// already-confirmed transaction.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct UnresolvableConflict<P> {
|
||||
pub already_confirmed_tx: (P, Txid),
|
||||
pub update_tx: (P, Txid),
|
||||
}
|
||||
|
||||
impl<P: core::fmt::Debug> core::fmt::Display for UnresolvableConflict<P> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let Self {
|
||||
already_confirmed_tx,
|
||||
update_tx,
|
||||
} = self;
|
||||
write!(f, "update transaction {} at height {:?} conflicts with an already confirmed transaction {} at height {:?}",
|
||||
update_tx.1, update_tx.0, already_confirmed_tx.1, already_confirmed_tx.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> From<UnresolvableConflict<P>> for UpdateError<P> {
|
||||
fn from(inner: UnresolvableConflict<P>) -> Self {
|
||||
Self::UnresolvableConflict(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> From<UnresolvableConflict<P>> for InsertTxError<P> {
|
||||
fn from(inner: UnresolvableConflict<P>) -> Self {
|
||||
Self::UnresolvableConflict(inner)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<P: core::fmt::Debug> std::error::Error for UnresolvableConflict<P> {}
|
||||
16
crates/chain/src/descriptor_ext.rs
Normal file
16
crates/chain/src/descriptor_ext.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use crate::miniscript::{Descriptor, DescriptorPublicKey};
|
||||
|
||||
/// A trait to extend the functionality of a miniscript descriptor.
|
||||
pub trait DescriptorExt {
|
||||
/// Returns the minimum value (in satoshis) that an output should have to be broadcastable.
|
||||
fn dust_value(&self) -> u64;
|
||||
}
|
||||
|
||||
impl DescriptorExt for Descriptor<DescriptorPublicKey> {
|
||||
fn dust_value(&self) -> u64 {
|
||||
self.at_derivation_index(0)
|
||||
.script_pubkey()
|
||||
.dust_value()
|
||||
.to_sat()
|
||||
}
|
||||
}
|
||||
30
crates/chain/src/example_utils.rs
Normal file
30
crates/chain/src/example_utils.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
#![allow(unused)]
|
||||
use alloc::vec::Vec;
|
||||
use bitcoin::{
|
||||
consensus,
|
||||
hashes::{hex::FromHex, Hash},
|
||||
Transaction,
|
||||
};
|
||||
|
||||
use crate::BlockId;
|
||||
|
||||
pub const RAW_TX_1: &'static str = "0200000000010116d6174da7183d70d0a7d4dc314d517a7d135db79ad63515028b293a76f4f9d10000000000feffffff023a21fc8350060000160014531c405e1881ef192294b8813631e258bf98ea7a1027000000000000225120a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c024730440220591b1a172a122da49ba79a3e79f98aaa03fd7a372f9760da18890b6a327e6010022013e82319231da6c99abf8123d7c07e13cf9bd8d76e113e18dc452e5024db156d012102318a2d558b2936c52e320decd6d92a88d7f530be91b6fe0af5caf41661e77da3ef2e0100";
|
||||
pub const RAW_TX_2: &'static str = "02000000000101a688607020cfae91a61e7c516b5ef1264d5d77f17200c3866826c6c808ebf1620000000000feffffff021027000000000000225120a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c20fd48ff530600001600146886c525e41d4522042bd0b159dfbade2504a6bb024730440220740ff7e665cd20565d4296b549df8d26b941be3f1e3af89a0b60e50c0dbeb69a02206213ab7030cf6edc6c90d4ccf33010644261e029950a688dc0b1a9ebe6ddcc5a012102f2ac6b396a97853cb6cd62242c8ae4842024742074475023532a51e9c53194253e760100";
|
||||
pub const RAW_TX_3: &'static str = "0200000000010135d67ee47b557e68b8c6223958f597381965ed719f1207ee2b9e20432a24a5dc0100000000feffffff021027000000000000225120a82f29944d65b86ae6b5e5cc75e294ead6c59391a1edc5e016e3498c67fc7bbb62215a5055060000160014070df7671dea67a50c4799a744b5c9be8f4bac690247304402207ebf8d29f71fd03e7e6977b3ea78ca5fcc5c49a42ae822348fc401862fdd766c02201d7e4ff0684ecb008b6142f36ead1b0b4d615524c4f58c261113d361f4427e25012103e6a75e2fab85e5ecad641afc4ffba7222f998649d9f18cac92f0fcc8618883b3ee760100";
|
||||
pub const RAW_TX_4: &'static str = "02000000000101d00e8f76ed313e19b339ee293c0f52b0325c95e24c8f3966fa353fb2bedbcf580100000000feffffff021027000000000000225120882d74e5d0572d5a816cef0041a96b6c1de832f6f9676d9605c44d5e9a97d3dc9cda55fe53060000160014852b5864b8edd42fab4060c87f818e50780865ff0247304402201dccbb9bed7fba924b6d249c5837cc9b37470c0e3d8fbea77cb59baba3efe6fa0220700cc170916913b9bfc2bc0fefb6af776e8b542c561702f136cddc1c7aa43141012103acec3fc79dbbca745815c2a807dc4e81010c80e308e84913f59cb42a275dad97f3760100";
|
||||
|
||||
pub fn tx_from_hex(s: &str) -> Transaction {
|
||||
let raw = Vec::from_hex(s).expect("data must be in hex");
|
||||
consensus::deserialize(raw.as_slice()).expect("must deserialize")
|
||||
}
|
||||
|
||||
pub fn new_hash<H: Hash>(s: &str) -> H {
|
||||
<H as bitcoin::hashes::Hash>::hash(s.as_bytes())
|
||||
}
|
||||
|
||||
pub fn new_block_id(height: u32, hash: &str) -> BlockId {
|
||||
BlockId {
|
||||
height,
|
||||
hash: new_hash(hash),
|
||||
}
|
||||
}
|
||||
313
crates/chain/src/keychain.rs
Normal file
313
crates/chain/src/keychain.rs
Normal file
@@ -0,0 +1,313 @@
|
||||
//! Module for keychain based structures.
|
||||
//!
|
||||
//! A keychain here is a set of application defined indexes for a minscript descriptor where we can
|
||||
//! derive script pubkeys at a particular derivation index. The application's index is simply
|
||||
//! anything that implements `Ord`.
|
||||
//!
|
||||
//! [`KeychainTxOutIndex`] indexes script pubkeys of keychains and scans in relevant outpoints (that
|
||||
//! has a `txout` containing an indexed script pubkey). Internally, this uses [`SpkTxOutIndex`], but
|
||||
//! also maintains "revealed" and "lookahead" index count per keychain.
|
||||
//!
|
||||
//! [`KeychainTracker`] combines [`ChainGraph`] and [`KeychainTxOutIndex`] and enforces atomic
|
||||
//! changes between both these structures. [`KeychainScan`] is a structure used to update to
|
||||
//! [`KeychainTracker`] and changes made on a [`KeychainTracker`] are reported by
|
||||
//! [`KeychainChangeSet`]s.
|
||||
//!
|
||||
//! [`SpkTxOutIndex`]: crate::SpkTxOutIndex
|
||||
use crate::{
|
||||
chain_graph::{self, ChainGraph},
|
||||
collections::BTreeMap,
|
||||
sparse_chain::ChainPosition,
|
||||
tx_graph::TxGraph,
|
||||
AsTransaction, ForEachTxOut,
|
||||
};
|
||||
use bitcoin::Transaction;
|
||||
|
||||
#[cfg(feature = "miniscript")]
|
||||
pub mod persist;
|
||||
#[cfg(feature = "miniscript")]
|
||||
pub use persist::*;
|
||||
#[cfg(feature = "miniscript")]
|
||||
mod tracker;
|
||||
#[cfg(feature = "miniscript")]
|
||||
pub use tracker::*;
|
||||
#[cfg(feature = "miniscript")]
|
||||
mod txout_index;
|
||||
#[cfg(feature = "miniscript")]
|
||||
pub use txout_index::*;
|
||||
|
||||
/// Represents updates to the derivation index of a [`KeychainTxOutIndex`].
|
||||
///
|
||||
/// It can be applied to [`KeychainTxOutIndex`] with [`apply_additions`]. [`DerivationAdditions] are
|
||||
/// monotone in that they will never decrease the revealed derivation index.
|
||||
///
|
||||
/// [`KeychainTxOutIndex`]: crate::keychain::KeychainTxOutIndex
|
||||
/// [`apply_additions`]: crate::keychain::KeychainTxOutIndex::apply_additions
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(
|
||||
crate = "serde_crate",
|
||||
bound(
|
||||
deserialize = "K: Ord + serde::Deserialize<'de>",
|
||||
serialize = "K: Ord + serde::Serialize"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[must_use]
|
||||
pub struct DerivationAdditions<K>(BTreeMap<K, u32>);
|
||||
|
||||
impl<K> DerivationAdditions<K> {
|
||||
/// Returns whether the additions are empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Get the inner map of keychain to its new derivation index.
|
||||
pub fn as_inner(&self) -> &BTreeMap<K, u32> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Ord> DerivationAdditions<K> {
|
||||
/// Append another [`DerivationAdditions`] into self.
|
||||
///
|
||||
/// If keychain already exists, increases the index when other's index > self's index.
|
||||
/// If keychain did not exist, append the new keychain.
|
||||
pub fn append(&mut self, mut other: Self) {
|
||||
self.0.iter_mut().for_each(|(key, index)| {
|
||||
if let Some(other_index) = other.0.remove(key) {
|
||||
*index = other_index.max(*index);
|
||||
}
|
||||
});
|
||||
|
||||
self.0.append(&mut other.0);
|
||||
}
|
||||
}
|
||||
|
||||
impl<K> Default for DerivationAdditions<K> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<K> AsRef<BTreeMap<K, u32>> for DerivationAdditions<K> {
|
||||
fn as_ref(&self) -> &BTreeMap<K, u32> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
/// An update that includes the last active indexes of each keychain.
|
||||
pub struct KeychainScan<K, P, T = Transaction> {
|
||||
/// The update data in the form of a chain that could be applied
|
||||
pub update: ChainGraph<P, T>,
|
||||
/// The last active indexes of each keychain
|
||||
pub last_active_indices: BTreeMap<K, u32>,
|
||||
}
|
||||
|
||||
impl<K, P, T> Default for KeychainScan<K, P, T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
update: Default::default(),
|
||||
last_active_indices: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, P, T> From<ChainGraph<P, T>> for KeychainScan<K, P, T> {
|
||||
fn from(update: ChainGraph<P, T>) -> Self {
|
||||
KeychainScan {
|
||||
update,
|
||||
last_active_indices: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents changes to a [`KeychainTracker`].
|
||||
///
|
||||
/// This is essentially a combination of [`DerivationAdditions`] and [`chain_graph::ChangeSet`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(
|
||||
crate = "serde_crate",
|
||||
bound(
|
||||
deserialize = "K: Ord + serde::Deserialize<'de>, P: serde::Deserialize<'de>, T: Ord + serde::Deserialize<'de>",
|
||||
serialize = "K: Ord + serde::Serialize, P: serde::Serialize, T: Ord+ serde::Serialize"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[must_use]
|
||||
pub struct KeychainChangeSet<K, P, T = Transaction> {
|
||||
/// The changes in local keychain derivation indices
|
||||
pub derivation_indices: DerivationAdditions<K>,
|
||||
/// The changes that have occurred in the blockchain
|
||||
pub chain_graph: chain_graph::ChangeSet<P, T>,
|
||||
}
|
||||
|
||||
impl<K, P, T> Default for KeychainChangeSet<K, P, T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
chain_graph: Default::default(),
|
||||
derivation_indices: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, P, T> KeychainChangeSet<K, P, T> {
|
||||
/// Returns whether the [`KeychainChangeSet`] is empty (no changes recorded).
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.chain_graph.is_empty() && self.derivation_indices.is_empty()
|
||||
}
|
||||
|
||||
/// Appends the changes in `other` into `self` such that applying `self` afterwards has the same
|
||||
/// effect as sequentially applying the original `self` and `other`.
|
||||
///
|
||||
/// Note the derivation indices cannot be decreased so `other` will only change the derivation
|
||||
/// index for a keychain if it's entry is higher than the one in `self`.
|
||||
pub fn append(&mut self, other: KeychainChangeSet<K, P, T>)
|
||||
where
|
||||
K: Ord,
|
||||
P: ChainPosition,
|
||||
T: Ord,
|
||||
{
|
||||
self.derivation_indices.append(other.derivation_indices);
|
||||
self.chain_graph.append(other.chain_graph);
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, P, T> From<chain_graph::ChangeSet<P, T>> for KeychainChangeSet<K, P, T> {
|
||||
fn from(changeset: chain_graph::ChangeSet<P, T>) -> Self {
|
||||
Self {
|
||||
chain_graph: changeset,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, P, T> From<DerivationAdditions<K>> for KeychainChangeSet<K, P, T> {
|
||||
fn from(additions: DerivationAdditions<K>) -> Self {
|
||||
Self {
|
||||
derivation_indices: additions,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, P, T> AsRef<TxGraph<T>> for KeychainScan<K, P, T> {
|
||||
fn as_ref(&self) -> &TxGraph<T> {
|
||||
self.update.graph()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, P, T: AsTransaction> ForEachTxOut for KeychainChangeSet<K, P, T> {
|
||||
fn for_each_txout(&self, f: impl FnMut((bitcoin::OutPoint, &bitcoin::TxOut))) {
|
||||
self.chain_graph.for_each_txout(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Balance differentiated in various categories.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(crate = "serde_crate",)
|
||||
)]
|
||||
pub struct Balance {
|
||||
/// All coinbase outputs not yet matured
|
||||
pub immature: u64,
|
||||
/// Unconfirmed UTXOs generated by a wallet tx
|
||||
pub trusted_pending: u64,
|
||||
/// Unconfirmed UTXOs received from an external wallet
|
||||
pub untrusted_pending: u64,
|
||||
/// Confirmed and immediately spendable balance
|
||||
pub confirmed: u64,
|
||||
}
|
||||
|
||||
impl Balance {
|
||||
/// Get sum of trusted_pending and confirmed coins.
|
||||
///
|
||||
/// This is the balance you can spend right now that shouldn't get cancelled via another party
|
||||
/// double spending it.
|
||||
pub fn trusted_spendable(&self) -> u64 {
|
||||
self.confirmed + self.trusted_pending
|
||||
}
|
||||
|
||||
/// Get the whole balance visible to the wallet.
|
||||
pub fn total(&self) -> u64 {
|
||||
self.confirmed + self.trusted_pending + self.untrusted_pending + self.immature
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Balance {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{{ immature: {}, trusted_pending: {}, untrusted_pending: {}, confirmed: {} }}",
|
||||
self.immature, self.trusted_pending, self.untrusted_pending, self.confirmed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Add for Balance {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self {
|
||||
immature: self.immature + other.immature,
|
||||
trusted_pending: self.trusted_pending + other.trusted_pending,
|
||||
untrusted_pending: self.untrusted_pending + other.untrusted_pending,
|
||||
confirmed: self.confirmed + other.confirmed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use bitcoin::Transaction;
|
||||
|
||||
use crate::TxHeight;
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn append_keychain_derivation_indices() {
|
||||
#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug)]
|
||||
enum Keychain {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
let mut lhs_di = BTreeMap::<Keychain, u32>::default();
|
||||
let mut rhs_di = BTreeMap::<Keychain, u32>::default();
|
||||
lhs_di.insert(Keychain::One, 7);
|
||||
lhs_di.insert(Keychain::Two, 0);
|
||||
rhs_di.insert(Keychain::One, 3);
|
||||
rhs_di.insert(Keychain::Two, 5);
|
||||
lhs_di.insert(Keychain::Three, 3);
|
||||
rhs_di.insert(Keychain::Four, 4);
|
||||
let mut lhs = KeychainChangeSet {
|
||||
derivation_indices: DerivationAdditions(lhs_di),
|
||||
chain_graph: chain_graph::ChangeSet::<TxHeight, Transaction>::default(),
|
||||
};
|
||||
|
||||
let rhs = KeychainChangeSet {
|
||||
derivation_indices: DerivationAdditions(rhs_di),
|
||||
chain_graph: chain_graph::ChangeSet::<TxHeight, Transaction>::default(),
|
||||
};
|
||||
|
||||
lhs.append(rhs);
|
||||
|
||||
// Exiting index doesn't update if new index in `other` is lower than `self`
|
||||
assert_eq!(lhs.derivation_indices.0.get(&Keychain::One), Some(&7));
|
||||
// Existing index updates if new index in `other` is higher than `self.
|
||||
assert_eq!(lhs.derivation_indices.0.get(&Keychain::Two), Some(&5));
|
||||
// Existing index unchanged, if keychain doesn't exist in `other`
|
||||
assert_eq!(lhs.derivation_indices.0.get(&Keychain::Three), Some(&3));
|
||||
// New keychain gets added if keychain is in `other`, but not in `self`.
|
||||
assert_eq!(lhs.derivation_indices.0.get(&Keychain::Four), Some(&4));
|
||||
}
|
||||
}
|
||||
105
crates/chain/src/keychain/persist.rs
Normal file
105
crates/chain/src/keychain/persist.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
//! Persistence for changes made to a [`KeychainTracker`].
|
||||
//!
|
||||
//! BDK's [`KeychainTracker`] needs somewhere to persist changes it makes during operation.
|
||||
//! Operations like giving out a new address are crucial to persist so that next time the
|
||||
//! application is loaded it can find transactions related to that address.
|
||||
//!
|
||||
//! Note that the [`KeychainTracker`] does not read this persisted data during operation since it
|
||||
//! always has a copy in memory.
|
||||
//!
|
||||
//! [`KeychainTracker`]: crate::keychain::KeychainTracker
|
||||
|
||||
use crate::{keychain, sparse_chain::ChainPosition};
|
||||
|
||||
/// `Persist` wraps a [`PersistBackend`] to create a convenient staging area for changes before they
|
||||
/// are persisted. Not all changes made to the [`KeychainTracker`] need to be written to disk right
|
||||
/// away so you can use [`Persist::stage`] to *stage* it first and then [`Persist::commit`] to
|
||||
/// finally write it to disk.
|
||||
///
|
||||
/// [`KeychainTracker`]: keychain::KeychainTracker
|
||||
#[derive(Debug)]
|
||||
pub struct Persist<K, P, B> {
|
||||
backend: B,
|
||||
stage: keychain::KeychainChangeSet<K, P>,
|
||||
}
|
||||
|
||||
impl<K, P, B: PersistBackend<K, P>> Persist<K, P, B> {
|
||||
/// Create a new `Persist` from a [`PersistBackend`].
|
||||
pub fn new(backend: B) -> Self {
|
||||
Self {
|
||||
backend,
|
||||
stage: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Stage a `changeset` to later persistence with [`commit`].
|
||||
///
|
||||
/// [`commit`]: Self::commit
|
||||
pub fn stage(&mut self, changeset: keychain::KeychainChangeSet<K, P>)
|
||||
where
|
||||
K: Ord,
|
||||
P: ChainPosition,
|
||||
{
|
||||
self.stage.append(changeset)
|
||||
}
|
||||
|
||||
/// Get the changes that haven't been commited yet
|
||||
pub fn staged(&self) -> &keychain::KeychainChangeSet<K, P> {
|
||||
&self.stage
|
||||
}
|
||||
|
||||
/// Commit the staged changes to the underlying persistence backend.
|
||||
///
|
||||
/// Retuns a backend defined error if this fails
|
||||
pub fn commit(&mut self) -> Result<(), B::WriteError> {
|
||||
self.backend.append_changeset(&self.stage)?;
|
||||
self.stage = Default::default();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A persistence backend for [`Persist`].
|
||||
pub trait PersistBackend<K, P> {
|
||||
/// 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.
|
||||
type LoadError: core::fmt::Debug;
|
||||
|
||||
/// Appends a new changeset to the persistance backend.
|
||||
///
|
||||
/// It is up to the backend what it does with this. It could store every changeset in a list or
|
||||
/// it insert the actual changes to a more structured database. All it needs to guarantee is
|
||||
/// that [`load_into_keychain_tracker`] restores a keychain tracker to what it should be if all
|
||||
/// changesets had been applied sequentially.
|
||||
///
|
||||
/// [`load_into_keychain_tracker`]: Self::load_into_keychain_tracker
|
||||
fn append_changeset(
|
||||
&mut self,
|
||||
changeset: &keychain::KeychainChangeSet<K, P>,
|
||||
) -> Result<(), Self::WriteError>;
|
||||
|
||||
/// Applies all the changesets the backend has received to `tracker`.
|
||||
fn load_into_keychain_tracker(
|
||||
&mut self,
|
||||
tracker: &mut keychain::KeychainTracker<K, P>,
|
||||
) -> Result<(), Self::LoadError>;
|
||||
}
|
||||
|
||||
impl<K, P> PersistBackend<K, P> for () {
|
||||
type WriteError = ();
|
||||
type LoadError = ();
|
||||
|
||||
fn append_changeset(
|
||||
&mut self,
|
||||
_changeset: &keychain::KeychainChangeSet<K, P>,
|
||||
) -> Result<(), Self::WriteError> {
|
||||
Ok(())
|
||||
}
|
||||
fn load_into_keychain_tracker(
|
||||
&mut self,
|
||||
_tracker: &mut keychain::KeychainTracker<K, P>,
|
||||
) -> Result<(), Self::LoadError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
315
crates/chain/src/keychain/tracker.rs
Normal file
315
crates/chain/src/keychain/tracker.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
use bitcoin::Transaction;
|
||||
use miniscript::{Descriptor, DescriptorPublicKey};
|
||||
|
||||
use crate::{
|
||||
chain_graph::{self, ChainGraph},
|
||||
collections::*,
|
||||
keychain::{KeychainChangeSet, KeychainScan, KeychainTxOutIndex},
|
||||
sparse_chain::{self, SparseChain},
|
||||
tx_graph::TxGraph,
|
||||
AsTransaction, BlockId, FullTxOut, IntoOwned, TxHeight,
|
||||
};
|
||||
|
||||
use super::{Balance, DerivationAdditions};
|
||||
|
||||
/// A convenient combination of a [`KeychainTxOutIndex`] and a [`ChainGraph`].
|
||||
///
|
||||
/// The [`KeychainTracker`] atomically updates its [`KeychainTxOutIndex`] whenever new chain data is
|
||||
/// incorporated into its internal [`ChainGraph`].
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KeychainTracker<K, P, T = Transaction> {
|
||||
/// Index between script pubkeys to transaction outputs
|
||||
pub txout_index: KeychainTxOutIndex<K>,
|
||||
chain_graph: ChainGraph<P, T>,
|
||||
}
|
||||
|
||||
impl<K, P, T> KeychainTracker<K, P, T>
|
||||
where
|
||||
P: sparse_chain::ChainPosition,
|
||||
K: Ord + Clone + core::fmt::Debug,
|
||||
T: AsTransaction + Clone + Ord,
|
||||
{
|
||||
/// Add a keychain to the tracker's `txout_index` with a descriptor to derive addresses for it.
|
||||
/// This is just shorthand for calling [`KeychainTxOutIndex::add_keychain`] on the internal
|
||||
/// `txout_index`.
|
||||
///
|
||||
/// Adding a keychain means you will be able to derive new script pubkeys under that keychain
|
||||
/// and the tracker will discover transaction outputs with those script pubkeys.
|
||||
pub fn add_keychain(&mut self, keychain: K, descriptor: Descriptor<DescriptorPublicKey>) {
|
||||
self.txout_index.add_keychain(keychain, descriptor)
|
||||
}
|
||||
|
||||
/// Get the internal map of keychains to their descriptors. This is just shorthand for calling
|
||||
/// [`KeychainTxOutIndex::keychains`] on the internal `txout_index`.
|
||||
pub fn keychains(&mut self) -> &BTreeMap<K, Descriptor<DescriptorPublicKey>> {
|
||||
self.txout_index.keychains()
|
||||
}
|
||||
|
||||
/// Get the checkpoint limit of the internal [`SparseChain`].
|
||||
///
|
||||
/// Refer to [`SparseChain::checkpoint_limit`] for more.
|
||||
pub fn checkpoint_limit(&self) -> Option<usize> {
|
||||
self.chain_graph.checkpoint_limit()
|
||||
}
|
||||
|
||||
/// Set the checkpoint limit of the internal [`SparseChain`].
|
||||
///
|
||||
/// Refer to [`SparseChain::set_checkpoint_limit`] for more.
|
||||
pub fn set_checkpoint_limit(&mut self, limit: Option<usize>) {
|
||||
self.chain_graph.set_checkpoint_limit(limit)
|
||||
}
|
||||
|
||||
/// Determines the resultant [`KeychainChangeSet`] if the given [`KeychainScan`] is applied.
|
||||
///
|
||||
/// Internally, we call [`ChainGraph::determine_changeset`] and also determine the additions of
|
||||
/// [`KeychainTxOutIndex`].
|
||||
pub fn determine_changeset<T2>(
|
||||
&self,
|
||||
scan: &KeychainScan<K, P, T2>,
|
||||
) -> Result<KeychainChangeSet<K, P, T>, chain_graph::UpdateError<P>>
|
||||
where
|
||||
T2: IntoOwned<T> + Clone,
|
||||
{
|
||||
// TODO: `KeychainTxOutIndex::determine_additions`
|
||||
let mut derivation_indices = scan.last_active_indices.clone();
|
||||
derivation_indices.retain(|keychain, index| {
|
||||
match self.txout_index.last_revealed_index(keychain) {
|
||||
Some(existing) => *index > existing,
|
||||
None => true,
|
||||
}
|
||||
});
|
||||
|
||||
Ok(KeychainChangeSet {
|
||||
derivation_indices: DerivationAdditions(derivation_indices),
|
||||
chain_graph: self.chain_graph.determine_changeset(&scan.update)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Directly applies a [`KeychainScan`] on [`KeychainTracker`].
|
||||
///
|
||||
/// This is equivilant to calling [`determine_changeset`] and [`apply_changeset`] in sequence.
|
||||
///
|
||||
/// [`determine_changeset`]: Self::determine_changeset
|
||||
/// [`apply_changeset`]: Self::apply_changeset
|
||||
pub fn apply_update<T2>(
|
||||
&mut self,
|
||||
scan: KeychainScan<K, P, T2>,
|
||||
) -> Result<KeychainChangeSet<K, P, T>, chain_graph::UpdateError<P>>
|
||||
where
|
||||
T2: IntoOwned<T> + Clone,
|
||||
{
|
||||
let changeset = self.determine_changeset(&scan)?;
|
||||
self.apply_changeset(changeset.clone());
|
||||
Ok(changeset)
|
||||
}
|
||||
|
||||
/// Applies the changes in `changeset` to [`KeychainTracker`].
|
||||
///
|
||||
/// Internally, this calls [`KeychainTxOutIndex::apply_additions`] and
|
||||
/// [`ChainGraph::apply_changeset`] in sequence.
|
||||
pub fn apply_changeset(&mut self, changeset: KeychainChangeSet<K, P, T>) {
|
||||
let KeychainChangeSet {
|
||||
derivation_indices,
|
||||
chain_graph,
|
||||
} = changeset;
|
||||
self.txout_index.apply_additions(derivation_indices);
|
||||
let _ = self.txout_index.scan(&chain_graph);
|
||||
self.chain_graph.apply_changeset(chain_graph)
|
||||
}
|
||||
|
||||
/// Iterates through [`FullTxOut`]s that are considered to exist in our representation of the
|
||||
/// blockchain/mempool.
|
||||
///
|
||||
/// In other words, these are `txout`s of confirmed and in-mempool transactions, based on our
|
||||
/// view of the blockchain/mempool.
|
||||
pub fn full_txouts(&self) -> impl Iterator<Item = (&(K, u32), FullTxOut<P>)> + '_ {
|
||||
self.txout_index
|
||||
.txouts()
|
||||
.filter_map(|(spk_i, op, _)| Some((spk_i, self.chain_graph.full_txout(op)?)))
|
||||
}
|
||||
|
||||
/// Iterates through [`FullTxOut`]s that are unspent outputs.
|
||||
///
|
||||
/// Refer to [`full_txouts`] for more.
|
||||
///
|
||||
/// [`full_txouts`]: Self::full_txouts
|
||||
pub fn full_utxos(&self) -> impl Iterator<Item = (&(K, u32), FullTxOut<P>)> + '_ {
|
||||
self.full_txouts()
|
||||
.filter(|(_, txout)| txout.spent_by.is_none())
|
||||
}
|
||||
|
||||
/// Returns a reference to the internal [`ChainGraph`].
|
||||
pub fn chain_graph(&self) -> &ChainGraph<P, T> {
|
||||
&self.chain_graph
|
||||
}
|
||||
|
||||
/// Returns a reference to the internal [`TxGraph`] (which is part of the [`ChainGraph`]).
|
||||
pub fn graph(&self) -> &TxGraph<T> {
|
||||
&self.chain_graph().graph()
|
||||
}
|
||||
|
||||
/// Returns a reference to the internal [`SparseChain`] (which is part of the [`ChainGraph`]).
|
||||
pub fn chain(&self) -> &SparseChain<P> {
|
||||
&self.chain_graph().chain()
|
||||
}
|
||||
|
||||
/// Determines the changes as result of inserting `block_id` (a height and block hash) into the
|
||||
/// tracker.
|
||||
///
|
||||
/// The caller is responsible for guaranteeing that a block exists at that height. If a
|
||||
/// checkpoint already exists at that height with a different hash this will return an error.
|
||||
/// Otherwise it will return `Ok(true)` if the checkpoint didn't already exist or `Ok(false)`
|
||||
/// if it did.
|
||||
///
|
||||
/// **Warning**: This function modifies the internal state of the tracker. You are responsible
|
||||
/// for persisting these changes to disk if you need to restore them.
|
||||
pub fn insert_checkpoint_preview(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
) -> Result<KeychainChangeSet<K, P, T>, chain_graph::InsertCheckpointError> {
|
||||
Ok(KeychainChangeSet {
|
||||
chain_graph: self.chain_graph.insert_checkpoint_preview(block_id)?,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Directly insert a `block_id` into the tracker.
|
||||
///
|
||||
/// This is equivalent of calling [`insert_checkpoint_preview`] and [`apply_changeset`] in
|
||||
/// sequence.
|
||||
///
|
||||
/// [`insert_checkpoint_preview`]: Self::insert_checkpoint_preview
|
||||
/// [`apply_changeset`]: Self::apply_changeset
|
||||
pub fn insert_checkpoint(
|
||||
&mut self,
|
||||
block_id: BlockId,
|
||||
) -> Result<KeychainChangeSet<K, P, T>, chain_graph::InsertCheckpointError> {
|
||||
let changeset = self.insert_checkpoint_preview(block_id)?;
|
||||
self.apply_changeset(changeset.clone());
|
||||
Ok(changeset)
|
||||
}
|
||||
|
||||
/// Determines the changes as result of inserting a transaction into the inner [`ChainGraph`]
|
||||
/// and optionally into the inner chain at `position`.
|
||||
///
|
||||
/// **Warning**: This function modifies the internal state of the chain graph. You are
|
||||
/// responsible for persisting these changes to disk if you need to restore them.
|
||||
pub fn insert_tx_preview(
|
||||
&self,
|
||||
tx: T,
|
||||
pos: P,
|
||||
) -> Result<KeychainChangeSet<K, P, T>, chain_graph::InsertTxError<P>> {
|
||||
Ok(KeychainChangeSet {
|
||||
chain_graph: self.chain_graph.insert_tx_preview(tx, pos)?,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Directly insert a transaction into the inner [`ChainGraph`] and optionally into the inner
|
||||
/// chain at `position`.
|
||||
///
|
||||
/// This is equivilant of calling [`insert_tx_preview`] and [`apply_changeset`] in sequence.
|
||||
///
|
||||
/// [`insert_tx_preview`]: Self::insert_tx_preview
|
||||
/// [`apply_changeset`]: Self::apply_changeset
|
||||
pub fn insert_tx(
|
||||
&mut self,
|
||||
tx: T,
|
||||
pos: P,
|
||||
) -> Result<KeychainChangeSet<K, P, T>, chain_graph::InsertTxError<P>> {
|
||||
let changeset = self.insert_tx_preview(tx, pos)?;
|
||||
self.apply_changeset(changeset.clone());
|
||||
Ok(changeset)
|
||||
}
|
||||
|
||||
/// Returns the *balance* of the keychain i.e. the value of unspent transaction outputs tracked.
|
||||
///
|
||||
/// The caller provides a `should_trust` predicate which must decide whether the value of
|
||||
/// unconfirmed outputs on this keychain are guaranteed to be realized or not. For example:
|
||||
///
|
||||
/// - For an *internal* (change) keychain `should_trust` should in general be `true` since even if
|
||||
/// you lose an internal output due to eviction you will always gain back the value from whatever output the
|
||||
/// unconfirmed transaction was spending (since that output is presumeably from your wallet).
|
||||
/// - For an *external* keychain you might want `should_trust` to return `false` since someone may cancel (by double spending)
|
||||
/// a payment made to addresses on that keychain.
|
||||
///
|
||||
/// When in doubt set `should_trust` to return false. This doesn't do anything other than change
|
||||
/// where the unconfirmed output's value is accounted for in `Balance`.
|
||||
pub fn balance(&self, mut should_trust: impl FnMut(&K) -> bool) -> Balance {
|
||||
let mut immature = 0;
|
||||
let mut trusted_pending = 0;
|
||||
let mut untrusted_pending = 0;
|
||||
let mut confirmed = 0;
|
||||
let last_sync_height = self.chain().latest_checkpoint().map(|latest| latest.height);
|
||||
for ((keychain, _), utxo) in self.full_utxos() {
|
||||
let chain_position = &utxo.chain_position;
|
||||
|
||||
match chain_position.height() {
|
||||
TxHeight::Confirmed(_) => {
|
||||
if utxo.is_on_coinbase {
|
||||
if utxo.is_mature(
|
||||
last_sync_height
|
||||
.expect("since it's confirmed we must have a checkpoint"),
|
||||
) {
|
||||
confirmed += utxo.txout.value;
|
||||
} else {
|
||||
immature += utxo.txout.value;
|
||||
}
|
||||
} else {
|
||||
confirmed += utxo.txout.value;
|
||||
}
|
||||
}
|
||||
TxHeight::Unconfirmed => {
|
||||
if should_trust(keychain) {
|
||||
trusted_pending += utxo.txout.value;
|
||||
} else {
|
||||
untrusted_pending += utxo.txout.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Balance {
|
||||
immature,
|
||||
trusted_pending,
|
||||
untrusted_pending,
|
||||
confirmed,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the balance of all spendable confirmed unspent outputs of this tracker at a
|
||||
/// particular height.
|
||||
pub fn balance_at(&self, height: u32) -> u64 {
|
||||
self.full_txouts()
|
||||
.filter(|(_, full_txout)| full_txout.is_spendable_at(height))
|
||||
.map(|(_, full_txout)| full_txout.txout.value)
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, P> Default for KeychainTracker<K, P> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
txout_index: Default::default(),
|
||||
chain_graph: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, P> AsRef<SparseChain<P>> for KeychainTracker<K, P> {
|
||||
fn as_ref(&self) -> &SparseChain<P> {
|
||||
self.chain_graph.chain()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, P> AsRef<TxGraph> for KeychainTracker<K, P> {
|
||||
fn as_ref(&self) -> &TxGraph {
|
||||
self.chain_graph.graph()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, P> AsRef<ChainGraph<P>> for KeychainTracker<K, P> {
|
||||
fn as_ref(&self) -> &ChainGraph<P> {
|
||||
&self.chain_graph
|
||||
}
|
||||
}
|
||||
585
crates/chain/src/keychain/txout_index.rs
Normal file
585
crates/chain/src/keychain/txout_index.rs
Normal file
@@ -0,0 +1,585 @@
|
||||
use crate::{
|
||||
collections::*,
|
||||
miniscript::{Descriptor, DescriptorPublicKey},
|
||||
ForEachTxOut, SpkTxOutIndex,
|
||||
};
|
||||
use alloc::{borrow::Cow, vec::Vec};
|
||||
use bitcoin::{secp256k1::Secp256k1, OutPoint, Script, TxOut};
|
||||
use core::{fmt::Debug, ops::Deref};
|
||||
|
||||
use super::DerivationAdditions;
|
||||
|
||||
/// Maximum [BIP32](https://bips.xyz/32) derivation index.
|
||||
pub const BIP32_MAX_INDEX: u32 = 1 << 31 - 1;
|
||||
|
||||
/// A convenient wrapper around [`SpkTxOutIndex`] that relates script pubkeys to miniscript public
|
||||
/// [`Descriptor`]s.
|
||||
///
|
||||
/// Descriptors are referenced by the provided keychain generic (`K`).
|
||||
///
|
||||
/// Script pubkeys for a descriptor are revealed chronologically from index 0. I.e. If the last
|
||||
/// revealed index of a descriptor is 5, scripts of indices 0 to 4 are guaranteed to already be
|
||||
/// revealed. In addition to revealed scripts, we have a `lookahead` parameter for each keychain
|
||||
/// which defines the number of script pubkeys to store ahead of the last revealed index.
|
||||
///
|
||||
/// Methods that could update the last revealed index will return [`DerivationAdditions`] to report
|
||||
/// these changes. This can be persisted for future recovery.
|
||||
///
|
||||
/// ## Synopsis
|
||||
///
|
||||
/// ```
|
||||
/// use bdk_chain::keychain::KeychainTxOutIndex;
|
||||
/// # use bdk_chain::{ miniscript::{Descriptor, DescriptorPublicKey} };
|
||||
/// # use core::str::FromStr;
|
||||
///
|
||||
/// // imagine our service has internal and external addresses but also addresses for users
|
||||
/// #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
|
||||
/// enum MyKeychain {
|
||||
/// External,
|
||||
/// Internal,
|
||||
/// MyAppUser {
|
||||
/// user_id: u32
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut txout_index = KeychainTxOutIndex::<MyKeychain>::default();
|
||||
///
|
||||
/// # let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
|
||||
/// # let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
|
||||
/// # let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
|
||||
/// # let descriptor_for_user_42 = external_descriptor.clone();
|
||||
/// txout_index.add_keychain(MyKeychain::External, external_descriptor);
|
||||
/// txout_index.add_keychain(MyKeychain::Internal, internal_descriptor);
|
||||
/// txout_index.add_keychain(MyKeychain::MyAppUser { user_id: 42 }, descriptor_for_user_42);
|
||||
///
|
||||
/// let new_spk_for_user = txout_index.reveal_next_spk(&MyKeychain::MyAppUser{ user_id: 42 });
|
||||
/// ```
|
||||
///
|
||||
/// [`Ord`]: core::cmp::Ord
|
||||
/// [`SpkTxOutIndex`]: crate::spk_txout_index::SpkTxOutIndex
|
||||
/// [`Descriptor`]: crate::miniscript::Descriptor
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KeychainTxOutIndex<K> {
|
||||
inner: SpkTxOutIndex<(K, u32)>,
|
||||
// descriptors of each keychain
|
||||
keychains: BTreeMap<K, Descriptor<DescriptorPublicKey>>,
|
||||
// last stored indexes
|
||||
last_revealed: BTreeMap<K, u32>,
|
||||
// lookahead settings for each keychain
|
||||
lookahead: BTreeMap<K, u32>,
|
||||
}
|
||||
|
||||
impl<K> Default for KeychainTxOutIndex<K> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: SpkTxOutIndex::default(),
|
||||
keychains: BTreeMap::default(),
|
||||
last_revealed: BTreeMap::default(),
|
||||
lookahead: BTreeMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K> Deref for KeychainTxOutIndex<K> {
|
||||
type Target = SpkTxOutIndex<(K, u32)>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
||||
/// Scans an object for relevant outpoints, which are stored and indexed internally.
|
||||
///
|
||||
/// If the matched script pubkey is part of the lookahead, the last stored index is updated for
|
||||
/// the script pubkey's keychain and the [`DerivationAdditions`] returned will reflect the
|
||||
/// change.
|
||||
///
|
||||
/// Typically this method is used in two situations:
|
||||
///
|
||||
/// 1. After loading transaction data from disk you may scan over all the txouts to restore all
|
||||
/// your txouts.
|
||||
/// 2. When getting new data from the chain you usually scan it before incorporating it into
|
||||
/// your chain state (i.e. `SparseChain`, `ChainGraph`).
|
||||
///
|
||||
/// See [`ForEachTxout`] for the types that support this.
|
||||
///
|
||||
/// [`ForEachTxout`]: crate::ForEachTxOut
|
||||
pub fn scan(&mut self, txouts: &impl ForEachTxOut) -> DerivationAdditions<K> {
|
||||
let mut additions = DerivationAdditions::<K>::default();
|
||||
txouts.for_each_txout(|(op, txout)| additions.append(self.scan_txout(op, txout)));
|
||||
additions
|
||||
}
|
||||
|
||||
/// Scan a single outpoint for a matching script pubkey.
|
||||
///
|
||||
/// If it matches the index will store and index it.
|
||||
pub fn scan_txout(&mut self, op: OutPoint, txout: &TxOut) -> DerivationAdditions<K> {
|
||||
match self.inner.scan_txout(op, txout).cloned() {
|
||||
Some((keychain, index)) => self.reveal_to_target(&keychain, index).1,
|
||||
None => DerivationAdditions::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a reference to the internal [`SpkTxOutIndex`].
|
||||
pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// Return a reference to the internal map of keychain to descriptors.
|
||||
pub fn keychains(&self) -> &BTreeMap<K, Descriptor<DescriptorPublicKey>> {
|
||||
&self.keychains
|
||||
}
|
||||
|
||||
/// Add a keychain to the tracker's `txout_index` with a descriptor to derive addresses for it.
|
||||
///
|
||||
/// Adding a keychain means you will be able to derive new script pubkeys under that keychain
|
||||
/// and the txout index will discover transaction outputs with those script pubkeys.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if a different `descriptor` is introduced to the same `keychain`.
|
||||
pub fn add_keychain(&mut self, keychain: K, descriptor: Descriptor<DescriptorPublicKey>) {
|
||||
let old_descriptor = &*self.keychains.entry(keychain).or_insert(descriptor.clone());
|
||||
assert_eq!(
|
||||
&descriptor, old_descriptor,
|
||||
"keychain already contains a different descriptor"
|
||||
);
|
||||
}
|
||||
|
||||
/// Return the lookahead setting for each keychain.
|
||||
///
|
||||
/// Refer to [`set_lookahead`] for a deeper explanation on `lookahead`.
|
||||
///
|
||||
/// [`set_lookahead`]: Self::set_lookahead
|
||||
pub fn lookaheads(&self) -> &BTreeMap<K, u32> {
|
||||
&self.lookahead
|
||||
}
|
||||
|
||||
/// Convenience method to call [`set_lookahead`] for all keychains.
|
||||
///
|
||||
/// [`set_lookahead`]: Self::set_lookahead
|
||||
pub fn set_lookahead_for_all(&mut self, lookahead: u32) {
|
||||
for keychain in &self.keychains.keys().cloned().collect::<Vec<_>>() {
|
||||
self.lookahead.insert(keychain.clone(), lookahead);
|
||||
self.replenish_lookahead(&keychain);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the lookahead count for `keychain`.
|
||||
///
|
||||
/// The lookahead is the number of scripts to cache ahead of the last stored script index. This
|
||||
/// is useful during a scan via [`scan`] or [`scan_txout`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if `keychain` does not exist.
|
||||
///
|
||||
/// [`scan`]: Self::scan
|
||||
/// [`scan_txout`]: Self::scan_txout
|
||||
pub fn set_lookahead(&mut self, keychain: &K, lookahead: u32) {
|
||||
self.lookahead.insert(keychain.clone(), lookahead);
|
||||
self.replenish_lookahead(keychain);
|
||||
}
|
||||
|
||||
/// Convenience method to call [`lookahead_to_target`] for multiple keychains.
|
||||
///
|
||||
/// [`lookahead_to_target`]: Self::lookahead_to_target
|
||||
pub fn lookahead_to_target_multi(&mut self, target_indexes: BTreeMap<K, u32>) {
|
||||
for (keychain, target_index) in target_indexes {
|
||||
self.lookahead_to_target(&keychain, target_index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Store lookahead scripts until `target_index`.
|
||||
///
|
||||
/// This does not change the `lookahead` setting.
|
||||
pub fn lookahead_to_target(&mut self, keychain: &K, target_index: u32) {
|
||||
let next_index = self.next_store_index(keychain);
|
||||
if let Some(temp_lookahead) = target_index.checked_sub(next_index).filter(|&v| v > 0) {
|
||||
let old_lookahead = self.lookahead.insert(keychain.clone(), temp_lookahead);
|
||||
self.replenish_lookahead(keychain);
|
||||
|
||||
// revert
|
||||
match old_lookahead {
|
||||
Some(lookahead) => self.lookahead.insert(keychain.clone(), lookahead),
|
||||
None => self.lookahead.remove(keychain),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn replenish_lookahead(&mut self, keychain: &K) {
|
||||
let descriptor = self.keychains.get(keychain).expect("keychain must exist");
|
||||
let next_store_index = self.next_store_index(keychain);
|
||||
let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1);
|
||||
let lookahead = self.lookahead.get(keychain).map_or(0, |v| *v);
|
||||
|
||||
for (new_index, new_spk) in range_descriptor_spks(
|
||||
Cow::Borrowed(descriptor),
|
||||
next_store_index..next_reveal_index + lookahead,
|
||||
) {
|
||||
let _inserted = self
|
||||
.inner
|
||||
.insert_spk((keychain.clone(), new_index), new_spk);
|
||||
debug_assert!(_inserted, "replenish lookahead: must not have existing spk: keychain={:?}, lookahead={}, next_store_index={}, next_reveal_index={}", keychain, lookahead, next_store_index, next_reveal_index);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_store_index(&self, keychain: &K) -> u32 {
|
||||
self.inner()
|
||||
.all_spks()
|
||||
.range((keychain.clone(), u32::MIN)..(keychain.clone(), u32::MAX))
|
||||
.last()
|
||||
.map_or(0, |((_, v), _)| *v + 1)
|
||||
}
|
||||
|
||||
/// Generates script pubkey iterators for every `keychain`. The iterators iterate over all
|
||||
/// derivable script pubkeys.
|
||||
pub fn spks_of_all_keychains(
|
||||
&self,
|
||||
) -> BTreeMap<K, impl Iterator<Item = (u32, Script)> + Clone> {
|
||||
self.keychains
|
||||
.iter()
|
||||
.map(|(keychain, descriptor)| {
|
||||
(
|
||||
keychain.clone(),
|
||||
range_descriptor_spks(Cow::Owned(descriptor.clone()), 0..),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Generates a script pubkey iterator for the given `keychain`'s descriptor (if exists). The
|
||||
/// iterator iterates over all derivable scripts of the keychain's descriptor.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if `keychain` does not exist.
|
||||
pub fn spks_of_keychain(&self, keychain: &K) -> impl Iterator<Item = (u32, Script)> + Clone {
|
||||
let descriptor = self
|
||||
.keychains
|
||||
.get(keychain)
|
||||
.expect("keychain must exist")
|
||||
.clone();
|
||||
range_descriptor_spks(Cow::Owned(descriptor), 0..)
|
||||
}
|
||||
|
||||
/// Convenience method to get [`revealed_spks_of_keychain`] of all keychains.
|
||||
///
|
||||
/// [`revealed_spks_of_keychain`]: Self::revealed_spks_of_keychain
|
||||
pub fn revealed_spks_of_all_keychains(
|
||||
&self,
|
||||
) -> BTreeMap<K, impl Iterator<Item = (u32, &Script)> + Clone> {
|
||||
self.keychains
|
||||
.keys()
|
||||
.map(|keychain| (keychain.clone(), self.revealed_spks_of_keychain(keychain)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Iterates over the script pubkeys revealed by this index under `keychain`.
|
||||
pub fn revealed_spks_of_keychain(
|
||||
&self,
|
||||
keychain: &K,
|
||||
) -> impl DoubleEndedIterator<Item = (u32, &Script)> + Clone {
|
||||
let next_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1);
|
||||
self.inner
|
||||
.all_spks()
|
||||
.range((keychain.clone(), u32::MIN)..(keychain.clone(), next_index))
|
||||
.map(|((_, derivation_index), spk)| (*derivation_index, spk))
|
||||
}
|
||||
|
||||
/// Get the next derivation index for `keychain`. This is the index after the last revealed
|
||||
/// derivation index.
|
||||
///
|
||||
/// The second field in the returned tuple represents whether the next derivation index is new.
|
||||
/// There are two scenarios where the next derivation index is reused (not new):
|
||||
///
|
||||
/// 1. The keychain's descriptor has no wildcard, and a script has already been revealed.
|
||||
/// 2. The number of revealed scripts has already reached 2^31 (refer to BIP-32).
|
||||
///
|
||||
/// Not checking the second field of the tuple may result in address reuse.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `keychain` does not exist.
|
||||
pub fn next_index(&self, keychain: &K) -> (u32, bool) {
|
||||
let descriptor = self.keychains.get(keychain).expect("keychain must exist");
|
||||
let last_index = self.last_revealed.get(keychain).cloned();
|
||||
|
||||
// we can only get the next index if wildcard exists
|
||||
let has_wildcard = descriptor.has_wildcard();
|
||||
|
||||
match last_index {
|
||||
// if there is no index, next_index is always 0
|
||||
None => (0, true),
|
||||
// descriptors without wildcards can only have one index
|
||||
Some(_) if !has_wildcard => (0, false),
|
||||
// derivation index must be < 2^31 (BIP-32)
|
||||
Some(index) if index > BIP32_MAX_INDEX => {
|
||||
unreachable!("index is out of bounds")
|
||||
}
|
||||
Some(index) if index == BIP32_MAX_INDEX => (index, false),
|
||||
// get next derivation index
|
||||
Some(index) => (index + 1, true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the last derivation index that is revealed for each keychain.
|
||||
///
|
||||
/// Keychains with no revealed indices will not be included in the returned [`BTreeMap`].
|
||||
pub fn last_revealed_indices(&self) -> &BTreeMap<K, u32> {
|
||||
&self.last_revealed
|
||||
}
|
||||
|
||||
/// Get the last derivation index revealed for `keychain`.
|
||||
pub fn last_revealed_index(&self, keychain: &K) -> Option<u32> {
|
||||
self.last_revealed.get(keychain).cloned()
|
||||
}
|
||||
|
||||
/// Convenience method to call [`Self::reveal_to_target`] on multiple keychains.
|
||||
pub fn reveal_to_target_multi(
|
||||
&mut self,
|
||||
keychains: &BTreeMap<K, u32>,
|
||||
) -> (
|
||||
BTreeMap<K, impl Iterator<Item = (u32, Script)>>,
|
||||
DerivationAdditions<K>,
|
||||
) {
|
||||
let mut additions = DerivationAdditions::default();
|
||||
let mut spks = BTreeMap::new();
|
||||
|
||||
for (keychain, &index) in keychains {
|
||||
let (new_spks, new_additions) = self.reveal_to_target(&keychain, index);
|
||||
if !new_additions.is_empty() {
|
||||
spks.insert(keychain.clone(), new_spks);
|
||||
additions.append(new_additions);
|
||||
}
|
||||
}
|
||||
|
||||
(spks, additions)
|
||||
}
|
||||
|
||||
/// Reveals script pubkeys of the `keychain`'s descriptor **up to and including** the
|
||||
/// `target_index`.
|
||||
///
|
||||
/// If the `target_index` cannot be reached (due to the descriptor having no wildcard, and/or
|
||||
/// the `target_index` is in the hardened index range), this method will do a best-effort and
|
||||
/// reveal up to the last possible index.
|
||||
///
|
||||
/// This returns an iterator of newly revealed indices (along side their scripts), and a
|
||||
/// [`DerivationAdditions`] which reports updates to the latest revealed index. If no new script
|
||||
/// pubkeys are revealed, both of these will be empty.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `keychain` does not exist.
|
||||
pub fn reveal_to_target(
|
||||
&mut self,
|
||||
keychain: &K,
|
||||
target_index: u32,
|
||||
) -> (impl Iterator<Item = (u32, Script)>, DerivationAdditions<K>) {
|
||||
let descriptor = self.keychains.get(keychain).expect("keychain must exist");
|
||||
let has_wildcard = descriptor.has_wildcard();
|
||||
|
||||
let target_index = if has_wildcard { target_index } else { 0 };
|
||||
let next_store_index = self.next_store_index(keychain);
|
||||
let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1);
|
||||
let lookahead = self.lookahead.get(keychain).map_or(0, |v| *v);
|
||||
|
||||
// if we are able to reveal new indexes, the latest revealed index goes here
|
||||
let mut revealed_index = None;
|
||||
|
||||
// if target is already surpassed, we have nothing to reveal
|
||||
if next_reveal_index <= target_index
|
||||
// if target is already stored (due to lookahead), this can be our new revealed index
|
||||
&& target_index < next_reveal_index + lookahead
|
||||
{
|
||||
revealed_index = Some(target_index);
|
||||
}
|
||||
|
||||
// we range over indexes that are not stored
|
||||
let range = next_reveal_index + lookahead..=target_index + lookahead;
|
||||
|
||||
for (new_index, new_spk) in range_descriptor_spks(Cow::Borrowed(descriptor), range) {
|
||||
// no need to store if already stored
|
||||
if new_index >= next_store_index {
|
||||
let _inserted = self
|
||||
.inner
|
||||
.insert_spk((keychain.clone(), new_index), new_spk);
|
||||
debug_assert!(_inserted, "must not have existing spk",);
|
||||
}
|
||||
|
||||
// everything after `target_index` is stored for lookahead only
|
||||
if new_index <= target_index {
|
||||
revealed_index = Some(new_index);
|
||||
}
|
||||
}
|
||||
|
||||
match revealed_index {
|
||||
Some(index) => {
|
||||
let _old_index = self.last_revealed.insert(keychain.clone(), index);
|
||||
debug_assert!(_old_index < Some(index));
|
||||
(
|
||||
range_descriptor_spks(
|
||||
Cow::Owned(descriptor.clone()),
|
||||
next_reveal_index..index + 1,
|
||||
),
|
||||
DerivationAdditions([(keychain.clone(), index)].into()),
|
||||
)
|
||||
}
|
||||
None => (
|
||||
range_descriptor_spks(
|
||||
Cow::Owned(descriptor.clone()),
|
||||
next_reveal_index..next_reveal_index,
|
||||
),
|
||||
DerivationAdditions::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to reveal the next script pubkey for `keychain`.
|
||||
///
|
||||
/// Returns the derivation index of the revealed script pubkey, the revealed script pubkey and a
|
||||
/// [`DerivationAdditions`] which represents changes in the last revealed index (if any).
|
||||
///
|
||||
/// When a new script cannot be revealed, we return the last revealed script and an empty
|
||||
/// [`DerivationAdditions`]. There are two scenarios when a new script pubkey cannot be derived:
|
||||
///
|
||||
/// 1. The descriptor has no wildcard and already has one script revealed.
|
||||
/// 2. The descriptor has already revealed scripts up to the numeric bound.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `keychain` does not exist.
|
||||
pub fn reveal_next_spk(&mut self, keychain: &K) -> ((u32, &Script), DerivationAdditions<K>) {
|
||||
let (next_index, _) = self.next_index(keychain);
|
||||
let additions = self.reveal_to_target(keychain, next_index).1;
|
||||
let script = self
|
||||
.inner
|
||||
.spk_at_index(&(keychain.clone(), next_index))
|
||||
.expect("script must already be stored");
|
||||
((next_index, script), additions)
|
||||
}
|
||||
|
||||
/// Gets the next unused script pubkey in the keychain. I.e. the script pubkey with the lowest
|
||||
/// index that has not been used yet.
|
||||
///
|
||||
/// This will derive and reveal a new script pubkey if no more unused script pubkeys exist.
|
||||
///
|
||||
/// If the descriptor has no wildcard and already has a used script pubkey, or if a descriptor
|
||||
/// has used all scripts up to the derivation bounds, the last derived script pubkey will be
|
||||
/// returned.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `keychain` has never been added to the index
|
||||
pub fn next_unused_spk(&mut self, keychain: &K) -> ((u32, &Script), DerivationAdditions<K>) {
|
||||
let need_new = self.unused_spks_of_keychain(keychain).next().is_none();
|
||||
// this rather strange branch is needed because of some lifetime issues
|
||||
if need_new {
|
||||
self.reveal_next_spk(keychain)
|
||||
} else {
|
||||
(
|
||||
self.unused_spks_of_keychain(keychain)
|
||||
.next()
|
||||
.expect("we already know next exists"),
|
||||
DerivationAdditions::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the script pubkey at `index` as used even though it hasn't seen an output with it.
|
||||
/// This only has an effect when the `index` had been added to `self` already and was unused.
|
||||
///
|
||||
/// Returns whether the `index` was originally present as `unused`.
|
||||
///
|
||||
/// This is useful when you want to reserve a script pubkey for something but don't want to add
|
||||
/// the transaction output using it to the index yet. Other callers will consider `index` on
|
||||
/// `keychain` used until you call [`unmark_used`].
|
||||
///
|
||||
/// [`unmark_used`]: Self::unmark_used
|
||||
pub fn mark_used(&mut self, keychain: &K, index: u32) -> bool {
|
||||
self.inner.mark_used(&(keychain.clone(), index))
|
||||
}
|
||||
|
||||
/// Undoes the effect of [`mark_used`]. Returns whether the `index` is inserted back into
|
||||
/// `unused`.
|
||||
///
|
||||
/// Note that if `self` has scanned an output with this script pubkey then this will have no
|
||||
/// effect.
|
||||
///
|
||||
/// [`mark_used`]: Self::mark_used
|
||||
pub fn unmark_used(&mut self, keychain: &K, index: u32) -> bool {
|
||||
self.inner.unmark_used(&(keychain.clone(), index))
|
||||
}
|
||||
|
||||
/// Iterates over all unused script pubkeys for a `keychain` that have been stored in the index.
|
||||
pub fn unused_spks_of_keychain(
|
||||
&self,
|
||||
keychain: &K,
|
||||
) -> impl DoubleEndedIterator<Item = (u32, &Script)> {
|
||||
let next_index = self.last_revealed.get(keychain).map_or(0, |&v| v + 1);
|
||||
let range = (keychain.clone(), u32::MIN)..(keychain.clone(), next_index);
|
||||
self.inner
|
||||
.unused_spks(range)
|
||||
.map(|((_, i), script)| (*i, script))
|
||||
}
|
||||
|
||||
/// Iterates over all the [`OutPoint`] that have a `TxOut` with a script pubkey derived from
|
||||
/// `keychain`.
|
||||
pub fn txouts_of_keychain(
|
||||
&self,
|
||||
keychain: &K,
|
||||
) -> impl DoubleEndedIterator<Item = (u32, OutPoint)> + '_ {
|
||||
self.inner
|
||||
.outputs_in_range((keychain.clone(), u32::MIN)..(keychain.clone(), u32::MAX))
|
||||
.map(|((_, i), op)| (*i, op))
|
||||
}
|
||||
|
||||
/// Returns the highest derivation index of the `keychain` where [`KeychainTxOutIndex`] has
|
||||
/// found a [`TxOut`] with it's script pubkey.
|
||||
pub fn last_used_index(&self, keychain: &K) -> Option<u32> {
|
||||
self.txouts_of_keychain(keychain).last().map(|(i, _)| i)
|
||||
}
|
||||
|
||||
/// Returns the highest derivation index of each keychain that [`KeychainTxOutIndex`] has found
|
||||
/// a [`TxOut`] with it's script pubkey.
|
||||
pub fn last_used_indices(&self) -> BTreeMap<K, u32> {
|
||||
self.keychains
|
||||
.iter()
|
||||
.filter_map(|(keychain, _)| {
|
||||
self.last_used_index(keychain)
|
||||
.map(|index| (keychain.clone(), index))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Applies the derivation additions to the [`KeychainTxOutIndex`], extending the number of
|
||||
/// derived scripts per keychain, as specified in the `additions`.
|
||||
pub fn apply_additions(&mut self, additions: DerivationAdditions<K>) {
|
||||
let _ = self.reveal_to_target_multi(&additions.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn range_descriptor_spks<'a, R>(
|
||||
descriptor: Cow<'a, Descriptor<DescriptorPublicKey>>,
|
||||
range: R,
|
||||
) -> impl Iterator<Item = (u32, Script)> + Clone + Send + 'a
|
||||
where
|
||||
R: Iterator<Item = u32> + Clone + Send + 'a,
|
||||
{
|
||||
let secp = Secp256k1::verification_only();
|
||||
let has_wildcard = descriptor.has_wildcard();
|
||||
range
|
||||
.into_iter()
|
||||
// non-wildcard descriptors can only have one derivation index (0)
|
||||
.take_while(move |&index| has_wildcard || index == 0)
|
||||
// we can only iterate over non-hardened indices
|
||||
.take_while(|&index| index <= BIP32_MAX_INDEX)
|
||||
// take until failure
|
||||
.map_while(move |index| {
|
||||
descriptor
|
||||
.derived_descriptor(&secp, index)
|
||||
.map(|desc| (index, desc.script_pubkey()))
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
89
crates/chain/src/lib.rs
Normal file
89
crates/chain/src/lib.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
//! This crate is a collection of core structures for [Bitcoin Dev Kit] (alpha release).
|
||||
//!
|
||||
//! The goal of this crate is give wallets the mechanisms needed to:
|
||||
//!
|
||||
//! 1. Figure out what data they need to fetch.
|
||||
//! 2. Process that data in a way that never leads to inconsistent states.
|
||||
//! 3. Fully index that data and expose it so that it can be consumed without friction.
|
||||
//!
|
||||
//! Our design goals for these mechanisms are:
|
||||
//!
|
||||
//! 1. Data source agnostic -- nothing in `bdk_chain` cares about where you get data from or whether
|
||||
//! you do it synchronously or asynchronously. If you know a fact about the blockchain you can just
|
||||
//! tell `bdk_chain`'s APIs about it and that information will be integrated if it can be done
|
||||
//! consistently.
|
||||
//! 2. Error free APIs.
|
||||
//! 3. Data persistence agnostic -- `bdk_chain` does not care where you cache on-chain data, what you
|
||||
//! cache or how you fetch it.
|
||||
//!
|
||||
//! [Bitcoin Dev Kit]: https://bitcoindevkit.org/
|
||||
#![no_std]
|
||||
pub use bitcoin;
|
||||
pub mod chain_graph;
|
||||
mod spk_txout_index;
|
||||
pub use spk_txout_index::*;
|
||||
mod chain_data;
|
||||
pub use chain_data::*;
|
||||
pub mod keychain;
|
||||
pub mod sparse_chain;
|
||||
mod tx_data_traits;
|
||||
pub mod tx_graph;
|
||||
pub use tx_data_traits::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod example_utils;
|
||||
|
||||
#[cfg(feature = "miniscript")]
|
||||
pub use miniscript;
|
||||
#[cfg(feature = "miniscript")]
|
||||
mod descriptor_ext;
|
||||
#[cfg(feature = "miniscript")]
|
||||
pub use descriptor_ext::DescriptorExt;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
pub extern crate serde_crate as serde;
|
||||
|
||||
#[cfg(feature = "bincode")]
|
||||
extern crate bincode;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
#[cfg(all(not(feature = "std"), feature = "hashbrown"))]
|
||||
extern crate hashbrown;
|
||||
|
||||
// When no-std use `alloc`'s Hash collections. This is activated by default
|
||||
#[cfg(all(not(feature = "std"), not(feature = "hashbrown")))]
|
||||
#[doc(hidden)]
|
||||
pub mod collections {
|
||||
#![allow(dead_code)]
|
||||
pub type HashSet<K> = alloc::collections::BTreeSet<K>;
|
||||
pub type HashMap<K, V> = alloc::collections::BTreeMap<K, V>;
|
||||
pub use alloc::collections::{btree_map as hash_map, *};
|
||||
}
|
||||
|
||||
// When we have std use `std`'s all collections
|
||||
#[cfg(all(feature = "std", not(feature = "hashbrown")))]
|
||||
#[doc(hidden)]
|
||||
pub mod collections {
|
||||
pub use std::collections::{hash_map, *};
|
||||
}
|
||||
|
||||
// With special feature `hashbrown` use `hashbrown`'s hash collections, and else from `alloc`.
|
||||
#[cfg(feature = "hashbrown")]
|
||||
#[doc(hidden)]
|
||||
pub mod collections {
|
||||
#![allow(dead_code)]
|
||||
pub type HashSet<K> = hashbrown::HashSet<K>;
|
||||
pub type HashMap<K, V> = hashbrown::HashMap<K, V>;
|
||||
pub use alloc::collections::*;
|
||||
pub use hashbrown::hash_map;
|
||||
}
|
||||
|
||||
/// How many confirmations are needed for a coinbase output to be spent
|
||||
pub const COINBASE_MATURITY: u32 = 100;
|
||||
1008
crates/chain/src/sparse_chain.rs
Normal file
1008
crates/chain/src/sparse_chain.rs
Normal file
File diff suppressed because it is too large
Load Diff
312
crates/chain/src/spk_txout_index.rs
Normal file
312
crates/chain/src/spk_txout_index.rs
Normal file
@@ -0,0 +1,312 @@
|
||||
use core::ops::RangeBounds;
|
||||
|
||||
use crate::{
|
||||
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
|
||||
ForEachTxOut,
|
||||
};
|
||||
use bitcoin::{self, OutPoint, Script, Transaction, TxOut, Txid};
|
||||
|
||||
/// An index storing [`TxOut`]s that have a script pubkey that matches those in a list.
|
||||
///
|
||||
/// The basic idea is that you insert script pubkeys you care about into the index with
|
||||
/// [`insert_spk`] and then when you call [`scan`] the index will look at any txouts you pass in and
|
||||
/// store and index any txouts matching one of its script pubkeys.
|
||||
///
|
||||
/// Each script pubkey is associated with a application defined index script index `I` which must be
|
||||
/// [`Ord`]. Usually this is used to associate the derivation index of the script pubkey or even a
|
||||
/// combination of `(keychain, derivation_index)`.
|
||||
///
|
||||
/// Note there is no harm in scanning transactions that disappear from the blockchain or were never
|
||||
/// in there in the first place. `SpkTxOutIndex` is intentionally *monotone* -- you cannot delete or
|
||||
/// modify txouts that have been indexed. To find out which txouts from the index are actually in the
|
||||
/// chain or unspent etc you must use other sources of information like a [`SparseChain`].
|
||||
///
|
||||
/// [`TxOut`]: bitcoin::TxOut
|
||||
/// [`insert_spk`]: Self::insert_spk
|
||||
/// [`Ord`]: core::cmp::Ord
|
||||
/// [`scan`]: Self::scan
|
||||
/// [`SparseChain`]: crate::sparse_chain::SparseChain
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpkTxOutIndex<I> {
|
||||
/// script pubkeys ordered by index
|
||||
spks: BTreeMap<I, Script>,
|
||||
/// A reverse lookup from spk to spk index
|
||||
spk_indices: HashMap<Script, I>,
|
||||
/// The set of unused indexes.
|
||||
unused: BTreeSet<I>,
|
||||
/// Lookup index and txout by outpoint.
|
||||
txouts: BTreeMap<OutPoint, (I, TxOut)>,
|
||||
/// Lookup from spk index to outpoints that had that spk
|
||||
spk_txouts: BTreeSet<(I, OutPoint)>,
|
||||
}
|
||||
|
||||
impl<I> Default for SpkTxOutIndex<I> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
txouts: Default::default(),
|
||||
spks: Default::default(),
|
||||
spk_indices: Default::default(),
|
||||
spk_txouts: Default::default(),
|
||||
unused: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 `FprEachTxOut` closure during scanning.
|
||||
macro_rules! scan_txout {
|
||||
($self:ident, $op:expr, $txout:expr) => {{
|
||||
let spk_i = $self.spk_indices.get(&$txout.script_pubkey);
|
||||
if let Some(spk_i) = spk_i {
|
||||
$self.txouts.insert($op, (spk_i.clone(), $txout.clone()));
|
||||
$self.spk_txouts.insert((spk_i.clone(), $op));
|
||||
$self.unused.remove(&spk_i);
|
||||
}
|
||||
spk_i
|
||||
}};
|
||||
}
|
||||
|
||||
impl<I: Clone + Ord> SpkTxOutIndex<I> {
|
||||
/// Scans an object containing many txouts.
|
||||
///
|
||||
/// Typically this is used in two situations:
|
||||
///
|
||||
/// 1. After loading transaction data from disk you may scan over all the txouts to restore all
|
||||
/// your txouts.
|
||||
/// 2. When getting new data from the chain you usually scan it before incorporating it into your chain state.
|
||||
///
|
||||
/// See [`ForEachTxout`] for the types that support this.
|
||||
///
|
||||
/// [`ForEachTxout`]: crate::ForEachTxOut
|
||||
pub fn scan(&mut self, txouts: &impl ForEachTxOut) -> BTreeSet<&I> {
|
||||
// let scanner = &mut SpkTxOutScanner::new(self);
|
||||
let mut scanned_indices = BTreeSet::new();
|
||||
|
||||
txouts.for_each_txout(|(op, txout)| {
|
||||
if let Some(spk_i) = scan_txout!(self, op, txout) {
|
||||
scanned_indices.insert(spk_i);
|
||||
}
|
||||
});
|
||||
|
||||
scanned_indices
|
||||
}
|
||||
|
||||
/// Scan a single `TxOut` for a matching script pubkey, and returns the index that matched the
|
||||
/// script pubkey (if any).
|
||||
pub fn scan_txout(&mut self, op: OutPoint, txout: &TxOut) -> Option<&I> {
|
||||
scan_txout!(self, op, txout)
|
||||
}
|
||||
|
||||
/// Iterate over all known txouts that spend to tracked script pubkeys.
|
||||
pub fn txouts(
|
||||
&self,
|
||||
) -> impl DoubleEndedIterator<Item = (&I, OutPoint, &TxOut)> + ExactSizeIterator {
|
||||
self.txouts
|
||||
.iter()
|
||||
.map(|(op, (index, txout))| (index, *op, txout))
|
||||
}
|
||||
|
||||
/// Finds all txouts on a transaction that has previously been scanned and indexed.
|
||||
pub fn txouts_in_tx(
|
||||
&self,
|
||||
txid: Txid,
|
||||
) -> impl DoubleEndedIterator<Item = (&I, OutPoint, &TxOut)> {
|
||||
self.txouts
|
||||
.range(OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX))
|
||||
.map(|(op, (index, txout))| (index, *op, txout))
|
||||
}
|
||||
|
||||
/// Iterates over all outputs with script pubkeys in an index range.
|
||||
pub fn outputs_in_range(
|
||||
&self,
|
||||
range: impl RangeBounds<I>,
|
||||
) -> impl DoubleEndedIterator<Item = (&I, OutPoint)> {
|
||||
use bitcoin::hashes::Hash;
|
||||
use core::ops::Bound::*;
|
||||
let min_op = OutPoint {
|
||||
txid: Txid::from_inner([0x00; 32]),
|
||||
vout: u32::MIN,
|
||||
};
|
||||
let max_op = OutPoint {
|
||||
txid: Txid::from_inner([0xff; 32]),
|
||||
vout: u32::MAX,
|
||||
};
|
||||
|
||||
let start = match range.start_bound() {
|
||||
Included(index) => Included((index.clone(), min_op)),
|
||||
Excluded(index) => Excluded((index.clone(), max_op)),
|
||||
Unbounded => Unbounded,
|
||||
};
|
||||
|
||||
let end = match range.end_bound() {
|
||||
Included(index) => Included((index.clone(), max_op)),
|
||||
Excluded(index) => Excluded((index.clone(), min_op)),
|
||||
Unbounded => Unbounded,
|
||||
};
|
||||
|
||||
self.spk_txouts.range((start, end)).map(|(i, op)| (i, *op))
|
||||
}
|
||||
|
||||
/// Returns the txout and script pubkey index of the `TxOut` at `OutPoint`.
|
||||
///
|
||||
/// Returns `None` if the `TxOut` hasn't been scanned or if nothing matching was found there.
|
||||
pub fn txout(&self, outpoint: OutPoint) -> Option<(&I, &TxOut)> {
|
||||
self.txouts
|
||||
.get(&outpoint)
|
||||
.map(|(spk_i, txout)| (spk_i, txout))
|
||||
}
|
||||
|
||||
/// Returns the script that has been inserted at the `index`.
|
||||
///
|
||||
/// If that index hasn't been inserted yet it will return `None`.
|
||||
pub fn spk_at_index(&self, index: &I) -> Option<&Script> {
|
||||
self.spks.get(index)
|
||||
}
|
||||
|
||||
/// The script pubkeys being tracked by the index.
|
||||
pub fn all_spks(&self) -> &BTreeMap<I, Script> {
|
||||
&self.spks
|
||||
}
|
||||
|
||||
/// Adds a script pubkey to scan for. Returns `false` and does nothing if spk already exists in the map
|
||||
///
|
||||
/// the index will look for outputs spending to whenever it scans new data.
|
||||
pub fn insert_spk(&mut self, index: I, spk: Script) -> bool {
|
||||
match self.spk_indices.entry(spk.clone()) {
|
||||
Entry::Vacant(value) => {
|
||||
value.insert(index.clone());
|
||||
self.spks.insert(index.clone(), spk);
|
||||
self.unused.insert(index);
|
||||
true
|
||||
}
|
||||
Entry::Occupied(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over a unused script pubkeys in a index range.
|
||||
///
|
||||
/// Here "unused" means that after the script pubkey was stored in the index, the index has
|
||||
/// never scanned a transaction output with it.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bdk_chain::SpkTxOutIndex;
|
||||
///
|
||||
/// // imagine our spks are indexed like (keychain, derivation_index).
|
||||
/// let txout_index = SpkTxOutIndex::<(u32,u32)>::default();
|
||||
/// let all_unused_spks = txout_index.unused_spks(..);
|
||||
/// let change_index = 1;
|
||||
/// let unused_change_spks = txout_index
|
||||
/// .unused_spks((change_index, u32::MIN)..(change_index, u32::MAX));
|
||||
/// ```
|
||||
pub fn unused_spks<R>(&self, range: R) -> impl DoubleEndedIterator<Item = (&I, &Script)>
|
||||
where
|
||||
R: RangeBounds<I>,
|
||||
{
|
||||
self.unused
|
||||
.range(range)
|
||||
.map(|index| (index, self.spk_at_index(index).expect("must exist")))
|
||||
}
|
||||
|
||||
/// Returns whether the script pubkey at `index` has been used or not.
|
||||
///
|
||||
/// Here "unused" means that after the script pubkey was stored in the index, the index has
|
||||
/// never scanned a transaction output with it.
|
||||
pub fn is_used(&self, index: &I) -> bool {
|
||||
self.unused.get(index).is_none()
|
||||
}
|
||||
|
||||
/// Marks the script pubkey at `index` as used even though it hasn't seen an output with it.
|
||||
/// This only has an effect when the `index` had been added to `self` already and was unused.
|
||||
///
|
||||
/// Returns whether the `index` was originally present as `unused`.
|
||||
///
|
||||
/// This is useful when you want to reserve a script pubkey for something but don't want to add
|
||||
/// the transaction output using it to the index yet. Other callers will consider `index` used
|
||||
/// until you call [`unmark_used`].
|
||||
///
|
||||
/// [`unmark_used`]: Self::unmark_used
|
||||
pub fn mark_used(&mut self, index: &I) -> bool {
|
||||
self.unused.remove(index)
|
||||
}
|
||||
|
||||
/// Undoes the effect of [`mark_used`]. Returns whether the `index` is inserted back into
|
||||
/// `unused`.
|
||||
///
|
||||
/// Note that if `self` has scanned an output with this script pubkey then this will have no
|
||||
/// effect.
|
||||
///
|
||||
/// [`mark_used`]: Self::mark_used
|
||||
pub fn unmark_used(&mut self, index: &I) -> bool {
|
||||
// we cannot set index as unused when it does not exist
|
||||
if !self.spks.contains_key(index) {
|
||||
return false;
|
||||
}
|
||||
// we cannot set index as unused when txouts are indexed under it
|
||||
if self.outputs_in_range(index..=index).next().is_some() {
|
||||
return false;
|
||||
}
|
||||
return self.unused.insert(index.clone());
|
||||
}
|
||||
|
||||
/// Returns the index associated with the script pubkey.
|
||||
pub fn index_of_spk(&self, script: &Script) -> Option<&I> {
|
||||
self.spk_indices.get(script)
|
||||
}
|
||||
|
||||
/// Computes total input value going from script pubkeys in the index (sent) and total output
|
||||
/// value going to script pubkeys in the index (received) in `tx`. For the `sent` to be computed
|
||||
/// correctly the output being spent must have already been scanned by the index. Calculating
|
||||
/// received just uses the transaction outputs directly so will be correct even if it has not
|
||||
/// been scanned.
|
||||
pub fn sent_and_received(&self, tx: &Transaction) -> (u64, u64) {
|
||||
let mut sent = 0;
|
||||
let mut received = 0;
|
||||
|
||||
for txin in &tx.input {
|
||||
if let Some((_, txout)) = self.txout(txin.previous_output) {
|
||||
sent += txout.value;
|
||||
}
|
||||
}
|
||||
for txout in &tx.output {
|
||||
if self.index_of_spk(&txout.script_pubkey).is_some() {
|
||||
received += txout.value;
|
||||
}
|
||||
}
|
||||
|
||||
(sent, received)
|
||||
}
|
||||
|
||||
/// Computes the net value that this transaction gives to the script pubkeys in the index and
|
||||
/// *takes* from the transaction outputs in the index. Shorthand for calling
|
||||
/// [`sent_and_received`] and subtracting sent from received.
|
||||
///
|
||||
/// [`sent_and_received`]: Self::sent_and_received
|
||||
pub fn net_value(&self, tx: &Transaction) -> i64 {
|
||||
let (sent, received) = self.sent_and_received(tx);
|
||||
received as i64 - sent as i64
|
||||
}
|
||||
|
||||
/// Whether any of the inputs of this transaction spend a txout tracked or whether any output
|
||||
/// matches one of our script pubkeys.
|
||||
///
|
||||
/// It is easily possible to misuse this method and get false negatives by calling it before you
|
||||
/// have scanned the `TxOut`s the transaction is spending. For example if you want to filter out
|
||||
/// all the transactions in a block that are irrelevant you **must first scan all the
|
||||
/// transactions in the block** and only then use this method.
|
||||
pub fn is_relevant(&self, tx: &Transaction) -> bool {
|
||||
let input_matches = tx
|
||||
.input
|
||||
.iter()
|
||||
.find(|input| self.txouts.contains_key(&input.previous_output))
|
||||
.is_some();
|
||||
let output_matches = tx
|
||||
.output
|
||||
.iter()
|
||||
.find(|output| self.spk_indices.contains_key(&output.script_pubkey))
|
||||
.is_some();
|
||||
input_matches || output_matches
|
||||
}
|
||||
}
|
||||
115
crates/chain/src/tx_data_traits.rs
Normal file
115
crates/chain/src/tx_data_traits.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use core::borrow::Borrow;
|
||||
|
||||
use alloc::{borrow::Cow, boxed::Box, rc::Rc, sync::Arc};
|
||||
use bitcoin::{Block, OutPoint, Transaction, TxOut};
|
||||
|
||||
/// Trait to do something with every txout contained in a structure.
|
||||
///
|
||||
/// We would prefer just work with things that can give us a `Iterator<Item=(OutPoint, &TxOut)>`
|
||||
/// here but rust's type system makes it extremely hard to do this (without trait objects).
|
||||
pub trait ForEachTxOut {
|
||||
/// The provided closure `f` will called with each `outpoint/txout` pair.
|
||||
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut)));
|
||||
}
|
||||
|
||||
impl ForEachTxOut for Block {
|
||||
fn for_each_txout(&self, mut f: impl FnMut((OutPoint, &TxOut))) {
|
||||
for tx in self.txdata.iter() {
|
||||
tx.for_each_txout(&mut f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for things that have a single [`Transaction`] in them.
|
||||
///
|
||||
/// This alows polymorphism in structures such as [`TxGraph<T>`] where `T` can be anything that
|
||||
/// implements `AsTransaction`. You might think that we could just use [`core::convert::AsRef`] for
|
||||
/// this but the problem is that we need to implement it on `Cow<T>` where `T: AsTransaction` which
|
||||
/// we can't do with a foreign trait like `AsTransaction`.
|
||||
///
|
||||
/// [`Transaction`]: bitcoin::Transaction
|
||||
/// [`TxGraph<T>`]: crate::tx_graph::TxGraph
|
||||
pub trait AsTransaction {
|
||||
/// Get a reference to the transaction.
|
||||
fn as_tx(&self) -> &Transaction;
|
||||
}
|
||||
|
||||
impl AsTransaction for Transaction {
|
||||
fn as_tx(&self) -> &Transaction {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsTransaction> AsTransaction for Rc<T> {
|
||||
fn as_tx(&self) -> &Transaction {
|
||||
self.as_ref().as_tx()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsTransaction> AsTransaction for Arc<T> {
|
||||
fn as_tx(&self) -> &Transaction {
|
||||
self.as_ref().as_tx()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsTransaction> AsTransaction for Box<T> {
|
||||
fn as_tx(&self) -> &Transaction {
|
||||
self.as_ref().as_tx()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AsTransaction + Clone> AsTransaction for Cow<'a, T> {
|
||||
fn as_tx(&self) -> &Transaction {
|
||||
<Cow<'_, T> as Borrow<T>>::borrow(self).as_tx()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ForEachTxOut for T
|
||||
where
|
||||
T: AsTransaction,
|
||||
{
|
||||
fn for_each_txout(&self, mut f: impl FnMut((OutPoint, &TxOut))) {
|
||||
let tx = self.as_tx();
|
||||
let txid = tx.txid();
|
||||
for (i, txout) in tx.output.iter().enumerate() {
|
||||
f((
|
||||
OutPoint {
|
||||
txid,
|
||||
vout: i as u32,
|
||||
},
|
||||
txout,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait like [`core::convert::Into`] for converting one thing into another.
|
||||
///
|
||||
/// We use it to convert one transaction type into another so that an update for `T2` can be used on
|
||||
/// a `TxGraph<T1>` as long as `T2: IntoOwned<T1>`.
|
||||
///
|
||||
/// We couldn't use `Into` because we needed to implement it for [`Cow<'a, T>`].
|
||||
///
|
||||
/// [`Cow<'a, T>`]: std::borrow::Cow
|
||||
pub trait IntoOwned<T> {
|
||||
/// Converts the provided type into another (owned) type.
|
||||
fn into_owned(self) -> T;
|
||||
}
|
||||
|
||||
impl<T> IntoOwned<T> for T {
|
||||
fn into_owned(self) -> T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Clone> IntoOwned<T> for Cow<'a, T> {
|
||||
fn into_owned(self) -> T {
|
||||
Cow::into_owned(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Clone> IntoOwned<T> for &'a T {
|
||||
fn into_owned(self) -> T {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
637
crates/chain/src/tx_graph.rs
Normal file
637
crates/chain/src/tx_graph.rs
Normal file
@@ -0,0 +1,637 @@
|
||||
//! Module for structures that store and traverse transactions.
|
||||
//!
|
||||
//! [`TxGraph`] is a monotone structure that inserts transactions and indexes spends. The
|
||||
//! [`Additions`] structure reports changes of [`TxGraph`], but can also be applied on to a
|
||||
//! [`TxGraph`] as well. Lastly, [`TxDescendants`] is an [`Iterator`] which traverses descendants of
|
||||
//! a given transaction.
|
||||
//!
|
||||
//! Conflicting transactions are allowed to coexist within a [`TxGraph`]. This is useful for
|
||||
//! identifying and traversing conflicts and descendants of a given transaction.
|
||||
//!
|
||||
//! # Previewing and applying changes
|
||||
//!
|
||||
//! Methods that either preview or apply changes to [`TxGraph`] will return [`Additions`].
|
||||
//! [`Additions`] can be applied back on to a [`TxGraph`], or be used to inform persistent storage
|
||||
//! of the changes to [`TxGraph`].
|
||||
//!
|
||||
//! ```
|
||||
//! # use bdk_chain::tx_graph::TxGraph;
|
||||
//! # use bdk_chain::example_utils::*;
|
||||
//! # use bitcoin::Transaction;
|
||||
//! # let tx_a = tx_from_hex(RAW_TX_1);
|
||||
//! # let tx_b = tx_from_hex(RAW_TX_2);
|
||||
//! let mut graph = TxGraph::<Transaction>::default();
|
||||
//!
|
||||
//! // preview a transaction insertion (not actually inserted)
|
||||
//! let additions = graph.insert_tx_preview(tx_a);
|
||||
//! // apply the insertion
|
||||
//! graph.apply_additions(additions);
|
||||
//!
|
||||
//! // you can also insert a transaction directly
|
||||
//! let already_applied_additions = graph.insert_tx(tx_b);
|
||||
//! ```
|
||||
//!
|
||||
//! A [`TxGraph`] can also be updated with another [`TxGraph`].
|
||||
//!
|
||||
//! ```
|
||||
//! # use bdk_chain::tx_graph::TxGraph;
|
||||
//! # use bdk_chain::example_utils::*;
|
||||
//! # use bitcoin::Transaction;
|
||||
//! # let tx_a = tx_from_hex(RAW_TX_1);
|
||||
//! # let tx_b = tx_from_hex(RAW_TX_2);
|
||||
//! let mut graph = TxGraph::<Transaction>::default();
|
||||
//! let update = TxGraph::<Transaction>::new(vec![tx_a, tx_b]);
|
||||
//!
|
||||
//! // preview additions as result of the update
|
||||
//! let additions = graph.determine_additions(&update);
|
||||
//! // apply the additions
|
||||
//! graph.apply_additions(additions);
|
||||
//!
|
||||
//! // we can also apply the update graph directly
|
||||
//! // the additions will be empty as we have already applied the same update above
|
||||
//! let additions = graph.apply_update(update);
|
||||
//! assert!(additions.is_empty());
|
||||
//! ```
|
||||
//!
|
||||
use crate::{collections::*, AsTransaction, ForEachTxOut, IntoOwned};
|
||||
use alloc::vec::Vec;
|
||||
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
||||
use core::ops::RangeInclusive;
|
||||
|
||||
/// A graph of transactions and spends.
|
||||
///
|
||||
/// See the [module-level documentation] for more.
|
||||
///
|
||||
/// [module-level documentation]: crate::tx_graph
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TxGraph<T = Transaction> {
|
||||
txs: HashMap<Txid, TxNode<T>>,
|
||||
spends: BTreeMap<OutPoint, HashSet<Txid>>,
|
||||
|
||||
// This atrocity exists so that `TxGraph::outspends()` can return a reference.
|
||||
// FIXME: This can be removed once `HashSet::new` is a const fn.
|
||||
empty_outspends: HashSet<Txid>,
|
||||
}
|
||||
|
||||
impl<T> Default for TxGraph<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
txs: Default::default(),
|
||||
spends: Default::default(),
|
||||
empty_outspends: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Node of a [`TxGraph`]. This can either be a whole transaction, or a partial transaction (where
|
||||
/// we only have select outputs).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum TxNode<T = Transaction> {
|
||||
Whole(T),
|
||||
Partial(BTreeMap<u32, TxOut>),
|
||||
}
|
||||
|
||||
impl<T> Default for TxNode<T> {
|
||||
fn default() -> Self {
|
||||
Self::Partial(BTreeMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsTransaction> TxGraph<T> {
|
||||
/// Iterate over all tx outputs known by [`TxGraph`].
|
||||
pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
|
||||
self.txs.iter().flat_map(|(txid, tx)| match tx {
|
||||
TxNode::Whole(tx) => tx
|
||||
.as_tx()
|
||||
.output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(vout, txout)| (OutPoint::new(*txid, vout as _), txout))
|
||||
.collect::<Vec<_>>(),
|
||||
TxNode::Partial(txouts) => txouts
|
||||
.iter()
|
||||
.map(|(vout, txout)| (OutPoint::new(*txid, *vout as _), txout))
|
||||
.collect::<Vec<_>>(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate over all full transactions in the graph.
|
||||
pub fn full_transactions(&self) -> impl Iterator<Item = &T> {
|
||||
self.txs.iter().filter_map(|(_, tx)| match tx {
|
||||
TxNode::Whole(tx) => Some(tx),
|
||||
TxNode::Partial(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a transaction by txid. This only returns `Some` for full transactions.
|
||||
///
|
||||
/// Refer to [`get_txout`] for getting a specific [`TxOut`].
|
||||
///
|
||||
/// [`get_txout`]: Self::get_txout
|
||||
pub fn get_tx(&self, txid: Txid) -> Option<&T> {
|
||||
match self.txs.get(&txid)? {
|
||||
TxNode::Whole(tx) => Some(tx),
|
||||
TxNode::Partial(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtains a single tx output (if any) at specified outpoint.
|
||||
pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
|
||||
match self.txs.get(&outpoint.txid)? {
|
||||
TxNode::Whole(tx) => tx.as_tx().output.get(outpoint.vout as usize),
|
||||
TxNode::Partial(txouts) => txouts.get(&outpoint.vout),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
|
||||
pub fn txouts(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
|
||||
Some(match self.txs.get(&txid)? {
|
||||
TxNode::Whole(tx) => tx
|
||||
.as_tx()
|
||||
.output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(vout, txout)| (vout as u32, txout))
|
||||
.collect::<BTreeMap<_, _>>(),
|
||||
TxNode::Partial(txouts) => txouts
|
||||
.iter()
|
||||
.map(|(vout, txout)| (*vout, txout))
|
||||
.collect::<BTreeMap<_, _>>(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction.
|
||||
/// Returns `Some(_)` if we have all the `TxOut`s being spent by `tx` in the graph (either as
|
||||
/// the full transactions or individual txouts). If the returned value is negative then the
|
||||
/// transaction is invalid according to the graph.
|
||||
///
|
||||
/// Returns `None` if we're missing an input for the tx in the graph.
|
||||
///
|
||||
/// Note `tx` does not have to be in the graph for this to work.
|
||||
pub fn calculate_fee(&self, tx: &Transaction) -> Option<i64> {
|
||||
if tx.is_coin_base() {
|
||||
return Some(0);
|
||||
}
|
||||
let inputs_sum = tx
|
||||
.input
|
||||
.iter()
|
||||
.map(|txin| {
|
||||
self.get_txout(txin.previous_output)
|
||||
.map(|txout| txout.value as i64)
|
||||
})
|
||||
.sum::<Option<i64>>()?;
|
||||
|
||||
let outputs_sum = tx
|
||||
.output
|
||||
.iter()
|
||||
.map(|txout| txout.value as i64)
|
||||
.sum::<i64>();
|
||||
|
||||
Some(inputs_sum - outputs_sum)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsTransaction + Ord + Clone> TxGraph<T> {
|
||||
/// Contruct a new [`TxGraph`] from a list of transaction.
|
||||
pub fn new(txs: impl IntoIterator<Item = T>) -> Self {
|
||||
let mut new = Self::default();
|
||||
for tx in txs.into_iter() {
|
||||
let _ = new.insert_tx(tx);
|
||||
}
|
||||
new
|
||||
}
|
||||
/// Inserts the given [`TxOut`] at [`OutPoint`].
|
||||
///
|
||||
/// Note this will ignore the action if we already have the full transaction that the txout is
|
||||
/// alledged to be on (even if it doesn't match it!).
|
||||
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> Additions<T> {
|
||||
let additions = self.insert_txout_preview(outpoint, txout);
|
||||
self.apply_additions(additions.clone());
|
||||
additions
|
||||
}
|
||||
|
||||
/// Inserts the given transaction into [`TxGraph`].
|
||||
///
|
||||
/// The [`Additions`] returned will be empty if `tx` already exists.
|
||||
pub fn insert_tx(&mut self, tx: T) -> Additions<T> {
|
||||
let additions = self.insert_tx_preview(tx);
|
||||
self.apply_additions(additions.clone());
|
||||
additions
|
||||
}
|
||||
|
||||
/// Extends this graph with another so that `self` becomes the union of the two sets of
|
||||
/// transactions.
|
||||
///
|
||||
/// The returned [`Additions`] is the set difference of `update` and `self` (transactions that
|
||||
/// exist in `update` but not in `self`).
|
||||
pub fn apply_update<T2>(&mut self, update: TxGraph<T2>) -> Additions<T>
|
||||
where
|
||||
T2: IntoOwned<T> + Clone,
|
||||
{
|
||||
let additions = self.determine_additions(&update);
|
||||
self.apply_additions(additions.clone());
|
||||
additions
|
||||
}
|
||||
|
||||
/// Applies [`Additions`] to [`TxGraph`].
|
||||
pub fn apply_additions(&mut self, additions: Additions<T>) {
|
||||
for tx in additions.tx {
|
||||
let txid = tx.as_tx().txid();
|
||||
|
||||
tx.as_tx()
|
||||
.input
|
||||
.iter()
|
||||
.map(|txin| txin.previous_output)
|
||||
// coinbase spends are not to be counted
|
||||
.filter(|outpoint| !outpoint.is_null())
|
||||
// record spend as this tx has spent this outpoint
|
||||
.for_each(|outpoint| {
|
||||
self.spends.entry(outpoint).or_default().insert(txid);
|
||||
});
|
||||
|
||||
if let Some(TxNode::Whole(old_tx)) = self.txs.insert(txid, TxNode::Whole(tx)) {
|
||||
debug_assert_eq!(
|
||||
old_tx.as_tx().txid(),
|
||||
txid,
|
||||
"old tx of same txid should not be different"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (outpoint, txout) in additions.txout {
|
||||
let tx_entry = self
|
||||
.txs
|
||||
.entry(outpoint.txid)
|
||||
.or_insert_with(TxNode::default);
|
||||
|
||||
match tx_entry {
|
||||
TxNode::Whole(_) => { /* do nothing since we already have full tx */ }
|
||||
TxNode::Partial(txouts) => {
|
||||
txouts.insert(outpoint.vout, txout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Previews the resultant [`Additions`] when [`Self`] is updated against the `update` graph.
|
||||
///
|
||||
/// The [`Additions`] would be the set difference of `update` and `self` (transactions that
|
||||
/// exist in `update` but not in `self`).
|
||||
pub fn determine_additions<'a, T2>(&self, update: &'a TxGraph<T2>) -> Additions<T>
|
||||
where
|
||||
T2: IntoOwned<T> + Clone,
|
||||
{
|
||||
let mut additions = Additions::<T>::default();
|
||||
|
||||
for (&txid, update_tx) in &update.txs {
|
||||
if self.get_tx(txid).is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match update_tx {
|
||||
TxNode::Whole(tx) => {
|
||||
if matches!(self.txs.get(&txid), None | Some(TxNode::Partial(_))) {
|
||||
additions
|
||||
.tx
|
||||
.insert(<T2 as IntoOwned<T>>::into_owned(tx.clone()));
|
||||
}
|
||||
}
|
||||
TxNode::Partial(partial) => {
|
||||
for (&vout, update_txout) in partial {
|
||||
let outpoint = OutPoint::new(txid, vout);
|
||||
|
||||
if self.get_txout(outpoint) != Some(&update_txout) {
|
||||
additions.txout.insert(outpoint, update_txout.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
additions
|
||||
}
|
||||
|
||||
/// Returns the resultant [`Additions`] if the given transaction is inserted. Does not actually
|
||||
/// mutate [`Self`].
|
||||
///
|
||||
/// The [`Additions`] result will be empty if `tx` already existed in `self`.
|
||||
pub fn insert_tx_preview(&self, tx: T) -> Additions<T> {
|
||||
let mut update = Self::default();
|
||||
update.txs.insert(tx.as_tx().txid(), TxNode::Whole(tx));
|
||||
self.determine_additions(&update)
|
||||
}
|
||||
|
||||
/// Returns the resultant [`Additions`] if the given `txout` is inserted at `outpoint`. Does not
|
||||
/// mutate `self`.
|
||||
///
|
||||
/// The [`Additions`] result will be empty if the `outpoint` (or a full transaction containing
|
||||
/// the `outpoint`) already existed in `self`.
|
||||
pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> Additions<T> {
|
||||
let mut update = Self::default();
|
||||
update.txs.insert(
|
||||
outpoint.txid,
|
||||
TxNode::Partial([(outpoint.vout, txout)].into()),
|
||||
);
|
||||
self.determine_additions(&update)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TxGraph<T> {
|
||||
/// The transactions spending from this output.
|
||||
///
|
||||
/// `TxGraph` allows conflicting transactions within the graph. Obviously the transactions in
|
||||
/// the returned will never be in the same blockchain.
|
||||
pub fn outspends(&self, outpoint: OutPoint) -> &HashSet<Txid> {
|
||||
self.spends.get(&outpoint).unwrap_or(&self.empty_outspends)
|
||||
}
|
||||
|
||||
/// Iterates over the transactions spending from `txid`.
|
||||
///
|
||||
/// The iterator item is a union of `(vout, txid-set)` where:
|
||||
///
|
||||
/// - `vout` is the provided `txid`'s outpoint that is being spent
|
||||
/// - `txid-set` is the set of txids that is spending the `vout`
|
||||
pub fn tx_outspends(
|
||||
&self,
|
||||
txid: Txid,
|
||||
) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ {
|
||||
let start = OutPoint { txid, vout: 0 };
|
||||
let end = OutPoint {
|
||||
txid,
|
||||
vout: u32::MAX,
|
||||
};
|
||||
self.spends
|
||||
.range(start..=end)
|
||||
.map(|(outpoint, spends)| (outpoint.vout, spends))
|
||||
}
|
||||
|
||||
/// Iterate over all partial transactions (outputs only) in the graph.
|
||||
pub fn partial_transactions(&self) -> impl Iterator<Item = (Txid, &BTreeMap<u32, TxOut>)> {
|
||||
self.txs.iter().filter_map(|(txid, tx)| match tx {
|
||||
TxNode::Whole(_) => None,
|
||||
TxNode::Partial(partial) => Some((*txid, partial)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates an iterator that both filters and maps descendants from the starting `txid`.
|
||||
///
|
||||
/// The supplied closure takes in two inputs `(depth, descendant_txid)`:
|
||||
///
|
||||
/// * `depth` is the distance between the starting `txid` and the `descendant_txid`. I.e. if the
|
||||
/// descendant is spending an output of the starting `txid`, the `depth` will be 1.
|
||||
/// * `descendant_txid` is the descendant's txid which we are considering to walk.
|
||||
///
|
||||
/// The supplied closure returns an `Option<T>`, allowing the caller to map each node it vists
|
||||
/// and decide whether to visit descendants.
|
||||
pub fn walk_descendants<'g, F, O>(&'g self, txid: Txid, walk_map: F) -> TxDescendants<F, T>
|
||||
where
|
||||
F: FnMut(usize, Txid) -> Option<O> + 'g,
|
||||
{
|
||||
TxDescendants::new_exclude_root(self, txid, walk_map)
|
||||
}
|
||||
|
||||
/// Creates an iterator that both filters and maps conflicting transactions (this includes
|
||||
/// descendants of directly-conflicting transactions, which are also considered conflicts).
|
||||
///
|
||||
/// Refer to [`Self::walk_descendants`] for `walk_map` usage.
|
||||
pub fn walk_conflicts<'g, F, O>(
|
||||
&'g self,
|
||||
tx: &'g Transaction,
|
||||
walk_map: F,
|
||||
) -> TxDescendants<F, T>
|
||||
where
|
||||
F: FnMut(usize, Txid) -> Option<O> + 'g,
|
||||
{
|
||||
let txids = self.direct_conflicts_of_tx(tx).map(|(_, txid)| txid);
|
||||
TxDescendants::from_multiple_include_root(self, txids, walk_map)
|
||||
}
|
||||
|
||||
/// Given a transaction, return an iterator of txids which directly conflict with the given
|
||||
/// transaction's inputs (spends). The conflicting txids are returned with the given
|
||||
/// transaction's vin (in which it conflicts).
|
||||
///
|
||||
/// Note that this only returns directly conflicting txids and does not include descendants of
|
||||
/// those txids (which are technically also conflicting).
|
||||
pub fn direct_conflicts_of_tx<'g>(
|
||||
&'g self,
|
||||
tx: &'g Transaction,
|
||||
) -> impl Iterator<Item = (usize, Txid)> + '_ {
|
||||
let txid = tx.txid();
|
||||
tx.input
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(vin, txin)| self.spends.get(&txin.previous_output).zip(Some(vin)))
|
||||
.flat_map(|(spends, vin)| core::iter::repeat(vin).zip(spends.iter().cloned()))
|
||||
.filter(move |(_, conflicting_txid)| *conflicting_txid != txid)
|
||||
}
|
||||
|
||||
/// Whether the graph has any transactions or outputs in it.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.txs.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure that represents changes to a [`TxGraph`].
|
||||
///
|
||||
/// It is named "additions" because [`TxGraph`] is monotone so transactions can only be added and
|
||||
/// not removed.
|
||||
///
|
||||
/// Refer to [module-level documentation] for more.
|
||||
///
|
||||
/// [module-level documentation]: crate::tx_graph
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(
|
||||
crate = "serde_crate",
|
||||
bound(
|
||||
deserialize = "T: Ord + serde::Deserialize<'de>",
|
||||
serialize = "T: Ord + serde::Serialize"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[must_use]
|
||||
pub struct Additions<T> {
|
||||
pub tx: BTreeSet<T>,
|
||||
pub txout: BTreeMap<OutPoint, TxOut>,
|
||||
}
|
||||
|
||||
impl<T> Additions<T> {
|
||||
/// Returns true if the [`Additions`] is empty (no transactions or txouts).
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.tx.is_empty() && self.txout.is_empty()
|
||||
}
|
||||
|
||||
/// Iterates over all outpoints contained within [`Additions`].
|
||||
pub fn txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)>
|
||||
where
|
||||
T: AsTransaction,
|
||||
{
|
||||
self.tx
|
||||
.iter()
|
||||
.flat_map(|tx| {
|
||||
tx.as_tx()
|
||||
.output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(vout, txout)| (OutPoint::new(tx.as_tx().txid(), vout as _), txout))
|
||||
})
|
||||
.chain(self.txout.iter().map(|(op, txout)| (*op, txout)))
|
||||
}
|
||||
|
||||
/// Appends the changes in `other` into self such that applying `self` afterwards has the same
|
||||
/// effect as sequentially applying the original `self` and `other`.
|
||||
pub fn append(&mut self, mut other: Additions<T>)
|
||||
where
|
||||
T: Ord,
|
||||
{
|
||||
self.tx.append(&mut other.tx);
|
||||
self.txout.append(&mut other.txout);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Additions<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tx: Default::default(),
|
||||
txout: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<TxGraph> for TxGraph {
|
||||
fn as_ref(&self) -> &TxGraph {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsTransaction> ForEachTxOut for Additions<T> {
|
||||
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
|
||||
self.txouts().for_each(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsTransaction> ForEachTxOut for TxGraph<T> {
|
||||
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
|
||||
self.all_txouts().for_each(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator that traverses transaction descendants.
|
||||
///
|
||||
/// This `struct` is created by the [`walk_descendants`] method of [`TxGraph`].
|
||||
///
|
||||
/// [`walk_descendants`]: TxGraph::walk_descendants
|
||||
pub struct TxDescendants<'g, F, T> {
|
||||
graph: &'g TxGraph<T>,
|
||||
visited: HashSet<Txid>,
|
||||
stack: Vec<(usize, Txid)>,
|
||||
filter_map: F,
|
||||
}
|
||||
|
||||
impl<'g, F, T> TxDescendants<'g, F, T> {
|
||||
/// Creates a `TxDescendants` that includes the starting `txid` when iterating.
|
||||
#[allow(unused)]
|
||||
pub(crate) fn new_include_root(graph: &'g TxGraph<T>, txid: Txid, filter_map: F) -> Self {
|
||||
Self {
|
||||
graph,
|
||||
visited: Default::default(),
|
||||
stack: [(0, txid)].into(),
|
||||
filter_map,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `TxDescendants` that excludes the starting `txid` when iterating.
|
||||
pub(crate) fn new_exclude_root(graph: &'g TxGraph<T>, txid: Txid, filter_map: F) -> Self {
|
||||
let mut descendants = Self {
|
||||
graph,
|
||||
visited: Default::default(),
|
||||
stack: Default::default(),
|
||||
filter_map,
|
||||
};
|
||||
descendants.populate_stack(1, txid);
|
||||
descendants
|
||||
}
|
||||
|
||||
/// Creates a `TxDescendants` from multiple starting transactions that includes the starting
|
||||
/// `txid`s when iterating.
|
||||
pub(crate) fn from_multiple_include_root<I>(
|
||||
graph: &'g TxGraph<T>,
|
||||
txids: I,
|
||||
filter_map: F,
|
||||
) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Txid>,
|
||||
{
|
||||
Self {
|
||||
graph,
|
||||
visited: Default::default(),
|
||||
stack: txids.into_iter().map(|txid| (0, txid)).collect(),
|
||||
filter_map,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `TxDescendants` from multiple starting transactions that excludes the starting
|
||||
/// `txid`s when iterating.
|
||||
#[allow(unused)]
|
||||
pub(crate) fn from_multiple_exclude_root<I>(
|
||||
graph: &'g TxGraph<T>,
|
||||
txids: I,
|
||||
filter_map: F,
|
||||
) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Txid>,
|
||||
{
|
||||
let mut descendants = Self {
|
||||
graph,
|
||||
visited: Default::default(),
|
||||
stack: Default::default(),
|
||||
filter_map,
|
||||
};
|
||||
for txid in txids {
|
||||
descendants.populate_stack(1, txid);
|
||||
}
|
||||
descendants
|
||||
}
|
||||
}
|
||||
|
||||
impl<'g, F, T> TxDescendants<'g, F, T> {
|
||||
fn populate_stack(&mut self, depth: usize, txid: Txid) {
|
||||
let spend_paths = self
|
||||
.graph
|
||||
.spends
|
||||
.range(tx_outpoint_range(txid))
|
||||
.flat_map(|(_, spends)| spends)
|
||||
.map(|&txid| (depth, txid));
|
||||
self.stack.extend(spend_paths);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'g, F, O, T> Iterator for TxDescendants<'g, F, T>
|
||||
where
|
||||
F: FnMut(usize, Txid) -> Option<O>,
|
||||
{
|
||||
type Item = O;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (op_spends, txid, item) = loop {
|
||||
// we have exhausted all paths when stack is empty
|
||||
let (op_spends, txid) = self.stack.pop()?;
|
||||
// we do not want to visit the same transaction twice
|
||||
if self.visited.insert(txid) {
|
||||
// ignore paths when user filters them out
|
||||
if let Some(item) = (self.filter_map)(op_spends, txid) {
|
||||
break (op_spends, txid, item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.populate_stack(op_spends + 1, txid);
|
||||
return Some(item);
|
||||
}
|
||||
}
|
||||
|
||||
fn tx_outpoint_range(txid: Txid) -> RangeInclusive<OutPoint> {
|
||||
OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX)
|
||||
}
|
||||
Reference in New Issue
Block a user