referrerpolicy=no-referrer-when-downgrade

snowbridge_pallet_ethereum_client/
impls.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
3use super::*;
4use frame_support::ensure;
5use snowbridge_beacon_primitives::{CommitmentError, ExecutionProof};
6use sp_runtime::DispatchError;
7
8use alloy_primitives::Log as AlloyLog;
9use snowbridge_beacon_primitives::merkle_proof::{generalized_index_length, subtree_index};
10use snowbridge_verification_primitives::{
11	receipt::verify_receipt_proof,
12	VerificationError::{self, *},
13	Verifier, *,
14};
15
16impl<T: Config> Verifier for Pallet<T> {
17	/// Verify a message by verifying the existence of the corresponding
18	/// Ethereum log in a block. Returns the log if successful. The execution header containing
19	/// the log is sent with the message. The beacon header containing the execution header
20	/// is also sent with the message, to check if the header is an ancestor of a finalized
21	/// header.
22	fn verify(event_log: &Log, proof: &Proof) -> Result<(), VerificationError> {
23		// Refuse to verify any Ethereum-side proof while the beacon light client is halted.
24		// Governance halts the light client when it suspects a compromise (e.g. sync committee
25		// takeover), at which point any signed headers/receipts must be treated as untrusted.
26		// Covers every Verifier consumer, including `inbound_queue_v2::submit` and
27		// `outbound_queue_v2::submit_delivery_receipt` (which would otherwise still drain
28		// pending relayer rewards while the bridge is halted).
29		ensure!(!Self::operating_mode().is_halted(), VerificationError::Halted);
30
31		let receipts_root = Self::verify_execution_proof(&proof.execution_proof)
32			.map_err(|e| InvalidExecutionProof(e.into()))?;
33
34		Self::verify_receipt_inclusion(
35			receipts_root,
36			event_log.tx_index,
37			&proof.receipt_proof,
38			event_log,
39		)?;
40
41		Ok(())
42	}
43}
44
45impl<T: Config> Pallet<T> {
46	/// Verifies that the receipt encoded in `proof.data` is included in the block given by
47	/// `proof.block_hash`.
48	pub fn verify_receipt_inclusion(
49		receipts_root: H256,
50		tx_index: u64,
51		receipt_proof: &[Vec<u8>],
52		log: &Log,
53	) -> Result<(), VerificationError> {
54		let receipt =
55			verify_receipt_proof(receipts_root, tx_index, receipt_proof).ok_or(InvalidProof)?;
56		if !receipt.logs().iter().any(|l| Self::check_log_match(log, l)) {
57			tracing::error!(
58				target: "ethereum-client",
59				"💫 Event log not found in receipt for transaction",
60			);
61			return Err(LogNotFound);
62		}
63		Ok(())
64	}
65
66	fn check_log_match(log: &Log, receipt_log: &AlloyLog) -> bool {
67		let equal = receipt_log.data.data.0 == log.data &&
68			receipt_log.address.0 == log.address.0 &&
69			receipt_log.topics().len() == log.topics.len();
70		if !equal {
71			return false;
72		}
73		for (_, (topic1, topic2)) in receipt_log.topics().iter().zip(log.topics.iter()).enumerate()
74		{
75			if topic1.0 != topic2.0 {
76				return false;
77			}
78		}
79		true
80	}
81
82	/// Validates an execution header with ancestry_proof against a finalized checkpoint on
83	/// chain.The beacon header containing the execution header is sent, plus the execution header,
84	/// along with a proof that the execution header is rooted in the beacon header body.
85	pub(crate) fn verify_execution_proof(
86		execution_proof: &ExecutionProof,
87	) -> Result<H256, DispatchError> {
88		let latest_finalized_state =
89			FinalizedBeaconState::<T>::get(LatestFinalizedBlockRoot::<T>::get())
90				.ok_or(Error::<T>::NotBootstrapped)?;
91		// Checks that the header is an ancestor of a finalized header, using slot number.
92		ensure!(
93			execution_proof.header.slot <= latest_finalized_state.slot,
94			Error::<T>::HeaderNotFinalized
95		);
96
97		let beacon_block_root: H256 = execution_proof
98			.header
99			.hash_tree_root()
100			.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
101
102		match &execution_proof.ancestry_proof {
103			Some(proof) => {
104				Self::verify_ancestry_proof(
105					beacon_block_root,
106					execution_proof.header.slot,
107					&proof.header_branch,
108					proof.finalized_block_root,
109				)?;
110			},
111			None => {
112				// If the ancestry proof is not provided, we expect this beacon header to be a
113				// finalized beacon header. We need to check that the header hash matches the
114				// finalized header root at the expected slot.
115				let state = <FinalizedBeaconState<T>>::get(beacon_block_root)
116					.ok_or(Error::<T>::ExpectedFinalizedHeaderNotStored)?;
117				if execution_proof.header.slot != state.slot {
118					return Err(Error::<T>::ExpectedFinalizedHeaderNotStored.into());
119				}
120			},
121		}
122
123		let (commitment, gindex) = Self::execution_commitment_at_slot(
124			&execution_proof.execution_header,
125			execution_proof.header.slot,
126			T::ForkVersions::get(),
127		)
128		.map_err(|e| match e {
129			CommitmentError::Merkleization => Error::<T>::BlockBodyHashTreeRootFailed,
130			CommitmentError::MalformedExecutionHeader => Error::<T>::MalformedExecutionHeader,
131			CommitmentError::EraMismatch => Error::<T>::ExecutionHeaderEraMismatch,
132		})?;
133		ensure!(
134			verify_merkle_branch(
135				commitment.leaf(),
136				&execution_proof.execution_branch,
137				subtree_index(gindex),
138				generalized_index_length(gindex),
139				execution_proof.header.body_root
140			),
141			Error::<T>::InvalidExecutionHeaderProof
142		);
143
144		Ok(commitment.receipts_root())
145	}
146
147	/// Verify that `block_root` is an ancestor of `finalized_block_root` Used to prove that
148	/// an execution header is an ancestor of a finalized header (i.e. the blocks are
149	/// on the same chain).
150	fn verify_ancestry_proof(
151		block_root: H256,
152		block_slot: u64,
153		block_root_proof: &[H256],
154		finalized_block_root: H256,
155	) -> DispatchResult {
156		let state = <FinalizedBeaconState<T>>::get(finalized_block_root)
157			.ok_or(Error::<T>::ExpectedFinalizedHeaderNotStored)?;
158
159		ensure!(block_slot < state.slot, Error::<T>::HeaderNotFinalized);
160
161		let index_in_array = block_slot % (SLOTS_PER_HISTORICAL_ROOT as u64);
162		let leaf_index = (SLOTS_PER_HISTORICAL_ROOT as u64) + index_in_array;
163
164		ensure!(
165			verify_merkle_branch(
166				block_root,
167				block_root_proof,
168				leaf_index as usize,
169				config::BLOCK_ROOT_AT_INDEX_DEPTH,
170				state.block_roots_root
171			),
172			Error::<T>::InvalidAncestryMerkleProof
173		);
174
175		Ok(())
176	}
177}