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