Skip to content

Commit

Permalink
Sync execution update on demand
Browse files Browse the repository at this point in the history
  • Loading branch information
yrong committed Mar 12, 2024
1 parent e553417 commit ef5cd64
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 165 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,8 @@ pub fn make_sync_committee_update() -> Box<Update> {
hex!("5340ad5877c72dca689ca04bc8fedb78d67a4801d99887937edd8ccd29f87e82").into(),
hex!("f5ff4b0c6190005015889879568f5f0d9c40134c7ec4ffdda47950dcd92395ad").into(),
],
execution_header: None,
execution_branch: None,
})
}

Expand Down Expand Up @@ -1165,7 +1167,9 @@ pub fn make_finalized_header_update() -> Box<Update> {
hex!("ecea7e1d3152d8130e83afdfe34b4de4ba2b69a33c9471991096daf454de9cf5").into(),
hex!("b2bf1758e50b2bfff29169fbc70fdb884b2b05bb615dbc53567574da6f4f1ae2").into(),
hex!("cd87069daf70975779126d6af833b7d636c75ca4d5e750ebcad0e76408a5e5bf").into(),
]
],
execution_header: None,
execution_branch: None,
})
}

Expand Down
74 changes: 37 additions & 37 deletions bridges/snowbridge/pallets/ethereum-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

use frame_support::{
dispatch::DispatchResult, pallet_prelude::OptionQuery, traits::Get, transactional,
};
use frame_support::{dispatch::DispatchResult, pallet_prelude::OptionQuery, traits::Get};
use frame_system::ensure_signed;
use primitives::{
fast_aggregate_verify, verify_merkle_branch, verify_receipt_proof, BeaconHeader, BlsError,
Expand Down Expand Up @@ -214,8 +212,7 @@ pub mod pallet {
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::force_checkpoint())]
#[transactional]
#[pallet::weight((T::WeightInfo::force_checkpoint(), DispatchClass::Operational))]
/// Used for pallet initialization and light client resetting. Needs to be called by
/// the root origin.
pub fn force_checkpoint(
Expand All @@ -234,7 +231,6 @@ pub mod pallet {
Some(_) => T::WeightInfo::submit_with_sync_committee(),
}
})]
#[transactional]
/// Submits a new finalized beacon header update. The update may contain the next
/// sync committee.
pub fn submit(origin: OriginFor<T>, update: Box<Update>) -> DispatchResult {
Expand All @@ -246,7 +242,6 @@ pub mod pallet {

#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::submit_execution_header())]
#[transactional]
/// Submits a new execution header update. The relevant related beacon header
/// is also included to prove the execution header, as well as ancestry proof data.
pub fn submit_execution_header(
Expand Down Expand Up @@ -330,32 +325,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 @@ -479,6 +453,28 @@ pub mod pallet {
)
.map_err(|e| Error::<T>::BLSVerificationFailed(e))?;

// Execution payload header corresponding to `beacon.body_root` (from Capella onward)
// https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/light-client/sync-protocol.md#modified-lightclientheader
if let Some(version_execution_header) = &update.execution_header {
let execution_header_root: H256 = version_execution_header
.hash_tree_root()
.map_err(|_| Error::<T>::BlockBodyHashTreeRootFailed)?;
ensure!(
&update.execution_branch.is_some(),
Error::<T>::InvalidExecutionHeaderProof
);
ensure!(
verify_merkle_branch(
execution_header_root,
&update.execution_branch.clone().unwrap(),
config::EXECUTION_HEADER_SUBTREE_INDEX,
config::EXECUTION_HEADER_DEPTH,
update.finalized_header.body_root
),
Error::<T>::InvalidExecutionHeaderProof
);
}

Ok(())
}

Expand Down Expand Up @@ -530,6 +526,19 @@ pub mod pallet {
)?;
}

if let Some(version_execution_header) = &update.execution_header {
let finalized_block_root: H256 = update
.finalized_header
.hash_tree_root()
.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
Self::store_execution_header(
version_execution_header.block_hash(),
version_execution_header.clone().into(),
update.finalized_header.slot,
finalized_block_root,
);
}

Ok(())
}

Expand All @@ -548,15 +557,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
114 changes: 2 additions & 112 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 @@ -348,34 +345,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 +577,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 +699,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
6 changes: 6 additions & 0 deletions bridges/snowbridge/primitives/beacon/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,12 @@ impl VersionedExecutionPayloadHeader {
}
}

impl Default for VersionedExecutionPayloadHeader {
fn default() -> Self {
VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader::default())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
23 changes: 8 additions & 15 deletions bridges/snowbridge/primitives/beacon/src/updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use sp_std::prelude::*;

use crate::types::{BeaconHeader, SyncAggregate, SyncCommittee, VersionedExecutionPayloadHeader};

#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo)]
#[derive(
Default, Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo,
)]
#[cfg_attr(
feature = "std",
derive(serde::Serialize, serde::Deserialize),
Expand All @@ -23,26 +25,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 All @@ -64,6 +53,10 @@ pub struct Update<const COMMITTEE_SIZE: usize, const COMMITTEE_BITS_SIZE: usize>
pub block_roots_root: H256,
/// The merkle path to prove the `block_roots_root` value.
pub block_roots_branch: Vec<H256>,
/// The execution header to be imported
pub execution_header: Option<VersionedExecutionPayloadHeader>,
/// The merkle proof for the execution_header
pub execution_branch: Option<Vec<H256>>,
}

#[derive(
Expand Down

0 comments on commit ef5cd64

Please sign in to comment.