1use super::transaction::{TransactionInfo, TypedReceipt};
2use alloy_consensus::{proofs::calculate_transaction_root, Header, EMPTY_OMMER_ROOT_HASH};
3use alloy_primitives::{Address, Bloom, Bytes, B256, B64, U256};
4use alloy_rlp::{RlpDecodable, RlpEncodable};
5
6type Transaction = crate::eth::transaction::MaybeImpersonatedTransaction;
8
9#[derive(Clone, Debug)]
11pub struct BlockInfo {
12 pub block: Block,
13 pub transactions: Vec<TransactionInfo>,
14 pub receipts: Vec<TypedReceipt>,
15}
16
17#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable)]
19pub struct Block {
20 pub header: Header,
21 pub transactions: Vec<Transaction>,
22 pub ommers: Vec<Header>,
23}
24
25impl Block {
26 pub fn new<T>(partial_header: PartialHeader, transactions: impl IntoIterator<Item = T>) -> Self
31 where
32 T: Into<Transaction>,
33 {
34 let transactions: Vec<_> = transactions.into_iter().map(Into::into).collect();
35 let transactions1: Vec<super::transaction::TypedTransaction> =
36 transactions.clone().into_iter().map(Into::into).collect();
37 let transactions_root = calculate_transaction_root(&transactions1);
38
39 Self {
40 header: Header {
41 parent_hash: partial_header.parent_hash,
42 beneficiary: partial_header.beneficiary,
43 ommers_hash: EMPTY_OMMER_ROOT_HASH,
44 state_root: partial_header.state_root,
45 transactions_root,
46 receipts_root: partial_header.receipts_root,
47 logs_bloom: partial_header.logs_bloom,
48 difficulty: partial_header.difficulty,
49 number: partial_header.number,
50 gas_limit: partial_header.gas_limit,
51 gas_used: partial_header.gas_used,
52 timestamp: partial_header.timestamp,
53 extra_data: partial_header.extra_data,
54 mix_hash: partial_header.mix_hash,
55 withdrawals_root: partial_header.withdrawals_root,
56 blob_gas_used: partial_header.blob_gas_used,
57 excess_blob_gas: partial_header.excess_blob_gas,
58 parent_beacon_block_root: partial_header.parent_beacon_block_root,
59 nonce: partial_header.nonce,
60 base_fee_per_gas: partial_header.base_fee,
61 requests_hash: partial_header.requests_hash,
62 },
63 transactions,
64 ommers: vec![],
65 }
66 }
67}
68
69#[derive(Clone, Debug, Default, PartialEq, Eq)]
71pub struct PartialHeader {
72 pub parent_hash: B256,
73 pub beneficiary: Address,
74 pub state_root: B256,
75 pub receipts_root: B256,
76 pub logs_bloom: Bloom,
77 pub difficulty: U256,
78 pub number: u64,
79 pub gas_limit: u64,
80 pub gas_used: u64,
81 pub timestamp: u64,
82 pub extra_data: Bytes,
83 pub mix_hash: B256,
84 pub blob_gas_used: Option<u64>,
85 pub excess_blob_gas: Option<u64>,
86 pub parent_beacon_block_root: Option<B256>,
87 pub nonce: B64,
88 pub base_fee: Option<u64>,
89 pub withdrawals_root: Option<B256>,
90 pub requests_hash: Option<B256>,
91}
92
93impl From<Header> for PartialHeader {
94 fn from(value: Header) -> Self {
95 Self {
96 parent_hash: value.parent_hash,
97 beneficiary: value.beneficiary,
98 state_root: value.state_root,
99 receipts_root: value.receipts_root,
100 logs_bloom: value.logs_bloom,
101 difficulty: value.difficulty,
102 number: value.number,
103 gas_limit: value.gas_limit,
104 gas_used: value.gas_used,
105 timestamp: value.timestamp,
106 extra_data: value.extra_data,
107 mix_hash: value.mix_hash,
108 nonce: value.nonce,
109 base_fee: value.base_fee_per_gas,
110 blob_gas_used: value.blob_gas_used,
111 excess_blob_gas: value.excess_blob_gas,
112 parent_beacon_block_root: value.parent_beacon_block_root,
113 requests_hash: value.requests_hash,
114 withdrawals_root: value.withdrawals_root,
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use alloy_primitives::{
122 b256,
123 hex::{self, FromHex},
124 };
125 use alloy_rlp::Decodable;
126
127 use super::*;
128 use std::str::FromStr;
129
130 #[test]
131 fn header_rlp_roundtrip() {
132 let mut header = Header {
133 parent_hash: Default::default(),
134 ommers_hash: Default::default(),
135 beneficiary: Default::default(),
136 state_root: Default::default(),
137 transactions_root: Default::default(),
138 receipts_root: Default::default(),
139 logs_bloom: Default::default(),
140 difficulty: Default::default(),
141 number: 124u64,
142 gas_limit: Default::default(),
143 gas_used: 1337u64,
144 timestamp: 0,
145 extra_data: Default::default(),
146 mix_hash: Default::default(),
147 nonce: B64::with_last_byte(99),
148 withdrawals_root: Default::default(),
149 blob_gas_used: Default::default(),
150 excess_blob_gas: Default::default(),
151 parent_beacon_block_root: Default::default(),
152 base_fee_per_gas: None,
153 requests_hash: None,
154 };
155
156 let encoded = alloy_rlp::encode(&header);
157 let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap();
158 assert_eq!(header, decoded);
159
160 header.base_fee_per_gas = Some(12345u64);
161
162 let encoded = alloy_rlp::encode(&header);
163 let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap();
164 assert_eq!(header, decoded);
165 }
166
167 #[test]
168 fn test_encode_block_header() {
169 use alloy_rlp::Encodable;
170
171 let expected = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap();
172 let mut data = vec![];
173 let header = Header {
174 parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
175 ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
176 beneficiary: Address::from_str("0000000000000000000000000000000000000000").unwrap(),
177 state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
178 transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
179 receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
180 logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(),
181 difficulty: U256::from(2222),
182 number: 0xd05u64,
183 gas_limit: 0x115cu64,
184 gas_used: 0x15b3u64,
185 timestamp: 0x1a0au64,
186 extra_data: hex::decode("7788").unwrap().into(),
187 mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
188 withdrawals_root: None,
189 blob_gas_used: None,
190 excess_blob_gas: None,
191 parent_beacon_block_root: None,
192 nonce: B64::ZERO,
193 base_fee_per_gas: None,
194 requests_hash: None,
195 };
196
197 header.encode(&mut data);
198 assert_eq!(hex::encode(&data), hex::encode(expected));
199 assert_eq!(header.length(), data.len());
200 }
201
202 #[test]
203 fn test_decode_block_header() {
205 let data = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap();
206 let expected = Header {
207 parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
208 ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
209 beneficiary: Address::from_str("0000000000000000000000000000000000000000").unwrap(),
210 state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
211 transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
212 receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
213 logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(),
214 difficulty: U256::from(2222),
215 number: 0xd05u64,
216 gas_limit: 0x115cu64,
217 gas_used: 0x15b3u64,
218 timestamp: 0x1a0au64,
219 extra_data: hex::decode("7788").unwrap().into(),
220 mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
221 nonce: B64::ZERO,
222 withdrawals_root: None,
223 blob_gas_used: None,
224 excess_blob_gas: None,
225 parent_beacon_block_root: None,
226 base_fee_per_gas: None,
227 requests_hash: None,
228 };
229 let header = Header::decode(&mut data.as_slice()).unwrap();
230 assert_eq!(header, expected);
231 }
232
233 #[test]
234 fn test_eip1559_block_header_hash() {
236 let expected_hash =
237 b256!("0x6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f");
238 let header = Header {
239 parent_hash: B256::from_str("e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2a").unwrap(),
240 ommers_hash: B256::from_str("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap(),
241 beneficiary: Address::from_str("ba5e000000000000000000000000000000000000").unwrap(),
242 state_root: B256::from_str("ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7").unwrap(),
243 transactions_root: B256::from_str("50f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accf").unwrap(),
244 receipts_root: B256::from_str("29b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9").unwrap(),
245 logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(),
246 difficulty: U256::from(0x020000),
247 number: 1u64,
248 gas_limit: U256::from(0x016345785d8a0000u128).to::<u64>(),
249 gas_used: U256::from(0x015534).to::<u64>(),
250 timestamp: 0x079e,
251 extra_data: hex::decode("42").unwrap().into(),
252 mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
253 nonce: B64::ZERO,
254 base_fee_per_gas: Some(875),
255 withdrawals_root: None,
256 blob_gas_used: None,
257 excess_blob_gas: None,
258 parent_beacon_block_root: None,
259 requests_hash: None,
260 };
261 assert_eq!(header.hash_slow(), expected_hash);
262 }
263
264 #[test]
265 fn block_network_roundtrip() {
267 use alloy_rlp::Encodable;
268
269 let data = hex::decode("f9034df90348a0fbdbd8d2d0ac5f14bd5fa90e547fe6f1d15019c724f8e7b60972d381cd5d9cf8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c9577e7945db22e38fc060909f2278c7746b0f9ba05017cfa3b0247e35197215ae8d610265ffebc8edca8ea66d6567eb0adecda867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018355bb7b871fffffffffffff808462bd0e1ab9014bf90148a00000000000000000000000000000000000000000000000000000000000000000f85494319fa8f1bc4e53410e92d10d918659b16540e60a945a573efb304d04c1224cd012313e827eca5dce5d94a9c831c5a268031176ebf5f3de5051e8cba0dbfe94c9577e7945db22e38fc060909f2278c7746b0f9b808400000000f8c9b841a6946f2d16f68338cbcbd8b117374ab421128ce422467088456bceba9d70c34106128e6d4564659cf6776c08a4186063c0a05f7cffd695c10cf26a6f301b67f800b8412b782100c18c35102dc0a37ece1a152544f04ad7dc1868d18a9570f744ace60870f822f53d35e89a2ea9709ccbf1f4a25ee5003944faa845d02dde0a41d5704601b841d53caebd6c8a82456e85c2806a9e08381f959a31fb94a77e58f00e38ad97b2e0355b8519ab2122662cbe022f2a4ef7ff16adc0b2d5dcd123181ec79705116db300a063746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365880000000000000000c0c0").unwrap();
270
271 let block = Block::decode(&mut data.as_slice()).unwrap();
272
273 let mut encoded = Vec::new();
275 block.encode(&mut encoded);
276 assert_eq!(data, encoded);
277
278 assert_eq!(block.length(), encoded.len());
280 }
281}