[bdk_chain_redesign] Better names, comments and generic bounds
* Instead of implementing `ChainPosition` for `ObservedIn<BlockId>` to use `FullTxOut` methods (`is_spendable_at` and `is_mature`), we create alternative versions of those methods that require bounds with `Anchor`. This removes all `ObservedIn<A>: ChainPosition` bounds for methods of `IndexedTxGraph`. * Various improvements to comments and names.
This commit is contained in:
parent
6e59dce10b
commit
89cfa4d78e
@ -6,49 +6,21 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Represents an observation of some chain data.
|
/// Represents an observation of some chain data.
|
||||||
|
///
|
||||||
|
/// The generic `A` should be a [`BlockAnchor`] implementation.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
|
||||||
pub enum ObservedIn<A> {
|
pub enum ObservedAs<A> {
|
||||||
/// The chain data is seen in a block identified by `A`.
|
/// The chain data is seen as confirmed, and in anchored by `A`.
|
||||||
Block(A),
|
Confirmed(A),
|
||||||
/// The chain data is seen in mempool at this given timestamp.
|
/// The chain data is seen in mempool at this given timestamp.
|
||||||
/// TODO: Call this `Unconfirmed`.
|
Unconfirmed(u64),
|
||||||
Mempool(u64),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Clone> ObservedIn<&A> {
|
impl<A: Clone> ObservedAs<&A> {
|
||||||
pub fn into_owned(self) -> ObservedIn<A> {
|
pub fn cloned(self) -> ObservedAs<A> {
|
||||||
match self {
|
match self {
|
||||||
ObservedIn::Block(a) => ObservedIn::Block(a.clone()),
|
ObservedAs::Confirmed(a) => ObservedAs::Confirmed(a.clone()),
|
||||||
ObservedIn::Mempool(last_seen) => ObservedIn::Mempool(last_seen),
|
ObservedAs::Unconfirmed(last_seen) => ObservedAs::Unconfirmed(last_seen),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChainPosition for ObservedIn<BlockId> {
|
|
||||||
fn height(&self) -> TxHeight {
|
|
||||||
match self {
|
|
||||||
ObservedIn::Block(block_id) => TxHeight::Confirmed(block_id.height),
|
|
||||||
ObservedIn::Mempool(_) => TxHeight::Unconfirmed,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max_ord_of_height(height: TxHeight) -> Self {
|
|
||||||
match height {
|
|
||||||
TxHeight::Confirmed(height) => ObservedIn::Block(BlockId {
|
|
||||||
height,
|
|
||||||
hash: Hash::from_inner([u8::MAX; 32]),
|
|
||||||
}),
|
|
||||||
TxHeight::Unconfirmed => Self::Mempool(u64::MAX),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn min_ord_of_height(height: TxHeight) -> Self {
|
|
||||||
match height {
|
|
||||||
TxHeight::Confirmed(height) => ObservedIn::Block(BlockId {
|
|
||||||
height,
|
|
||||||
hash: Hash::from_inner([u8::MIN; 32]),
|
|
||||||
}),
|
|
||||||
TxHeight::Unconfirmed => Self::Mempool(u64::MIN),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,20 +189,20 @@ impl From<(&u32, &BlockHash)> for BlockId {
|
|||||||
|
|
||||||
/// A `TxOut` with as much data as we can retrieve about it
|
/// A `TxOut` with as much data as we can retrieve about it
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct FullTxOut<I> {
|
pub struct FullTxOut<P> {
|
||||||
/// The location of the `TxOut`.
|
/// The location of the `TxOut`.
|
||||||
pub outpoint: OutPoint,
|
pub outpoint: OutPoint,
|
||||||
/// The `TxOut`.
|
/// The `TxOut`.
|
||||||
pub txout: TxOut,
|
pub txout: TxOut,
|
||||||
/// The position of the transaction in `outpoint` in the overall chain.
|
/// The position of the transaction in `outpoint` in the overall chain.
|
||||||
pub chain_position: I,
|
pub chain_position: P,
|
||||||
/// The txid and chain position of the transaction (if any) that has spent this output.
|
/// The txid and chain position of the transaction (if any) that has spent this output.
|
||||||
pub spent_by: Option<(I, Txid)>,
|
pub spent_by: Option<(P, Txid)>,
|
||||||
/// Whether this output is on a coinbase transaction.
|
/// Whether this output is on a coinbase transaction.
|
||||||
pub is_on_coinbase: bool,
|
pub is_on_coinbase: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: ChainPosition> FullTxOut<I> {
|
impl<P: ChainPosition> FullTxOut<P> {
|
||||||
/// Whether the utxo is/was/will be spendable at `height`.
|
/// 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
|
/// It is spendable if it is not an immature coinbase output and no spending tx has been
|
||||||
@ -269,15 +241,63 @@ impl<I: ChainPosition> FullTxOut<I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Clone> FullTxOut<ObservedIn<&A>> {
|
impl<A: BlockAnchor> FullTxOut<ObservedAs<A>> {
|
||||||
pub fn into_owned(self) -> FullTxOut<ObservedIn<A>> {
|
/// Whether the `txout` is considered mature.
|
||||||
FullTxOut {
|
///
|
||||||
outpoint: self.outpoint,
|
/// This is the alternative version of [`is_mature`] which depends on `chain_position` being a
|
||||||
txout: self.txout,
|
/// [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
|
||||||
chain_position: self.chain_position.into_owned(),
|
///
|
||||||
spent_by: self.spent_by.map(|(o, txid)| (o.into_owned(), txid)),
|
/// [`is_mature`]: Self::is_mature
|
||||||
is_on_coinbase: self.is_on_coinbase,
|
pub fn is_observed_as_mature(&self, tip: u32) -> bool {
|
||||||
|
if !self.is_on_coinbase {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tx_height = match &self.chain_position {
|
||||||
|
ObservedAs::Confirmed(anchor) => anchor.anchor_block().height,
|
||||||
|
ObservedAs::Unconfirmed(_) => {
|
||||||
|
debug_assert!(false, "coinbase tx can never be unconfirmed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let age = tip.saturating_sub(tx_height);
|
||||||
|
if age + 1 < COINBASE_MATURITY {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the utxo is/was/will be spendable with chain `tip`.
|
||||||
|
///
|
||||||
|
/// This is the alternative version of [`is_spendable_at`] which depends on `chain_position`
|
||||||
|
/// being a [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
|
||||||
|
///
|
||||||
|
/// [`is_spendable_at`]: Self::is_spendable_at
|
||||||
|
pub fn is_observed_as_spendable(&self, tip: u32) -> bool {
|
||||||
|
if !self.is_observed_as_mature(tip) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
match &self.chain_position {
|
||||||
|
ObservedAs::Confirmed(anchor) => {
|
||||||
|
if anchor.anchor_block().height > tip {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// [TODO] Why are unconfirmed txs always considered unspendable here?
|
||||||
|
ObservedAs::Unconfirmed(_) => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// if the spending tx is confirmed within tip height, the txout is no longer spendable
|
||||||
|
if let Some((ObservedAs::Confirmed(spending_anchor), _)) = &self.spent_by {
|
||||||
|
if spending_anchor.anchor_block().height <= tip {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ where
|
|||||||
let _ = inflated_chain
|
let _ = inflated_chain
|
||||||
.insert_tx(*txid, pos.clone())
|
.insert_tx(*txid, pos.clone())
|
||||||
.expect("must insert since this was already in update");
|
.expect("must insert since this was already in update");
|
||||||
let _ = inflated_graph.insert_tx(tx.clone());
|
let _ = inflated_graph.insert_tx(tx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@ -212,8 +212,8 @@ where
|
|||||||
/// the unconfirmed transaction list within the [`SparseChain`].
|
/// the unconfirmed transaction list within the [`SparseChain`].
|
||||||
pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, &Transaction)> {
|
pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, &Transaction)> {
|
||||||
let position = self.chain.tx_position(txid)?;
|
let position = self.chain.tx_position(txid)?;
|
||||||
let tx = self.graph.get_tx(txid).expect("must exist");
|
let full_tx = self.graph.get_tx(txid).expect("must exist");
|
||||||
Some((position, tx))
|
Some((position, full_tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the changes required to insert a transaction into the inner [`ChainGraph`] and
|
/// Determines the changes required to insert a transaction into the inner [`ChainGraph`] and
|
||||||
|
@ -4,16 +4,15 @@ use bitcoin::{OutPoint, Script, Transaction, TxOut};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
keychain::Balance,
|
keychain::Balance,
|
||||||
sparse_chain::ChainPosition,
|
|
||||||
tx_graph::{Additions, TxGraph, TxNode},
|
tx_graph::{Additions, TxGraph, TxNode},
|
||||||
BlockAnchor, ChainOracle, FullTxOut, ObservedIn, TxIndex,
|
BlockAnchor, ChainOracle, FullTxOut, ObservedAs, TxIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An outwards-facing view of a transaction that is part of the *best chain*'s history.
|
/// An outwards-facing view of a transaction that is part of the *best chain*'s history.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct TxInChain<'a, T, A> {
|
pub struct CanonicalTx<'a, T, A> {
|
||||||
/// Where the transaction is observed (in a block or in mempool).
|
/// Where the transaction is observed (in a block or in mempool).
|
||||||
pub observed_in: ObservedIn<&'a A>,
|
pub observed_as: ObservedAs<&'a A>,
|
||||||
/// The transaction with anchors and last seen timestamp.
|
/// The transaction with anchors and last seen timestamp.
|
||||||
pub tx: TxNode<'a, T, A>,
|
pub tx: TxNode<'a, T, A>,
|
||||||
}
|
}
|
||||||
@ -140,19 +139,23 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
outpoint: OutPoint,
|
outpoint: OutPoint,
|
||||||
txout: &TxOut,
|
txout: &TxOut,
|
||||||
observation: ObservedIn<A>,
|
observation: ObservedAs<A>,
|
||||||
) -> IndexedAdditions<A, I::Additions> {
|
) -> IndexedAdditions<A, I::Additions> {
|
||||||
let last_height = match &observation {
|
let last_height = match &observation {
|
||||||
ObservedIn::Block(anchor) => self.insert_height_internal(anchor.anchor_block().height),
|
ObservedAs::Confirmed(anchor) => {
|
||||||
ObservedIn::Mempool(_) => None,
|
self.insert_height_internal(anchor.anchor_block().height)
|
||||||
|
}
|
||||||
|
ObservedAs::Unconfirmed(_) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
IndexedAdditions {
|
IndexedAdditions {
|
||||||
graph_additions: {
|
graph_additions: {
|
||||||
let mut graph_additions = self.graph.insert_txout(outpoint, txout.clone());
|
let mut graph_additions = self.graph.insert_txout(outpoint, txout.clone());
|
||||||
graph_additions.append(match observation {
|
graph_additions.append(match observation {
|
||||||
ObservedIn::Block(anchor) => self.graph.insert_anchor(outpoint.txid, anchor),
|
ObservedAs::Confirmed(anchor) => {
|
||||||
ObservedIn::Mempool(seen_at) => {
|
self.graph.insert_anchor(outpoint.txid, anchor)
|
||||||
|
}
|
||||||
|
ObservedAs::Unconfirmed(seen_at) => {
|
||||||
self.graph.insert_seen_at(outpoint.txid, seen_at)
|
self.graph.insert_seen_at(outpoint.txid, seen_at)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -166,21 +169,23 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn insert_tx(
|
pub fn insert_tx(
|
||||||
&mut self,
|
&mut self,
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
observation: ObservedIn<A>,
|
observation: ObservedAs<A>,
|
||||||
) -> IndexedAdditions<A, I::Additions> {
|
) -> IndexedAdditions<A, I::Additions> {
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
|
|
||||||
let last_height = match &observation {
|
let last_height = match &observation {
|
||||||
ObservedIn::Block(anchor) => self.insert_height_internal(anchor.anchor_block().height),
|
ObservedAs::Confirmed(anchor) => {
|
||||||
ObservedIn::Mempool(_) => None,
|
self.insert_height_internal(anchor.anchor_block().height)
|
||||||
|
}
|
||||||
|
ObservedAs::Unconfirmed(_) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
IndexedAdditions {
|
IndexedAdditions {
|
||||||
graph_additions: {
|
graph_additions: {
|
||||||
let mut graph_additions = self.graph.insert_tx(tx.clone());
|
let mut graph_additions = self.graph.insert_tx(tx.clone());
|
||||||
graph_additions.append(match observation {
|
graph_additions.append(match observation {
|
||||||
ObservedIn::Block(anchor) => self.graph.insert_anchor(txid, anchor),
|
ObservedAs::Confirmed(anchor) => self.graph.insert_anchor(txid, anchor),
|
||||||
ObservedIn::Mempool(seen_at) => self.graph.insert_seen_at(txid, seen_at),
|
ObservedAs::Unconfirmed(seen_at) => self.graph.insert_seen_at(txid, seen_at),
|
||||||
});
|
});
|
||||||
graph_additions
|
graph_additions
|
||||||
},
|
},
|
||||||
@ -192,7 +197,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn filter_and_insert_txs<'t, T>(
|
pub fn filter_and_insert_txs<'t, T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
txs: T,
|
txs: T,
|
||||||
observation: ObservedIn<A>,
|
observation: ObservedAs<A>,
|
||||||
) -> IndexedAdditions<A, I::Additions>
|
) -> IndexedAdditions<A, I::Additions>
|
||||||
where
|
where
|
||||||
T: Iterator<Item = &'t Transaction>,
|
T: Iterator<Item = &'t Transaction>,
|
||||||
@ -220,7 +225,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn try_list_chain_txs<'a, C>(
|
pub fn try_list_chain_txs<'a, C>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: C,
|
chain: C,
|
||||||
) -> impl Iterator<Item = Result<TxInChain<'a, Transaction, A>, C::Error>>
|
) -> impl Iterator<Item = Result<CanonicalTx<'a, Transaction, A>, C::Error>>
|
||||||
where
|
where
|
||||||
C: ChainOracle + 'a,
|
C: ChainOracle + 'a,
|
||||||
{
|
{
|
||||||
@ -230,7 +235,12 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
.filter_map(move |tx| {
|
.filter_map(move |tx| {
|
||||||
self.graph
|
self.graph
|
||||||
.try_get_chain_position(&chain, tx.txid)
|
.try_get_chain_position(&chain, tx.txid)
|
||||||
.map(|v| v.map(|observed_in| TxInChain { observed_in, tx }))
|
.map(|v| {
|
||||||
|
v.map(|observed_in| CanonicalTx {
|
||||||
|
observed_as: observed_in,
|
||||||
|
tx,
|
||||||
|
})
|
||||||
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -238,7 +248,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn list_chain_txs<'a, C>(
|
pub fn list_chain_txs<'a, C>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: C,
|
chain: C,
|
||||||
) -> impl Iterator<Item = TxInChain<'a, Transaction, A>>
|
) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>>
|
||||||
where
|
where
|
||||||
C: ChainOracle<Error = Infallible> + 'a,
|
C: ChainOracle<Error = Infallible> + 'a,
|
||||||
{
|
{
|
||||||
@ -249,10 +259,9 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn try_list_chain_txouts<'a, C>(
|
pub fn try_list_chain_txouts<'a, C>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: C,
|
chain: C,
|
||||||
) -> impl Iterator<Item = Result<FullTxOut<ObservedIn<A>>, C::Error>> + 'a
|
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
|
||||||
where
|
where
|
||||||
C: ChainOracle + 'a,
|
C: ChainOracle + 'a,
|
||||||
ObservedIn<A>: ChainPosition,
|
|
||||||
{
|
{
|
||||||
self.graph
|
self.graph
|
||||||
.all_txouts()
|
.all_txouts()
|
||||||
@ -263,13 +272,13 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
let is_on_coinbase = graph_tx.is_coin_base();
|
let is_on_coinbase = graph_tx.is_coin_base();
|
||||||
|
|
||||||
let chain_position = match self.graph.try_get_chain_position(&chain, op.txid) {
|
let chain_position = match self.graph.try_get_chain_position(&chain, op.txid) {
|
||||||
Ok(Some(observed_at)) => observed_at.into_owned(),
|
Ok(Some(observed_at)) => observed_at.cloned(),
|
||||||
Ok(None) => return None,
|
Ok(None) => return None,
|
||||||
Err(err) => return Some(Err(err)),
|
Err(err) => return Some(Err(err)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let spent_by = match self.graph.try_get_spend_in_chain(&chain, op) {
|
let spent_by = match self.graph.try_get_spend_in_chain(&chain, op) {
|
||||||
Ok(Some((obs, txid))) => Some((obs.into_owned(), txid)),
|
Ok(Some((obs, txid))) => Some((obs.cloned(), txid)),
|
||||||
Ok(None) => None,
|
Ok(None) => None,
|
||||||
Err(err) => return Some(Err(err)),
|
Err(err) => return Some(Err(err)),
|
||||||
};
|
};
|
||||||
@ -289,10 +298,9 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn list_chain_txouts<'a, C>(
|
pub fn list_chain_txouts<'a, C>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: C,
|
chain: C,
|
||||||
) -> impl Iterator<Item = FullTxOut<ObservedIn<A>>> + 'a
|
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
|
||||||
where
|
where
|
||||||
C: ChainOracle<Error = Infallible> + 'a,
|
C: ChainOracle<Error = Infallible> + 'a,
|
||||||
ObservedIn<A>: ChainPosition,
|
|
||||||
{
|
{
|
||||||
self.try_list_chain_txouts(chain)
|
self.try_list_chain_txouts(chain)
|
||||||
.map(|r| r.expect("error in infallible"))
|
.map(|r| r.expect("error in infallible"))
|
||||||
@ -302,10 +310,9 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn try_list_chain_utxos<'a, C>(
|
pub fn try_list_chain_utxos<'a, C>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: C,
|
chain: C,
|
||||||
) -> impl Iterator<Item = Result<FullTxOut<ObservedIn<A>>, C::Error>> + 'a
|
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
|
||||||
where
|
where
|
||||||
C: ChainOracle + 'a,
|
C: ChainOracle + 'a,
|
||||||
ObservedIn<A>: ChainPosition,
|
|
||||||
{
|
{
|
||||||
self.try_list_chain_txouts(chain)
|
self.try_list_chain_txouts(chain)
|
||||||
.filter(|r| !matches!(r, Ok(txo) if txo.spent_by.is_none()))
|
.filter(|r| !matches!(r, Ok(txo) if txo.spent_by.is_none()))
|
||||||
@ -314,10 +321,9 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn list_chain_utxos<'a, C>(
|
pub fn list_chain_utxos<'a, C>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: C,
|
chain: C,
|
||||||
) -> impl Iterator<Item = FullTxOut<ObservedIn<A>>> + 'a
|
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
|
||||||
where
|
where
|
||||||
C: ChainOracle<Error = Infallible> + 'a,
|
C: ChainOracle<Error = Infallible> + 'a,
|
||||||
ObservedIn<A>: ChainPosition,
|
|
||||||
{
|
{
|
||||||
self.try_list_chain_utxos(chain)
|
self.try_list_chain_utxos(chain)
|
||||||
.map(|r| r.expect("error is infallible"))
|
.map(|r| r.expect("error is infallible"))
|
||||||
@ -331,7 +337,6 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
) -> Result<Balance, C::Error>
|
) -> Result<Balance, C::Error>
|
||||||
where
|
where
|
||||||
C: ChainOracle,
|
C: ChainOracle,
|
||||||
ObservedIn<A>: ChainPosition + Clone,
|
|
||||||
F: FnMut(&Script) -> bool,
|
F: FnMut(&Script) -> bool,
|
||||||
{
|
{
|
||||||
let mut immature = 0;
|
let mut immature = 0;
|
||||||
@ -343,16 +348,16 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
let txout = res?;
|
let txout = res?;
|
||||||
|
|
||||||
match &txout.chain_position {
|
match &txout.chain_position {
|
||||||
ObservedIn::Block(_) => {
|
ObservedAs::Confirmed(_) => {
|
||||||
if txout.is_on_coinbase {
|
if txout.is_on_coinbase {
|
||||||
if txout.is_mature(tip) {
|
if txout.is_observed_as_mature(tip) {
|
||||||
confirmed += txout.txout.value;
|
confirmed += txout.txout.value;
|
||||||
} else {
|
} else {
|
||||||
immature += txout.txout.value;
|
immature += txout.txout.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ObservedIn::Mempool(_) => {
|
ObservedAs::Unconfirmed(_) => {
|
||||||
if should_trust(&txout.txout.script_pubkey) {
|
if should_trust(&txout.txout.script_pubkey) {
|
||||||
trusted_pending += txout.txout.value;
|
trusted_pending += txout.txout.value;
|
||||||
} else {
|
} else {
|
||||||
@ -373,7 +378,6 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn balance<C, F>(&self, chain: C, tip: u32, should_trust: F) -> Balance
|
pub fn balance<C, F>(&self, chain: C, tip: u32, should_trust: F) -> Balance
|
||||||
where
|
where
|
||||||
C: ChainOracle<Error = Infallible>,
|
C: ChainOracle<Error = Infallible>,
|
||||||
ObservedIn<A>: ChainPosition + Clone,
|
|
||||||
F: FnMut(&Script) -> bool,
|
F: FnMut(&Script) -> bool,
|
||||||
{
|
{
|
||||||
self.try_balance(chain, tip, should_trust)
|
self.try_balance(chain, tip, should_trust)
|
||||||
@ -383,12 +387,11 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn try_balance_at<C>(&self, chain: C, height: u32) -> Result<u64, C::Error>
|
pub fn try_balance_at<C>(&self, chain: C, height: u32) -> Result<u64, C::Error>
|
||||||
where
|
where
|
||||||
C: ChainOracle,
|
C: ChainOracle,
|
||||||
ObservedIn<A>: ChainPosition + Clone,
|
|
||||||
{
|
{
|
||||||
let mut sum = 0;
|
let mut sum = 0;
|
||||||
for txo_res in self.try_list_chain_txouts(chain) {
|
for txo_res in self.try_list_chain_txouts(chain) {
|
||||||
let txo = txo_res?;
|
let txo = txo_res?;
|
||||||
if txo.is_spendable_at(height) {
|
if txo.is_observed_as_spendable(height) {
|
||||||
sum += txo.txout.value;
|
sum += txo.txout.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -398,7 +401,6 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn balance_at<C>(&self, chain: C, height: u32) -> u64
|
pub fn balance_at<C>(&self, chain: C, height: u32) -> u64
|
||||||
where
|
where
|
||||||
C: ChainOracle<Error = Infallible>,
|
C: ChainOracle<Error = Infallible>,
|
||||||
ObservedIn<A>: ChainPosition + Clone,
|
|
||||||
{
|
{
|
||||||
self.try_balance_at(chain, height)
|
self.try_balance_at(chain, height)
|
||||||
.expect("error is infallible")
|
.expect("error is infallible")
|
||||||
|
@ -123,7 +123,7 @@ impl LocalChain {
|
|||||||
|
|
||||||
/// Updates [`LocalChain`] with an update [`LocalChain`].
|
/// Updates [`LocalChain`] with an update [`LocalChain`].
|
||||||
///
|
///
|
||||||
/// This is equivilant to calling [`determine_changeset`] and [`apply_changeset`] in sequence.
|
/// This is equivalent to calling [`determine_changeset`] and [`apply_changeset`] in sequence.
|
||||||
///
|
///
|
||||||
/// [`determine_changeset`]: Self::determine_changeset
|
/// [`determine_changeset`]: Self::determine_changeset
|
||||||
/// [`apply_changeset`]: Self::apply_changeset
|
/// [`apply_changeset`]: Self::apply_changeset
|
||||||
|
@ -457,7 +457,7 @@ impl<P: core::fmt::Debug> core::fmt::Display for UpdateError<P> {
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
impl<P: core::fmt::Debug> std::error::Error for UpdateError<P> {}
|
impl<P: core::fmt::Debug> std::error::Error for UpdateError<P> {}
|
||||||
|
|
||||||
impl<P: ChainPosition> ChainOracle for SparseChain<P> {
|
impl<P> ChainOracle for SparseChain<P> {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
fn get_tip_in_best_chain(&self) -> Result<Option<BlockId>, Self::Error> {
|
fn get_tip_in_best_chain(&self) -> Result<Option<BlockId>, Self::Error> {
|
||||||
@ -473,7 +473,7 @@ impl<P: ChainPosition> ChainOracle for SparseChain<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: ChainPosition> SparseChain<P> {
|
impl<P> SparseChain<P> {
|
||||||
/// Creates a new chain from a list of block hashes and heights. The caller must guarantee they
|
/// Creates a new chain from a list of block hashes and heights. The caller must guarantee they
|
||||||
/// are in the same chain.
|
/// are in the same chain.
|
||||||
pub fn from_checkpoints<C>(checkpoints: C) -> Self
|
pub fn from_checkpoints<C>(checkpoints: C) -> Self
|
||||||
@ -504,13 +504,6 @@ impl<P: ChainPosition> SparseChain<P> {
|
|||||||
.map(|&hash| BlockId { height, hash })
|
.map(|&hash| BlockId { height, hash })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`ChainPosition`] of a `txid`.
|
|
||||||
///
|
|
||||||
/// This returns [`None`] if the transaction does not exist.
|
|
||||||
pub fn tx_position(&self, txid: Txid) -> Option<&P> {
|
|
||||||
self.txid_to_pos.get(&txid)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a [`BTreeMap`] of all checkpoints (block hashes by height).
|
/// Return a [`BTreeMap`] of all checkpoints (block hashes by height).
|
||||||
pub fn checkpoints(&self) -> &BTreeMap<u32, BlockHash> {
|
pub fn checkpoints(&self) -> &BTreeMap<u32, BlockHash> {
|
||||||
&self.checkpoints
|
&self.checkpoints
|
||||||
@ -526,6 +519,47 @@ impl<P: ChainPosition> SparseChain<P> {
|
|||||||
.map(|(&height, &hash)| BlockId { height, hash })
|
.map(|(&height, &hash)| BlockId { height, hash })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the value set as the checkpoint limit.
|
||||||
|
///
|
||||||
|
/// Refer to [`set_checkpoint_limit`].
|
||||||
|
///
|
||||||
|
/// [`set_checkpoint_limit`]: Self::set_checkpoint_limit
|
||||||
|
pub fn checkpoint_limit(&self) -> Option<usize> {
|
||||||
|
self.checkpoint_limit
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the checkpoint limit.
|
||||||
|
///
|
||||||
|
/// The checkpoint limit restricts the number of checkpoints that can be stored in [`Self`].
|
||||||
|
/// Oldest checkpoints are pruned first.
|
||||||
|
pub fn set_checkpoint_limit(&mut self, limit: Option<usize>) {
|
||||||
|
self.checkpoint_limit = limit;
|
||||||
|
self.prune_checkpoints();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prune_checkpoints(&mut self) -> Option<BTreeMap<u32, BlockHash>> {
|
||||||
|
let limit = self.checkpoint_limit?;
|
||||||
|
|
||||||
|
// find the last height to be pruned
|
||||||
|
let last_height = *self.checkpoints.keys().rev().nth(limit)?;
|
||||||
|
// first height to be kept
|
||||||
|
let keep_height = last_height + 1;
|
||||||
|
|
||||||
|
let mut split = self.checkpoints.split_off(&keep_height);
|
||||||
|
core::mem::swap(&mut self.checkpoints, &mut split);
|
||||||
|
|
||||||
|
Some(split)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: ChainPosition> SparseChain<P> {
|
||||||
|
/// Return the [`ChainPosition`] of a `txid`.
|
||||||
|
///
|
||||||
|
/// This returns [`None`] if the transaction does not exist.
|
||||||
|
pub fn tx_position(&self, txid: Txid) -> Option<&P> {
|
||||||
|
self.txid_to_pos.get(&txid)
|
||||||
|
}
|
||||||
|
|
||||||
/// Preview changes of updating [`Self`] with another chain that connects to it.
|
/// Preview changes of updating [`Self`] with another chain that connects to it.
|
||||||
///
|
///
|
||||||
/// If the `update` wishes to introduce confirmed transactions, it must contain a checkpoint
|
/// If the `update` wishes to introduce confirmed transactions, it must contain a checkpoint
|
||||||
@ -936,24 +970,6 @@ impl<P: ChainPosition> SparseChain<P> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the value set as the checkpoint limit.
|
|
||||||
///
|
|
||||||
/// Refer to [`set_checkpoint_limit`].
|
|
||||||
///
|
|
||||||
/// [`set_checkpoint_limit`]: Self::set_checkpoint_limit
|
|
||||||
pub fn checkpoint_limit(&self) -> Option<usize> {
|
|
||||||
self.checkpoint_limit
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the checkpoint limit.
|
|
||||||
///
|
|
||||||
/// The checkpoint limit restricts the number of checkpoints that can be stored in [`Self`].
|
|
||||||
/// Oldest checkpoints are pruned first.
|
|
||||||
pub fn set_checkpoint_limit(&mut self, limit: Option<usize>) {
|
|
||||||
self.checkpoint_limit = limit;
|
|
||||||
self.prune_checkpoints();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return [`Txid`]s that would be added to the sparse chain if this `changeset` was applied.
|
/// Return [`Txid`]s that would be added to the sparse chain if this `changeset` was applied.
|
||||||
pub fn changeset_additions<'a>(
|
pub fn changeset_additions<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
@ -969,20 +985,6 @@ impl<P: ChainPosition> SparseChain<P> {
|
|||||||
.map(|(&txid, _)| txid)
|
.map(|(&txid, _)| txid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prune_checkpoints(&mut self) -> Option<BTreeMap<u32, BlockHash>> {
|
|
||||||
let limit = self.checkpoint_limit?;
|
|
||||||
|
|
||||||
// find the last height to be pruned
|
|
||||||
let last_height = *self.checkpoints.keys().rev().nth(limit)?;
|
|
||||||
// first height to be kept
|
|
||||||
let keep_height = last_height + 1;
|
|
||||||
|
|
||||||
let mut split = self.checkpoints.split_off(&keep_height);
|
|
||||||
core::mem::swap(&mut self.checkpoints, &mut split);
|
|
||||||
|
|
||||||
Some(split)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds the transaction in the chain that spends `outpoint`.
|
/// Finds the transaction in the chain that spends `outpoint`.
|
||||||
///
|
///
|
||||||
/// [`TxGraph`] is used to provide the spend relationships.
|
/// [`TxGraph`] is used to provide the spend relationships.
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
//! assert!(additions.is_empty());
|
//! assert!(additions.is_empty());
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::{collections::*, BlockAnchor, ChainOracle, ForEachTxOut, ObservedIn};
|
use crate::{collections::*, BlockAnchor, ChainOracle, ForEachTxOut, ObservedAs};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
||||||
use core::{
|
use core::{
|
||||||
@ -91,10 +91,7 @@ impl<A> Default for TxGraph<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub type InChainTx<'a, T, A> = (ObservedIn<&'a A>, TxInGraph<'a, T, A>);
|
/// An outward-facing representation of a (transaction) node in the [`TxGraph`].
|
||||||
// pub type InChainTxOut<'a, I, A> = (&'a I, FullTxOut<ObservedIn<&'a A>>);
|
|
||||||
|
|
||||||
/// An outward-facing view of a transaction node that resides in a [`TxGraph`].
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct TxNode<'a, T, A> {
|
pub struct TxNode<'a, T, A> {
|
||||||
/// Txid of the transaction.
|
/// Txid of the transaction.
|
||||||
@ -601,7 +598,7 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
&self,
|
&self,
|
||||||
chain: C,
|
chain: C,
|
||||||
txid: Txid,
|
txid: Txid,
|
||||||
) -> Result<Option<ObservedIn<&A>>, C::Error>
|
) -> Result<Option<ObservedAs<&A>>, C::Error>
|
||||||
where
|
where
|
||||||
C: ChainOracle,
|
C: ChainOracle,
|
||||||
{
|
{
|
||||||
@ -614,7 +611,7 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
|
|
||||||
for anchor in anchors {
|
for anchor in anchors {
|
||||||
if chain.is_block_in_best_chain(anchor.anchor_block())? {
|
if chain.is_block_in_best_chain(anchor.anchor_block())? {
|
||||||
return Ok(Some(ObservedIn::Block(anchor)));
|
return Ok(Some(ObservedAs::Confirmed(anchor)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -643,10 +640,10 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(ObservedIn::Mempool(last_seen)))
|
Ok(Some(ObservedAs::Unconfirmed(last_seen)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_chain_position<C>(&self, chain: C, txid: Txid) -> Option<ObservedIn<&A>>
|
pub fn get_chain_position<C>(&self, chain: C, txid: Txid) -> Option<ObservedAs<&A>>
|
||||||
where
|
where
|
||||||
C: ChainOracle<Error = Infallible>,
|
C: ChainOracle<Error = Infallible>,
|
||||||
{
|
{
|
||||||
@ -658,7 +655,7 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
&self,
|
&self,
|
||||||
chain: C,
|
chain: C,
|
||||||
outpoint: OutPoint,
|
outpoint: OutPoint,
|
||||||
) -> Result<Option<(ObservedIn<&A>, Txid)>, C::Error>
|
) -> Result<Option<(ObservedAs<&A>, Txid)>, C::Error>
|
||||||
where
|
where
|
||||||
C: ChainOracle,
|
C: ChainOracle,
|
||||||
{
|
{
|
||||||
@ -678,7 +675,7 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_chain_spend<C>(&self, chain: C, outpoint: OutPoint) -> Option<(ObservedIn<&A>, Txid)>
|
pub fn get_chain_spend<C>(&self, chain: C, outpoint: OutPoint) -> Option<(ObservedAs<&A>, Txid)>
|
||||||
where
|
where
|
||||||
C: ChainOracle<Error = Infallible>,
|
C: ChainOracle<Error = Infallible>,
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user