1use super::transaction::TransactionInfo;
2use crate::eth::transaction::MaybeImpersonatedTransaction;
3use alloy_consensus::{
4 BlockBody, BlockHeader, EMPTY_OMMER_ROOT_HASH, Typed2718,
5 proofs::ordered_trie_root_with_encoder, transaction::RlpEcdsaEncodableTx,
6};
7use alloy_eips::eip2718::Encodable2718;
8use alloy_network::Network;
9use foundry_primitives::{FoundryHeader, FoundryTxEnvelope};
10
11pub type Block<T = FoundryTxEnvelope, H = FoundryHeader> =
13 alloy_consensus::Block<MaybeImpersonatedTransaction<T>, H>;
14
15#[derive(Clone, Debug)]
17pub struct BlockInfo<N: Network> {
18 pub block: Block<N::TxEnvelope>,
19 pub transactions: Vec<TransactionInfo>,
20 pub receipts: Vec<N::ReceiptEnvelope>,
21}
22
23pub trait EncodableBlockTransaction: Encodable2718 {
25 fn encode_2718_for_block(&self, out: &mut dyn bytes::BufMut);
27}
28
29impl EncodableBlockTransaction for FoundryTxEnvelope {
30 fn encode_2718_for_block(&self, out: &mut dyn bytes::BufMut) {
31 if let Self::Eip4844(tx) = self {
32 out.put_u8(self.ty());
33 tx.tx().tx().rlp_encode_signed(tx.signature(), out);
34 } else {
35 self.encode_2718(out);
36 }
37 }
38}
39
40pub fn canonical_block(mut block: Block) -> Block {
42 block.body.transactions = block
43 .body
44 .transactions
45 .into_iter()
46 .map(|tx| tx.map(FoundryTxEnvelope::into_canonical))
47 .collect();
48 block
49}
50
51pub fn create_block<T, Tx>(
57 mut header: FoundryHeader,
58 transactions: impl IntoIterator<Item = T>,
59) -> Block<Tx>
60where
61 Tx: EncodableBlockTransaction,
62 T: Into<MaybeImpersonatedTransaction<Tx>>,
63{
64 let transactions: Vec<_> = transactions.into_iter().map(Into::into).collect();
65 let transactions_root = ordered_trie_root_with_encoder(&transactions, |tx, out| {
66 tx.as_ref().encode_2718_for_block(out)
67 });
68
69 header.set_transactions_root(transactions_root);
70 header.set_ommers_hash(EMPTY_OMMER_ROOT_HASH);
71
72 let withdrawals = header.withdrawals_root().map(|_| Default::default());
73 let body = BlockBody { transactions, ommers: Vec::new(), withdrawals };
74 Block::new(header, body)
75}
76
77#[cfg(test)]
78mod tests {
79
80 use alloy_consensus::{
81 BlobTransactionSidecar, BlobTransactionSidecarVariant, Header, SignableTransaction,
82 TxEip4844, TxEip4844Variant, proofs::calculate_transaction_root,
83 };
84 use alloy_primitives::{
85 Address, B64, B256, Bloom, Signature, U256, address, b256,
86 hex::{self, FromHex},
87 };
88 use alloy_rlp::Decodable;
89
90 use super::*;
91
92 fn assert_blob_transaction_root(sidecar: BlobTransactionSidecarVariant) {
93 let tx = TxEip4844 {
94 chain_id: 1,
95 nonce: 0,
96 gas_limit: 21_000,
97 max_fee_per_gas: 1,
98 max_priority_fee_per_gas: 1,
99 to: Address::ZERO,
100 value: U256::ZERO,
101 access_list: Default::default(),
102 blob_versioned_hashes: vec![B256::ZERO],
103 max_fee_per_blob_gas: 1,
104 input: Default::default(),
105 };
106 let signature = Signature::new(U256::ONE, U256::ONE, false);
107 let canonical = FoundryTxEnvelope::Eip4844(
108 TxEip4844Variant::TxEip4844(tx.clone()).into_signed(signature),
109 );
110 let pooled = FoundryTxEnvelope::Eip4844(
111 TxEip4844Variant::TxEip4844WithSidecar(tx.with_sidecar(sidecar)).into_signed(signature),
112 );
113
114 let canonical_root = calculate_transaction_root(&[canonical]);
115 let pooled_root = calculate_transaction_root(std::slice::from_ref(&pooled));
116 let block = create_block(FoundryHeader::default(), [pooled]);
117
118 assert_ne!(canonical_root, pooled_root);
119 assert_eq!(block.header.transactions_root(), canonical_root);
120 }
121
122 #[test]
123 fn blob_transaction_root_uses_canonical_encoding() {
124 assert_blob_transaction_root(BlobTransactionSidecarVariant::Eip4844(
125 BlobTransactionSidecar::new(
126 vec![Default::default()],
127 vec![Default::default()],
128 vec![Default::default()],
129 ),
130 ));
131 assert_blob_transaction_root(BlobTransactionSidecarVariant::Eip7594(Default::default()));
132 }
133
134 #[test]
135 fn header_rlp_roundtrip() {
136 let mut header = Header {
137 parent_hash: Default::default(),
138 ommers_hash: Default::default(),
139 beneficiary: Default::default(),
140 state_root: Default::default(),
141 transactions_root: Default::default(),
142 receipts_root: Default::default(),
143 logs_bloom: Default::default(),
144 difficulty: Default::default(),
145 number: 124u64,
146 gas_limit: Default::default(),
147 gas_used: 1337u64,
148 timestamp: 0,
149 extra_data: Default::default(),
150 mix_hash: Default::default(),
151 nonce: B64::with_last_byte(99),
152 withdrawals_root: Default::default(),
153 blob_gas_used: Default::default(),
154 excess_blob_gas: Default::default(),
155 parent_beacon_block_root: Default::default(),
156 base_fee_per_gas: None,
157 requests_hash: None,
158 block_access_list_hash: None,
159 slot_number: None,
160 };
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 header.base_fee_per_gas = Some(12345u64);
167
168 let encoded = alloy_rlp::encode(&header);
169 let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap();
170 assert_eq!(header, decoded);
171 }
172
173 #[test]
174 fn test_encode_block_header() {
175 use alloy_rlp::Encodable;
176
177 let expected = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap();
178 let mut data = vec![];
179 let header = Header {
180 parent_hash: B256::ZERO,
181 ommers_hash: B256::ZERO,
182 beneficiary: Address::ZERO,
183 state_root: B256::ZERO,
184 transactions_root: B256::ZERO,
185 receipts_root: B256::ZERO,
186 logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(),
187 difficulty: U256::from(2222),
188 number: 0xd05u64,
189 gas_limit: 0x115cu64,
190 gas_used: 0x15b3u64,
191 timestamp: 0x1a0au64,
192 extra_data: hex::decode("7788").unwrap().into(),
193 mix_hash: B256::ZERO,
194 withdrawals_root: None,
195 blob_gas_used: None,
196 excess_blob_gas: None,
197 parent_beacon_block_root: None,
198 nonce: B64::ZERO,
199 base_fee_per_gas: None,
200 requests_hash: None,
201 block_access_list_hash: None,
202 slot_number: None,
203 };
204
205 header.encode(&mut data);
206 assert_eq!(hex::encode(&data), hex::encode(expected));
207 assert_eq!(header.length(), data.len());
208 }
209
210 #[test]
211 fn test_decode_block_header() {
213 let data = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap();
214 let expected = Header {
215 parent_hash: B256::ZERO,
216 ommers_hash: B256::ZERO,
217 beneficiary: Address::ZERO,
218 state_root: B256::ZERO,
219 transactions_root: B256::ZERO,
220 receipts_root: B256::ZERO,
221 logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(),
222 difficulty: U256::from(2222),
223 number: 0xd05u64,
224 gas_limit: 0x115cu64,
225 gas_used: 0x15b3u64,
226 timestamp: 0x1a0au64,
227 extra_data: hex::decode("7788").unwrap().into(),
228 mix_hash: B256::ZERO,
229 nonce: B64::ZERO,
230 withdrawals_root: None,
231 blob_gas_used: None,
232 excess_blob_gas: None,
233 parent_beacon_block_root: None,
234 base_fee_per_gas: None,
235 requests_hash: None,
236 block_access_list_hash: None,
237 slot_number: None,
238 };
239 let header = Header::decode(&mut data.as_slice()).unwrap();
240 assert_eq!(header, expected);
241 }
242
243 #[test]
244 fn test_eip1559_block_header_hash() {
246 let expected_hash =
247 b256!("0x6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f");
248 let header = Header {
249 parent_hash: b256!("e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2a"),
250 ommers_hash: b256!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"),
251 beneficiary: address!("ba5e000000000000000000000000000000000000"),
252 state_root: b256!("ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7"),
253 transactions_root: b256!("50f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accf"),
254 receipts_root: b256!("29b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9"),
255 logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(),
256 difficulty: U256::from(0x020000),
257 number: 1u64,
258 gas_limit: U256::from(0x016345785d8a0000u128).to::<u64>(),
259 gas_used: U256::from(0x015534).to::<u64>(),
260 timestamp: 0x079e,
261 extra_data: hex::decode("42").unwrap().into(),
262 mix_hash: B256::ZERO,
263 nonce: B64::ZERO,
264 base_fee_per_gas: Some(875),
265 withdrawals_root: None,
266 blob_gas_used: None,
267 excess_blob_gas: None,
268 parent_beacon_block_root: None,
269 requests_hash: None,
270 block_access_list_hash: None,
271 slot_number: None,
272 };
273 assert_eq!(header.hash_slow(), expected_hash);
274 }
275
276 #[test]
277 fn block_network_roundtrip() {
279 use alloy_rlp::Encodable;
280
281 let data = hex::decode("f9034df90348a0fbdbd8d2d0ac5f14bd5fa90e547fe6f1d15019c724f8e7b60972d381cd5d9cf8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c9577e7945db22e38fc060909f2278c7746b0f9ba05017cfa3b0247e35197215ae8d610265ffebc8edca8ea66d6567eb0adecda867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018355bb7b871fffffffffffff808462bd0e1ab9014bf90148a00000000000000000000000000000000000000000000000000000000000000000f85494319fa8f1bc4e53410e92d10d918659b16540e60a945a573efb304d04c1224cd012313e827eca5dce5d94a9c831c5a268031176ebf5f3de5051e8cba0dbfe94c9577e7945db22e38fc060909f2278c7746b0f9b808400000000f8c9b841a6946f2d16f68338cbcbd8b117374ab421128ce422467088456bceba9d70c34106128e6d4564659cf6776c08a4186063c0a05f7cffd695c10cf26a6f301b67f800b8412b782100c18c35102dc0a37ece1a152544f04ad7dc1868d18a9570f744ace60870f822f53d35e89a2ea9709ccbf1f4a25ee5003944faa845d02dde0a41d5704601b841d53caebd6c8a82456e85c2806a9e08381f959a31fb94a77e58f00e38ad97b2e0355b8519ab2122662cbe022f2a4ef7ff16adc0b2d5dcd123181ec79705116db300a063746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365880000000000000000c0c0").unwrap();
282
283 let block = <Block>::decode(&mut data.as_slice()).unwrap();
284
285 let mut encoded = Vec::new();
287 block.encode(&mut encoded);
288 assert_eq!(data, encoded);
289
290 assert_eq!(block.length(), encoded.len());
292 }
293
294 #[test]
295 fn created_block_preserves_withdrawals_commitment() {
296 for withdrawals_root in [None, Some(alloy_consensus::EMPTY_ROOT_HASH)] {
297 let header =
298 Header { base_fee_per_gas: Some(1), withdrawals_root, ..Default::default() };
299 let block = create_block::<FoundryTxEnvelope, _>(header.into(), []);
300 assert_eq!(block.body.calculate_withdrawals_root(), withdrawals_root);
301 let encoded = alloy_rlp::encode(&block);
302 let decoded = <Block>::decode(&mut encoded.as_slice()).unwrap();
303 assert_eq!(decoded.body.withdrawals, block.body.withdrawals);
304 }
305 }
306}