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