referrerpolicy=no-referrer-when-downgrade

snowbridge_ethereum/
header.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
3use codec::{Decode, Encode};
4use ethbloom::Bloom as EthBloom;
5use hex_literal::hex;
6use parity_bytes::Bytes;
7use rlp::RlpStream;
8use scale_info::TypeInfo;
9use sp_io::hashing::keccak_256;
10use sp_runtime::RuntimeDebug;
11use sp_std::prelude::*;
12
13#[cfg(feature = "std")]
14use serde::{Deserialize, Serialize};
15
16#[cfg(feature = "std")]
17use serde_big_array::BigArray;
18
19use ethereum_types::{Address, H256, H64, U256};
20
21use crate::{mpt, receipt};
22
23/// Complete block header id.
24#[derive(Clone, Copy, Default, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
25pub struct HeaderId {
26	/// Header number.
27	pub number: u64,
28	/// Header hash.
29	pub hash: H256,
30}
31
32const EMPTY_OMMERS_HASH: [u8; 32] =
33	hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347");
34
35/// An Ethereum block header.
36#[derive(Clone, Default, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
37#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
38pub struct Header {
39	/// Parent block hash.
40	pub parent_hash: H256,
41	/// Block timestamp.
42	pub timestamp: u64,
43	/// Block number.
44	pub number: u64,
45	/// Block author.
46	pub author: Address,
47
48	/// Transactions root.
49	pub transactions_root: H256,
50	/// Block ommers hash.
51	pub ommers_hash: H256,
52	/// Block extra data.
53	pub extra_data: Bytes,
54
55	/// State root.
56	pub state_root: H256,
57	/// Block receipts root.
58	pub receipts_root: H256,
59	/// Block bloom.
60	pub logs_bloom: Bloom,
61	/// Gas used for contracts execution.
62	pub gas_used: U256,
63	/// Block gas limit.
64	pub gas_limit: U256,
65
66	/// Block difficulty.
67	pub difficulty: U256,
68	/// Vector of post-RLP-encoded fields.
69	pub seal: Vec<Bytes>,
70
71	// Base fee per gas (EIP-1559), only in headers from the London hardfork onwards.
72	pub base_fee: Option<U256>,
73}
74
75impl Header {
76	/// Compute hash of this header (keccak of the RLP with seal).
77	pub fn compute_hash(&self) -> H256 {
78		keccak_256(&self.rlp(true)).into()
79	}
80
81	/// Compute hash of the truncated header i.e. excluding seal.
82	pub fn compute_partial_hash(&self) -> H256 {
83		keccak_256(&self.rlp(false)).into()
84	}
85
86	pub fn check_receipt_proof(
87		&self,
88		proof: &[Vec<u8>],
89	) -> Option<Result<receipt::Receipt, rlp::DecoderError>> {
90		match self.apply_merkle_proof(proof) {
91			Some((root, data)) if root == self.receipts_root => Some(rlp::decode(&data)),
92			Some((_, _)) => None,
93			None => None,
94		}
95	}
96
97	pub fn apply_merkle_proof(&self, proof: &[Vec<u8>]) -> Option<(H256, Vec<u8>)> {
98		let mut iter = proof.iter().rev();
99		let first_bytes = match iter.next() {
100			Some(b) => b,
101			None => return None,
102		};
103		let item_to_prove: mpt::ShortNode = rlp::decode(first_bytes).ok()?;
104
105		let final_hash: Option<[u8; 32]> = iter.try_fold(keccak_256(first_bytes), |acc, x| {
106			let node: Box<dyn mpt::Node> = x.as_slice().try_into().ok()?;
107			if (*node).contains_hash(acc.into()) {
108				return Some(keccak_256(x))
109			}
110			None
111		});
112
113		final_hash.map(|hash| (hash.into(), item_to_prove.value))
114	}
115
116	pub fn mix_hash(&self) -> Option<H256> {
117		let bytes: Bytes = self.decoded_seal_field(0, 32)?;
118		let size = bytes.len();
119		let mut mix_hash = [0u8; 32];
120		for i in 0..size {
121			mix_hash[31 - i] = bytes[size - 1 - i];
122		}
123		Some(mix_hash.into())
124	}
125
126	pub fn nonce(&self) -> Option<H64> {
127		let bytes: Bytes = self.decoded_seal_field(1, 8)?;
128		let size = bytes.len();
129		let mut nonce = [0u8; 8];
130		for i in 0..size {
131			nonce[7 - i] = bytes[size - 1 - i];
132		}
133		Some(nonce.into())
134	}
135
136	pub fn has_ommers(&self) -> bool {
137		self.ommers_hash != EMPTY_OMMERS_HASH.into()
138	}
139
140	fn decoded_seal_field(&self, index: usize, max_len: usize) -> Option<Bytes> {
141		let bytes: Bytes = rlp::decode(self.seal.get(index)?).ok()?;
142		if bytes.len() > max_len {
143			return None
144		}
145		Some(bytes)
146	}
147
148	/// Returns header RLP with or without seals.
149	/// For EIP-1559 baseFee addition refer to:
150	/// <https://github.com/openethereum/openethereum/blob/193b25a22d5ff07759c6431129e95235510516f9/crates/ethcore/types/src/header.rs#L341>
151	fn rlp(&self, with_seal: bool) -> Bytes {
152		let mut s = RlpStream::new();
153
154		let stream_length_without_seal = if self.base_fee.is_some() { 14 } else { 13 };
155
156		if with_seal {
157			s.begin_list(stream_length_without_seal + self.seal.len());
158		} else {
159			s.begin_list(stream_length_without_seal);
160		}
161
162		s.append(&self.parent_hash);
163		s.append(&self.ommers_hash);
164		s.append(&self.author);
165		s.append(&self.state_root);
166		s.append(&self.transactions_root);
167		s.append(&self.receipts_root);
168		s.append(&EthBloom::from(self.logs_bloom.0));
169		s.append(&self.difficulty);
170		s.append(&self.number);
171		s.append(&self.gas_limit);
172		s.append(&self.gas_used);
173		s.append(&self.timestamp);
174		s.append(&self.extra_data);
175
176		if with_seal {
177			for b in &self.seal {
178				s.append_raw(b, 1);
179			}
180		}
181
182		if let Some(base_fee) = self.base_fee {
183			s.append(&base_fee);
184		}
185
186		s.out().to_vec()
187	}
188}
189
190/// Logs bloom.
191#[derive(Clone, Debug, Encode, Decode, TypeInfo)]
192#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
193pub struct Bloom(#[cfg_attr(feature = "std", serde(with = "BigArray"))] [u8; 256]);
194
195impl<'a> From<&'a [u8; 256]> for Bloom {
196	fn from(buffer: &'a [u8; 256]) -> Bloom {
197		Bloom(*buffer)
198	}
199}
200
201impl PartialEq<Bloom> for Bloom {
202	fn eq(&self, other: &Bloom) -> bool {
203		self.0.iter().zip(other.0.iter()).all(|(l, r)| l == r)
204	}
205}
206
207impl Default for Bloom {
208	fn default() -> Self {
209		Bloom([0; 256])
210	}
211}
212
213impl rlp::Decodable for Bloom {
214	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
215		let v: Vec<u8> = rlp.as_val()?;
216		match v.len() {
217			256 => {
218				let mut bytes = [0u8; 256];
219				bytes.copy_from_slice(&v);
220				Ok(Self(bytes))
221			},
222			_ => Err(rlp::DecoderError::Custom("Expected 256 bytes")),
223		}
224	}
225}
226
227#[cfg(test)]
228mod tests {
229
230	use super::*;
231
232	#[test]
233	fn bloom_decode_rlp() {
234		let raw_bloom = hex!(
235			"
236			b901000420000000000000000000008002000000000001000000000001000000000000000000
237			0000000000000000000000000002000000080000000000000000200000000000000000000000
238			0000080000002200000000004000100000000000000000000000000000000000000000000000
239			0000000000000004000000001000010000000000080000000000400000000000000000000000
240			0000080000004000000000020000000000020000000000000000000000000000000000000000
241			0000040000000000020000000001000000000000000000000000000010000000020000200000
242			10200000000000010000000000000000000000000000000000000010000000
243		"
244		);
245		let expected_bytes = &raw_bloom[3..];
246		let bloom: Bloom = rlp::decode(&raw_bloom).unwrap();
247		assert_eq!(bloom.0, expected_bytes);
248	}
249
250	#[test]
251	fn header_compute_hash_poa() {
252		// PoA header
253		let header = Header {
254			parent_hash: Default::default(),
255			timestamp: 0,
256			number: 0,
257			author: Default::default(),
258			transactions_root: hex!(
259				"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
260			)
261			.into(),
262			ommers_hash: hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")
263				.into(),
264			extra_data: vec![],
265			state_root: hex!("eccf6b74c2bcbe115c71116a23fe963c54406010c244d9650526028ad3e32cce")
266				.into(),
267			receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
268				.into(),
269			logs_bloom: Default::default(),
270			gas_used: Default::default(),
271			gas_limit: 0x222222.into(),
272			difficulty: 0x20000.into(),
273			seal: vec![vec![0x80], {
274				let mut vec = vec![0xb8, 0x41];
275				vec.resize(67, 0);
276				vec
277			}],
278			base_fee: None,
279		};
280		assert_eq!(
281			header.compute_hash().as_bytes(),
282			hex!("9ff57c7fa155853586382022f0982b71c51fa313a0942f8c456300896643e890"),
283		);
284	}
285
286	#[test]
287	fn header_compute_hash_pow() {
288		// <https://etherscan.io/block/11090290>
289		let nonce = hex!("6935bbe7b63c4f8e").to_vec();
290		let mix_hash =
291			hex!("be3adfb0087be62b28b716e2cdf3c79329df5caa04c9eee035d35b5d52102815").to_vec();
292		let header = Header {
293			parent_hash: hex!("bede0bddd6f32c895fc505ffe0c39d9bde58e9a5272f31a3dee448b796edcbe3")
294				.into(),
295			timestamp: 1603160977,
296			number: 11090290,
297			author: hex!("ea674fdde714fd979de3edf0f56aa9716b898ec8").into(),
298			transactions_root: hex!(
299				"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
300			)
301			.into(),
302			ommers_hash: hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")
303				.into(),
304			extra_data: hex!("65746865726d696e652d61736961312d33").to_vec(),
305			state_root: hex!("7dcb8aca872b712bad81df34a89d4efedc293566ffc3eeeb5cbcafcc703e42c9")
306				.into(),
307			receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
308				.into(),
309			logs_bloom: Default::default(),
310			gas_used: 0.into(),
311			gas_limit: 0xbe8c19.into(),
312			difficulty: 0xbc140caa61087i64.into(),
313			seal: vec![rlp::encode(&mix_hash).to_vec(), rlp::encode(&nonce).to_vec()],
314			base_fee: None,
315		};
316		assert_eq!(
317			header.compute_hash().as_bytes(),
318			hex!("0f9bdc91c2e0140acb873330742bda8c8181fa3add91fe7ae046251679cedef7"),
319		);
320	}
321
322	#[test]
323	fn header_pow_seal_fields_extracted_correctly() {
324		let nonce: H64 = hex!("6935bbe7b63c4f8e").into();
325		let mix_hash: H256 =
326			hex!("be3adfb0087be62b28b716e2cdf3c79329df5caa04c9eee035d35b5d52102815").into();
327		let header = Header {
328			seal: vec![
329				rlp::encode(&mix_hash.0.to_vec()).to_vec(),
330				rlp::encode(&nonce.0.to_vec()).to_vec(),
331			],
332			..Default::default()
333		};
334
335		assert_eq!(header.nonce().unwrap(), nonce);
336		assert_eq!(header.mix_hash().unwrap(), mix_hash);
337	}
338
339	#[test]
340	fn header_pow_seal_fields_return_none_for_invalid_values() {
341		let nonce = hex!("696935bbe7b63c4f8e").to_vec();
342		let mix_hash =
343			hex!("bebe3adfb0087be62b28b716e2cdf3c79329df5caa04c9eee035d35b5d52102815").to_vec();
344		let mut header = Header {
345			seal: vec![rlp::encode(&mix_hash).to_vec(), rlp::encode(&nonce).to_vec()],
346			..Default::default()
347		};
348		assert_eq!(header.nonce(), None);
349		assert_eq!(header.mix_hash(), None);
350
351		header.seal = Vec::new();
352		assert_eq!(header.nonce(), None);
353		assert_eq!(header.mix_hash(), None);
354	}
355
356	#[test]
357	fn header_check_receipt_proof() {
358		let header = Header {
359			receipts_root: hex!("fd5e397a84884641f53c496804f24b5276cbb8c5c9cfc2342246be8e3ce5ad02")
360				.into(),
361			..Default::default()
362		};
363
364		// Valid proof
365		let proof_receipt5 = vec!(
366			hex!("f90131a0b5ba404eb5a6a88e56579f4d37ef9813b5ad7f86f0823ff3b407ac5a6bb465eca0398ead2655e78e03c127ce22c5830e90f18b1601ec055f938336c084feb915a9a026d322c26e46c50942c1aabde50e36df5cde572aed650ce73ea3182c6e90a02ca00600a356135f4db1db0d9842264cdff2652676f881669e91e316c0b6dd783011a0837f1deb4075336da320388c1edfffc56c448a43f4a5ba031300d32a7b509fc5a01c3ac82fd65b4aba7f9afaf604d9c82ec7e2deb573a091ae235751bc5c0c288da05d454159d9071b0f68b6e0503d290f23ac7602c1db0c569dee4605d8f5298f09a00bbed10350ec954448df795f6fd46e3faefc800ede061b3840eedc6e2b07a74da0acb02d26a3650f2064c14a435fdf1f668d8655daf455ebdf671713a7c089b3898080808080808080").to_vec(),
367			hex!("f901f180a00046a08d4f0bdbdc6b31903086ce323182bce6725e7d9415f7ff91ee8f4820bda0e7cd26ad5f3d2771e4b5ab788e268a14a10209f94ee918eb6c829d21d3d11c1da00d4a56d9e9a6751874fd86c7e3cb1c6ad5a848da62751325f478978a00ea966ea064b81920c8f04a8a1e21f53a8280e739fbb7b00b2ab92493ca3f610b70e8ac85a0b1040ed4c55a73178b76abb16f946ce5bebd6b93ab873c83327df54047d12c27a0de6485e9ac58dc6e2b04b4bb38f562684f0b1a2ee586cc11079e7d9a9dc40b32a0d394f4d3532c3124a65fa36e69147e04fd20453a72ee9c50660f17e13ce9df48a066501003fc3e3478efd2803cd0eded6bbe9243ca01ba754d6327071ddbcbc649a0b2684e518f325fee39fc8ea81b68f3f5c785be00d087f3bed8857ae2ee8da26ea071060a5c52042e8d7ce21092f8ecf06053beb9a0b773a6f91a30c4220aa276b2a0fc22436632574ccf6043d0986dede27ea94c9ca9a3bb5ec03ce776a4ddef24a9a05a8a1d6698c4e7d8cc3a2506cb9b12ea9a079c9c7099bc919dc804033cc556e4a0170c468b0716fd36d161f0bf05875f15756a2976de92f9efe7716320509d79c9a0182f909a90cab169f3efb62387f9cccdd61440acc4deec42f68a4f7ca58075c7a055cf0e9202ac75689b76318f1171f3a44465eddc06aae0713bfb6b34fdd27b7980").to_vec(),
368			hex!("f904de20b904daf904d701830652f0b9010004200000000000000000000080020000000000010000000000010000000000000000000000000000000000000000000002000000080000000000000000200000000000000000000000000008000000220000000000400010000000000000000000000000000000000000000000000000000000000000040000000010000100000000000800000000004000000000000000000000000000080000004000000000020000000000020000000000000000000000000000000000000000000004000000000002000000000100000000000000000000000000001000000002000020000010200000000000010000000000000000000000000000000000000010000000f903ccf89b9421130f34829b4c343142047a28ce96ec07814b15f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007d843005c7433c16b27ff939cb37471541561ebda0000000000000000000000000e9c1281aae66801fa35ec404d5f2aea393ff6988a000000000000000000000000000000000000000000000000000000005d09b7380f89b9421130f34829b4c343142047a28ce96ec07814b15f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000007d843005c7433c16b27ff939cb37471541561ebda00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0ffffffffffffffffffffffffffffffffffffffffffffffffffffffcc840c6920f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000e9c1281aae66801fa35ec404d5f2aea393ff6988a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000003e973b5a5d1078ef87994e9c1281aae66801fa35ec404d5f2aea393ff6988e1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000000001f1420ad1d40000000000000000000000000000000000000000000000014ad400879d159a38f8fc94e9c1281aae66801fa35ec404d5f2aea393ff6988f863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488db88000000000000000000000000000000000000000000000000000000005d415f3320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e973b5a5d1078ef87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a07fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000003e973b5a5d1078e").to_vec(),
369		);
370		assert!(header.check_receipt_proof(&proof_receipt5).is_some());
371
372		// Various invalid proofs
373		let proof_empty: Vec<Vec<u8>> = vec![];
374		let proof_missing_full_node = vec![proof_receipt5[0].clone(), proof_receipt5[2].clone()];
375		let proof_missing_short_node1 = vec![proof_receipt5[0].clone(), proof_receipt5[1].clone()];
376		let proof_missing_short_node2 = vec![proof_receipt5[0].clone()];
377		let proof_invalid_encoding = vec![proof_receipt5[2][2..].to_vec()];
378		let proof_no_full_node = vec![proof_receipt5[2].clone(), proof_receipt5[2].clone()];
379		assert!(header.check_receipt_proof(&proof_empty).is_none());
380		assert!(header.check_receipt_proof(&proof_missing_full_node).is_none());
381
382		assert_eq!(
383			header.check_receipt_proof(&proof_missing_short_node1),
384			Some(Err(rlp::DecoderError::Custom("Unsupported receipt type")))
385		);
386
387		assert_eq!(
388			header.check_receipt_proof(&proof_missing_short_node2),
389			Some(Err(rlp::DecoderError::Custom("Unsupported receipt type")))
390		);
391
392		assert!(header.check_receipt_proof(&proof_invalid_encoding).is_none());
393		assert!(header.check_receipt_proof(&proof_no_full_node).is_none());
394	}
395
396	#[test]
397	fn header_check_receipt_proof_with_intermediate_short_node() {
398		let header = Header {
399			receipts_root: hex!("d128e3a57142d2bf15bc0cbcac7ad54f40750d571b5c3097e425882c10c9ba66")
400				.into(),
401			..Default::default()
402		};
403
404		let proof_receipt263 = vec![
405			hex!("f90131a00d3cb8d3f57ac1c0e12918a2ebe0cafed8c273577b9dd73e7ed1079b403ef494a0678b9835b834f8a287c0dd33a8fca9146e456ca688555ed4ec1361a2180b778da0fe42da181a46677a043b3d9d4b8bb05a6a17b7b5c010c17e7c1d31cfb7c4f911a0c89f0e2c53241cdb578e1f2b4caf6ba36e00500bdc57fecd66b84a6a58394c19a086c3c1fae5a0575940b5d38e111c469d07883106c26856f3ef608469a2081f13a06c5992ff00aab6226a70a032fd2f571ba22f797321f45e2daa73020d638d21b0a050861e9503ef68728f6c90a44f7fe1bceb2a9bdab6957bbe7136166bd849561ea006aa6eaca8a07e57176e9aa41e6a09edfb7678d1a112404e0ec779d7e567e82ea0bb0b430d303ba21b0af11c487b8a218bd75db54c98940b3f11bad8ff47cad3ef8080808080808080").to_vec(),
406			hex!("f871a0246de222036ee6a03329b0105da0a6b3f916fc95a9ed5a403a581a0c4d74242ca0ac108a49a88b57a05ac34a108b39f1e45f6f167f2b9fbc8d52fb58e2e5a6af1ea0fcfe07ac2ccd3c28b6eab68d1bce112f6f6dbd9023e4ec3c05b96615aa803d798080808080808080808080808080").to_vec(),
407			hex!("e4820001a04fff54398cad4d05ea6abfd8b0f3b4fe14c04d7ff5f5211c5b927d9cf72ac1d8").to_vec(),
408			hex!("f851a096d010643ca2d47412ca66898286b5f2412963b9ec051b33e570d575914c9c5ca028cd24c652989542fe89479ec6388eac4592432242af5ba97563b3ac7c71c019808080808080808080808080808080").to_vec(),
409			hex!("f90211a0bb35a84c5b1dcb78ec9d32614912c696e62df77bebf9ab326ee55b5d3acdde46a01084b30dac8df0accfcd0fd6330b7f6fc72a4651246d0694be9162151686a620a03eed50afdce7909d784c6157c445a444c806b5f23d31f3b63786f600c84a95b2a0af5232f1df6c6d41879804d081abe867002abe26ba3e5f8e0254a83a54769831a0607915fb13dd5da594256389a45007a67a7f7a86e95d38d8462792b6c98a722ea00e1260fda1730f2738c650ce2bfba83857bc10f8fb119ebc4fb39acba24e6fbaa0d11de17e417327457812675ca3b84ae8e1b64827abfe01420953697c8313d5b1a05fcaf2f7a88f76336a0c32ffc78acb87ae2005454bd25d658035331be3173b46a03f94f4952ab9e650f83cfd0e7f367b1bcc493aacf39a06f16c4a2e1b5605da48a0bdb4ec79785ca8ae22d60f1bbd42d707b4d7ec4aff231a3ebab755e315b35053a043a67c3f2bcef37c8f47a673adcb7061007a553696d1092408601c11b2e6846aa0c519d5af48cae87c7f4538845417c9735813bee892a6fe2dda79f5c414e8576aa0f7058256e09589501d7c231d739e61c84a850e139690989d24fda6058b432e98a081a52faab520978cb19ce14400dba0cd5bcdc4e5a3c0740678aa8f97ee0e5c56a0bcecc61cadeae52518e3b68a48af4b11603dfd9d99d99d7985efa6d2de44f904a02cba4accfc6f39bc5adb6d4440eb6358b4a5103ef93298e4e694f1f940f8b48280").to_vec(),
410			hex!("f901ae20b901aaf901a70183bb444eb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000001000000000000000000000000000100000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000080000000000000000000000000000000000000000000000002000000000000000000081000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000f89df89b94dac17f958d2ee523a2206206994597c13d831ec7f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000002e514404ff6823f1b46a8318a709251db414e5e1a000000000000000000000000055021c55847c00d764357a352e5803237d328954a0000000000000000000000000000000000000000000000000000000000201c370").to_vec(),
411		];
412		assert!(header.check_receipt_proof(&proof_receipt263).is_some());
413	}
414}