anvil_core/eth/
block.rs

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