Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync execution update on demand #123

Merged
merged 5 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1165,7 +1165,7 @@ pub fn make_finalized_header_update() -> Box<Update> {
hex!("ecea7e1d3152d8130e83afdfe34b4de4ba2b69a33c9471991096daf454de9cf5").into(),
hex!("b2bf1758e50b2bfff29169fbc70fdb884b2b05bb615dbc53567574da6f4f1ae2").into(),
hex!("cd87069daf70975779126d6af833b7d636c75ca4d5e750ebcad0e76408a5e5bf").into(),
]
],
})
}

Expand Down
57 changes: 3 additions & 54 deletions bridges/snowbridge/pallets/ethereum-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ use frame_support::{
use frame_system::ensure_signed;
use primitives::{
fast_aggregate_verify, verify_merkle_branch, verify_receipt_proof, BeaconHeader, BlsError,
CompactBeaconState, CompactExecutionHeader, ExecutionHeaderState, ForkData, ForkVersion,
ForkVersions, PublicKeyPrepared, SigningData,
CompactBeaconState, CompactExecutionHeader, ForkData, ForkVersion, ForkVersions,
PublicKeyPrepared, SigningData,
};
use snowbridge_core::{BasicOperatingMode, RingBufferMap};
use sp_core::H256;
Expand Down Expand Up @@ -187,12 +187,6 @@ pub mod pallet {
pub(super) type NextSyncCommittee<T: Config> =
StorageValue<_, SyncCommitteePrepared, ValueQuery>;

/// Latest imported execution header
#[pallet::storage]
#[pallet::getter(fn latest_execution_state)]
pub(super) type LatestExecutionState<T: Config> =
StorageValue<_, ExecutionHeaderState, ValueQuery>;

/// Execution Headers
#[pallet::storage]
pub type ExecutionHeaders<T: Config> =
Expand Down Expand Up @@ -321,7 +315,6 @@ pub mod pallet {
<CurrentSyncCommittee<T>>::set(sync_committee_prepared);
<NextSyncCommittee<T>>::kill();
InitialCheckpointRoot::<T>::set(header_root);
<LatestExecutionState<T>>::kill();

Self::store_validators_root(update.validators_root);
Self::store_finalized_header(header_root, update.header, update.block_roots_root)?;
Expand All @@ -330,32 +323,11 @@ pub mod pallet {
}

pub(crate) fn process_update(update: &Update) -> DispatchResult {
Self::cross_check_execution_state()?;
Self::verify_update(update)?;
Self::apply_update(update)?;
Ok(())
}

/// Cross check to make sure that execution header import does not fall too far behind
/// finalised beacon header import. If that happens just return an error and pause
/// processing until execution header processing has caught up.
pub(crate) fn cross_check_execution_state() -> DispatchResult {
let latest_finalized_state =
FinalizedBeaconState::<T>::get(LatestFinalizedBlockRoot::<T>::get())
.ok_or(Error::<T>::NotBootstrapped)?;
let latest_execution_state = Self::latest_execution_state();
// The execution header import should be at least within the slot range of a sync
// committee period.
let max_latency = config::EPOCHS_PER_SYNC_COMMITTEE_PERIOD * config::SLOTS_PER_EPOCH;
ensure!(
latest_execution_state.beacon_slot == 0 ||
latest_finalized_state.slot <
latest_execution_state.beacon_slot + max_latency as u64,
Error::<T>::ExecutionHeaderTooFarBehind
);
Ok(())
}

/// References and strictly follows <https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#validate_light_client_update>
/// Verifies that provided next sync committee is valid through a series of checks
/// (including checking that a sync committee period isn't skipped and that the header is
Expand Down Expand Up @@ -548,15 +520,6 @@ pub mod pallet {
Error::<T>::HeaderNotFinalized
);

// Checks that we don't skip execution headers, they need to be imported sequentially.
let latest_execution_state: ExecutionHeaderState = Self::latest_execution_state();
ensure!(
latest_execution_state.block_number == 0 ||
update.execution_header.block_number() ==
latest_execution_state.block_number + 1,
Error::<T>::ExecutionHeaderSkippedBlock
);

// Gets the hash tree root of the execution header, in preparation for the execution
// header proof (used to check that the execution header is rooted in the beacon
// header body.
Expand Down Expand Up @@ -605,8 +568,6 @@ pub mod pallet {
Self::store_execution_header(
update.execution_header.block_hash(),
update.execution_header.clone().into(),
update.header.slot,
block_root,
);

Ok(())
Expand Down Expand Up @@ -692,12 +653,7 @@ pub mod pallet {
/// Stores the provided execution header in pallet storage. The header is stored
/// in a ring buffer map, with the block hash as map key. The last imported execution
/// header is also kept in storage, for the relayer to check import progress.
pub fn store_execution_header(
block_hash: H256,
header: CompactExecutionHeader,
beacon_slot: u64,
beacon_block_root: H256,
) {
pub fn store_execution_header(block_hash: H256, header: CompactExecutionHeader) {
let block_number = header.block_number;

<ExecutionHeaderBuffer<T>>::insert(block_hash, header);
Expand All @@ -709,13 +665,6 @@ pub mod pallet {
block_number
);

LatestExecutionState::<T>::mutate(|s| {
s.beacon_block_root = beacon_block_root;
s.beacon_slot = beacon_slot;
s.block_hash = block_hash;
s.block_number = block_number;
});

Self::deposit_event(Event::ExecutionHeaderImported { block_hash, block_number });
}

Expand Down
132 changes: 6 additions & 126 deletions bridges/snowbridge/pallets/ethereum-client/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::{
functions::compute_period, pallet::ExecutionHeaders, sync_committee_sum, verify_merkle_branch,
BeaconHeader, CompactBeaconState, Error, ExecutionHeaderBuffer, FinalizedBeaconState,
LatestExecutionState, LatestFinalizedBlockRoot, NextSyncCommittee, SyncCommitteePrepared,
LatestFinalizedBlockRoot, NextSyncCommittee, SyncCommitteePrepared,
};

use crate::mock::{
Expand All @@ -18,10 +18,7 @@ pub use crate::mock::*;
use crate::config::{EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH};
use frame_support::{assert_err, assert_noop, assert_ok};
use hex_literal::hex;
use primitives::{
CompactExecutionHeader, ExecutionHeaderState, Fork, ForkVersions, NextSyncCommitteeUpdate,
VersionedExecutionPayloadHeader,
};
use primitives::{CompactExecutionHeader, Fork, ForkVersions, NextSyncCommitteeUpdate};
use rand::{thread_rng, Rng};
use snowbridge_core::{
inbound::{VerificationError, Verifier},
Expand Down Expand Up @@ -220,31 +217,21 @@ pub fn execution_header_pruning() {

let mut stored_hashes = vec![];

for i in 0..execution_header_prune_threshold {
for _ 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,
);
EthereumBeaconClient::store_execution_header(hash, CompactExecutionHeader::default());
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 {
for _ 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,
);
EthereumBeaconClient::store_execution_header(hash, CompactExecutionHeader::default());

stored_hashes.push(hash);
}
Expand Down Expand Up @@ -348,34 +335,6 @@ fn find_present_keys() {
});
}

#[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]
Expand Down Expand Up @@ -608,40 +567,6 @@ fn submit_update_with_skipped_sync_committee_period() {
});
}

#[test]
fn submit_update_execution_headers_too_far_behind() {
let checkpoint = Box::new(load_checkpoint_update_fixture());
let finalized_header_update = Box::new(load_finalized_header_update_fixture());
let execution_header_update = Box::new(load_execution_header_update_fixture());
let next_update = Box::new(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), finalized_header_update));
assert_ok!(EthereumBeaconClient::submit_execution_header(
RuntimeOrigin::signed(1),
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), next_update),
Error::<Test>::ExecutionHeaderTooFarBehind
);
});
}

