168 lines
4.5 KiB
Rust
168 lines
4.5 KiB
Rust
|
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))
|
||
|
)
|
||
|
}
|