[bdk_chain_redesign] Initial work on LocalChain.
				
					
				
			This commit is contained in:
		
							parent
							
								
									34d0277e44
								
							
						
					
					
						commit
						468701a129
					
				| @ -11,6 +11,7 @@ pub enum ObservedIn<A> { | |||||||
|     /// The chain data is seen in a block identified by `A`.
 |     /// The chain data is seen in a block identified by `A`.
 | ||||||
|     Block(A), |     Block(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`.
 | ||||||
|     Mempool(u64), |     Mempool(u64), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -68,7 +68,7 @@ impl<A: BlockAnchor, D: TxIndexAdditions> TxIndexAdditions for IndexedAdditions< | |||||||
| 
 | 
 | ||||||
| pub struct IndexedTxGraph<A, I> { | pub struct IndexedTxGraph<A, I> { | ||||||
|     graph: TxGraph<A>, |     graph: TxGraph<A>, | ||||||
|     index: I, |     index: I, // [TODO] Make public
 | ||||||
|     last_height: u32, |     last_height: u32, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -219,6 +219,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> { | |||||||
|         self.last_height |         self.last_height | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // [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: C, | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ mod chain_data; | |||||||
| pub use chain_data::*; | pub use chain_data::*; | ||||||
| pub mod indexed_tx_graph; | pub mod indexed_tx_graph; | ||||||
| pub mod keychain; | pub mod keychain; | ||||||
|  | pub mod local_chain; | ||||||
| pub mod sparse_chain; | pub mod sparse_chain; | ||||||
| mod tx_data_traits; | mod tx_data_traits; | ||||||
| pub mod tx_graph; | pub mod tx_graph; | ||||||
|  | |||||||
							
								
								
									
										140
									
								
								crates/chain/src/local_chain.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								crates/chain/src/local_chain.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | |||||||
|  | use core::convert::Infallible; | ||||||
|  | 
 | ||||||
|  | use alloc::{collections::BTreeMap, vec::Vec}; | ||||||
|  | use bitcoin::BlockHash; | ||||||
|  | 
 | ||||||
|  | use crate::{BlockId, ChainOracle}; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] | ||||||
|  | pub struct LocalChain { | ||||||
|  |     blocks: BTreeMap<u32, BlockHash>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // [TODO] We need a cache/snapshot thing for chain oracle.
 | ||||||
|  | // * Minimize calls to remotes.
 | ||||||
|  | // * Can we cache it forever? Should we drop stuff?
 | ||||||
|  | // * Assume anything deeper than (i.e. 10) blocks won't be reorged.
 | ||||||
|  | // * Is this a cache on txs or block? or both?
 | ||||||
|  | // [TODO] Parents of children are confirmed if children are confirmed.
 | ||||||
|  | impl ChainOracle for LocalChain { | ||||||
|  |     type Error = Infallible; | ||||||
|  | 
 | ||||||
|  |     fn get_block_in_best_chain(&self, height: u32) -> Result<Option<BlockHash>, Self::Error> { | ||||||
|  |         Ok(self.blocks.get(&height).cloned()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl AsRef<BTreeMap<u32, BlockHash>> for LocalChain { | ||||||
|  |     fn as_ref(&self) -> &BTreeMap<u32, BlockHash> { | ||||||
|  |         &self.blocks | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<LocalChain> for BTreeMap<u32, BlockHash> { | ||||||
|  |     fn from(value: LocalChain) -> Self { | ||||||
|  |         value.blocks | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl LocalChain { | ||||||
|  |     pub fn tip(&self) -> Option<BlockId> { | ||||||
|  |         self.blocks | ||||||
|  |             .iter() | ||||||
|  |             .last() | ||||||
|  |             .map(|(&height, &hash)| BlockId { height, hash }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// This is like the sparsechain's logic, expect we must guarantee that all invalidated heights
 | ||||||
|  |     /// are to be re-filled.
 | ||||||
|  |     pub fn determine_changeset<U>(&self, update: &U) -> Result<ChangeSet, UpdateError> | ||||||
|  |     where | ||||||
|  |         U: AsRef<BTreeMap<u32, BlockHash>>, | ||||||
|  |     { | ||||||
|  |         let update = update.as_ref(); | ||||||
|  |         let update_tip = match update.keys().last().cloned() { | ||||||
|  |             Some(tip) => tip, | ||||||
|  |             None => return Ok(ChangeSet::default()), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         // this is the latest height where both the update and local chain has the same block hash
 | ||||||
|  |         let agreement_height = update | ||||||
|  |             .iter() | ||||||
|  |             .rev() | ||||||
|  |             .find(|&(u_height, u_hash)| self.blocks.get(u_height) == Some(u_hash)) | ||||||
|  |             .map(|(&height, _)| height); | ||||||
|  | 
 | ||||||
|  |         // the lower bound of the range to invalidate
 | ||||||
|  |         let invalidate_lb = match agreement_height { | ||||||
|  |             Some(height) if height == update_tip => u32::MAX, | ||||||
|  |             Some(height) => height + 1, | ||||||
|  |             None => 0, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         // the first block's height to invalidate in the local chain
 | ||||||
|  |         let invalidate_from = self.blocks.range(invalidate_lb..).next().map(|(&h, _)| h); | ||||||
|  | 
 | ||||||
|  |         // the first block of height to invalidate (if any) should be represented in the update
 | ||||||
|  |         if let Some(first_invalid) = invalidate_from { | ||||||
|  |             if !update.contains_key(&first_invalid) { | ||||||
|  |                 return Err(UpdateError::NotConnected(first_invalid)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let invalidated_heights = invalidate_from | ||||||
|  |             .into_iter() | ||||||
|  |             .flat_map(|from_height| self.blocks.range(from_height..).map(|(h, _)| h)); | ||||||
|  | 
 | ||||||
|  |         // invalidated heights must all exist in the update
 | ||||||
|  |         let mut missing_heights = Vec::<u32>::new(); | ||||||
|  |         for invalidated_height in invalidated_heights { | ||||||
|  |             if !update.contains_key(invalidated_height) { | ||||||
|  |                 missing_heights.push(*invalidated_height); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if !missing_heights.is_empty() { | ||||||
|  |             return Err(UpdateError::MissingHeightsInUpdate(missing_heights)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let mut changeset = BTreeMap::<u32, BlockHash>::new(); | ||||||
|  |         for (height, new_hash) in update { | ||||||
|  |             let original_hash = self.blocks.get(height); | ||||||
|  |             if Some(new_hash) != original_hash { | ||||||
|  |                 changeset.insert(*height, *new_hash); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Ok(ChangeSet(changeset)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Default)] | ||||||
|  | pub struct ChangeSet(pub BTreeMap<u32, BlockHash>); | ||||||
|  | 
 | ||||||
|  | /// Represents an update failure of [`LocalChain`].j
 | ||||||
|  | #[derive(Clone, Debug, PartialEq)] | ||||||
|  | pub enum UpdateError { | ||||||
|  |     /// The update cannot be applied to the chain because the chain suffix it represents did not
 | ||||||
|  |     /// connect to the existing chain. This error case contains the checkpoint height to include so
 | ||||||
|  |     /// that the chains can connect.
 | ||||||
|  |     NotConnected(u32), | ||||||
|  |     /// If the update results in displacements of original blocks, the update should include all new
 | ||||||
|  |     /// block hashes that have displaced the original block hashes. This error case contains the
 | ||||||
|  |     /// heights of all missing block hashes in the update.
 | ||||||
|  |     MissingHeightsInUpdate(Vec<u32>), | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl core::fmt::Display for UpdateError { | ||||||
|  |     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||||
|  |         match self { | ||||||
|  |             UpdateError::NotConnected(heights) => write!( | ||||||
|  |                 f, | ||||||
|  |                 "the update cannot connect with the chain, try include blockhash at height {}", | ||||||
|  |                 heights | ||||||
|  |             ), | ||||||
|  |             UpdateError::MissingHeightsInUpdate(missing_heights) => write!( | ||||||
|  |                 f, | ||||||
|  |                 "block hashes of these heights must be included in the update to succeed: {:?}", | ||||||
|  |                 missing_heights | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -58,6 +58,10 @@ impl BlockAnchor for (u32, BlockHash) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Represents a service that tracks the best chain history.
 | /// 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.
 | ||||||
| pub trait ChainOracle { | pub trait ChainOracle { | ||||||
|     /// Error type.
 |     /// Error type.
 | ||||||
|     type Error: core::fmt::Debug; |     type Error: core::fmt::Debug; | ||||||
| @ -71,6 +75,8 @@ pub trait ChainOracle { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // [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 { | impl<C: ChainOracle> ChainOracle for &C { | ||||||
|     type Error = C::Error; |     type Error = C::Error; | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user