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