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, Typed2718, proofs::ordered_trie_root_with_encoder,
6    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/// Returns a block whose transactions use canonical block-body representations.
44pub fn canonical_block(mut block: Block) -> Block {
45    block.body.transactions = block
46        .body
47        .transactions
48        .into_iter()
49        .map(|tx| tx.map(FoundryTxEnvelope::into_canonical))
50        .collect();
51    block
52}
53
54/// Helper function to create a new block with Header and Anvil transactions, generic over the
55/// transaction envelope with a default of [`FoundryTxEnvelope`].
56///
57/// Note: if the `impersonate-tx` feature is enabled this will also accept
58/// `MaybeImpersonatedTransaction`.
59pub fn create_block<T, Tx>(
60    mut header: FoundryHeader,
61    transactions: impl IntoIterator<Item = T>,
62) -> Block<Tx>
63where
64    Tx: EncodableBlockTransaction,
65    T: Into<MaybeImpersonatedTransaction<Tx>>,
66{
67    let transactions: Vec<_> = transactions.into_iter().map(Into::into).collect();
68    let transactions_root = ordered_trie_root_with_encoder(&transactions, |tx, out| {
69        tx.as_ref().encode_2718_for_block(out)
70    });
71
72    header.set_transactions_root(transactions_root);
73    header.set_ommers_hash(EMPTY_OMMER_ROOT_HASH);
74
75    let body = BlockBody { transactions, ommers: Vec::new(), withdrawals: None };
76    Block::new(header, body)
77}
78
79#[cfg(test)]
80mod tests {
81    use alloy_consensus::{
82        BlobTransactionSidecar, BlobTransactionSidecarVariant, BlockHeader, SignableTransaction,
83        TxEip4844, TxEip4844Variant, proofs::calculate_transaction_root,
84    };
85    use alloy_primitives::{
86        Address, B64, B256, Bloom, Signature, U256, b256,
87        hex::{self, FromHex},
88    };
89    use alloy_rlp::Decodable;
90
91    use super::*;
92    use std::str::FromStr;
93
94    fn assert_blob_transaction_root(sidecar: BlobTransactionSidecarVariant) {
95        let tx = TxEip4844 {
96            chain_id: 1,
97            nonce: 0,
98            gas_limit: 21_000,
99            max_fee_per_gas: 1,
100            max_priority_fee_per_gas: 1,
101            to: Address::ZERO,
102            value: U256::ZERO,
103            access_list: Default::default(),
104            blob_versioned_hashes: vec![B256::ZERO],
105            max_fee_per_blob_gas: 1,
106            input: Default::default(),
107        };
108        let signature = Signature::new(U256::from(1), U256::from(1), false);
109        let canonical = FoundryTxEnvelope::Eip4844(
110            TxEip4844Variant::TxEip4844(tx.clone()).into_signed(signature),
111        );
112        let pooled = FoundryTxEnvelope::Eip4844(
113            TxEip4844Variant::TxEip4844WithSidecar(tx.with_sidecar(sidecar)).into_signed(signature),
114        );
115
116        let canonical_root = calculate_transaction_root(&[canonical]);
117        let pooled_root = calculate_transaction_root(std::slice::from_ref(&pooled));
118        let block = create_block(FoundryHeader::default(), [pooled]);
119
120        assert_ne!(canonical_root, pooled_root);
121        assert_eq!(block.header.transactions_root(), canonical_root);
122    }
123
124    #[test]
125    fn blob_transaction_root_uses_canonical_encoding() {
126        assert_blob_transaction_root(BlobTransactionSidecarVariant::Eip4844(
127            BlobTransactionSidecar::new(
128                vec![Default::default()],
129                vec![Default::default()],
130                vec![Default::default()],
131            ),
132        ));
133        assert_blob_transaction_root(BlobTransactionSidecarVariant::Eip7594(Default::default()));
134    }
135
136    #[test]
137    fn header_rlp_roundtrip() {
138        let mut header = Header {
139            parent_hash: Default::default(),
140            ommers_hash: Default::default(),
141            beneficiary: Default::default(),
142            state_root: Default::default(),
143            transactions_root: Default::default(),
144            receipts_root: Default::default(),
145            logs_bloom: Default::default(),
146            difficulty: Default::default(),
147            number: 124u64,
148            gas_limit: Default::default(),
149            gas_used: 1337u64,
150            timestamp: 0,
151            extra_data: Default::default(),
152            mix_hash: Default::default(),
153            nonce: B64::with_last_byte(99),
154            withdrawals_root: Default::default(),
155            blob_gas_used: Default::default(),
156            excess_blob_gas: Default::default(),
157            parent_beacon_block_root: Default::default(),
158            base_fee_per_gas: None,
159            requests_hash: None,
160            block_access_list_hash: None,
161            slot_number: None,
162        };
163
164        let encoded = alloy_rlp::encode(&header);
165        let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap();
166        assert_eq!(header, decoded);
167
168        header.base_fee_per_gas = Some(12345u64);
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
175    #[test]
176    fn test_encode_block_header() {
177        use alloy_rlp::Encodable;
178
179        let expected = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap();
180        let mut data = vec![];
181        let header = Header {
182            parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
183            ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
184            beneficiary: Address::from_str("0000000000000000000000000000000000000000").unwrap(),
185            state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
186            transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
187            receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
188            logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(),
189            difficulty: U256::from(2222),
190            number: 0xd05u64,
191            gas_limit: 0x115cu64,
192            gas_used: 0x15b3u64,
193            timestamp: 0x1a0au64,
194            extra_data: hex::decode("7788").unwrap().into(),
195            mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
196            withdrawals_root: None,
197            blob_gas_used: None,
198            excess_blob_gas: None,
199            parent_beacon_block_root: None,
200            nonce: B64::ZERO,
201            base_fee_per_gas: None,
202            requests_hash: None,
203            block_access_list_hash: None,
204            slot_number: None,
205        };
206
207        header.encode(&mut data);
208        assert_eq!(hex::encode(&data), hex::encode(expected));
209        assert_eq!(header.length(), data.len());
210    }
211
212    #[test]
213    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
214    fn test_decode_block_header() {
215        let data = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap();
216        let expected = Header {
217            parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
218            ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
219            beneficiary: Address::from_str("0000000000000000000000000000000000000000").unwrap(),
220            state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
221            transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
222            receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
223            logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(),
224            difficulty: U256::from(2222),
225            number: 0xd05u64,
226            gas_limit: 0x115cu64,
227            gas_used: 0x15b3u64,
228            timestamp: 0x1a0au64,
229            extra_data: hex::decode("7788").unwrap().into(),
230            mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
231            nonce: B64::ZERO,
232            withdrawals_root: None,
233            blob_gas_used: None,
234            excess_blob_gas: None,
235            parent_beacon_block_root: None,
236            base_fee_per_gas: None,
237            requests_hash: None,
238            block_access_list_hash: None,
239            slot_number: None,
240        };
241        let header = Header::decode(&mut data.as_slice()).unwrap();
242        assert_eq!(header, expected);
243    }
244
245    #[test]
246    // Test vector from: https://github.com/ethereum/tests/blob/f47bbef4da376a49c8fc3166f09ab8a6d182f765/BlockchainTests/ValidBlocks/bcEIP1559/baseFee.json#L15-L36
247    fn test_eip1559_block_header_hash() {
248        let expected_hash =
249            b256!("0x6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f");
250        let header = Header {
251            parent_hash: B256::from_str("e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2a").unwrap(),
252            ommers_hash: B256::from_str("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap(),
253            beneficiary: Address::from_str("ba5e000000000000000000000000000000000000").unwrap(),
254            state_root: B256::from_str("ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7").unwrap(),
255            transactions_root: B256::from_str("50f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accf").unwrap(),
256            receipts_root: B256::from_str("29b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9").unwrap(),
257            logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(),
258            difficulty: U256::from(0x020000),
259            number: 1u64,
260            gas_limit: U256::from(0x016345785d8a0000u128).to::<u64>(),
261            gas_used: U256::from(0x015534).to::<u64>(),
262            timestamp: 0x079e,
263            extra_data: hex::decode("42").unwrap().into(),
264            mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
265            nonce: B64::ZERO,
266            base_fee_per_gas: Some(875),
267            withdrawals_root: None,
268            blob_gas_used: None,
269            excess_blob_gas: None,
270            parent_beacon_block_root: None,
271            requests_hash: None,
272            block_access_list_hash: None,
273            slot_number: None,
274        };
275        assert_eq!(header.hash_slow(), expected_hash);
276    }
277
278    #[test]
279    // Test vector from network
280    fn block_network_roundtrip() {
281        use alloy_rlp::Encodable;
282
283        let data = hex::decode("f9034df90348a0fbdbd8d2d0ac5f14bd5fa90e547fe6f1d15019c724f8e7b60972d381cd5d9cf8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c9577e7945db22e38fc060909f2278c7746b0f9ba05017cfa3b0247e35197215ae8d610265ffebc8edca8ea66d6567eb0adecda867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018355bb7b871fffffffffffff808462bd0e1ab9014bf90148a00000000000000000000000000000000000000000000000000000000000000000f85494319fa8f1bc4e53410e92d10d918659b16540e60a945a573efb304d04c1224cd012313e827eca5dce5d94a9c831c5a268031176ebf5f3de5051e8cba0dbfe94c9577e7945db22e38fc060909f2278c7746b0f9b808400000000f8c9b841a6946f2d16f68338cbcbd8b117374ab421128ce422467088456bceba9d70c34106128e6d4564659cf6776c08a4186063c0a05f7cffd695c10cf26a6f301b67f800b8412b782100c18c35102dc0a37ece1a152544f04ad7dc1868d18a9570f744ace60870f822f53d35e89a2ea9709ccbf1f4a25ee5003944faa845d02dde0a41d5704601b841d53caebd6c8a82456e85c2806a9e08381f959a31fb94a77e58f00e38ad97b2e0355b8519ab2122662cbe022f2a4ef7ff16adc0b2d5dcd123181ec79705116db300a063746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365880000000000000000c0c0").unwrap();
284
285        let block = <Block>::decode(&mut data.as_slice()).unwrap();
286
287        // encode and check that it matches the original data
288        let mut encoded = Vec::new();
289        block.encode(&mut encoded);
290        assert_eq!(data, encoded);
291
292        // check that length of encoding is the same as the output of `length`
293        assert_eq!(block.length(), encoded.len());
294    }
295}