Skip to content

Commit 9ce0bd6

Browse files
committed
fix(chain)!: In apply_changeset{_to checkpoint} propagate the error from extend
Now that `apply_changeset` can fail due to inconsistent data in addition to `MissingGenesisError`, we need to propagate the error rather than expect that the chain update can always succeed. Added private struct `ApplyChangeSetError`. This error is returned by `apply_changeset_to_checkpoint`. BREAKING: - Changed `LocalChain::from_blocks` to return `Result<Self, Option<CheckPoint<D>>` - Changed `LocalChain::from_changeset` to return `Result<Option<Self>, CannotConnectError)` - Changed `LocalChain::apply_changeset` to return `Result<(), CannotConnectError>`
1 parent 2898ef3 commit 9ce0bd6

1 file changed

Lines changed: 42 additions & 19 deletions

File tree

crates/chain/src/local_chain.rs

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,22 @@ pub use bdk_core::{CheckPoint, CheckPointIter};
1313
use bitcoin::block::Header;
1414
use bitcoin::BlockHash;
1515

16+
/// Error for `apply_changeset_to_checkpoint`.
17+
#[derive(Debug)]
18+
struct ApplyChangeSetError<D> {
19+
cp: Option<CheckPoint<D>>,
20+
}
21+
1622
/// Apply `changeset` to the checkpoint.
23+
///
24+
/// # Errors
25+
///
26+
/// - If constructing the new chain from the provided `changeset` fails, then a
27+
/// [`ApplyChangeSetError`] is returned.
1728
fn apply_changeset_to_checkpoint<D>(
1829
mut init_cp: CheckPoint<D>,
1930
changeset: &ChangeSet<D>,
20-
) -> Result<CheckPoint<D>, MissingGenesisError>
31+
) -> Result<CheckPoint<D>, ApplyChangeSetError<D>>
2132
where
2233
D: ToBlockHash + fmt::Debug + Clone,
2334
{
@@ -50,8 +61,11 @@ where
5061
let new_tip = match base {
5162
Some(base) => base
5263
.extend(extension)
53-
.expect("extension is strictly greater than base"),
54-
None => LocalChain::from_blocks(extension)?.tip(),
64+
.map_err(Option::Some)
65+
.map_err(|cp| ApplyChangeSetError { cp })?,
66+
None => LocalChain::from_blocks(extension)
67+
.map_err(|cp| ApplyChangeSetError { cp })?
68+
.tip(),
5569
};
5670
init_cp = new_tip;
5771
}
@@ -255,30 +269,36 @@ where
255269
///
256270
/// The [`BTreeMap`] enforces the height order. However, the caller must ensure the blocks are
257271
/// all of the same chain.
258-
pub fn from_blocks(blocks: BTreeMap<u32, D>) -> Result<Self, MissingGenesisError> {
259-
let genesis_hash = blocks
260-
.get(&0)
261-
.map(ToBlockHash::to_blockhash)
262-
.ok_or(MissingGenesisError)?;
272+
///
273+
/// Returns `Err(None)` if `blocks` is empty or doesn't contain a value at height `0` a.k.a
274+
/// the "genesis" block.
275+
/// If `blocks` contains inconsistent or invalid data, then returns `Err(Some(..))`
276+
/// containing the last valid checkpoint.
277+
pub fn from_blocks(blocks: BTreeMap<u32, D>) -> Result<Self, Option<CheckPoint<D>>> {
278+
let genesis_hash = blocks.get(&0).map(ToBlockHash::to_blockhash).ok_or(None)?;
279+
let tip = CheckPoint::from_blocks(blocks)?;
263280

264-
Ok(Self {
265-
genesis_hash,
266-
tip: CheckPoint::from_blocks(blocks).expect("blocks must be in order"),
267-
})
281+
Ok(Self { genesis_hash, tip })
268282
}
269283

270284
/// Construct a [`LocalChain`] from an initial `changeset`.
271-
pub fn from_changeset(changeset: ChangeSet<D>) -> Result<Self, MissingGenesisError> {
285+
///
286+
/// # Returns
287+
///
288+
/// - `Ok(None)` if `changeset` doesn't contain a value at height 0, a.k.a "genesis".
289+
/// - `CannotConnectError` if the `changeset` can't be applied.
290+
/// - Otherwise returns a new [`LocalChain`] with the changeset applied
291+
pub fn from_changeset(changeset: ChangeSet<D>) -> Result<Option<Self>, CannotConnectError> {
272292
let genesis_entry = changeset.blocks.get(&0).cloned().flatten();
273293
let genesis_data = match genesis_entry {
274294
Some(data) => data,
275-
None => return Err(MissingGenesisError),
295+
None => return Ok(None),
276296
};
277297

278298
let (mut chain, _) = Self::from_genesis(genesis_data);
279299
chain.apply_changeset(&changeset)?;
280300
debug_assert!(chain._check_changeset_is_applied(&changeset));
281-
Ok(chain)
301+
Ok(Some(chain))
282302
}
283303

284304
/// Construct a [`LocalChain`] from a given `checkpoint` tip.
@@ -317,9 +337,12 @@ where
317337
}
318338

319339
/// Apply the given `changeset`.
320-
pub fn apply_changeset(&mut self, changeset: &ChangeSet<D>) -> Result<(), MissingGenesisError> {
340+
pub fn apply_changeset(&mut self, changeset: &ChangeSet<D>) -> Result<(), CannotConnectError> {
321341
let old_tip = self.tip.clone();
322-
let new_tip = apply_changeset_to_checkpoint(old_tip, changeset)?;
342+
let new_tip =
343+
apply_changeset_to_checkpoint(old_tip, changeset).map_err(|e| CannotConnectError {
344+
try_include_height: e.cp.as_ref().map_or(0, CheckPoint::height),
345+
})?;
323346
self.tip = new_tip;
324347
debug_assert!(self._check_changeset_is_applied(changeset));
325348
Ok(())
@@ -702,9 +725,9 @@ where
702725
}
703726

704727
// Apply changeset to tip.
705-
let new_tip = apply_changeset_to_checkpoint(self.tip(), &changeset).map_err(|_| {
728+
let new_tip = apply_changeset_to_checkpoint(self.tip(), &changeset).map_err(|e| {
706729
CannotConnectError {
707-
try_include_height: 0,
730+
try_include_height: e.cp.as_ref().map_or(0, CheckPoint::height),
708731
}
709732
})?;
710733

0 commit comments

Comments
 (0)