[bdk_chain_redesign] Consistent ChainOracle
The problem with the previous `ChainOracle` interface is that it had no guarantee for consistency. For example, a block deemed to be part of the "best chain" can be reorged out. So when `ChainOracle` is called multiple times for an operation (such as getting the UTXO set), the returned result may be inconsistent. This PR changes `ChainOracle::is_block_in_chain` to take in another input `static_block`, ensuring `block` is an ancestor of `static_block`. Thus, if `static_block` is consistent across the operation, the result will be consistent also. `is_block_in_chain` now returns `Option<bool>`. The `None` case means that the oracle implementation cannot determine whether block is an ancestor of static block. `IndexedTxGraph::list_chain_txouts` handles this case by checking child spends that are in chain, and if so, the parent tx must be in chain too.
This commit is contained in:
parent
bff80ec378
commit
611d2e3ea2
@ -248,7 +248,7 @@ impl<A: BlockAnchor> FullTxOut<ObservedAs<A>> {
|
||||
/// [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
|
||||
///
|
||||
/// [`is_mature`]: Self::is_mature
|
||||
pub fn is_observed_as_mature(&self, tip: u32) -> bool {
|
||||
pub fn is_observed_as_confirmed_and_mature(&self, tip: u32) -> bool {
|
||||
if !self.is_on_coinbase {
|
||||
return false;
|
||||
}
|
||||
@ -275,8 +275,8 @@ impl<A: BlockAnchor> FullTxOut<ObservedAs<A>> {
|
||||
/// 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) {
|
||||
pub fn is_observed_as_confirmed_and_spendable(&self, tip: u32) -> bool {
|
||||
if !self.is_observed_as_confirmed_and_mature(tip) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1,162 +1,77 @@
|
||||
use core::{convert::Infallible, marker::PhantomData};
|
||||
use crate::collections::HashSet;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::{collections::VecDeque, vec::Vec};
|
||||
use bitcoin::BlockHash;
|
||||
|
||||
use crate::BlockId;
|
||||
|
||||
/// Represents a service that tracks the best chain history.
|
||||
/// TODO: How do we ensure the chain oracle is consistent across a single call?
|
||||
/// * We need to somehow lock the data! What if the ChainOracle is remote?
|
||||
/// * Get tip method! And check the tip still exists at the end! And every internal call
|
||||
/// does not go beyond the initial tip.
|
||||
/// Represents a service that tracks the blockchain.
|
||||
///
|
||||
/// The main method is [`is_block_in_chain`] which determines whether a given block of [`BlockId`]
|
||||
/// is an ancestor of another "static block".
|
||||
///
|
||||
/// [`is_block_in_chain`]: Self::is_block_in_chain
|
||||
pub trait ChainOracle {
|
||||
/// Error type.
|
||||
type Error: core::fmt::Debug;
|
||||
|
||||
/// Get the height and hash of the tip in the best chain.
|
||||
fn get_tip_in_best_chain(&self) -> Result<Option<BlockId>, Self::Error>;
|
||||
|
||||
/// Returns the block hash (if any) of the given `height`.
|
||||
fn get_block_in_best_chain(&self, height: u32) -> Result<Option<BlockHash>, Self::Error>;
|
||||
|
||||
/// Determines whether the block of [`BlockId`] exists in the best chain.
|
||||
fn is_block_in_best_chain(&self, block_id: BlockId) -> Result<bool, Self::Error> {
|
||||
Ok(matches!(self.get_block_in_best_chain(block_id.height)?, Some(h) if h == block_id.hash))
|
||||
}
|
||||
/// Determines whether `block` of [`BlockId`] exists as an ancestor of `static_block`.
|
||||
///
|
||||
/// If `None` is returned, it means the implementation cannot determine whether `block` exists.
|
||||
fn is_block_in_chain(
|
||||
&self,
|
||||
block: BlockId,
|
||||
static_block: BlockId,
|
||||
) -> Result<Option<bool>, Self::Error>;
|
||||
}
|
||||
|
||||
// [TODO] We need stuff for smart pointers. Maybe? How does rust lib do this?
|
||||
// Box<dyn ChainOracle>, Arc<dyn ChainOracle> ????? I will figure it out
|
||||
impl<C: ChainOracle> ChainOracle for &C {
|
||||
type Error = C::Error;
|
||||
|
||||
fn get_tip_in_best_chain(&self) -> Result<Option<BlockId>, Self::Error> {
|
||||
<C as ChainOracle>::get_tip_in_best_chain(self)
|
||||
}
|
||||
|
||||
fn get_block_in_best_chain(&self, height: u32) -> Result<Option<BlockHash>, Self::Error> {
|
||||
<C as ChainOracle>::get_block_in_best_chain(self, height)
|
||||
}
|
||||
|
||||
fn is_block_in_best_chain(&self, block_id: BlockId) -> Result<bool, Self::Error> {
|
||||
<C as ChainOracle>::is_block_in_best_chain(self, block_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// This structure increases the performance of getting chain data.
|
||||
#[derive(Debug)]
|
||||
pub struct Cache<C> {
|
||||
assume_final_depth: u32,
|
||||
tip_height: u32,
|
||||
cache: BTreeMap<u32, BlockHash>,
|
||||
/// A cache structure increases the performance of getting chain data.
|
||||
///
|
||||
/// A simple FIFO cache replacement policy is used. Something more efficient and advanced can be
|
||||
/// implemented later.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CacheBackend<C> {
|
||||
cache: HashSet<(BlockHash, BlockHash)>,
|
||||
fifo: VecDeque<(BlockHash, BlockHash)>,
|
||||
marker: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C> Cache<C> {
|
||||
/// Creates a new [`Cache`].
|
||||
impl<C> CacheBackend<C> {
|
||||
/// Get the number of elements in the cache.
|
||||
pub fn cache_size(&self) -> usize {
|
||||
self.cache.len()
|
||||
}
|
||||
|
||||
/// Prunes the cache to reach the `max_size` target.
|
||||
///
|
||||
/// `assume_final_depth` represents the minimum number of blocks above the block in question
|
||||
/// when we can assume the block is final (reorgs cannot happen). I.e. a value of 0 means the
|
||||
/// tip is assumed to be final. The cache only caches blocks that are assumed to be final.
|
||||
pub fn new(assume_final_depth: u32) -> Self {
|
||||
Self {
|
||||
assume_final_depth,
|
||||
tip_height: 0,
|
||||
cache: Default::default(),
|
||||
marker: Default::default(),
|
||||
/// Returns pruned elements.
|
||||
pub fn prune(&mut self, max_size: usize) -> Vec<(BlockHash, BlockHash)> {
|
||||
let prune_count = self.cache.len().saturating_sub(max_size);
|
||||
(0..prune_count)
|
||||
.filter_map(|_| self.fifo.pop_front())
|
||||
.filter(|k| self.cache.remove(k))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn contains(&self, static_block: BlockId, block: BlockId) -> bool {
|
||||
if static_block.height < block.height
|
||||
|| static_block.height == block.height && static_block.hash != block.hash
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
self.cache.contains(&(static_block.hash, block.hash))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, static_block: BlockId, block: BlockId) -> bool {
|
||||
let cache_key = (static_block.hash, block.hash);
|
||||
|
||||
if self.cache.insert(cache_key) {
|
||||
self.fifo.push_back(cache_key);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ChainOracle> Cache<C> {
|
||||
/// This is the topmost (highest) block height that we assume as final (no reorgs possible).
|
||||
///
|
||||
/// Blocks higher than this height are not cached.
|
||||
pub fn assume_final_height(&self) -> u32 {
|
||||
self.tip_height.saturating_sub(self.assume_final_depth)
|
||||
}
|
||||
|
||||
/// Update the `tip_height` with the [`ChainOracle`]'s tip.
|
||||
///
|
||||
/// `tip_height` is used with `assume_final_depth` to determine whether we should cache a
|
||||
/// certain block height (`tip_height` - `assume_final_depth`).
|
||||
pub fn try_update_tip_height(&mut self, chain: C) -> Result<(), C::Error> {
|
||||
let tip = chain.get_tip_in_best_chain()?;
|
||||
if let Some(BlockId { height, .. }) = tip {
|
||||
self.tip_height = height;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a block from the cache with the [`ChainOracle`] as fallback.
|
||||
///
|
||||
/// If the block does not exist in cache, the logic fallbacks to fetching from the internal
|
||||
/// [`ChainOracle`]. If the block is at or below the "assume final height", we will also store
|
||||
/// the missing block in the cache.
|
||||
pub fn try_get_block(&mut self, chain: C, height: u32) -> Result<Option<BlockHash>, C::Error> {
|
||||
if let Some(&hash) = self.cache.get(&height) {
|
||||
return Ok(Some(hash));
|
||||
}
|
||||
|
||||
let hash = chain.get_block_in_best_chain(height)?;
|
||||
|
||||
if hash.is_some() && height > self.tip_height {
|
||||
self.tip_height = height;
|
||||
}
|
||||
|
||||
// only cache block if at least as deep as `assume_final_depth`
|
||||
let assume_final_height = self.tip_height.saturating_sub(self.assume_final_depth);
|
||||
if height <= assume_final_height {
|
||||
if let Some(hash) = hash {
|
||||
self.cache.insert(height, hash);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
/// Determines whether the block of `block_id` is in the chain using the cache.
|
||||
///
|
||||
/// This uses [`try_get_block`] internally.
|
||||
///
|
||||
/// [`try_get_block`]: Self::try_get_block
|
||||
pub fn try_is_block_in_chain(&mut self, chain: C, block_id: BlockId) -> Result<bool, C::Error> {
|
||||
match self.try_get_block(chain, block_id.height)? {
|
||||
Some(hash) if hash == block_id.hash => Ok(true),
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ChainOracle<Error = Infallible>> Cache<C> {
|
||||
/// Updates the `tip_height` with the [`ChainOracle`]'s tip.
|
||||
///
|
||||
/// This is the no-error version of [`try_update_tip_height`].
|
||||
///
|
||||
/// [`try_update_tip_height`]: Self::try_update_tip_height
|
||||
pub fn update_tip_height(&mut self, chain: C) {
|
||||
self.try_update_tip_height(chain)
|
||||
.expect("chain oracle error is infallible")
|
||||
}
|
||||
|
||||
/// Get a block from the cache with the [`ChainOracle`] as fallback.
|
||||
///
|
||||
/// This is the no-error version of [`try_get_block`].
|
||||
///
|
||||
/// [`try_get_block`]: Self::try_get_block
|
||||
pub fn get_block(&mut self, chain: C, height: u32) -> Option<BlockHash> {
|
||||
self.try_get_block(chain, height)
|
||||
.expect("chain oracle error is infallible")
|
||||
}
|
||||
|
||||
/// Determines whether the block at `block_id` is in the chain using the cache.
|
||||
///
|
||||
/// This is the no-error version of [`try_is_block_in_chain`].
|
||||
///
|
||||
/// [`try_is_block_in_chain`]: Self::try_is_block_in_chain
|
||||
pub fn is_block_in_best_chain(&mut self, chain: C, block_id: BlockId) -> bool {
|
||||
self.try_is_block_in_chain(chain, block_id)
|
||||
.expect("chain oracle error is infallible")
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use bitcoin::{OutPoint, Script, Transaction, TxOut};
|
||||
use crate::{
|
||||
keychain::Balance,
|
||||
tx_graph::{Additions, TxGraph, TxNode},
|
||||
Append, BlockAnchor, ChainOracle, FullTxOut, ObservedAs, TxIndex,
|
||||
Append, BlockAnchor, BlockId, ChainOracle, FullTxOut, ObservedAs, TxIndex,
|
||||
};
|
||||
|
||||
/// An outwards-facing view of a transaction that is part of the *best chain*'s history.
|
||||
@ -220,7 +220,8 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
||||
// [TODO] Have to methods, one for relevant-only, and one for any. Have one in `TxGraph`.
|
||||
pub fn try_list_chain_txs<'a, C>(
|
||||
&'a self,
|
||||
chain: C,
|
||||
chain: &'a C,
|
||||
static_block: BlockId,
|
||||
) -> impl Iterator<Item = Result<CanonicalTx<'a, Transaction, A>, C::Error>>
|
||||
where
|
||||
C: ChainOracle + 'a,
|
||||
@ -230,7 +231,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
||||
.filter(|tx| self.index.is_tx_relevant(tx))
|
||||
.filter_map(move |tx| {
|
||||
self.graph
|
||||
.try_get_chain_position(&chain, tx.txid)
|
||||
.try_get_chain_position(chain, static_block, tx.txid)
|
||||
.map(|v| {
|
||||
v.map(|observed_in| CanonicalTx {
|
||||
observed_as: observed_in,
|
||||
@ -243,18 +244,20 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
||||
|
||||
pub fn list_chain_txs<'a, C>(
|
||||
&'a self,
|
||||
chain: C,
|
||||
chain: &'a C,
|
||||
static_block: BlockId,
|
||||
) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>>
|
||||
where
|
||||
C: ChainOracle<Error = Infallible> + 'a,
|
||||
{
|
||||
self.try_list_chain_txs(chain)
|
||||
self.try_list_chain_txs(chain, static_block)
|
||||
.map(|r| r.expect("error is infallible"))
|
||||
}
|
||||
|
||||
pub fn try_list_chain_txouts<'a, C>(
|
||||
&'a self,
|
||||
chain: C,
|
||||
chain: &'a C,
|
||||
static_block: BlockId,
|
||||
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
|
||||
where
|
||||
C: ChainOracle + 'a,
|
||||
@ -267,13 +270,17 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
||||
|
||||
let is_on_coinbase = graph_tx.is_coin_base();
|
||||
|
||||
let chain_position = match self.graph.try_get_chain_position(&chain, op.txid) {
|
||||
Ok(Some(observed_at)) => observed_at.cloned(),
|
||||
Ok(None) => return None,
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
let chain_position =
|
||||
match self
|
||||
.graph
|
||||
.try_get_chain_position(chain, static_block, op.txid)
|
||||
{
|
||||
Ok(Some(observed_at)) => observed_at.cloned(),
|
||||
Ok(None) => return None,
|
||||
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, static_block, op) {
|
||||
Ok(Some((obs, txid))) => Some((obs.cloned(), txid)),
|
||||
Ok(None) => None,
|
||||
Err(err) => return Some(Err(err)),
|
||||
@ -293,41 +300,45 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
||||
|
||||
pub fn list_chain_txouts<'a, C>(
|
||||
&'a self,
|
||||
chain: C,
|
||||
chain: &'a C,
|
||||
static_block: BlockId,
|
||||
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
|
||||
where
|
||||
C: ChainOracle<Error = Infallible> + 'a,
|
||||
{
|
||||
self.try_list_chain_txouts(chain)
|
||||
self.try_list_chain_txouts(chain, static_block)
|
||||
.map(|r| r.expect("error in infallible"))
|
||||
}
|
||||
|
||||
/// Return relevant unspents.
|
||||
pub fn try_list_chain_utxos<'a, C>(
|
||||
&'a self,
|
||||
chain: C,
|
||||
chain: &'a C,
|
||||
static_block: BlockId,
|
||||
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
|
||||
where
|
||||
C: ChainOracle + 'a,
|
||||
{
|
||||
self.try_list_chain_txouts(chain)
|
||||
self.try_list_chain_txouts(chain, static_block)
|
||||
.filter(|r| !matches!(r, Ok(txo) if txo.spent_by.is_none()))
|
||||
}
|
||||
|
||||
pub fn list_chain_utxos<'a, C>(
|
||||
&'a self,
|
||||
chain: C,
|
||||
chain: &'a C,
|
||||
static_block: BlockId,
|
||||
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
|
||||
where
|
||||
C: ChainOracle<Error = Infallible> + 'a,
|
||||
{
|
||||
self.try_list_chain_utxos(chain)
|
||||
self.try_list_chain_utxos(chain, static_block)
|
||||
.map(|r| r.expect("error is infallible"))
|
||||
}
|
||||
|
||||
pub fn try_balance<C, F>(
|
||||
&self,
|
||||
chain: C,
|
||||
chain: &C,
|
||||
static_block: BlockId,
|
||||
tip: u32,
|
||||
mut should_trust: F,
|
||||
) -> Result<Balance, C::Error>
|
||||
@ -340,13 +351,13 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
||||
let mut untrusted_pending = 0;
|
||||
let mut confirmed = 0;
|
||||
|
||||
for res in self.try_list_chain_txouts(&chain) {
|
||||
for res in self.try_list_chain_txouts(chain, static_block) {
|
||||
let txout = res?;
|
||||
|
||||
match &txout.chain_position {
|
||||
ObservedAs::Confirmed(_) => {
|
||||
if txout.is_on_coinbase {
|
||||
if txout.is_observed_as_mature(tip) {
|
||||
if txout.is_observed_as_confirmed_and_mature(tip) {
|
||||
confirmed += txout.txout.value;
|
||||
} else {
|
||||
immature += txout.txout.value;
|
||||
@ -371,34 +382,45 @@ 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,
|
||||
static_block: BlockId,
|
||||
tip: u32,
|
||||
should_trust: F,
|
||||
) -> Balance
|
||||
where
|
||||
C: ChainOracle<Error = Infallible>,
|
||||
F: FnMut(&Script) -> bool,
|
||||
{
|
||||
self.try_balance(chain, tip, should_trust)
|
||||
self.try_balance(chain, static_block, tip, should_trust)
|
||||
.expect("error is infallible")
|
||||
}
|
||||
|
||||
pub fn try_balance_at<C>(&self, chain: C, height: u32) -> Result<u64, C::Error>
|
||||
pub fn try_balance_at<C>(
|
||||
&self,
|
||||
chain: &C,
|
||||
static_block: BlockId,
|
||||
height: u32,
|
||||
) -> Result<u64, C::Error>
|
||||
where
|
||||
C: ChainOracle,
|
||||
{
|
||||
let mut sum = 0;
|
||||
for txo_res in self.try_list_chain_txouts(chain) {
|
||||
for txo_res in self.try_list_chain_txouts(chain, static_block) {
|
||||
let txo = txo_res?;
|
||||
if txo.is_observed_as_spendable(height) {
|
||||
if txo.is_observed_as_confirmed_and_spendable(height) {
|
||||
sum += txo.txout.value;
|
||||
}
|
||||
}
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
pub fn balance_at<C>(&self, chain: C, height: u32) -> u64
|
||||
pub fn balance_at<C>(&self, chain: &C, static_block: BlockId, height: u32) -> u64
|
||||
where
|
||||
C: ChainOracle<Error = Infallible>,
|
||||
{
|
||||
self.try_balance_at(chain, height)
|
||||
self.try_balance_at(chain, static_block, height)
|
||||
.expect("error is infallible")
|
||||
}
|
||||
}
|
||||
|
@ -22,16 +22,25 @@ pub struct LocalChain {
|
||||
impl ChainOracle for LocalChain {
|
||||
type Error = Infallible;
|
||||
|
||||
fn get_tip_in_best_chain(&self) -> Result<Option<BlockId>, Self::Error> {
|
||||
Ok(self
|
||||
.blocks
|
||||
.iter()
|
||||
.last()
|
||||
.map(|(&height, &hash)| BlockId { height, hash }))
|
||||
}
|
||||
|
||||
fn get_block_in_best_chain(&self, height: u32) -> Result<Option<BlockHash>, Self::Error> {
|
||||
Ok(self.blocks.get(&height).cloned())
|
||||
fn is_block_in_chain(
|
||||
&self,
|
||||
block: BlockId,
|
||||
static_block: BlockId,
|
||||
) -> Result<Option<bool>, Self::Error> {
|
||||
if block.height > static_block.height {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(
|
||||
match (
|
||||
self.blocks.get(&block.height),
|
||||
self.blocks.get(&static_block.height),
|
||||
) {
|
||||
(Some(&hash), Some(&static_hash)) => {
|
||||
Some(hash == block.hash && static_hash == static_block.hash)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -460,16 +460,20 @@ impl<P: core::fmt::Debug> std::error::Error for UpdateError<P> {}
|
||||
impl<P> ChainOracle for SparseChain<P> {
|
||||
type Error = Infallible;
|
||||
|
||||
fn get_tip_in_best_chain(&self) -> Result<Option<BlockId>, Self::Error> {
|
||||
Ok(self
|
||||
.checkpoints
|
||||
.iter()
|
||||
.last()
|
||||
.map(|(&height, &hash)| BlockId { height, hash }))
|
||||
}
|
||||
|
||||
fn get_block_in_best_chain(&self, height: u32) -> Result<Option<BlockHash>, Self::Error> {
|
||||
Ok(self.checkpoint_at(height).map(|b| b.hash))
|
||||
fn is_block_in_chain(
|
||||
&self,
|
||||
block: BlockId,
|
||||
static_block: BlockId,
|
||||
) -> Result<Option<bool>, Self::Error> {
|
||||
Ok(
|
||||
match (
|
||||
self.checkpoint_at(block.height),
|
||||
self.checkpoint_at(static_block.height),
|
||||
) {
|
||||
(Some(b), Some(static_b)) => Some(b == block && static_b == static_block),
|
||||
_ => None,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@
|
||||
//! assert!(additions.is_empty());
|
||||
//! ```
|
||||
|
||||
use crate::{collections::*, BlockAnchor, ChainOracle, ForEachTxOut, ObservedAs};
|
||||
use crate::{collections::*, BlockAnchor, BlockId, ChainOracle, ForEachTxOut, ObservedAs};
|
||||
use alloc::vec::Vec;
|
||||
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
||||
use core::{
|
||||
@ -596,7 +596,8 @@ impl<A: BlockAnchor> TxGraph<A> {
|
||||
/// TODO: Also return conflicting tx list, ordered by last_seen.
|
||||
pub fn try_get_chain_position<C>(
|
||||
&self,
|
||||
chain: C,
|
||||
chain: &C,
|
||||
static_block: BlockId,
|
||||
txid: Txid,
|
||||
) -> Result<Option<ObservedAs<&A>>, C::Error>
|
||||
where
|
||||
@ -610,8 +611,28 @@ impl<A: BlockAnchor> TxGraph<A> {
|
||||
};
|
||||
|
||||
for anchor in anchors {
|
||||
if chain.is_block_in_best_chain(anchor.anchor_block())? {
|
||||
return Ok(Some(ObservedAs::Confirmed(anchor)));
|
||||
match chain.is_block_in_chain(anchor.anchor_block(), static_block)? {
|
||||
Some(true) => return Ok(Some(ObservedAs::Confirmed(anchor))),
|
||||
Some(false) => continue,
|
||||
// if we cannot determine whether block is in the best chain, we can check whether
|
||||
// a spending transaction is confirmed in best chain, and if so, it is guaranteed
|
||||
// that the tx being spent (this tx) is in the best chain
|
||||
None => {
|
||||
let spending_anchors = self
|
||||
.spends
|
||||
.range(OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX))
|
||||
.flat_map(|(_, spending_txids)| spending_txids)
|
||||
.filter_map(|spending_txid| self.txs.get(spending_txid))
|
||||
.flat_map(|(_, spending_anchors, _)| spending_anchors);
|
||||
for spending_anchor in spending_anchors {
|
||||
match chain
|
||||
.is_block_in_chain(spending_anchor.anchor_block(), static_block)?
|
||||
{
|
||||
Some(true) => return Ok(Some(ObservedAs::Confirmed(anchor))),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -620,8 +641,7 @@ impl<A: BlockAnchor> TxGraph<A> {
|
||||
let tx = match tx_node {
|
||||
TxNodeInternal::Whole(tx) => tx,
|
||||
TxNodeInternal::Partial(_) => {
|
||||
// [TODO] Unfortunately, we can't iterate over conflicts of partial txs right now!
|
||||
// [TODO] So we just assume the partial tx does not exist in the best chain :/
|
||||
// Partial transactions (outputs only) cannot have conflicts.
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
@ -629,8 +649,8 @@ impl<A: BlockAnchor> TxGraph<A> {
|
||||
// If a conflicting tx is in the best chain, or has `last_seen` higher than this tx, then
|
||||
// this tx cannot exist in the best chain
|
||||
for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx_node(txid)) {
|
||||
for block_id in conflicting_tx.anchors.iter().map(A::anchor_block) {
|
||||
if chain.is_block_in_best_chain(block_id)? {
|
||||
for block in conflicting_tx.anchors.iter().map(A::anchor_block) {
|
||||
if chain.is_block_in_chain(block, static_block)? == Some(true) {
|
||||
// conflicting tx is in best chain, so the current tx cannot be in best chain!
|
||||
return Ok(None);
|
||||
}
|
||||
@ -643,31 +663,37 @@ impl<A: BlockAnchor> TxGraph<A> {
|
||||
Ok(Some(ObservedAs::Unconfirmed(last_seen)))
|
||||
}
|
||||
|
||||
pub fn get_chain_position<C>(&self, chain: C, txid: Txid) -> Option<ObservedAs<&A>>
|
||||
pub fn get_chain_position<C>(
|
||||
&self,
|
||||
chain: &C,
|
||||
static_block: BlockId,
|
||||
txid: Txid,
|
||||
) -> Option<ObservedAs<&A>>
|
||||
where
|
||||
C: ChainOracle<Error = Infallible>,
|
||||
{
|
||||
self.try_get_chain_position(chain, txid)
|
||||
self.try_get_chain_position(chain, static_block, txid)
|
||||
.expect("error is infallible")
|
||||
}
|
||||
|
||||
pub fn try_get_spend_in_chain<C>(
|
||||
&self,
|
||||
chain: C,
|
||||
chain: &C,
|
||||
static_block: BlockId,
|
||||
outpoint: OutPoint,
|
||||
) -> Result<Option<(ObservedAs<&A>, Txid)>, C::Error>
|
||||
where
|
||||
C: ChainOracle,
|
||||
{
|
||||
if self
|
||||
.try_get_chain_position(&chain, outpoint.txid)?
|
||||
.try_get_chain_position(chain, static_block, outpoint.txid)?
|
||||
.is_none()
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(spends) = self.spends.get(&outpoint) {
|
||||
for &txid in spends {
|
||||
if let Some(observed_at) = self.try_get_chain_position(&chain, txid)? {
|
||||
if let Some(observed_at) = self.try_get_chain_position(chain, static_block, txid)? {
|
||||
return Ok(Some((observed_at, txid)));
|
||||
}
|
||||
}
|
||||
@ -675,11 +701,16 @@ impl<A: BlockAnchor> TxGraph<A> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn get_chain_spend<C>(&self, chain: C, outpoint: OutPoint) -> Option<(ObservedAs<&A>, Txid)>
|
||||
pub fn get_chain_spend<C>(
|
||||
&self,
|
||||
chain: &C,
|
||||
static_block: BlockId,
|
||||
outpoint: OutPoint,
|
||||
) -> Option<(ObservedAs<&A>, Txid)>
|
||||
where
|
||||
C: ChainOracle<Error = Infallible>,
|
||||
{
|
||||
self.try_get_spend_in_chain(chain, outpoint)
|
||||
self.try_get_spend_in_chain(chain, static_block, outpoint)
|
||||
.expect("error is infallible")
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user