Skip to main content

anvil_core/eth/
block.rs

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
14/// Type alias for a block containing potentially impersonated transactions.
15pub type Block<T = FoundryTxEnvelope, H = FoundryHeader> =
16    alloy_consensus::Block<MaybeImpersonatedTransaction<T>, H>;
17
18/// Container type that gathers all block data, generic over a [`Network`].
19#[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
26/// A transaction that can be encoded for inclusion in a block body.
27pub trait EncodableBlockTransaction: Encodable2718 {
28    /// Encodes the transaction in its canonical block-body form.
29    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
43/// Drops pooled sidecars so a transaction uses its canonical block-body representation.
44pub 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
53/// Returns a block whose transactions use canonical block-body representations.
54pub 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
60/// Helper function to create a new block with Header and Anvil transactions, generic over the
61/// transaction envelope with a default of [`FoundryTxEnvelope`].
62///
63/// Note: if the `impersonate-tx` feature is enabled this will also accept
64/// `MaybeImpersonatedTransaction`.
65pub 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    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
220    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    // Test vector from: https://github.com/ethereum/tests/blob/f47bbef4da376a49c8fc3166f09ab8a6d182f765/BlockchainTests/ValidBlocks/bcEIP1559/baseFee.json#L15-L36
253    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    // Test vector from network
286    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        // encode and check that it matches the original data
294        let mut encoded = Vec::new();
295        block.encode(&mut encoded);
296        assert_eq!(data, encoded);
297
298        // check that length of encoding is the same as the output of `length`
299        assert_eq!(block.length(), encoded.len());
300    }
301}