Skip to content

Commit

Permalink
Snowbridge: Synchronize from Snowfork repository (paritytech#3761)
Browse files Browse the repository at this point in the history
This PR includes the following 2 improvements:

Author: @yrong
- #123
- #125

The Ethereum client syncs beacon headers as they are finalized, and
imports every execution header. When a message is received, it is
verified against the import execution header. This is unnecessary, since
the execution header can be sent with the message as proof. The recent
Deneb Ethereum upgrade made it easier to locate the relevant beacon
header from an execution header, and so this improvement was made
possible. This resolves a concern @svyatonik had in our initial Rococo
PR:
paritytech#2522 (comment)

Author: @yrong
- #118

When the AH sovereign account (who pays relayer rewards) is depleted,
the inbound message will not fail. The relayer just will not receive
rewards.

Both these changes were done by @yrong, many thanks. ❤️

---------

Co-authored-by: claravanstaden <Cats 4 life!>
Co-authored-by: Ron <yrong1997@gmail.com>
Co-authored-by: Vincent Geddes <vincent@snowfork.com>
Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com>
  • Loading branch information
4 people committed Jun 19, 2024
1 parent e046d3b commit 50de744
Show file tree
Hide file tree
Showing 34 changed files with 1,118 additions and 1,307 deletions.
272 changes: 179 additions & 93 deletions bridges/snowbridge/parachain/pallets/ethereum-client/fixtures/src/lib.rs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -65,24 +65,6 @@ mod benchmarks {
Ok(())
}

#[benchmark]
fn submit_execution_header() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let checkpoint_update = make_checkpoint();
let finalized_header_update = make_finalized_header_update();
let execution_header_update = make_execution_header_update();
let execution_header_hash = execution_header_update.execution_header.block_hash();
EthereumBeaconClient::<T>::process_checkpoint_update(&checkpoint_update)?;
EthereumBeaconClient::<T>::process_update(&finalized_header_update)?;

#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), Box::new(*execution_header_update));

assert!(<ExecutionHeaders<T>>::contains_key(execution_header_hash));

Ok(())
}

