[bdk_chain_redesign] Test LocalChain
This is mostly copying over the relevant tests from `SparseChain`. Changes are made to `local_chain::ChangeSet` to re-add the ability to remove blocks.
This commit is contained in:
@@ -5,6 +5,14 @@ macro_rules! h {
|
||||
}};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! local_chain {
|
||||
[ $(($height:expr, $block_hash:expr)), * ] => {{
|
||||
#[allow(unused_mut)]
|
||||
bdk_chain::local_chain::LocalChain::from_blocks([$(($height, $block_hash).into()),*])
|
||||
}};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! chain {
|
||||
($([$($tt:tt)*]),*) => { chain!( checkpoints: [$([$($tt)*]),*] ) };
|
||||
|
||||
167
crates/chain/tests/test_local_chain.rs
Normal file
167
crates/chain/tests/test_local_chain.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
use bdk_chain::local_chain::{LocalChain, UpdateNotConnectedError};
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn add_first_tip() {
|
||||
let chain = LocalChain::default();
|
||||
assert_eq!(
|
||||
chain.determine_changeset(&local_chain![(0, h!("A"))]),
|
||||
Ok([(0, Some(h!("A")))].into()),
|
||||
"add first tip"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_second_tip() {
|
||||
let chain = local_chain![(0, h!("A"))];
|
||||
assert_eq!(
|
||||
chain.determine_changeset(&local_chain![(0, h!("A")), (1, h!("B"))]),
|
||||
Ok([(1, Some(h!("B")))].into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_disjoint_chains_cannot_merge() {
|
||||
let chain1 = local_chain![(0, h!("A"))];
|
||||
let chain2 = local_chain![(1, h!("B"))];
|
||||
assert_eq!(
|
||||
chain1.determine_changeset(&chain2),
|
||||
Err(UpdateNotConnectedError(0))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_chains_should_merge() {
|
||||
let chain1 = local_chain![(0, h!("A"))];
|
||||
let chain2 = local_chain![(0, h!("A"))];
|
||||
assert_eq!(chain1.determine_changeset(&chain2), Ok(Default::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_introduce_older_checkpoints() {
|
||||
let chain1 = local_chain![(2, h!("C")), (3, h!("D"))];
|
||||
let chain2 = local_chain![(1, h!("B")), (2, h!("C"))];
|
||||
|
||||
assert_eq!(
|
||||
chain1.determine_changeset(&chain2),
|
||||
Ok([(1, Some(h!("B")))].into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_blockhash_before_agreement_point() {
|
||||
let chain1 = local_chain![(0, h!("im-wrong")), (1, h!("we-agree"))];
|
||||
let chain2 = local_chain![(0, h!("fix")), (1, h!("we-agree"))];
|
||||
|
||||
assert_eq!(
|
||||
chain1.determine_changeset(&chain2),
|
||||
Ok([(0, Some(h!("fix")))].into())
|
||||
)
|
||||
}
|
||||
|
||||
/// B and C are in both chain and update
|
||||
/// ```
|
||||
/// | 0 | 1 | 2 | 3 | 4
|
||||
/// chain | B C
|
||||
/// update | A B C D
|
||||
/// ```
|
||||
/// This should succeed with the point of agreement being C and A should be added in addition.
|
||||
#[test]
|
||||
fn two_points_of_agreement() {
|
||||
let chain1 = local_chain![(1, h!("B")), (2, h!("C"))];
|
||||
let chain2 = local_chain![(0, h!("A")), (1, h!("B")), (2, h!("C")), (3, h!("D"))];
|
||||
|
||||
assert_eq!(
|
||||
chain1.determine_changeset(&chain2),
|
||||
Ok([(0, Some(h!("A"))), (3, Some(h!("D")))].into()),
|
||||
);
|
||||
}
|
||||
|
||||
/// Update and chain does not connect:
|
||||
/// ```
|
||||
/// | 0 | 1 | 2 | 3 | 4
|
||||
/// chain | B C
|
||||
/// update | A B D
|
||||
/// ```
|
||||
/// This should fail as we cannot figure out whether C & D are on the same chain
|
||||
#[test]
|
||||
fn update_and_chain_does_not_connect() {
|
||||
let chain1 = local_chain![(1, h!("B")), (2, h!("C"))];
|
||||
let chain2 = local_chain![(0, h!("A")), (1, h!("B")), (3, h!("D"))];
|
||||
|
||||
assert_eq!(
|
||||
chain1.determine_changeset(&chain2),
|
||||
Err(UpdateNotConnectedError(2)),
|
||||
);
|
||||
}
|
||||
|
||||
/// Transient invalidation:
|
||||
/// ```
|
||||
/// | 0 | 1 | 2 | 3 | 4 | 5
|
||||
/// chain | A B C E
|
||||
/// update | A B' C' D
|
||||
/// ```
|
||||
/// This should succeed and invalidate B,C and E with point of agreement being A.
|
||||
#[test]
|
||||
fn transitive_invalidation_applies_to_checkpoints_higher_than_invalidation() {
|
||||
let chain1 = local_chain![(0, h!("A")), (2, h!("B")), (3, h!("C")), (5, h!("E"))];
|
||||
let chain2 = local_chain![(0, h!("A")), (2, h!("B'")), (3, h!("C'")), (4, h!("D"))];
|
||||
|
||||
assert_eq!(
|
||||
chain1.determine_changeset(&chain2),
|
||||
Ok([
|
||||
(2, Some(h!("B'"))),
|
||||
(3, Some(h!("C'"))),
|
||||
(4, Some(h!("D"))),
|
||||
(5, None),
|
||||
]
|
||||
.into())
|
||||
);
|
||||
}
|
||||
|
||||
/// Transient invalidation:
|
||||
/// ```
|
||||
/// | 0 | 1 | 2 | 3 | 4
|
||||
/// chain | B C E
|
||||
/// update | B' C' D
|
||||
/// ```
|
||||
///
|
||||
/// This should succeed and invalidate B, C and E with no point of agreement
|
||||
#[test]
|
||||
fn transitive_invalidation_applies_to_checkpoints_higher_than_invalidation_no_point_of_agreement() {
|
||||
let chain1 = local_chain![(1, h!("B")), (2, h!("C")), (4, h!("E"))];
|
||||
let chain2 = local_chain![(1, h!("B'")), (2, h!("C'")), (3, h!("D"))];
|
||||
|
||||
assert_eq!(
|
||||
chain1.determine_changeset(&chain2),
|
||||
Ok([
|
||||
(1, Some(h!("B'"))),
|
||||
(2, Some(h!("C'"))),
|
||||
(3, Some(h!("D"))),
|
||||
(4, None)
|
||||
]
|
||||
.into())
|
||||
)
|
||||
}
|
||||
|
||||
/// Transient invalidation:
|
||||
/// ```
|
||||
/// | 0 | 1 | 2 | 3 | 4
|
||||
/// chain | A B C E
|
||||
/// update | B' C' D
|
||||
/// ```
|
||||
///
|
||||
/// This should fail since although it tells us that B and C are invalid it doesn't tell us whether
|
||||
/// A was invalid.
|
||||
#[test]
|
||||
fn invalidation_but_no_connection() {
|
||||
let chain1 = local_chain![(0, h!("A")), (1, h!("B")), (2, h!("C")), (4, h!("E"))];
|
||||
let chain2 = local_chain![(1, h!("B'")), (2, h!("C'")), (3, h!("D"))];
|
||||
|
||||
assert_eq!(
|
||||
chain1.determine_changeset(&chain2),
|
||||
Err(UpdateNotConnectedError(0))
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user