2023-10-12 16:55:32 +08:00
|
|
|
use bdk_chain::local_chain::{
|
|
|
|
AlterCheckPointError, CannotConnectError, ChangeSet, LocalChain, Update,
|
|
|
|
};
|
2023-05-03 15:01:39 +08:00
|
|
|
use bitcoin::BlockHash;
|
2023-04-20 15:29:20 +08:00
|
|
|
|
|
|
|
#[macro_use]
|
|
|
|
mod common;
|
|
|
|
|
2023-07-19 17:42:52 +08:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct TestLocalChain<'a> {
|
|
|
|
name: &'static str,
|
|
|
|
chain: LocalChain,
|
|
|
|
update: Update,
|
|
|
|
exp: ExpectedResult<'a>,
|
2023-04-20 15:29:20 +08:00
|
|
|
}
|
|
|
|
|
2023-07-19 17:42:52 +08:00
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
enum ExpectedResult<'a> {
|
|
|
|
Ok {
|
|
|
|
changeset: &'a [(u32, Option<BlockHash>)],
|
|
|
|
init_changeset: &'a [(u32, Option<BlockHash>)],
|
|
|
|
},
|
|
|
|
Err(CannotConnectError),
|
2023-04-20 15:29:20 +08:00
|
|
|
}
|
|
|
|
|
2023-07-19 17:42:52 +08:00
|
|
|
impl<'a> TestLocalChain<'a> {
|
|
|
|
fn run(mut self) {
|
|
|
|
println!("[TestLocalChain] test: {}", self.name);
|
|
|
|
let got_changeset = match self.chain.apply_update(self.update) {
|
|
|
|
Ok(changeset) => changeset,
|
|
|
|
Err(got_err) => {
|
|
|
|
assert_eq!(
|
|
|
|
ExpectedResult::Err(got_err),
|
|
|
|
self.exp,
|
|
|
|
"{}: unexpected error",
|
|
|
|
self.name
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
2023-04-20 15:29:20 +08:00
|
|
|
|
2023-07-19 17:42:52 +08:00
|
|
|
match self.exp {
|
|
|
|
ExpectedResult::Ok {
|
|
|
|
changeset,
|
|
|
|
init_changeset,
|
|
|
|
} => {
|
|
|
|
assert_eq!(
|
|
|
|
got_changeset,
|
|
|
|
changeset.iter().cloned().collect(),
|
|
|
|
"{}: unexpected changeset",
|
|
|
|
self.name
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
self.chain.initial_changeset(),
|
|
|
|
init_changeset.iter().cloned().collect(),
|
|
|
|
"{}: unexpected initial changeset",
|
|
|
|
self.name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
ExpectedResult::Err(err) => panic!(
|
|
|
|
"{}: expected error ({}), got non-error result: {:?}",
|
|
|
|
self.name, err, got_changeset
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
2023-04-20 15:29:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2023-07-19 17:42:52 +08:00
|
|
|
fn update_local_chain() {
|
|
|
|
[
|
|
|
|
TestLocalChain {
|
|
|
|
name: "add first tip",
|
2023-10-12 16:55:32 +08:00
|
|
|
chain: local_chain![(0, h!("A"))],
|
2023-07-19 17:42:52 +08:00
|
|
|
update: chain_update![(0, h!("A"))],
|
|
|
|
exp: ExpectedResult::Ok {
|
2023-10-12 16:55:32 +08:00
|
|
|
changeset: &[],
|
2023-07-19 17:42:52 +08:00
|
|
|
init_changeset: &[(0, Some(h!("A")))],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
TestLocalChain {
|
|
|
|
name: "add second tip",
|
|
|
|
chain: local_chain![(0, h!("A"))],
|
|
|
|
update: chain_update![(0, h!("A")), (1, h!("B"))],
|
|
|
|
exp: ExpectedResult::Ok {
|
|
|
|
changeset: &[(1, Some(h!("B")))],
|
|
|
|
init_changeset: &[(0, Some(h!("A"))), (1, Some(h!("B")))],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
TestLocalChain {
|
|
|
|
name: "two disjoint chains cannot merge",
|
2023-10-12 16:55:32 +08:00
|
|
|
chain: local_chain![(0, h!("_")), (1, h!("A"))],
|
|
|
|
update: chain_update![(0, h!("_")), (2, h!("B"))],
|
2023-07-19 17:42:52 +08:00
|
|
|
exp: ExpectedResult::Err(CannotConnectError {
|
2023-10-12 16:55:32 +08:00
|
|
|
try_include_height: 1,
|
2023-07-19 17:42:52 +08:00
|
|
|
}),
|
|
|
|
},
|
|
|
|
TestLocalChain {
|
|
|
|
name: "two disjoint chains cannot merge (existing chain longer)",
|
2023-10-12 16:55:32 +08:00
|
|
|
chain: local_chain![(0, h!("_")), (2, h!("A"))],
|
|
|
|
update: chain_update![(0, h!("_")), (1, h!("B"))],
|
2023-07-19 17:42:52 +08:00
|
|
|
exp: ExpectedResult::Err(CannotConnectError {
|
2023-10-12 16:55:32 +08:00
|
|
|
try_include_height: 2,
|
2023-07-19 17:42:52 +08:00
|
|
|
}),
|
|
|
|
},
|
|
|
|
TestLocalChain {
|
|
|
|
name: "duplicate chains should merge",
|
|
|
|
chain: local_chain![(0, h!("A"))],
|
|
|
|
update: chain_update![(0, h!("A"))],
|
|
|
|
exp: ExpectedResult::Ok {
|
|
|
|
changeset: &[],
|
|
|
|
init_changeset: &[(0, Some(h!("A")))],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Introduce an older checkpoint (B)
|
|
|
|
// | 0 | 1 | 2 | 3
|
2023-10-12 16:55:32 +08:00
|
|
|
// chain | _ C D
|
|
|
|
// update | _ B C
|
2023-07-19 17:42:52 +08:00
|
|
|
TestLocalChain {
|
|
|
|
name: "can introduce older checkpoint",
|
2023-10-12 16:55:32 +08:00
|
|
|
chain: local_chain![(0, h!("_")), (2, h!("C")), (3, h!("D"))],
|
|
|
|
update: chain_update![(0, h!("_")), (1, h!("B")), (2, h!("C"))],
|
2023-07-19 17:42:52 +08:00
|
|
|
exp: ExpectedResult::Ok {
|
|
|
|
changeset: &[(1, Some(h!("B")))],
|
2023-10-12 16:55:32 +08:00
|
|
|
init_changeset: &[(0, Some(h!("_"))), (1, Some(h!("B"))), (2, Some(h!("C"))), (3, Some(h!("D")))],
|
2023-07-19 17:42:52 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
// Introduce an older checkpoint (A) that is not directly behind PoA
|
2023-10-12 16:55:32 +08:00
|
|
|
// | 0 | 2 | 3 | 4
|
|
|
|
// chain | _ B C
|
|
|
|
// update | _ A C
|
2023-07-19 17:42:52 +08:00
|
|
|
TestLocalChain {
|
|
|
|
name: "can introduce older checkpoint 2",
|
2023-10-12 16:55:32 +08:00
|
|
|
chain: local_chain![(0, h!("_")), (3, h!("B")), (4, h!("C"))],
|
|
|
|
update: chain_update![(0, h!("_")), (2, h!("A")), (4, h!("C"))],
|
2023-07-19 17:42:52 +08:00
|
|
|
exp: ExpectedResult::Ok {
|
|
|
|
changeset: &[(2, Some(h!("A")))],
|
2023-10-12 16:55:32 +08:00
|
|
|
init_changeset: &[(0, Some(h!("_"))), (2, Some(h!("A"))), (3, Some(h!("B"))), (4, Some(h!("C")))],
|
2023-07-19 17:42:52 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
// Introduce an older checkpoint (B) that is not the oldest checkpoint
|
2023-10-12 16:55:32 +08:00
|
|
|
// | 0 | 1 | 2 | 3
|
|
|
|
// chain | _ A C
|
|
|
|
// update | _ B C
|
2023-07-19 17:42:52 +08:00
|
|
|
TestLocalChain {
|
|
|
|
name: "can introduce older checkpoint 3",
|
2023-10-12 16:55:32 +08:00
|
|
|
chain: local_chain![(0, h!("_")), (1, h!("A")), (3, h!("C"))],
|
|
|
|
update: chain_update![(0, h!("_")), (2, h!("B")), (3, h!("C"))],
|
2023-07-19 17:42:52 +08:00
|
|
|
exp: ExpectedResult::Ok {
|
|
|
|
changeset: &[(2, Some(h!("B")))],
|
2023-10-12 16:55:32 +08:00
|
|
|
init_changeset: &[(0, Some(h!("_"))), (1, Some(h!("A"))), (2, Some(h!("B"))), (3, Some(h!("C")))],
|
2023-07-19 17:42:52 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
// Introduce two older checkpoints below the PoA
|
2023-10-12 16:55:32 +08:00
|
|
|
// | 0 | 1 | 2 | 3
|
|
|
|
// chain | _ C
|
|
|
|
// update | _ A B C
|
2023-07-19 17:42:52 +08:00
|
|
|
TestLocalChain {
|
|
|
|
name: "introduce two older checkpoints below PoA",
|
2023-10-12 16:55:32 +08:00
|
|
|
chain: local_chain![(0, h!("_")), (3, h!("C"))],
|
|
|
|
update: chain_update![(0, h!("_")), (1, h!("A")), (2, h!("B")), (3, h!("C"))],
|
2023-07-19 17:42:52 +08:00
|
|
|
exp: ExpectedResult::Ok {
|
|
|
|
changeset: &[(1, Some(h!("A"))), (2, Some(h!("B")))],
|
2023-10-12 16:55:32 +08:00
|
|
|
init_changeset: &[(0, Some(h!("_"))), (1, Some(h!("A"))), (2, Some(h!("B"))), (3, Some(h!("C")))],
|
2023-07-19 17:42:52 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
TestLocalChain {
|
|
|
|
name: "fix blockhash before agreement point",
|
|
|
|
chain: local_chain![(0, h!("im-wrong")), (1, h!("we-agree"))],
|
|
|
|
update: chain_update![(0, h!("fix")), (1, h!("we-agree"))],
|
|
|
|
exp: ExpectedResult::Ok {
|
|
|
|
changeset: &[(0, Some(h!("fix")))],
|
|
|
|
init_changeset: &[(0, Some(h!("fix"))), (1, Some(h!("we-agree")))],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// B and C are in both chain and update
|
|
|
|
// | 0 | 1 | 2 | 3 | 4
|
2023-10-12 16:55:32 +08:00
|
|
|
// chain | _ B C
|
|
|
|
// update | _ A B C D
|
2023-07-19 17:42:52 +08:00
|
|
|
// This should succeed with the point of agreement being C and A should be added in addition.
|
|
|
|
TestLocalChain {
|
|
|
|
name: "two points of agreement",
|
2023-10-12 16:55:32 +08:00
|
|
|
chain: local_chain![(0, h!("_")), (2, h!("B")), (3, h!("C"))],
|
|
|
|
update: chain_update![(0, h!("_")), (1, h!("A")), (2, h!("B")), (3, h!("C")), (4, h!("D"))],
|
2023-07-19 17:42:52 +08:00
|
|
|
exp: ExpectedResult::Ok {
|
2023-10-12 16:55:32 +08:00
|
|
|
changeset: &[(1, Some(h!("A"))), (4, Some(h!("D")))],
|
2023-07-19 17:42:52 +08:00
|
|
|
init_changeset: &[
|
2023-10-12 16:55:32 +08:00
|
|
|
(0, Some(h!("_"))),
|
|
|
|
(1, Some(h!("A"))),
|
|
|
|
(2, Some(h!("B"))),
|
|
|
|
(3, Some(h!("C"))),
|
|
|
|
(4, Some(h!("D"))),
|
2023-07-19 17:42:52 +08:00
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Update and chain does not connect:
|
|
|
|
// | 0 | 1 | 2 | 3 | 4
|
2023-10-12 16:55:32 +08:00
|
|
|
// chain | _ B C
|
|
|
|
// update | _ A B D
|
2023-07-19 17:42:52 +08:00
|
|
|
// This should fail as we cannot figure out whether C & D are on the same chain
|
|
|
|
TestLocalChain {
|
|
|
|
name: "update and chain does not connect",
|
2023-10-12 16:55:32 +08:00
|
|
|
chain: local_chain![(0, h!("_")), (2, h!("B")), (3, h!("C"))],
|
|
|
|
update: chain_update![(0, h!("_")), (1, h!("A")), (2, h!("B")), (4, h!("D"))],
|
2023-07-19 17:42:52 +08:00
|
|
|
exp: ExpectedResult::Err(CannotConnectError {
|
2023-10-12 16:55:32 +08:00
|
|
|
try_include_height: 3,
|
2023-07-19 17:42:52 +08:00
|
|
|
}),
|
|
|
|
},
|
|
|
|
// Transient invalidation:
|
|
|
|
// | 0 | 1 | 2 | 3 | 4 | 5
|
2023-10-12 16:55:32 +08:00
|
|
|
// chain | _ B C E
|
|
|
|
// update | _ B' C' D
|
2023-07-19 17:42:52 +08:00
|
|
|
// This should succeed and invalidate B,C and E with point of agreement being A.
|
|
|
|
TestLocalChain {
|
|
|
|
name: "transitive invalidation applies to checkpoints higher than invalidation",
|
2023-10-12 16:55:32 +08:00
|
|
|
chain: local_chain![(0, h!("_")), (2, h!("B")), (3, h!("C")), (5, h!("E"))],
|
|
|
|
update: chain_update![(0, h!("_")), (2, h!("B'")), (3, h!("C'")), (4, h!("D"))],
|
2023-07-19 17:42:52 +08:00
|
|
|
exp: ExpectedResult::Ok {
|
|
|
|
changeset: &[
|
|
|
|
(2, Some(h!("B'"))),
|
|
|
|
(3, Some(h!("C'"))),
|
|
|
|
(4, Some(h!("D"))),
|
|
|
|
(5, None),
|
|
|
|
],
|
|
|
|
init_changeset: &[
|
2023-10-12 16:55:32 +08:00
|
|
|
(0, Some(h!("_"))),
|
2023-07-19 17:42:52 +08:00
|
|
|
(2, Some(h!("B'"))),
|
|
|
|
(3, Some(h!("C'"))),
|
|
|
|
(4, Some(h!("D"))),
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Transient invalidation:
|
|
|
|
// | 0 | 1 | 2 | 3 | 4
|
2023-10-12 16:55:32 +08:00
|
|
|
// chain | _ B C E
|
|
|
|
// update | _ B' C' D
|
2023-07-19 17:42:52 +08:00
|
|
|
// This should succeed and invalidate B, C and E with no point of agreement
|
|
|
|
TestLocalChain {
|
|
|
|
name: "transitive invalidation applies to checkpoints higher than invalidation no point of agreement",
|
2023-10-12 16:55:32 +08:00
|
|
|
chain: local_chain![(0, h!("_")), (1, h!("B")), (2, h!("C")), (4, h!("E"))],
|
|
|
|
update: chain_update![(0, h!("_")), (1, h!("B'")), (2, h!("C'")), (3, h!("D"))],
|
2023-07-19 17:42:52 +08:00
|
|
|
exp: ExpectedResult::Ok {
|
|
|
|
changeset: &[
|
|
|
|
(1, Some(h!("B'"))),
|
|
|
|
(2, Some(h!("C'"))),
|
|
|
|
(3, Some(h!("D"))),
|
|
|
|
(4, None)
|
|
|
|
],
|
|
|
|
init_changeset: &[
|
2023-10-12 16:55:32 +08:00
|
|
|
(0, Some(h!("_"))),
|
2023-07-19 17:42:52 +08:00
|
|
|
(1, Some(h!("B'"))),
|
|
|
|
(2, Some(h!("C'"))),
|
|
|
|
(3, Some(h!("D"))),
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Transient invalidation:
|
2023-10-12 16:55:32 +08:00
|
|
|
// | 0 | 1 | 2 | 3 | 4 | 5
|
|
|
|
// chain | _ A B C E
|
|
|
|
// update | _ B' C' D
|
2023-07-19 17:42:52 +08:00
|
|
|
// This should fail since although it tells us that B and C are invalid it doesn't tell us whether
|
|
|
|
// A was invalid.
|
|
|
|
TestLocalChain {
|
|
|
|
name: "invalidation but no connection",
|
2023-10-12 16:55:32 +08:00
|
|
|
chain: local_chain![(0, h!("_")), (1, h!("A")), (2, h!("B")), (3, h!("C")), (5, h!("E"))],
|
|
|
|
update: chain_update![(0, h!("_")), (2, h!("B'")), (3, h!("C'")), (4, h!("D"))],
|
|
|
|
exp: ExpectedResult::Err(CannotConnectError { try_include_height: 1 }),
|
2023-07-19 17:42:52 +08:00
|
|
|
},
|
|
|
|
// Introduce blocks between two points of agreement
|
|
|
|
// | 0 | 1 | 2 | 3 | 4 | 5
|
|
|
|
// chain | A B D E
|
|
|
|
// update | A C E F
|
|
|
|
TestLocalChain {
|
|
|
|
name: "introduce blocks between two points of agreement",
|
|
|
|
chain: local_chain![(0, h!("A")), (1, h!("B")), (3, h!("D")), (4, h!("E"))],
|
|
|
|
update: chain_update![(0, h!("A")), (2, h!("C")), (4, h!("E")), (5, h!("F"))],
|
|
|
|
exp: ExpectedResult::Ok {
|
|
|
|
changeset: &[
|
|
|
|
(2, Some(h!("C"))),
|
|
|
|
(5, Some(h!("F"))),
|
|
|
|
],
|
|
|
|
init_changeset: &[
|
|
|
|
(0, Some(h!("A"))),
|
|
|
|
(1, Some(h!("B"))),
|
|
|
|
(2, Some(h!("C"))),
|
|
|
|
(3, Some(h!("D"))),
|
|
|
|
(4, Some(h!("E"))),
|
|
|
|
(5, Some(h!("F"))),
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
]
|
|
|
|
.into_iter()
|
|
|
|
.for_each(TestLocalChain::run);
|
2023-04-20 15:29:20 +08:00
|
|
|
}
|
2023-05-03 15:01:39 +08:00
|
|
|
|
|
|
|
#[test]
|
2023-07-19 17:42:52 +08:00
|
|
|
fn local_chain_insert_block() {
|
2023-05-03 15:01:39 +08:00
|
|
|
struct TestCase {
|
|
|
|
original: LocalChain,
|
|
|
|
insert: (u32, BlockHash),
|
2023-10-12 16:55:32 +08:00
|
|
|
expected_result: Result<ChangeSet, AlterCheckPointError>,
|
2023-05-03 15:01:39 +08:00
|
|
|
expected_final: LocalChain,
|
|
|
|
}
|
|
|
|
|
|
|
|
let test_cases = [
|
|
|
|
TestCase {
|
2023-10-12 16:55:32 +08:00
|
|
|
original: local_chain![(0, h!("_"))],
|
2023-05-03 15:01:39 +08:00
|
|
|
insert: (5, h!("block5")),
|
|
|
|
expected_result: Ok([(5, Some(h!("block5")))].into()),
|
2023-10-12 16:55:32 +08:00
|
|
|
expected_final: local_chain![(0, h!("_")), (5, h!("block5"))],
|
2023-05-03 15:01:39 +08:00
|
|
|
},
|
|
|
|
TestCase {
|
2023-10-12 16:55:32 +08:00
|
|
|
original: local_chain![(0, h!("_")), (3, h!("A"))],
|
2023-05-03 15:01:39 +08:00
|
|
|
insert: (4, h!("B")),
|
|
|
|
expected_result: Ok([(4, Some(h!("B")))].into()),
|
2023-10-12 16:55:32 +08:00
|
|
|
expected_final: local_chain![(0, h!("_")), (3, h!("A")), (4, h!("B"))],
|
2023-05-03 15:01:39 +08:00
|
|
|
},
|
|
|
|
TestCase {
|
2023-10-12 16:55:32 +08:00
|
|
|
original: local_chain![(0, h!("_")), (4, h!("B"))],
|
2023-05-03 15:01:39 +08:00
|
|
|
insert: (3, h!("A")),
|
|
|
|
expected_result: Ok([(3, Some(h!("A")))].into()),
|
2023-10-12 16:55:32 +08:00
|
|
|
expected_final: local_chain![(0, h!("_")), (3, h!("A")), (4, h!("B"))],
|
2023-05-03 15:01:39 +08:00
|
|
|
},
|
|
|
|
TestCase {
|
2023-10-12 16:55:32 +08:00
|
|
|
original: local_chain![(0, h!("_")), (2, h!("K"))],
|
2023-05-03 15:01:39 +08:00
|
|
|
insert: (2, h!("K")),
|
|
|
|
expected_result: Ok([].into()),
|
2023-10-12 16:55:32 +08:00
|
|
|
expected_final: local_chain![(0, h!("_")), (2, h!("K"))],
|
2023-05-03 15:01:39 +08:00
|
|
|
},
|
|
|
|
TestCase {
|
2023-10-12 16:55:32 +08:00
|
|
|
original: local_chain![(0, h!("_")), (2, h!("K"))],
|
2023-05-03 15:01:39 +08:00
|
|
|
insert: (2, h!("J")),
|
2023-10-12 16:55:32 +08:00
|
|
|
expected_result: Err(AlterCheckPointError {
|
2023-05-03 15:01:39 +08:00
|
|
|
height: 2,
|
|
|
|
original_hash: h!("K"),
|
2023-10-12 16:55:32 +08:00
|
|
|
update_hash: Some(h!("J")),
|
2023-05-03 15:01:39 +08:00
|
|
|
}),
|
2023-10-12 16:55:32 +08:00
|
|
|
expected_final: local_chain![(0, h!("_")), (2, h!("K"))],
|
2023-05-03 15:01:39 +08:00
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
for (i, t) in test_cases.into_iter().enumerate() {
|
|
|
|
let mut chain = t.original;
|
|
|
|
assert_eq!(
|
|
|
|
chain.insert_block(t.insert.into()),
|
|
|
|
t.expected_result,
|
|
|
|
"[{}] unexpected result when inserting block",
|
|
|
|
i,
|
|
|
|
);
|
|
|
|
assert_eq!(chain, t.expected_final, "[{}] unexpected final chain", i,);
|
|
|
|
}
|
|
|
|
}
|