[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`].
 |     /// [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// [`is_mature`]: Self::is_mature
 |     /// [`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 { |         if !self.is_on_coinbase { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| @ -275,8 +275,8 @@ impl<A: BlockAnchor> FullTxOut<ObservedAs<A>> { | |||||||
|     /// being a [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
 |     /// being a [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// [`is_spendable_at`]: Self::is_spendable_at
 |     /// [`is_spendable_at`]: Self::is_spendable_at
 | ||||||
|     pub fn is_observed_as_spendable(&self, tip: u32) -> bool { |     pub fn is_observed_as_confirmed_and_spendable(&self, tip: u32) -> bool { | ||||||
|         if !self.is_observed_as_mature(tip) { |         if !self.is_observed_as_confirmed_and_mature(tip) { | ||||||
|             return false; |             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 bitcoin::BlockHash; | ||||||
| 
 | 
 | ||||||
| use crate::BlockId; | use crate::BlockId; | ||||||
| 
 | 
 | ||||||
| /// Represents a service that tracks the best chain history.
 | /// Represents a service that tracks the blockchain.
 | ||||||
| /// 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?
 | /// The main method is [`is_block_in_chain`] which determines whether a given block of [`BlockId`]
 | ||||||
| /// * Get tip method! And check the tip still exists at the end! And every internal call
 | /// is an ancestor of another "static block".
 | ||||||
| ///   does not go beyond the initial tip.
 | ///
 | ||||||
|  | /// [`is_block_in_chain`]: Self::is_block_in_chain
 | ||||||
| pub trait ChainOracle { | pub trait ChainOracle { | ||||||
|     /// Error type.
 |     /// Error type.
 | ||||||
|     type Error: core::fmt::Debug; |     type Error: core::fmt::Debug; | ||||||
| 
 | 
 | ||||||
|     /// Get the height and hash of the tip in the best chain.
 |     /// Determines whether `block` of [`BlockId`] exists as an ancestor of `static_block`.
 | ||||||
|     fn get_tip_in_best_chain(&self) -> Result<Option<BlockId>, Self::Error>; |     ///
 | ||||||
| 
 |     /// If `None` is returned, it means the implementation cannot determine whether `block` exists.
 | ||||||
|     /// Returns the block hash (if any) of the given `height`.
 |     fn is_block_in_chain( | ||||||
|     fn get_block_in_best_chain(&self, height: u32) -> Result<Option<BlockHash>, Self::Error>; |         &self, | ||||||
| 
 |         block: BlockId, | ||||||
|     /// Determines whether the block of [`BlockId`] exists in the best chain.
 |         static_block: BlockId, | ||||||
|     fn is_block_in_best_chain(&self, block_id: BlockId) -> Result<bool, Self::Error> { |     ) -> Result<Option<bool>, Self::Error>; | ||||||
|         Ok(matches!(self.get_block_in_best_chain(block_id.height)?, Some(h) if h == block_id.hash)) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // [TODO] We need stuff for smart pointers. Maybe? How does rust lib do this?
 | /// A cache structure increases the performance of getting chain data.
 | ||||||
| // Box<dyn ChainOracle>, Arc<dyn ChainOracle> ????? I will figure it out
 | ///
 | ||||||
| impl<C: ChainOracle> ChainOracle for &C { | /// A simple FIFO cache replacement policy is used. Something more efficient and advanced can be
 | ||||||
|     type Error = C::Error; | /// implemented later.
 | ||||||
| 
 | #[derive(Debug, Default)] | ||||||
|     fn get_tip_in_best_chain(&self) -> Result<Option<BlockId>, Self::Error> { | pub struct CacheBackend<C> { | ||||||
|         <C as ChainOracle>::get_tip_in_best_chain(self) |     cache: HashSet<(BlockHash, BlockHash)>, | ||||||
|     } |     fifo: VecDeque<(BlockHash, BlockHash)>, | ||||||
| 
 |  | ||||||
|     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>, |  | ||||||
|     marker: PhantomData<C>, |     marker: PhantomData<C>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<C> Cache<C> { | impl<C> CacheBackend<C> { | ||||||
|     /// Creates a new [`Cache`].
 |     /// 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
 |     /// Returns pruned elements.
 | ||||||
|     /// when we can assume the block is final (reorgs cannot happen). I.e. a value of 0 means the
 |     pub fn prune(&mut self, max_size: usize) -> Vec<(BlockHash, BlockHash)> { | ||||||
|     /// tip is assumed to be final. The cache only caches blocks that are assumed to be final.
 |         let prune_count = self.cache.len().saturating_sub(max_size); | ||||||
|     pub fn new(assume_final_depth: u32) -> Self { |         (0..prune_count) | ||||||
|         Self { |             .filter_map(|_| self.fifo.pop_front()) | ||||||
|             assume_final_depth, |             .filter(|k| self.cache.remove(k)) | ||||||
|             tip_height: 0, |             .collect() | ||||||
|             cache: Default::default(), |     } | ||||||
|             marker: Default::default(), | 
 | ||||||
|  |     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::{ | use crate::{ | ||||||
|     keychain::Balance, |     keychain::Balance, | ||||||
|     tx_graph::{Additions, TxGraph, TxNode}, |     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.
 | /// 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`.
 |     // [TODO] Have to methods, one for relevant-only, and one for any. Have one in `TxGraph`.
 | ||||||
|     pub fn try_list_chain_txs<'a, C>( |     pub fn try_list_chain_txs<'a, C>( | ||||||
|         &'a self, |         &'a self, | ||||||
|         chain: C, |         chain: &'a C, | ||||||
|  |         static_block: BlockId, | ||||||
|     ) -> impl Iterator<Item = Result<CanonicalTx<'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 +231,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> { | |||||||
|             .filter(|tx| self.index.is_tx_relevant(tx)) |             .filter(|tx| self.index.is_tx_relevant(tx)) | ||||||
|             .filter_map(move |tx| { |             .filter_map(move |tx| { | ||||||
|                 self.graph |                 self.graph | ||||||
|                     .try_get_chain_position(&chain, tx.txid) |                     .try_get_chain_position(chain, static_block, tx.txid) | ||||||
|                     .map(|v| { |                     .map(|v| { | ||||||
|                         v.map(|observed_in| CanonicalTx { |                         v.map(|observed_in| CanonicalTx { | ||||||
|                             observed_as: observed_in, |                             observed_as: observed_in, | ||||||
| @ -243,18 +244,20 @@ 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: &'a C, | ||||||
|  |         static_block: BlockId, | ||||||
|     ) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>> |     ) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>> | ||||||
|     where |     where | ||||||
|         C: ChainOracle<Error = Infallible> + 'a, |         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")) |             .map(|r| r.expect("error is infallible")) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn try_list_chain_txouts<'a, C>( |     pub fn try_list_chain_txouts<'a, C>( | ||||||
|         &'a self, |         &'a self, | ||||||
|         chain: C, |         chain: &'a C, | ||||||
|  |         static_block: BlockId, | ||||||
|     ) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a |     ) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a | ||||||
|     where |     where | ||||||
|         C: ChainOracle + 'a, |         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 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, static_block, op.txid) | ||||||
|  |                     { | ||||||
|                         Ok(Some(observed_at)) => observed_at.cloned(), |                         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, static_block, op) { | ||||||
|                     Ok(Some((obs, txid))) => Some((obs.cloned(), 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)), | ||||||
| @ -293,41 +300,45 @@ 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: &'a C, | ||||||
|  |         static_block: BlockId, | ||||||
|     ) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a |     ) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a | ||||||
|     where |     where | ||||||
|         C: ChainOracle<Error = Infallible> + 'a, |         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")) |             .map(|r| r.expect("error in infallible")) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Return relevant unspents.
 |     /// Return relevant unspents.
 | ||||||
|     pub fn try_list_chain_utxos<'a, C>( |     pub fn try_list_chain_utxos<'a, C>( | ||||||
|         &'a self, |         &'a self, | ||||||
|         chain: C, |         chain: &'a C, | ||||||
|  |         static_block: BlockId, | ||||||
|     ) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a |     ) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a | ||||||
|     where |     where | ||||||
|         C: ChainOracle + 'a, |         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())) |             .filter(|r| !matches!(r, Ok(txo) if txo.spent_by.is_none())) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn list_chain_utxos<'a, C>( |     pub fn list_chain_utxos<'a, C>( | ||||||
|         &'a self, |         &'a self, | ||||||
|         chain: C, |         chain: &'a C, | ||||||
|  |         static_block: BlockId, | ||||||
|     ) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a |     ) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a | ||||||
|     where |     where | ||||||
|         C: ChainOracle<Error = Infallible> + 'a, |         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")) |             .map(|r| r.expect("error is infallible")) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn try_balance<C, F>( |     pub fn try_balance<C, F>( | ||||||
|         &self, |         &self, | ||||||
|         chain: C, |         chain: &C, | ||||||
|  |         static_block: BlockId, | ||||||
|         tip: u32, |         tip: u32, | ||||||
|         mut should_trust: F, |         mut should_trust: F, | ||||||
|     ) -> Result<Balance, C::Error> |     ) -> Result<Balance, C::Error> | ||||||
| @ -340,13 +351,13 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> { | |||||||
|         let mut untrusted_pending = 0; |         let mut untrusted_pending = 0; | ||||||
|         let mut confirmed = 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?; |             let txout = res?; | ||||||
| 
 | 
 | ||||||
|             match &txout.chain_position { |             match &txout.chain_position { | ||||||
|                 ObservedAs::Confirmed(_) => { |                 ObservedAs::Confirmed(_) => { | ||||||
|                     if txout.is_on_coinbase { |                     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; |                             confirmed += txout.txout.value; | ||||||
|                         } else { |                         } else { | ||||||
|                             immature += txout.txout.value; |                             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 |     where | ||||||
|         C: ChainOracle<Error = Infallible>, |         C: ChainOracle<Error = Infallible>, | ||||||
|         F: FnMut(&Script) -> bool, |         F: FnMut(&Script) -> bool, | ||||||
|     { |     { | ||||||
|         self.try_balance(chain, tip, should_trust) |         self.try_balance(chain, static_block, tip, should_trust) | ||||||
|             .expect("error is infallible") |             .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 |     where | ||||||
|         C: ChainOracle, |         C: ChainOracle, | ||||||
|     { |     { | ||||||
|         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, static_block) { | ||||||
|             let txo = txo_res?; |             let txo = txo_res?; | ||||||
|             if txo.is_observed_as_spendable(height) { |             if txo.is_observed_as_confirmed_and_spendable(height) { | ||||||
|                 sum += txo.txout.value; |                 sum += txo.txout.value; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         Ok(sum) |         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 |     where | ||||||
|         C: ChainOracle<Error = Infallible>, |         C: ChainOracle<Error = Infallible>, | ||||||
|     { |     { | ||||||
|         self.try_balance_at(chain, height) |         self.try_balance_at(chain, static_block, height) | ||||||
|             .expect("error is infallible") |             .expect("error is infallible") | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -22,16 +22,25 @@ pub struct LocalChain { | |||||||
| impl ChainOracle for LocalChain { | impl ChainOracle for LocalChain { | ||||||
|     type Error = Infallible; |     type Error = Infallible; | ||||||
| 
 | 
 | ||||||
|     fn get_tip_in_best_chain(&self) -> Result<Option<BlockId>, Self::Error> { |     fn is_block_in_chain( | ||||||
|         Ok(self |         &self, | ||||||
|             .blocks |         block: BlockId, | ||||||
|             .iter() |         static_block: BlockId, | ||||||
|             .last() |     ) -> Result<Option<bool>, Self::Error> { | ||||||
|             .map(|(&height, &hash)| BlockId { height, hash })) |         if block.height > static_block.height { | ||||||
|  |             return Ok(None); | ||||||
|         } |         } | ||||||
| 
 |         Ok( | ||||||
|     fn get_block_in_best_chain(&self, height: u32) -> Result<Option<BlockHash>, Self::Error> { |             match ( | ||||||
|         Ok(self.blocks.get(&height).cloned()) |                 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> { | 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 is_block_in_chain( | ||||||
|         Ok(self |         &self, | ||||||
|             .checkpoints |         block: BlockId, | ||||||
|             .iter() |         static_block: BlockId, | ||||||
|             .last() |     ) -> Result<Option<bool>, Self::Error> { | ||||||
|             .map(|(&height, &hash)| BlockId { height, hash })) |         Ok( | ||||||
|     } |             match ( | ||||||
| 
 |                 self.checkpoint_at(block.height), | ||||||
|     fn get_block_in_best_chain(&self, height: u32) -> Result<Option<BlockHash>, Self::Error> { |                 self.checkpoint_at(static_block.height), | ||||||
|         Ok(self.checkpoint_at(height).map(|b| b.hash)) |             ) { | ||||||
|  |                 (Some(b), Some(static_b)) => Some(b == block && static_b == static_block), | ||||||
|  |                 _ => None, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -55,7 +55,7 @@ | |||||||
| //! assert!(additions.is_empty());
 | //! assert!(additions.is_empty());
 | ||||||
| //! ```
 | //! ```
 | ||||||
| 
 | 
 | ||||||
| use crate::{collections::*, BlockAnchor, ChainOracle, ForEachTxOut, ObservedAs}; | use crate::{collections::*, BlockAnchor, BlockId, 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::{ | ||||||
| @ -596,7 +596,8 @@ impl<A: BlockAnchor> TxGraph<A> { | |||||||
|     /// TODO: Also return conflicting tx list, ordered by last_seen.
 |     /// TODO: Also return conflicting tx list, ordered by last_seen.
 | ||||||
|     pub fn try_get_chain_position<C>( |     pub fn try_get_chain_position<C>( | ||||||
|         &self, |         &self, | ||||||
|         chain: C, |         chain: &C, | ||||||
|  |         static_block: BlockId, | ||||||
|         txid: Txid, |         txid: Txid, | ||||||
|     ) -> Result<Option<ObservedAs<&A>>, C::Error> |     ) -> Result<Option<ObservedAs<&A>>, C::Error> | ||||||
|     where |     where | ||||||
| @ -610,8 +611,28 @@ impl<A: BlockAnchor> TxGraph<A> { | |||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         for anchor in anchors { |         for anchor in anchors { | ||||||
|             if chain.is_block_in_best_chain(anchor.anchor_block())? { |             match chain.is_block_in_chain(anchor.anchor_block(), static_block)? { | ||||||
|                 return Ok(Some(ObservedAs::Confirmed(anchor))); |                 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 { |         let tx = match tx_node { | ||||||
|             TxNodeInternal::Whole(tx) => tx, |             TxNodeInternal::Whole(tx) => tx, | ||||||
|             TxNodeInternal::Partial(_) => { |             TxNodeInternal::Partial(_) => { | ||||||
|                 // [TODO] Unfortunately, we can't iterate over conflicts of partial txs right now!
 |                 // Partial transactions (outputs only) cannot have conflicts.
 | ||||||
|                 // [TODO] So we just assume the partial tx does not exist in the best chain :/
 |  | ||||||
|                 return Ok(None); |                 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
 |         // 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
 |         // this tx cannot exist in the best chain
 | ||||||
|         for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx_node(txid)) { |         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) { |             for block in conflicting_tx.anchors.iter().map(A::anchor_block) { | ||||||
|                 if chain.is_block_in_best_chain(block_id)? { |                 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!
 |                     // conflicting tx is in best chain, so the current tx cannot be in best chain!
 | ||||||
|                     return Ok(None); |                     return Ok(None); | ||||||
|                 } |                 } | ||||||
| @ -643,31 +663,37 @@ impl<A: BlockAnchor> TxGraph<A> { | |||||||
|         Ok(Some(ObservedAs::Unconfirmed(last_seen))) |         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 |     where | ||||||
|         C: ChainOracle<Error = Infallible>, |         C: ChainOracle<Error = Infallible>, | ||||||
|     { |     { | ||||||
|         self.try_get_chain_position(chain, txid) |         self.try_get_chain_position(chain, static_block, txid) | ||||||
|             .expect("error is infallible") |             .expect("error is infallible") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn try_get_spend_in_chain<C>( |     pub fn try_get_spend_in_chain<C>( | ||||||
|         &self, |         &self, | ||||||
|         chain: C, |         chain: &C, | ||||||
|  |         static_block: BlockId, | ||||||
|         outpoint: OutPoint, |         outpoint: OutPoint, | ||||||
|     ) -> Result<Option<(ObservedAs<&A>, Txid)>, C::Error> |     ) -> Result<Option<(ObservedAs<&A>, Txid)>, C::Error> | ||||||
|     where |     where | ||||||
|         C: ChainOracle, |         C: ChainOracle, | ||||||
|     { |     { | ||||||
|         if self |         if self | ||||||
|             .try_get_chain_position(&chain, outpoint.txid)? |             .try_get_chain_position(chain, static_block, outpoint.txid)? | ||||||
|             .is_none() |             .is_none() | ||||||
|         { |         { | ||||||
|             return Ok(None); |             return Ok(None); | ||||||
|         } |         } | ||||||
|         if let Some(spends) = self.spends.get(&outpoint) { |         if let Some(spends) = self.spends.get(&outpoint) { | ||||||
|             for &txid in spends { |             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))); |                     return Ok(Some((observed_at, txid))); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @ -675,11 +701,16 @@ impl<A: BlockAnchor> TxGraph<A> { | |||||||
|         Ok(None) |         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 |     where | ||||||
|         C: ChainOracle<Error = Infallible>, |         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") |             .expect("error is infallible") | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user