Skip to content

Commit 085bf94

Browse files
committed
[chain_redesign] Add LocalChain::insert_block
1 parent e413d3e commit 085bf94

File tree

2 files changed

+108
-1
lines changed

2 files changed

+108
-1
lines changed

crates/chain/src/local_chain.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,31 @@ impl LocalChain {
173173
pub fn heights(&self) -> BTreeSet<u32> {
174174
self.blocks.keys().cloned().collect()
175175
}
176+
177+
/// Insert a block of [`BlockId`] into the [`LocalChain`].
178+
///
179+
/// # Error
180+
///
181+
/// If the insertion height already contains a block, and the block has a different blockhash,
182+
/// this will result in an [`InsertBlockNotMatchingError`].
183+
pub fn insert_block(
184+
&mut self,
185+
block_id: BlockId,
186+
) -> Result<ChangeSet, InsertBlockNotMatchingError> {
187+
let mut update = Self::from_blocks(self.tip());
188+
189+
if let Some(original_hash) = update.blocks.insert(block_id.height, block_id.hash) {
190+
if original_hash != block_id.hash {
191+
return Err(InsertBlockNotMatchingError {
192+
height: block_id.height,
193+
original_hash,
194+
update_hash: block_id.hash,
195+
});
196+
}
197+
}
198+
199+
Ok(self.apply_update(update).expect("should always connect"))
200+
}
176201
}
177202

178203
/// This is the return value of [`determine_changeset`] and represents changes to [`LocalChain`].
@@ -201,3 +226,24 @@ impl core::fmt::Display for UpdateNotConnectedError {
201226

202227
#[cfg(feature = "std")]
203228
impl std::error::Error for UpdateNotConnectedError {}
229+
230+
/// Represents a failure when trying to insert a checkpoint into [`LocalChain`].
231+
#[derive(Clone, Debug, PartialEq)]
232+
pub struct InsertBlockNotMatchingError {
233+
pub height: u32,
234+
pub original_hash: BlockHash,
235+
pub update_hash: BlockHash,
236+
}
237+
238+
impl core::fmt::Display for InsertBlockNotMatchingError {
239+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
240+
write!(
241+
f,
242+
"failed to insert block at height {} as blockhashes conflict: original={}, update={}",
243+
self.height, self.original_hash, self.update_hash
244+
)
245+
}
246+
}
247+
248+
#[cfg(feature = "std")]
249+
impl std::error::Error for InsertBlockNotMatchingError {}

crates/chain/tests/test_local_chain.rs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use bdk_chain::local_chain::{LocalChain, UpdateNotConnectedError};
1+
use bdk_chain::local_chain::{
2+
ChangeSet, InsertBlockNotMatchingError, LocalChain, UpdateNotConnectedError,
3+
};
4+
use bitcoin::BlockHash;
25

36
#[macro_use]
47
mod common;
@@ -165,3 +168,61 @@ fn invalidation_but_no_connection() {
165168
Err(UpdateNotConnectedError(0))
166169
)
167170
}
171+
172+
#[test]
173+
fn insert_block() {
174+
struct TestCase {
175+
original: LocalChain,
176+
insert: (u32, BlockHash),
177+
expected_result: Result<ChangeSet, InsertBlockNotMatchingError>,
178+
expected_final: LocalChain,
179+
}
180+
181+
let test_cases = [
182+
TestCase {
183+
original: local_chain![],
184+
insert: (5, h!("block5")),
185+
expected_result: Ok([(5, Some(h!("block5")))].into()),
186+
expected_final: local_chain![(5, h!("block5"))],
187+
},
188+
TestCase {
189+
original: local_chain![(3, h!("A"))],
190+
insert: (4, h!("B")),
191+
expected_result: Ok([(4, Some(h!("B")))].into()),
192+
expected_final: local_chain![(3, h!("A")), (4, h!("B"))],
193+
},
194+
TestCase {
195+
original: local_chain![(4, h!("B"))],
196+
insert: (3, h!("A")),
197+
expected_result: Ok([(3, Some(h!("A")))].into()),
198+
expected_final: local_chain![(3, h!("A")), (4, h!("B"))],
199+
},
200+
TestCase {
201+
original: local_chain![(2, h!("K"))],
202+
insert: (2, h!("K")),
203+
expected_result: Ok([].into()),
204+
expected_final: local_chain![(2, h!("K"))],
205+
},
206+
TestCase {
207+
original: local_chain![(2, h!("K"))],
208+
insert: (2, h!("J")),
209+
expected_result: Err(InsertBlockNotMatchingError {
210+
height: 2,
211+
original_hash: h!("K"),
212+
update_hash: h!("J"),
213+
}),
214+
expected_final: local_chain![(2, h!("K"))],
215+
},
216+
];
217+
218+
for (i, t) in test_cases.into_iter().enumerate() {
219+
let mut chain = t.original;
220+
assert_eq!(
221+
chain.insert_block(t.insert.into()),
222+
t.expected_result,
223+
"[{}] unexpected result when inserting block",
224+
i,
225+
);
226+
assert_eq!(chain, t.expected_final, "[{}] unexpected final chain", i,);
227+
}
228+
}

0 commit comments

Comments
 (0)