#[test]
fn submit_irrelevant_update() {
let checkpoint = Box::new(load_checkpoint_update_fixture());
Expand Down Expand Up @@ -764,51 +689,6 @@ fn submit_execution_header_update_invalid_execution_header_proof() {
});
}

#[test]
fn submit_execution_header_update_that_skips_block() {
let checkpoint = Box::new(load_checkpoint_update_fixture());
let finalized_header_update = Box::new(load_finalized_header_update_fixture());
let execution_header_update = Box::new(load_execution_header_update_fixture());
let mut skipped_block_execution_header_update =
Box::new(load_execution_header_update_fixture());
let mut skipped_execution_header =
skipped_block_execution_header_update.execution_header.clone();

skipped_execution_header = match skipped_execution_header {
VersionedExecutionPayloadHeader::Capella(execution_payload_header) => {
let mut mut_execution_payload_header = execution_payload_header.clone();
mut_execution_payload_header.block_number = execution_payload_header.block_number + 2;
VersionedExecutionPayloadHeader::Capella(mut_execution_payload_header)
},
VersionedExecutionPayloadHeader::Deneb(execution_payload_header) => {
let mut mut_execution_payload_header = execution_payload_header.clone();
mut_execution_payload_header.block_number = execution_payload_header.block_number + 2;
VersionedExecutionPayloadHeader::Deneb(mut_execution_payload_header)
},
};

skipped_block_execution_header_update.execution_header = skipped_execution_header;

new_tester().execute_with(|| {
assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint));
assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_header_update));
assert_ok!(EthereumBeaconClient::submit_execution_header(
RuntimeOrigin::signed(1),
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),
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 = Box::new(load_checkpoint_update_fixture());
Expand Down
15 changes: 1 addition & 14 deletions bridges/snowbridge/primitives/beacon/src/updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,13 @@ pub struct CheckpointUpdate<const COMMITTEE_SIZE: usize> {
pub block_roots_branch: Vec<H256>,
}

impl<const COMMITTEE_SIZE: usize> Default for CheckpointUpdate<COMMITTEE_SIZE> {
fn default() -> Self {
CheckpointUpdate {
header: Default::default(),
current_sync_committee: Default::default(),
current_sync_committee_branch: Default::default(),
validators_root: Default::default(),
block_roots_root: Default::default(),
block_roots_branch: Default::default(),
}
}
}

#[derive(
Default, Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo,
)]
#[cfg_attr(
feature = "std",
derive(serde::Deserialize),
serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))
serde(bound(serialize = ""), bound(deserialize = ""))
)]
pub struct Update<const COMMITTEE_SIZE: usize, const COMMITTEE_BITS_SIZE: usize> {
/// A recent header attesting to the finalized header, using its `state_root`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ pub fn send_inbound_message(fixture: InboundQueueFixture) -> DispatchResult {
EthereumBeaconClient::store_execution_header(
fixture.message.proof.block_hash,
fixture.execution_header,
0,
H256::default(),
);

EthereumInboundQueue::submit(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ pub mod benchmark_helpers {

impl<T: snowbridge_pallet_ethereum_client::Config> BenchmarkHelper<T> for Runtime {
fn initialize_storage(block_hash: H256, header: CompactExecutionHeader) {
EthereumBeaconClient::store_execution_header(block_hash, header, 0, H256::default())
EthereumBeaconClient::store_execution_header(block_hash, header)
}
}

Expand Down
Loading