Skip to content

Commit

Permalink
fixup! refactor(chain): LocalChain takes a generic
Browse files Browse the repository at this point in the history
  • Loading branch information
LagginTimes committed Sep 10, 2024
1 parent 248b822 commit 1e56666
Showing 1 changed file with 141 additions and 91 deletions.
232 changes: 141 additions & 91 deletions crates/chain/src/local_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ use bitcoin::block::Header;
use bitcoin::BlockHash;

/// Apply `changeset` to the checkpoint.
fn apply_changeset_to_checkpoint(
mut init_cp: CheckPoint,
changeset: &ChangeSet,
) -> Result<CheckPoint, MissingGenesisError> {
fn apply_changeset_to_checkpoint<B>(
mut init_cp: CheckPoint<B>,
changeset: &ChangeSet<B>,
) -> Result<CheckPoint<B>, MissingGenesisError>
where
B: Copy + core::fmt::Debug + bdk_core::ToBlockHash,
{
if let Some(start_height) = changeset.blocks.keys().next().cloned() {
// changes after point of agreement
let mut extension = BTreeMap::default();
// point of agreement
let mut base: Option<CheckPoint> = None;
let mut base: Option<CheckPoint<B>> = None;

for cp in init_cp.iter() {
if cp.height() >= start_height {
Expand All @@ -44,7 +47,7 @@ fn apply_changeset_to_checkpoint(
Some(base) => base
.extend_data(extension)
.expect("extension is strictly greater than base"),
None => LocalChain::from_blocks(extension)?.tip(),
None => LocalChain::from_data(extension)?.tip(),
};
init_cp = new_tip;
}
Expand Down Expand Up @@ -96,11 +99,6 @@ where
}

impl LocalChain<BlockHash> {
/// Get the genesis hash.
pub fn genesis_hash(&self) -> BlockHash {
self.tip.get(0).expect("genesis must exist").block_id().hash
}

/// Construct [`LocalChain`] from genesis `hash`.
#[must_use]
pub fn from_genesis_hash(hash: BlockHash) -> (Self, ChangeSet) {
Expand Down Expand Up @@ -128,45 +126,12 @@ impl LocalChain<BlockHash> {
Ok(chain)
}

/// Construct a [`LocalChain`] from a given `checkpoint` tip.
pub fn from_tip(tip: CheckPoint) -> Result<Self, MissingGenesisError> {
let genesis_cp = tip.iter().last().expect("must have at least one element");
if genesis_cp.height() != 0 {
return Err(MissingGenesisError);
}
Ok(Self { tip })
}

/// Constructs a [`LocalChain`] from a [`BTreeMap`] of height to [`BlockHash`].
///
/// The [`BTreeMap`] enforces the height order. However, the caller must ensure the blocks are
/// all of the same chain.
pub fn from_blocks(blocks: BTreeMap<u32, BlockHash>) -> Result<Self, MissingGenesisError> {
if !blocks.contains_key(&0) {
return Err(MissingGenesisError);
}

let mut tip: Option<CheckPoint<BlockHash>> = None;
for block in &blocks {
match tip {
Some(curr) => {
tip = Some(
curr.push(BlockId::from(block))
.expect("BTreeMap is ordered"),
)
}
None => tip = Some(CheckPoint::new(BlockId::from(block))),
}
}

Ok(Self {
tip: tip.expect("already checked to have genesis"),
})
}

/// Get the highest checkpoint.
pub fn tip(&self) -> CheckPoint<BlockHash> {
self.tip.clone()
LocalChain::from_data(blocks)
}

/// Applies the given `update` to the chain.
Expand Down Expand Up @@ -278,11 +243,7 @@ impl LocalChain<BlockHash> {

/// Apply the given `changeset`.
pub fn apply_changeset(&mut self, changeset: &ChangeSet) -> Result<(), MissingGenesisError> {
let old_tip = self.tip.clone();
let new_tip = apply_changeset_to_checkpoint(old_tip, changeset)?;
self.tip = new_tip;
debug_assert!(self._check_changeset_is_applied(changeset));
Ok(())
self.apply_data_changeset(changeset)
}

/// Insert a [`BlockId`].
Expand All @@ -291,29 +252,7 @@ impl LocalChain<BlockHash> {
///
/// Replacing the block hash of an existing checkpoint will result in an error.
pub fn insert_block(&mut self, block_id: BlockId) -> Result<ChangeSet, AlterCheckPointError> {
if let Some(original_cp) = self.tip.get(block_id.height) {
let original_hash = original_cp.hash();
if original_hash != block_id.hash {
return Err(AlterCheckPointError {
height: block_id.height,
original_hash,
update_hash: Some(block_id.hash),
});
}
return Ok(ChangeSet::default());
}

let mut changeset = ChangeSet::default();
changeset
.blocks
.insert(block_id.height, Some(block_id.hash));
self.apply_changeset(&changeset)
.map_err(|_| AlterCheckPointError {
height: 0,
original_hash: self.genesis_hash(),
update_hash: changeset.blocks.get(&0).cloned().flatten(),
})?;
Ok(changeset)
self.insert_data(block_id.height, block_id.hash)
}

/// Removes blocks from (and inclusive of) the given `block_id`.
Expand Down Expand Up @@ -372,31 +311,36 @@ impl LocalChain<BlockHash> {
}

fn _check_changeset_is_applied(&self, changeset: &ChangeSet) -> bool {
let mut curr_cp = self.tip.clone();
for (height, exp_hash) in changeset.blocks.iter().rev() {
match curr_cp.get(*height) {
Some(query_cp) => {
if query_cp.height() != *height || Some(query_cp.hash()) != *exp_hash {
return false;
}
curr_cp = query_cp;
}
None => {
if exp_hash.is_some() {
return false;
}
}
}
self._check_data_changeset_is_applied(changeset)
}
}

impl<B> LocalChain<B> {
/// Get the highest checkpoint.
pub fn tip(&self) -> CheckPoint<B> {
self.tip.clone()
}

/// Get the genesis hash.
pub fn genesis_hash(&self) -> BlockHash {
self.tip.get(0).expect("genesis must exist").block_id().hash
}

/// Construct a [`LocalChain`] from a given `checkpoint` tip.
pub fn from_tip(tip: CheckPoint<B>) -> Result<Self, MissingGenesisError> {
let genesis_cp = tip.iter().last().expect("must have at least one element");
if genesis_cp.height() != 0 {
return Err(MissingGenesisError);
}
true
Ok(Self { tip })
}

/// Get checkpoint at given `height` (if it exists).
///
/// This is a shorthand for calling [`CheckPoint::get`] on the [`tip`].
///
/// [`tip`]: LocalChain::tip
pub fn get(&self, height: u32) -> Option<CheckPoint> {
pub fn get(&self, height: u32) -> Option<CheckPoint<B>> {
self.tip.get(height)
}

Expand All @@ -408,14 +352,120 @@ impl LocalChain<BlockHash> {
/// This is a shorthand for calling [`CheckPoint::range`] on the [`tip`].
///
/// [`tip`]: LocalChain::tip
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint>
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint<B>>
where
R: RangeBounds<u32>,
{
self.tip.range(range)
}
}

impl<B> LocalChain<B>
where
B: Copy + core::fmt::Debug + bdk_core::ToBlockHash,
{
/// Apply the given `changeset`.
pub fn apply_data_changeset(
&mut self,
changeset: &ChangeSet<B>,
) -> Result<(), MissingGenesisError> {
let old_tip = self.tip.clone();
let new_tip = apply_changeset_to_checkpoint(old_tip, changeset)?;
self.tip = new_tip;
debug_assert!(self._check_data_changeset_is_applied(changeset));
Ok(())
}

fn _check_data_changeset_is_applied(&self, changeset: &ChangeSet<B>) -> bool {
let mut curr_cp = self.tip.clone();
for (height, data) in changeset.blocks.iter().rev() {
match curr_cp.get(*height) {
Some(query_cp) => {
if let Some(data) = data {
if query_cp.height() != *height || query_cp.hash() != data.to_blockhash() {
return false;
}
} else {
return false;
}
curr_cp = query_cp;
}
None => {
if data.is_some() {
return false;
}
}
}
}
true
}

/// Insert data into a [`LocalChain`].
///
/// # Errors
///
/// Replacing the block hash of an existing checkpoint will result in an error.
pub fn insert_data(
&mut self,
height: u32,
data: B,
) -> Result<ChangeSet<B>, AlterCheckPointError> {
if let Some(original_cp) = self.tip.get(height) {
let original_hash = original_cp.hash();
if original_hash != data.to_blockhash() {
return Err(AlterCheckPointError {
height,
original_hash,
update_hash: Some(data.to_blockhash()),
});
}
return Ok(ChangeSet::default());
}

let mut changeset = ChangeSet::<B>::default();
changeset.blocks.insert(height, Some(data));
self.apply_data_changeset(&changeset)
.map_err(|_| AlterCheckPointError {
height: 0,
original_hash: self.genesis_hash(),
update_hash: Some(
changeset
.blocks
.get(&0)
.cloned()
.flatten()
.unwrap()
.to_blockhash(),
),
})?;
Ok(changeset)
}

/// Constructs a [`LocalChain`] from a [`BTreeMap`] of height and data.
///
/// The [`BTreeMap`] enforces the height order. However, the caller must ensure the blocks are
/// all of the same chain.
pub fn from_data(blocks: BTreeMap<u32, B>) -> Result<Self, MissingGenesisError> {
if !blocks.contains_key(&0) {
return Err(MissingGenesisError);
}

let mut tip: Option<CheckPoint<B>> = None;
for (height, data) in &blocks {
match tip {
Some(curr) => {
tip = Some(curr.push_data(*height, *data).expect("BTreeMap is ordered"))
}
None => tip = Some(CheckPoint::from_data(*height, *data)),
}
}

Ok(Self {
tip: tip.expect("already checked to have genesis"),
})
}
}

/// The [`ChangeSet`] represents changes to [`LocalChain`].
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
Expand Down

0 comments on commit 1e56666

Please sign in to comment.