Skip to content

Commit

Permalink
Sync execution update on demand (#123)
Browse files Browse the repository at this point in the history
* Sync execution update on demand

* Fix updating LatestExecutionState

* Revert change unrelated

* Remove unused

* Remove execution header from update & Remove LatestExecutionState
  • Loading branch information
yrong authored Mar 14, 2024
1 parent e553417 commit 5bac4ee
Show file tree
Hide file tree
Showing 6 changed files with 12 additions and 198 deletions.
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

0 comments on commit 5bac4ee

Please sign in to comment.