#[benchmark(extra)]
fn bls_fast_aggregate_verify_pre_aggregated() -> Result<(), BenchmarkError> {
EthereumBeaconClient::<T>::process_checkpoint_update(&make_checkpoint())?;
Expand Down
138 changes: 103 additions & 35 deletions bridges/snowbridge/parachain/pallets/ethereum-client/src/impls.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::*;
use frame_support::ensure;
use primitives::ExecutionProof;

use snowbridge_core::inbound::{
VerificationError::{self, *},
Expand All @@ -14,32 +16,13 @@ impl<T: Config> Verifier for Pallet<T> {
/// the log should be in the beacon client storage, meaning it has been verified and is an
/// ancestor of a finalized beacon block.
fn verify(event_log: &Log, proof: &Proof) -> Result<(), VerificationError> {
log::info!(
target: "ethereum-client",
"💫 Verifying message with block hash {}",
proof.block_hash,
);
Self::verify_execution_proof(&proof.execution_proof)
.map_err(|e| InvalidExecutionProof(e.into()))?;

let header = <ExecutionHeaderBuffer<T>>::get(proof.block_hash).ok_or(HeaderNotFound)?;

let receipt = match Self::verify_receipt_inclusion(header.receipts_root, proof) {
Ok(receipt) => receipt,
Err(err) => {
log::error!(
target: "ethereum-client",
"💫 Verification of receipt inclusion failed for block {}: {:?}",
proof.block_hash,
err
);
return Err(err)
},
};

log::trace!(
target: "ethereum-client",
"💫 Verified receipt inclusion for transaction at index {} in block {}",
proof.tx_index, proof.block_hash,
);
let receipt = Self::verify_receipt_inclusion(
proof.execution_proof.execution_header.receipts_root(),
&proof.receipt_proof.1,
)?;

event_log.validate().map_err(|_| InvalidLog)?;

Expand All @@ -53,18 +36,11 @@ impl<T: Config> Verifier for Pallet<T> {
if !receipt.contains_log(&event_log) {
log::error!(
target: "ethereum-client",
"💫 Event log not found in receipt for transaction at index {} in block {}",
proof.tx_index, proof.block_hash,
"💫 Event log not found in receipt for transaction",
);
return Err(LogNotFound)
}

log::info!(
target: "ethereum-client",
"💫 Receipt verification successful for {}",
proof.block_hash,
);

Ok(())
}
}
Expand All @@ -74,9 +50,9 @@ impl<T: Config> Pallet<T> {
/// `proof.block_hash`.
pub fn verify_receipt_inclusion(
receipts_root: H256,
proof: &Proof,
receipt_proof: &[Vec<u8>],
) -> Result<Receipt, VerificationError> {
let result = verify_receipt_proof(receipts_root, &proof.data.1).ok_or(InvalidProof)?;
let result = verify_receipt_proof(receipts_root, receipt_proof).ok_or(InvalidProof)?;

match result {
Ok(receipt) => Ok(receipt),
Expand All @@ -90,4 +66,96 @@ impl<T: Config> Pallet<T> {
},
}
}

/// Validates an execution header with ancestry_proof against a finalized checkpoint on
/// chain.The beacon header containing the execution header is sent, plus the execution header,
/// along with a proof that the execution header is rooted in the beacon header body.
pub(crate) fn verify_execution_proof(execution_proof: &ExecutionProof) -> DispatchResult {
let latest_finalized_state =
FinalizedBeaconState::<T>::get(LatestFinalizedBlockRoot::<T>::get())
.ok_or(Error::<T>::NotBootstrapped)?;
// Checks that the header is an ancestor of a finalized header, using slot number.
ensure!(
execution_proof.header.slot <= latest_finalized_state.slot,
Error::<T>::HeaderNotFinalized
);

// 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.
let execution_header_root: H256 = execution_proof
.execution_header
.hash_tree_root()
.map_err(|_| Error::<T>::BlockBodyHashTreeRootFailed)?;

ensure!(
verify_merkle_branch(
execution_header_root,
&execution_proof.execution_branch,
config::EXECUTION_HEADER_SUBTREE_INDEX,
config::EXECUTION_HEADER_DEPTH,
execution_proof.header.body_root
),
Error::<T>::InvalidExecutionHeaderProof
);

let beacon_block_root: H256 = execution_proof
.header
.hash_tree_root()
.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;

match &execution_proof.ancestry_proof {
Some(proof) => {
Self::verify_ancestry_proof(
beacon_block_root,
execution_proof.header.slot,
&proof.header_branch,
proof.finalized_block_root,
)?;
},
None => {
// If the ancestry proof is not provided, we expect this beacon header to be a
// finalized beacon header. We need to check that the header hash matches the
// finalized header root at the expected slot.
let state = <FinalizedBeaconState<T>>::get(beacon_block_root)
.ok_or(Error::<T>::ExpectedFinalizedHeaderNotStored)?;
if execution_proof.header.slot != state.slot {
return Err(Error::<T>::ExpectedFinalizedHeaderNotStored.into())
}
},
}

Ok(())
}

/// Verify that `block_root` is an ancestor of `finalized_block_root` Used to prove that
/// an execution header is an ancestor of a finalized header (i.e. the blocks are
/// on the same chain).
fn verify_ancestry_proof(
block_root: H256,
block_slot: u64,
block_root_proof: &[H256],
finalized_block_root: H256,
) -> DispatchResult {
let state = <FinalizedBeaconState<T>>::get(finalized_block_root)
.ok_or(Error::<T>::ExpectedFinalizedHeaderNotStored)?;

ensure!(block_slot < state.slot, Error::<T>::HeaderNotFinalized);

let index_in_array = block_slot % (SLOTS_PER_HISTORICAL_ROOT as u64);
let leaf_index = (SLOTS_PER_HISTORICAL_ROOT as u64) + index_in_array;

ensure!(
verify_merkle_branch(
block_root,
block_root_proof,
leaf_index as usize,
config::BLOCK_ROOT_AT_INDEX_DEPTH,
state.block_roots_root
),
Error::<T>::InvalidAncestryMerkleProof
);

Ok(())
}
}
Loading

0 comments on commit 50de744

Please sign in to comment.