referrerpolicy=no-referrer-when-downgrade
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::*;
use frame_support::ensure;
use snowbridge_beacon_primitives::ExecutionProof;

use snowbridge_core::inbound::{
	VerificationError::{self, *},
	*,
};
use snowbridge_ethereum::Receipt;

impl<T: Config> Verifier for Pallet<T> {
	/// Verify a message by verifying the existence of the corresponding
	/// Ethereum log in a block. Returns the log if successful. The execution header containing
	/// 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> {
		Self::verify_execution_proof(&proof.execution_proof)
			.map_err(|e| InvalidExecutionProof(e.into()))?;

		let receipt = Self::verify_receipt_inclusion(
			proof.execution_proof.execution_header.receipts_root(),
			&proof.receipt_proof.1,
		)?;

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

		// Convert snowbridge_core::inbound::Log to snowbridge_ethereum::Log.
		let event_log = snowbridge_ethereum::Log {
			address: event_log.address,
			topics: event_log.topics.clone(),
			data: event_log.data.clone(),
		};

		if !receipt.contains_log(&event_log) {
			log::error!(
				target: "ethereum-client",
				"💫 Event log not found in receipt for transaction",
			);
			return Err(LogNotFound)
		}

		Ok(())
	}
}

impl<T: Config> Pallet<T> {
	/// Verifies that the receipt encoded in `proof.data` is included in the block given by
	/// `proof.block_hash`.
	pub fn verify_receipt_inclusion(
		receipts_root: H256,
		receipt_proof: &[Vec<u8>],
	) -> Result<Receipt, VerificationError> {
		let result = verify_receipt_proof(receipts_root, receipt_proof).ok_or(InvalidProof)?;

		match result {
			Ok(receipt) => Ok(receipt),
			Err(err) => {
				log::trace!(
					target: "ethereum-client",
					"💫 Failed to decode transaction receipt: {}",
					err
				);
				Err(InvalidProof)
			},
		}
	}

	/// 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(())
	}
}