Files
pezkuwi-subxt/bridges/snowbridge/parachain/pallets/ethereum-client/src/tests.rs
T
Clara van Staden bab0348372 Update Snowbridge versions and prep for publishing (#2894)
- updates snowbridge crates to `0.9.0`
- updates Cargo.toml files in preparation for publishing to crates.io
- adds Kusama and Polkadot Snowbridge runtime config crates
- moves runtime tests from the Snowbridge subtree into the bridge hub
tests dir

---------

Co-authored-by: claravanstaden <Cats 4 life!>
Co-authored-by: Ron <yrong1997@gmail.com>
2024-01-10 17:51:27 +01:00

1046 lines
34 KiB
Rust

// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use crate::{
functions::compute_period, pallet::ExecutionHeaders, sync_committee_sum, verify_merkle_branch,
BeaconHeader, CompactBeaconState, Error, ExecutionHeaderBuffer, FinalizedBeaconState,
LatestExecutionState, LatestFinalizedBlockRoot, NextSyncCommittee, SyncCommitteePrepared,
};
use crate::mock::{
get_message_verification_header, get_message_verification_payload,
load_checkpoint_update_fixture, load_execution_header_update_fixture,
load_finalized_header_update_fixture, load_next_finalized_header_update_fixture,
load_next_sync_committee_update_fixture, load_sync_committee_update_fixture,
};
#[cfg(feature = "beacon-spec-minimal")]
pub use crate::config::minimal::*;
#[cfg(feature = "beacon-spec-minimal")]
pub use crate::mock::minimal::*;
#[cfg(not(feature = "beacon-spec-minimal"))]
pub use crate::config::mainnet::*;
#[cfg(not(feature = "beacon-spec-minimal"))]
pub use crate::mock::mainnet::*;
use frame_support::{assert_err, assert_noop, assert_ok};
use hex_literal::hex;
use primitives::{
CompactExecutionHeader, ExecutionHeaderState, Fork, ForkVersions, NextSyncCommitteeUpdate,
};
use rand::{thread_rng, Rng};
use snowbridge_core::{
inbound::{VerificationError, Verifier},
RingBufferMap,
};
use sp_core::H256;
use sp_runtime::DispatchError;
/// Arbitrary hash used for tests and invalid hashes.
const TEST_HASH: [u8; 32] =
hex!["5f6f02af29218292d21a69b64a794a7c0873b3e0f54611972863706e8cbdf371"];
/* UNIT TESTS */
#[test]
pub fn sum_sync_committee_participation() {
new_tester().execute_with(|| {
assert_eq!(sync_committee_sum(&[0, 1, 0, 1, 1, 0, 1, 0, 1]), 5);
});
}
#[test]
pub fn compute_domain() {
new_tester().execute_with(|| {
let domain = EthereumBeaconClient::compute_domain(
hex!("07000000").into(),
hex!("00000001"),
hex!("5dec7ae03261fde20d5b024dfabce8bac3276c9a4908e23d50ba8c9b50b0adff").into(),
);
assert_ok!(&domain);
assert_eq!(
domain.unwrap(),
hex!("0700000046324489ceb6ada6d118eacdbe94f49b1fcb49d5481a685979670c7c").into()
);
});
}
#[test]
pub fn compute_signing_root_bls() {
new_tester().execute_with(|| {
let signing_root = EthereumBeaconClient::compute_signing_root(
&BeaconHeader {
slot: 3529537,
proposer_index: 192549,
parent_root: hex!(
"1f8dc05ea427f78e84e2e2666e13c3befb7106fd1d40ef8a3f67cf615f3f2a4c"
)
.into(),
state_root: hex!(
"0dfb492a83da711996d2d76b64604f9bca9dc08b6c13cf63b3be91742afe724b"
)
.into(),
body_root: hex!("66fba38f7c8c2526f7ddfe09c1a54dd12ff93bdd4d0df6a0950e88e802228bfa")
.into(),
},
hex!("07000000afcaaba0efab1ca832a15152469bb09bb84641c405171dfa2d3fb45f").into(),
);
assert_ok!(&signing_root);
assert_eq!(
signing_root.unwrap(),
hex!("3ff6e9807da70b2f65cdd58ea1b25ed441a1d589025d2c4091182026d7af08fb").into()
);
});
}
#[test]
pub fn compute_signing_root() {
new_tester().execute_with(|| {
let signing_root = EthereumBeaconClient::compute_signing_root(
&BeaconHeader {
slot: 222472,
proposer_index: 10726,
parent_root: hex!(
"5d481a9721f0ecce9610eab51d400d223683d599b7fcebca7e4c4d10cdef6ebb"
)
.into(),
state_root: hex!(
"14eb4575895f996a84528b789ff2e4d5148242e2983f03068353b2c37015507a"
)
.into(),
body_root: hex!("7bb669c75b12e0781d6fa85d7fc2f32d64eafba89f39678815b084c156e46cac")
.into(),
},
hex!("07000000e7acb21061790987fa1c1e745cccfb358370b33e8af2b2c18938e6c2").into(),
);
assert_ok!(&signing_root);
assert_eq!(
signing_root.unwrap(),
hex!("da12b6a6d3516bc891e8a49f82fc1925cec40b9327e06457f695035303f55cd8").into()
);
});
}
#[test]
pub fn compute_domain_bls() {
new_tester().execute_with(|| {
let domain = EthereumBeaconClient::compute_domain(
hex!("07000000").into(),
hex!("01000000"),
hex!("4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95").into(),
);
assert_ok!(&domain);
assert_eq!(
domain.unwrap(),
hex!("07000000afcaaba0efab1ca832a15152469bb09bb84641c405171dfa2d3fb45f").into()
);
});
}
#[test]
pub fn verify_merkle_branch_for_finalized_root() {
new_tester().execute_with(|| {
assert!(verify_merkle_branch(
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
&[
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
hex!("5f6f02af29218292d21a69b64a794a7c0873b3e0f54611972863706e8cbdf371").into(),
hex!("e7125ff9ab5a840c44bedb4731f440a405b44e15f2d1a89e27341b432fabe13d").into(),
hex!("002c1fe5bc0bd62db6f299a582f2a80a6d5748ccc82e7ed843eaf0ae0739f74a").into(),
hex!("d2dc4ba9fd4edff6716984136831e70a6b2e74fca27b8097a820cbbaa5a6e3c3").into(),
hex!("91f77a19d8afa4a08e81164bb2e570ecd10477b3b65c305566a6d2be88510584").into(),
],
crate::config::FINALIZED_ROOT_INDEX,
crate::config::FINALIZED_ROOT_DEPTH,
hex!("e46559327592741956f6beaa0f52e49625eb85dce037a0bd2eff333c743b287f").into()
));
});
}
#[test]
pub fn verify_merkle_branch_fails_if_depth_and_branch_dont_match() {
new_tester().execute_with(|| {
assert!(!verify_merkle_branch(
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
&[
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
hex!("5f6f02af29218292d21a69b64a794a7c0873b3e0f54611972863706e8cbdf371").into(),
hex!("e7125ff9ab5a840c44bedb4731f440a405b44e15f2d1a89e27341b432fabe13d").into(),
],
crate::config::FINALIZED_ROOT_INDEX,
crate::config::FINALIZED_ROOT_DEPTH,
hex!("e46559327592741956f6beaa0f52e49625eb85dce037a0bd2eff333c743b287f").into()
));
});
}
#[test]
pub fn sync_committee_participation_is_supermajority() {
let bits =
hex!("bffffffff7f1ffdfcfeffeffbfdffffbfffffdffffefefffdffff7f7ffff77fffdf7bff77ffdf7fffafffffff77fefffeff7effffffff5f7fedfffdfb6ddff7b"
);
let participation = primitives::decompress_sync_committee_bits::<512, 64>(bits);
assert_ok!(EthereumBeaconClient::sync_committee_participation_is_supermajority(&participation));
}
#[test]
pub fn sync_committee_participation_is_supermajority_errors_when_not_supermajority() {
new_tester().execute_with(|| {
let participation: [u8; 512] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1,
1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1,
0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
];
assert_err!(
EthereumBeaconClient::sync_committee_participation_is_supermajority(&participation),
Error::<Test>::SyncCommitteeParticipantsNotSupermajority
);
});
}
#[test]
pub fn execution_header_pruning() {
new_tester().execute_with(|| {
let execution_header_prune_threshold = ExecutionHeadersPruneThreshold::get();
let to_be_deleted = execution_header_prune_threshold / 2;
let mut stored_hashes = vec![];
for i in 0..execution_header_prune_threshold {
let mut hash = H256::default();
thread_rng().try_fill(&mut hash.0[..]).unwrap();
EthereumBeaconClient::store_execution_header(
hash,
CompactExecutionHeader::default(),
i as u64,
hash,
);
stored_hashes.push(hash);
}
// We should have stored everything until now
assert_eq!({ ExecutionHeaders::<Test>::iter().count() }, stored_hashes.len());
// Let's push extra entries so that some of the previous entries are deleted.
for i in 0..to_be_deleted {
let mut hash = H256::default();
thread_rng().try_fill(&mut hash.0[..]).unwrap();
EthereumBeaconClient::store_execution_header(
hash,
CompactExecutionHeader::default(),
(i + execution_header_prune_threshold) as u64,
hash,
);
stored_hashes.push(hash);
}
// We should have only stored upto `execution_header_prune_threshold`
assert_eq!(
ExecutionHeaders::<Test>::iter().count() as u32,
execution_header_prune_threshold
);
// First `to_be_deleted` items must be deleted
for i in 0..to_be_deleted {
assert!(!ExecutionHeaders::<Test>::contains_key(stored_hashes[i as usize]));
}
// Other entries should be part of data
for i in to_be_deleted..(to_be_deleted + execution_header_prune_threshold) {
assert!(ExecutionHeaders::<Test>::contains_key(stored_hashes[i as usize]));
}
});
}
#[test]
fn compute_fork_version() {
let mock_fork_versions = ForkVersions {
genesis: Fork { version: [0, 0, 0, 0], epoch: 0 },
altair: Fork { version: [0, 0, 0, 1], epoch: 10 },
bellatrix: Fork { version: [0, 0, 0, 2], epoch: 20 },
capella: Fork { version: [0, 0, 0, 3], epoch: 30 },
};
new_tester().execute_with(|| {
assert_eq!(EthereumBeaconClient::select_fork_version(&mock_fork_versions, 0), [0, 0, 0, 0]);
assert_eq!(EthereumBeaconClient::select_fork_version(&mock_fork_versions, 1), [0, 0, 0, 0]);
assert_eq!(
EthereumBeaconClient::select_fork_version(&mock_fork_versions, 10),
[0, 0, 0, 1]
);
assert_eq!(
EthereumBeaconClient::select_fork_version(&mock_fork_versions, 21),
[0, 0, 0, 2]
);
assert_eq!(
EthereumBeaconClient::select_fork_version(&mock_fork_versions, 20),
[0, 0, 0, 2]
);
assert_eq!(
EthereumBeaconClient::select_fork_version(&mock_fork_versions, 32),
[0, 0, 0, 3]
);
});
}
#[test]
fn find_absent_keys() {
let participation: [u8; 32] = [
0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1,
];
let update = load_sync_committee_update_fixture();
let sync_committee_prepared: SyncCommitteePrepared =
(&update.next_sync_committee_update.unwrap().next_sync_committee)
.try_into()
.unwrap();
new_tester().execute_with(|| {
let pubkeys = EthereumBeaconClient::find_pubkeys(
&participation,
(*sync_committee_prepared.pubkeys).as_ref(),
false,
);
assert_eq!(pubkeys.len(), 2);
assert_eq!(pubkeys[0], sync_committee_prepared.pubkeys[0]);
assert_eq!(pubkeys[1], sync_committee_prepared.pubkeys[7]);
});
}
#[test]
fn find_present_keys() {
let participation: [u8; 32] = [
0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0,
];
let update = load_sync_committee_update_fixture();
let sync_committee_prepared: SyncCommitteePrepared =
(&update.next_sync_committee_update.unwrap().next_sync_committee)
.try_into()
.unwrap();
new_tester().execute_with(|| {
let pubkeys = EthereumBeaconClient::find_pubkeys(
&participation,
(*sync_committee_prepared.pubkeys).as_ref(),
true,
);
assert_eq!(pubkeys.len(), 4);
assert_eq!(pubkeys[0], sync_committee_prepared.pubkeys[1]);
assert_eq!(pubkeys[1], sync_committee_prepared.pubkeys[8]);
assert_eq!(pubkeys[2], sync_committee_prepared.pubkeys[26]);
assert_eq!(pubkeys[3], sync_committee_prepared.pubkeys[30]);
});
}
#[test]
fn cross_check_execution_state() {
new_tester().execute_with(|| {
let header_root: H256 = TEST_HASH.into();
<FinalizedBeaconState<Test>>::insert(
header_root,
CompactBeaconState {
// set slot to period 5
slot: ((EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) * 5) as u64,
block_roots_root: Default::default(),
},
);
LatestFinalizedBlockRoot::<Test>::set(header_root);
<LatestExecutionState<Test>>::set(ExecutionHeaderState {
beacon_block_root: Default::default(),
// set slot to period 2
beacon_slot: ((EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) * 2) as u64,
block_hash: Default::default(),
block_number: 0,
});
assert_err!(
EthereumBeaconClient::cross_check_execution_state(),
Error::<Test>::ExecutionHeaderTooFarBehind
);
});
}
/* SYNC PROCESS TESTS */
#[test]
fn process_initial_checkpoint() {
let checkpoint = load_checkpoint_update_fixture();
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::force_checkpoint(
RuntimeOrigin::root(),
Box::new(checkpoint.clone())
));
let block_root: H256 = checkpoint.header.hash_tree_root().unwrap();
assert!(<FinalizedBeaconState<Test>>::contains_key(block_root));
});
}
#[test]
fn process_initial_checkpoint_with_invalid_sync_committee_proof() {
let mut checkpoint = load_checkpoint_update_fixture();
checkpoint.current_sync_committee_branch[0] = TEST_HASH.into();
new_tester().execute_with(|| {
assert_err!(
EthereumBeaconClient::force_checkpoint(RuntimeOrigin::root(), Box::new(checkpoint)),
Error::<Test>::InvalidSyncCommitteeMerkleProof
);
});
}
#[test]
fn process_initial_checkpoint_with_invalid_blocks_root_proof() {
let mut checkpoint = load_checkpoint_update_fixture();
checkpoint.block_roots_branch[0] = TEST_HASH.into();
new_tester().execute_with(|| {
assert_err!(
EthereumBeaconClient::force_checkpoint(RuntimeOrigin::root(), Box::new(checkpoint)),
Error::<Test>::InvalidBlockRootsRootMerkleProof
);
});
}
#[test]
fn submit_update_in_current_period() {
let checkpoint = load_checkpoint_update_fixture();
let update = load_finalized_header_update_fixture();
let initial_period = compute_period(checkpoint.header.slot);
let update_period = compute_period(update.finalized_header.slot);
assert_eq!(initial_period, update_period);
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(update.clone())
));
let block_root: H256 = update.finalized_header.hash_tree_root().unwrap();
assert!(<FinalizedBeaconState<Test>>::contains_key(block_root));
});
}
#[test]
fn submit_update_with_sync_committee_in_current_period() {
let checkpoint = load_checkpoint_update_fixture();
let update = load_sync_committee_update_fixture();
let init_period = compute_period(checkpoint.header.slot);
let update_period = compute_period(update.finalized_header.slot);
assert_eq!(init_period, update_period);
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert!(!<NextSyncCommittee<Test>>::exists());
assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)));
assert!(<NextSyncCommittee<Test>>::exists());
});
}
#[test]
fn submit_update_in_next_period() {
let checkpoint = load_checkpoint_update_fixture();
let sync_committee_update = load_sync_committee_update_fixture();
let update = load_next_finalized_header_update_fixture();
let sync_committee_period = compute_period(sync_committee_update.finalized_header.slot);
let next_sync_committee_period = compute_period(update.finalized_header.slot);
assert_eq!(sync_committee_period + 1, next_sync_committee_period);
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(sync_committee_update.clone())
));
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(update.clone())
));
let block_root: H256 = update.finalized_header.clone().hash_tree_root().unwrap();
assert!(<FinalizedBeaconState<Test>>::contains_key(block_root));
});
}
#[test]
fn submit_update_with_invalid_header_proof() {
let checkpoint = load_checkpoint_update_fixture();
let mut update = load_sync_committee_update_fixture();
let init_period = compute_period(checkpoint.header.slot);
let update_period = compute_period(update.finalized_header.slot);
assert_eq!(init_period, update_period);
update.finality_branch[0] = TEST_HASH.into();
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert!(!<NextSyncCommittee<Test>>::exists());
assert_err!(
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)),
Error::<Test>::InvalidHeaderMerkleProof
);
});
}
#[test]
fn submit_update_with_invalid_block_roots_proof() {
let checkpoint = load_checkpoint_update_fixture();
let mut update = load_sync_committee_update_fixture();
let init_period = compute_period(checkpoint.header.slot);
let update_period = compute_period(update.finalized_header.slot);
assert_eq!(init_period, update_period);
update.block_roots_branch[0] = TEST_HASH.into();
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert!(!<NextSyncCommittee<Test>>::exists());
assert_err!(
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)),
Error::<Test>::InvalidBlockRootsRootMerkleProof
);
});
}
#[test]
fn submit_update_with_invalid_next_sync_committee_proof() {
let checkpoint = load_checkpoint_update_fixture();
let mut update = load_sync_committee_update_fixture();
let init_period = compute_period(checkpoint.header.slot);
let update_period = compute_period(update.finalized_header.slot);
assert_eq!(init_period, update_period);
if let Some(ref mut next_sync_committee_update) = update.next_sync_committee_update {
next_sync_committee_update.next_sync_committee_branch[0] = TEST_HASH.into();
}
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert!(!<NextSyncCommittee<Test>>::exists());
assert_err!(
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)),
Error::<Test>::InvalidSyncCommitteeMerkleProof
);
});
}
#[test]
fn submit_update_with_skipped_period() {
let checkpoint = load_checkpoint_update_fixture();
let sync_committee_update = load_sync_committee_update_fixture();
let mut update = load_next_finalized_header_update_fixture();
update.signature_slot += (EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) as u64;
update.attested_header.slot = update.signature_slot - 1;
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(sync_committee_update.clone())
));
assert_err!(
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)),
Error::<Test>::SkippedSyncCommitteePeriod
);
});
}
#[test]
fn submit_update_with_sync_committee_in_next_period() {
let checkpoint = load_checkpoint_update_fixture();
let update = load_sync_committee_update_fixture();
let next_update = load_next_sync_committee_update_fixture();
let update_period = compute_period(update.finalized_header.slot);
let next_update_period = compute_period(next_update.finalized_header.slot);
assert_eq!(update_period + 1, next_update_period);
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert!(!<NextSyncCommittee<Test>>::exists());
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(update.clone())
));
assert!(<NextSyncCommittee<Test>>::exists());
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(next_update.clone())
));
let last_finalized_state =
FinalizedBeaconState::<Test>::get(LatestFinalizedBlockRoot::<Test>::get()).unwrap();
let last_synced_period = compute_period(last_finalized_state.slot);
assert_eq!(last_synced_period, next_update_period);
});
}
#[test]
fn submit_update_with_sync_committee_invalid_signature_slot() {
let checkpoint = load_checkpoint_update_fixture();
let mut update = load_sync_committee_update_fixture();
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
// makes a invalid update with signature_slot should be more than attested_slot
update.signature_slot = update.attested_header.slot;
assert_err!(
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)),
Error::<Test>::InvalidUpdateSlot
);
});
}
#[test]
fn submit_update_with_skipped_sync_committee_period() {
let checkpoint = load_checkpoint_update_fixture();
let finalized_update = load_next_finalized_header_update_fixture();
let checkpoint_period = compute_period(checkpoint.header.slot);
let next_sync_committee_period = compute_period(finalized_update.finalized_header.slot);
assert_eq!(checkpoint_period + 1, next_sync_committee_period);
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_err!(
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(finalized_update)),
Error::<Test>::SkippedSyncCommitteePeriod
);
});
}
#[test]
fn submit_update_execution_headers_too_far_behind() {
let checkpoint = load_checkpoint_update_fixture();
let finalized_header_update = load_finalized_header_update_fixture();
let execution_header_update = load_execution_header_update_fixture();
let next_update = load_next_sync_committee_update_fixture();
new_tester().execute_with(|| {
let far_ahead_finalized_header_slot = finalized_header_update.finalized_header.slot +
(EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH * 2) as u64;
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(finalized_header_update)
));
assert_ok!(EthereumBeaconClient::submit_execution_header(
RuntimeOrigin::signed(1),
Box::new(execution_header_update)
));
let header_root: H256 = TEST_HASH.into();
<FinalizedBeaconState<Test>>::insert(
header_root,
CompactBeaconState {
slot: far_ahead_finalized_header_slot,
block_roots_root: Default::default(),
},
);
LatestFinalizedBlockRoot::<Test>::set(header_root);
assert_err!(
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(next_update)),
Error::<Test>::ExecutionHeaderTooFarBehind
);
});
}
#[test]
fn submit_irrelevant_update() {
let checkpoint = load_checkpoint_update_fixture();
let mut update = load_next_finalized_header_update_fixture();
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
// makes an invalid update where the attested_header slot value should be greater than the
// checkpoint slot value
update.finalized_header.slot = checkpoint.header.slot;
update.attested_header.slot = checkpoint.header.slot;
update.signature_slot = checkpoint.header.slot + 1;
assert_err!(
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)),
Error::<Test>::IrrelevantUpdate
);
});
}
#[test]
fn submit_update_with_missing_bootstrap() {
let update = load_next_finalized_header_update_fixture();
new_tester().execute_with(|| {
assert_err!(
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)),
Error::<Test>::NotBootstrapped
);
});
}
#[test]
fn submit_update_with_invalid_sync_committee_update() {
let checkpoint = load_checkpoint_update_fixture();
let update = load_sync_committee_update_fixture();
let mut next_update = load_next_sync_committee_update_fixture();
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)));
// makes update with invalid next_sync_committee
<FinalizedBeaconState<Test>>::mutate(<LatestFinalizedBlockRoot<Test>>::get(), |x| {
let prev = x.unwrap();
*x = Some(CompactBeaconState { slot: next_update.attested_header.slot, ..prev });
});
next_update.attested_header.slot += 1;
next_update.signature_slot = next_update.attested_header.slot + 1;
let next_sync_committee = NextSyncCommitteeUpdate::default();
next_update.next_sync_committee_update = Some(next_sync_committee);
assert_err!(
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(next_update)),
Error::<Test>::InvalidSyncCommitteeUpdate
);
});
}
#[test]
fn submit_execution_header_update() {
let checkpoint = load_checkpoint_update_fixture();
let finalized_header_update = load_finalized_header_update_fixture();
let execution_header_update = load_execution_header_update_fixture();
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(finalized_header_update)
));
assert_ok!(EthereumBeaconClient::submit_execution_header(
RuntimeOrigin::signed(1),
Box::new(execution_header_update.clone())
));
assert!(<ExecutionHeaders<Test>>::contains_key(
execution_header_update.execution_header.block_hash
));
});
}
#[test]
fn submit_execution_header_update_invalid_ancestry_proof() {
let checkpoint = load_checkpoint_update_fixture();
let finalized_header_update = load_finalized_header_update_fixture();
let mut execution_header_update = load_execution_header_update_fixture();
if let Some(ref mut ancestry_proof) = execution_header_update.ancestry_proof {
ancestry_proof.header_branch[0] = TEST_HASH.into()
}
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(finalized_header_update)
));
assert_err!(
EthereumBeaconClient::submit_execution_header(
RuntimeOrigin::signed(1),
Box::new(execution_header_update)
),
Error::<Test>::InvalidAncestryMerkleProof
);
});
}
#[test]
fn submit_execution_header_update_invalid_execution_header_proof() {
let checkpoint = load_checkpoint_update_fixture();
let finalized_header_update = load_finalized_header_update_fixture();
let mut execution_header_update = load_execution_header_update_fixture();
execution_header_update.execution_branch[0] = TEST_HASH.into();
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(finalized_header_update)
));
assert_err!(
EthereumBeaconClient::submit_execution_header(
RuntimeOrigin::signed(1),
Box::new(execution_header_update)
),
Error::<Test>::InvalidExecutionHeaderProof
);
});
}
#[test]
fn submit_execution_header_update_that_skips_block() {
let checkpoint = load_checkpoint_update_fixture();
let finalized_header_update = load_finalized_header_update_fixture();
let execution_header_update = load_execution_header_update_fixture();
let mut skipped_block_execution_header_update = load_execution_header_update_fixture();
skipped_block_execution_header_update.execution_header.block_number =
execution_header_update.execution_header.block_number + 2;
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(finalized_header_update)
));
assert_ok!(EthereumBeaconClient::submit_execution_header(
RuntimeOrigin::signed(1),
Box::new(execution_header_update.clone())
));
assert!(<ExecutionHeaders<Test>>::contains_key(
execution_header_update.execution_header.block_hash
));
assert_err!(
EthereumBeaconClient::submit_execution_header(
RuntimeOrigin::signed(1),
Box::new(skipped_block_execution_header_update)
),
Error::<Test>::ExecutionHeaderSkippedBlock
);
});
}
#[test]
fn submit_execution_header_update_that_is_also_finalized_header_which_is_not_stored() {
let checkpoint = load_checkpoint_update_fixture();
let finalized_header_update = load_finalized_header_update_fixture();
let mut execution_header_update = load_execution_header_update_fixture();
execution_header_update.ancestry_proof = None;
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(finalized_header_update)
));
assert_err!(
EthereumBeaconClient::submit_execution_header(
RuntimeOrigin::signed(1),
Box::new(execution_header_update)
),
Error::<Test>::ExpectedFinalizedHeaderNotStored
);
});
}
#[test]
fn submit_execution_header_update_that_is_also_finalized_header_which_is_stored_but_slots_dont_match(
) {
let checkpoint = load_checkpoint_update_fixture();
let finalized_header_update = load_finalized_header_update_fixture();
let mut execution_header_update = load_execution_header_update_fixture();
execution_header_update.ancestry_proof = None;
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(finalized_header_update)
));
let block_root: H256 = execution_header_update.header.hash_tree_root().unwrap();
<FinalizedBeaconState<Test>>::insert(
block_root,
CompactBeaconState {
slot: execution_header_update.header.slot + 1,
block_roots_root: Default::default(),
},
);
LatestFinalizedBlockRoot::<Test>::set(block_root);
assert_err!(
EthereumBeaconClient::submit_execution_header(
RuntimeOrigin::signed(1),
Box::new(execution_header_update)
),
Error::<Test>::ExpectedFinalizedHeaderNotStored
);
});
}
#[test]
fn submit_execution_header_not_finalized() {
let checkpoint = load_checkpoint_update_fixture();
let finalized_header_update = load_finalized_header_update_fixture();
let update = load_execution_header_update_fixture();
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(
RuntimeOrigin::signed(1),
Box::new(finalized_header_update)
));
<FinalizedBeaconState<Test>>::mutate(<LatestFinalizedBlockRoot<Test>>::get(), |x| {
let prev = x.unwrap();
*x = Some(CompactBeaconState { slot: update.header.slot - 1, ..prev });
});
assert_err!(
EthereumBeaconClient::submit_execution_header(
RuntimeOrigin::signed(1),
Box::new(update)
),
Error::<Test>::HeaderNotFinalized
);
});
}
/* IMPLS */
#[test]
fn verify_message() {
let header = get_message_verification_header();
let (event_log, proof) = get_message_verification_payload();
let block_hash = proof.block_hash;
new_tester().execute_with(|| {
<ExecutionHeaderBuffer<Test>>::insert(block_hash, header);
assert_ok!(EthereumBeaconClient::verify(&event_log, &proof));
});
}
#[test]
fn verify_message_missing_header() {
let (event_log, proof) = get_message_verification_payload();
new_tester().execute_with(|| {
assert_err!(
EthereumBeaconClient::verify(&event_log, &proof),
VerificationError::HeaderNotFound
);
});
}
#[test]
fn verify_message_invalid_proof() {
let header = get_message_verification_header();
let (event_log, mut proof) = get_message_verification_payload();
proof.data.1[0] = TEST_HASH.into();
let block_hash = proof.block_hash;
new_tester().execute_with(|| {
<ExecutionHeaderBuffer<Test>>::insert(block_hash, header);
assert_err!(
EthereumBeaconClient::verify(&event_log, &proof),
VerificationError::InvalidProof
);
});
}
#[test]
fn verify_message_invalid_receipts_root() {
let mut header = get_message_verification_header();
let (event_log, proof) = get_message_verification_payload();
let block_hash = proof.block_hash;
header.receipts_root = TEST_HASH.into();
new_tester().execute_with(|| {
<ExecutionHeaderBuffer<Test>>::insert(block_hash, header);
assert_err!(
EthereumBeaconClient::verify(&event_log, &proof),
VerificationError::InvalidProof
);
});
}
#[test]
fn verify_message_invalid_log() {
let header = get_message_verification_header();
let (mut event_log, proof) = get_message_verification_payload();
let block_hash = proof.block_hash;
event_log.topics = vec![H256::zero(); 10];
new_tester().execute_with(|| {
<ExecutionHeaderBuffer<Test>>::insert(block_hash, header);
assert_err!(
EthereumBeaconClient::verify(&event_log, &proof),
VerificationError::InvalidLog
);
});
}
#[test]
fn verify_message_receipt_does_not_contain_log() {
let header = get_message_verification_header();
let (mut event_log, proof) = get_message_verification_payload();
let block_hash = proof.block_hash;
event_log.data = hex!("f9013c94ee9170abfbf9421ad6dd07f6bdec9d89f2b581e0f863a01b11dcf133cc240f682dab2d3a8e4cd35c5da8c9cf99adac4336f8512584c5ada000000000000000000000000000000000000000000000000000000000000003e8a00000000000000000000000000000000000000000000000000000000000000002b8c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000068000f000000000000000101d184c103f7acc340847eee82a0b909e3358bc28d440edffa1352b13227e8ee646f3ea37456dec70100000101001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c0000e8890423c78a0000000000000000000000000000000000000000000000000000000000000000").to_vec();
new_tester().execute_with(|| {
<ExecutionHeaderBuffer<Test>>::insert(block_hash, header);
assert_err!(
EthereumBeaconClient::verify(&event_log, &proof),
VerificationError::LogNotFound
);
});
}
#[test]
fn set_operating_mode() {
let checkpoint = load_checkpoint_update_fixture();
let update = load_finalized_header_update_fixture();
let execution_header_update = load_execution_header_update_fixture();
new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::set_operating_mode(
RuntimeOrigin::root(),
snowbridge_core::BasicOperatingMode::Halted
));
assert_noop!(
EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)),
Error::<Test>::Halted
);
assert_noop!(
EthereumBeaconClient::submit_execution_header(
RuntimeOrigin::signed(1),
Box::new(execution_header_update)
),
Error::<Test>::Halted
);
});
}
#[test]
fn set_operating_mode_root_only() {
new_tester().execute_with(|| {
assert_noop!(
EthereumBeaconClient::set_operating_mode(
RuntimeOrigin::signed(1),
snowbridge_core::BasicOperatingMode::Halted
),
DispatchError::BadOrigin
);
});
}