Skip to main content

reth_provider/test_utils/
blocks.rs

1//! Dummy blocks and data for tests
2use crate::{DBProvider, DatabaseProviderRW, ExecutionOutcome};
3use alloy_consensus::{TxLegacy, EMPTY_OMMER_ROOT_HASH};
4use alloy_primitives::{
5    b256, hex_literal::hex, map::HashMap, Address, BlockNumber, Bytes, Log, TxKind, B256, U256,
6};
7
8use alloy_consensus::Header;
9use alloy_eips::eip4895::{Withdrawal, Withdrawals};
10use alloy_primitives::Signature;
11use reth_db_api::{database::Database, models::StoredBlockBodyIndices, tables};
12use reth_ethereum_primitives::{BlockBody, Receipt, Transaction, TransactionSigned, TxType};
13use reth_node_types::NodeTypes;
14use reth_primitives_traits::{Account, RecoveredBlock, SealedBlock, SealedHeader};
15use reth_trie::root::{state_root_unhashed, storage_root_unhashed};
16use revm::{database::BundleState, state::AccountInfo};
17use std::{str::FromStr, sync::LazyLock};
18
19/// Assert genesis block
20pub fn assert_genesis_block<DB: Database, N: NodeTypes>(
21    provider: &DatabaseProviderRW<DB, N>,
22    g: SealedBlock<reth_ethereum_primitives::Block>,
23) {
24    let n = g.number;
25    let h = B256::ZERO;
26    let tx = provider;
27
28    // check if tables contain only the genesis block data
29    assert_eq!(tx.table::<tables::Headers>().unwrap(), vec![(g.number, g.header().clone())]);
30
31    assert_eq!(tx.table::<tables::HeaderNumbers>().unwrap(), vec![(h, n)]);
32    assert_eq!(tx.table::<tables::CanonicalHeaders>().unwrap(), vec![(n, h)]);
33    assert_eq!(
34        tx.table::<tables::BlockBodyIndices>().unwrap(),
35        vec![(0, StoredBlockBodyIndices::default())]
36    );
37    assert_eq!(tx.table::<tables::BlockOmmers>().unwrap(), vec![]);
38    assert_eq!(tx.table::<tables::BlockWithdrawals>().unwrap(), vec![]);
39    assert_eq!(tx.table::<tables::Transactions>().unwrap(), vec![]);
40    assert_eq!(tx.table::<tables::TransactionBlocks>().unwrap(), vec![]);
41    assert_eq!(tx.table::<tables::TransactionHashNumbers>().unwrap(), vec![]);
42    assert_eq!(tx.table::<tables::Receipts>().unwrap(), vec![]);
43    assert_eq!(tx.table::<tables::PlainAccountState>().unwrap(), vec![]);
44    assert_eq!(tx.table::<tables::PlainStorageState>().unwrap(), vec![]);
45    assert_eq!(tx.table::<tables::AccountsHistory>().unwrap(), vec![]);
46    assert_eq!(tx.table::<tables::StoragesHistory>().unwrap(), vec![]);
47    // Reorged bytecodes are not reverted per https://github.com/paradigmxyz/reth/issues/1588
48    // assert_eq!(tx.table::<tables::Bytecodes>().unwrap(), vec![]);
49    assert_eq!(tx.table::<tables::AccountChangeSets>().unwrap(), vec![]);
50    assert_eq!(tx.table::<tables::StorageChangeSets>().unwrap(), vec![]);
51    assert_eq!(tx.table::<tables::HashedAccounts>().unwrap(), vec![]);
52    assert_eq!(tx.table::<tables::HashedStorages>().unwrap(), vec![]);
53    assert_eq!(tx.table::<tables::AccountsTrie>().unwrap(), vec![]);
54    assert_eq!(tx.table::<tables::StoragesTrie>().unwrap(), vec![]);
55    assert_eq!(tx.table::<tables::TransactionSenders>().unwrap(), vec![]);
56    // StageCheckpoints is not updated in tests
57}
58
59pub(crate) static TEST_BLOCK: LazyLock<SealedBlock<reth_ethereum_primitives::Block>> =
60    LazyLock::new(|| {
61        SealedBlock::from_sealed_parts(
62            SealedHeader::new(
63                Header {
64                    parent_hash: hex!(
65                        "c86e8cc0310ae7c531c758678ddbfd16fc51c8cef8cec650b032de9869e8b94f"
66                    )
67                    .into(),
68                    ommers_hash: EMPTY_OMMER_ROOT_HASH,
69                    beneficiary: hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba").into(),
70                    state_root: hex!(
71                        "50554882fbbda2c2fd93fdc466db9946ea262a67f7a76cc169e714f105ab583d"
72                    )
73                    .into(),
74                    transactions_root: hex!(
75                        "0967f09ef1dfed20c0eacfaa94d5cd4002eda3242ac47eae68972d07b106d192"
76                    )
77                    .into(),
78                    receipts_root: hex!(
79                        "e3c8b47fbfc94667ef4cceb17e5cc21e3b1eebd442cebb27f07562b33836290d"
80                    )
81                    .into(),
82                    difficulty: U256::from(131_072),
83                    number: 0,
84                    gas_limit: 1_000_000,
85                    gas_used: 14_352,
86                    timestamp: 1_000,
87                    ..Default::default()
88                },
89                hex!("cf7b274520720b50e6a4c3e5c4d553101f44945396827705518ce17cb7219a42").into(),
90            ),
91            BlockBody {
92                transactions: vec![TransactionSigned::new_unhashed(
93            Transaction::Legacy(TxLegacy {
94                gas_price: 10,
95                gas_limit: 400_000,
96                to: TxKind::Call(hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87").into()),
97                ..Default::default()
98            }),
99            Signature::new(
100                U256::from_str(
101                    "51983300959770368863831494747186777928121405155922056726144551509338672451120",
102                )
103                .unwrap(),
104                U256::from_str(
105                    "29056683545955299640297374067888344259176096769870751649153779895496107008675",
106                )
107                .unwrap(),
108                false,
109            )
110        )],
111                ..Default::default()
112            },
113        )
114    });
115
116/// Test chain with genesis, blocks, execution results
117/// that have valid changesets.
118#[derive(Debug)]
119pub struct BlockchainTestData {
120    /// Genesis
121    pub genesis: SealedBlock<reth_ethereum_primitives::Block>,
122    /// Blocks with its execution result
123    pub blocks: Vec<(RecoveredBlock<reth_ethereum_primitives::Block>, ExecutionOutcome)>,
124}
125
126impl BlockchainTestData {
127    /// Create test data with two blocks that are connected, specifying their block numbers.
128    pub fn default_from_number(first: BlockNumber) -> Self {
129        let one = block1(first);
130        let mut extended_execution_outcome = one.1.clone();
131        let two = block2(first + 1, one.0.hash(), &extended_execution_outcome);
132        extended_execution_outcome.extend(two.1.clone());
133        let three = block3(first + 2, two.0.hash(), &extended_execution_outcome);
134        extended_execution_outcome.extend(three.1.clone());
135        let four = block4(first + 3, three.0.hash(), &extended_execution_outcome);
136        extended_execution_outcome.extend(four.1.clone());
137        let five = block5(first + 4, four.0.hash(), &extended_execution_outcome);
138        Self { genesis: genesis(), blocks: vec![one, two, three, four, five] }
139    }
140}
141
142impl Default for BlockchainTestData {
143    fn default() -> Self {
144        let one = block1(1);
145        let mut extended_execution_outcome = one.1.clone();
146        let two = block2(2, one.0.hash(), &extended_execution_outcome);
147        extended_execution_outcome.extend(two.1.clone());
148        let three = block3(3, two.0.hash(), &extended_execution_outcome);
149        extended_execution_outcome.extend(three.1.clone());
150        let four = block4(4, three.0.hash(), &extended_execution_outcome);
151        extended_execution_outcome.extend(four.1.clone());
152        let five = block5(5, four.0.hash(), &extended_execution_outcome);
153        Self { genesis: genesis(), blocks: vec![one, two, three, four, five] }
154    }
155}
156
157/// Genesis block
158pub fn genesis() -> SealedBlock<reth_ethereum_primitives::Block> {
159    SealedBlock::from_sealed_parts(
160        SealedHeader::new(
161            Header { number: 0, difficulty: U256::from(1), ..Default::default() },
162            B256::ZERO,
163        ),
164        Default::default(),
165    )
166}
167
168fn bundle_state_root(execution_outcome: &ExecutionOutcome) -> B256 {
169    state_root_unhashed(execution_outcome.bundle_accounts_iter().filter_map(
170        |(address, account)| {
171            account.info.as_ref().map(|info| {
172                (
173                    address,
174                    Account::from(info).into_trie_account(storage_root_unhashed(
175                        account
176                            .storage
177                            .iter()
178                            .filter(|(_, value)| !value.present_value.is_zero())
179                            .map(|(slot, value)| ((*slot).into(), value.present_value)),
180                    )),
181                )
182            })
183        },
184    ))
185}
186
187/// Block one that points to genesis
188fn block1(
189    number: BlockNumber,
190) -> (RecoveredBlock<reth_ethereum_primitives::Block>, ExecutionOutcome) {
191    // block changes
192    let account1: Address = [0x60; 20].into();
193    let account2: Address = [0x61; 20].into();
194    let slot = U256::from(5);
195    let info = AccountInfo { nonce: 1, balance: U256::from(10), ..Default::default() };
196
197    let execution_outcome = ExecutionOutcome::new(
198        BundleState::builder(number..=number)
199            .state_present_account_info(account1, info.clone())
200            .revert_account_info(number, account1, Some(None))
201            .state_present_account_info(account2, info)
202            .revert_account_info(number, account2, Some(None))
203            .state_storage(account1, HashMap::from_iter([(slot, (U256::ZERO, U256::from(10)))]))
204            .build(),
205        vec![vec![Receipt {
206            tx_type: TxType::Eip2930,
207            success: true,
208            cumulative_gas_used: 300,
209            logs: vec![Log::new_unchecked(
210                Address::new([0x60; 20]),
211                vec![B256::with_last_byte(1), B256::with_last_byte(2)],
212                Bytes::default(),
213            )],
214        }]],
215        number,
216        Vec::new(),
217    );
218
219    let state_root = bundle_state_root(&execution_outcome);
220    assert_eq!(
221        state_root,
222        b256!("0x5d035ccb3e75a9057452ff060b773b213ec1fc353426174068edfc3971a0b6bd")
223    );
224
225    let (mut header, mut body) = TEST_BLOCK.clone().split_header_body();
226    body.withdrawals = Some(Withdrawals::new(vec![Withdrawal::default()]));
227    header.number = number;
228    header.state_root = state_root;
229    header.parent_hash = B256::ZERO;
230    let block = SealedBlock::seal_parts(header, body);
231
232    (RecoveredBlock::new_sealed(block, vec![Address::new([0x30; 20])]), execution_outcome)
233}
234
235/// Block two that points to block 1
236fn block2(
237    number: BlockNumber,
238    parent_hash: B256,
239    prev_execution_outcome: &ExecutionOutcome,
240) -> (RecoveredBlock<reth_ethereum_primitives::Block>, ExecutionOutcome) {
241    // block changes
242    let account: Address = [0x60; 20].into();
243    let slot = U256::from(5);
244
245    let execution_outcome = ExecutionOutcome::new(
246        BundleState::builder(number..=number)
247            .state_present_account_info(
248                account,
249                AccountInfo { nonce: 3, balance: U256::from(20), ..Default::default() },
250            )
251            .state_storage(account, HashMap::from_iter([(slot, (U256::ZERO, U256::from(15)))]))
252            .revert_account_info(
253                number,
254                account,
255                Some(Some(AccountInfo { nonce: 1, balance: U256::from(10), ..Default::default() })),
256            )
257            .revert_storage(number, account, Vec::from([(slot, U256::from(10))]))
258            .build(),
259        vec![vec![Receipt {
260            tx_type: TxType::Eip1559,
261            success: false,
262            cumulative_gas_used: 400,
263            logs: vec![Log::new_unchecked(
264                Address::new([0x61; 20]),
265                vec![B256::with_last_byte(3), B256::with_last_byte(4)],
266                Bytes::default(),
267            )],
268        }]],
269        number,
270        Vec::new(),
271    );
272
273    let mut extended = prev_execution_outcome.clone();
274    extended.extend(execution_outcome.clone());
275    let state_root = bundle_state_root(&extended);
276    assert_eq!(
277        state_root,
278        b256!("0x90101a13dd059fa5cca99ed93d1dc23657f63626c5b8f993a2ccbdf7446b64f8")
279    );
280
281    let (mut header, mut body) = TEST_BLOCK.clone().split_header_body();
282
283    body.withdrawals = Some(Withdrawals::new(vec![Withdrawal::default()]));
284    header.number = number;
285    header.state_root = state_root;
286    // parent_hash points to block1 hash
287    header.parent_hash = parent_hash;
288    let block = SealedBlock::seal_parts(header, body);
289
290    (RecoveredBlock::new_sealed(block, vec![Address::new([0x31; 20])]), execution_outcome)
291}
292
293/// Block three that points to block 2
294fn block3(
295    number: BlockNumber,
296    parent_hash: B256,
297    prev_execution_outcome: &ExecutionOutcome,
298) -> (RecoveredBlock<reth_ethereum_primitives::Block>, ExecutionOutcome) {
299    let address_range = 1..=20;
300    let slot_range = 1..=100;
301
302    let mut bundle_state_builder = BundleState::builder(number..=number);
303    for idx in address_range {
304        let address = Address::with_last_byte(idx);
305        bundle_state_builder = bundle_state_builder
306            .state_present_account_info(
307                address,
308                AccountInfo { nonce: 1, balance: U256::from(idx), ..Default::default() },
309            )
310            .state_storage(
311                address,
312                slot_range
313                    .clone()
314                    .map(|slot| (U256::from(slot), (U256::ZERO, U256::from(slot))))
315                    .collect(),
316            )
317            .revert_account_info(number, address, Some(None))
318            .revert_storage(number, address, Vec::new());
319    }
320    let execution_outcome = ExecutionOutcome::new(
321        bundle_state_builder.build(),
322        vec![vec![Receipt {
323            tx_type: TxType::Eip1559,
324            success: true,
325            cumulative_gas_used: 400,
326            logs: vec![Log::new_unchecked(
327                Address::new([0x61; 20]),
328                vec![B256::with_last_byte(3), B256::with_last_byte(4)],
329                Bytes::default(),
330            )],
331        }]],
332        number,
333        Vec::new(),
334    );
335
336    let mut extended = prev_execution_outcome.clone();
337    extended.extend(execution_outcome.clone());
338    let state_root = bundle_state_root(&extended);
339
340    let (mut header, mut body) = TEST_BLOCK.clone().split_header_body();
341    body.withdrawals = Some(Withdrawals::new(vec![Withdrawal::default()]));
342    header.number = number;
343    header.state_root = state_root;
344    // parent_hash points to block1 hash
345    header.parent_hash = parent_hash;
346    let block = SealedBlock::seal_parts(header, body);
347
348    (RecoveredBlock::new_sealed(block, vec![Address::new([0x31; 20])]), execution_outcome)
349}
350
351/// Block four that points to block 3
352fn block4(
353    number: BlockNumber,
354    parent_hash: B256,
355    prev_execution_outcome: &ExecutionOutcome,
356) -> (RecoveredBlock<reth_ethereum_primitives::Block>, ExecutionOutcome) {
357    let address_range = 1..=20;
358    let slot_range = 1..=100;
359
360    let mut bundle_state_builder = BundleState::builder(number..=number);
361    for idx in address_range {
362        let address = Address::with_last_byte(idx);
363        // increase balance for every even account and destroy every odd
364        bundle_state_builder = if idx.is_multiple_of(2) {
365            bundle_state_builder
366                .state_present_account_info(
367                    address,
368                    AccountInfo { nonce: 1, balance: U256::from(idx * 2), ..Default::default() },
369                )
370                .state_storage(
371                    address,
372                    slot_range
373                        .clone()
374                        .map(|slot| (U256::from(slot), (U256::from(slot), U256::from(slot * 2))))
375                        .collect(),
376                )
377        } else {
378            bundle_state_builder.state_address(address).state_storage(
379                address,
380                slot_range
381                    .clone()
382                    .map(|slot| (U256::from(slot), (U256::from(slot), U256::ZERO)))
383                    .collect(),
384            )
385        };
386        // record previous account info
387        bundle_state_builder = bundle_state_builder
388            .revert_account_info(
389                number,
390                address,
391                Some(Some(AccountInfo {
392                    nonce: 1,
393                    balance: U256::from(idx),
394                    ..Default::default()
395                })),
396            )
397            .revert_storage(
398                number,
399                address,
400                slot_range.clone().map(|slot| (U256::from(slot), U256::from(slot))).collect(),
401            );
402    }
403    let execution_outcome = ExecutionOutcome::new(
404        bundle_state_builder.build(),
405        vec![vec![Receipt {
406            tx_type: TxType::Eip1559,
407            success: true,
408            cumulative_gas_used: 400,
409            logs: vec![Log::new_unchecked(
410                Address::new([0x61; 20]),
411                vec![B256::with_last_byte(3), B256::with_last_byte(4)],
412                Bytes::default(),
413            )],
414        }]],
415        number,
416        Vec::new(),
417    );
418
419    let mut extended = prev_execution_outcome.clone();
420    extended.extend(execution_outcome.clone());
421    let state_root = bundle_state_root(&extended);
422
423    let (mut header, mut body) = TEST_BLOCK.clone().split_header_body();
424    body.withdrawals = Some(Withdrawals::new(vec![Withdrawal::default()]));
425    header.number = number;
426    header.state_root = state_root;
427    // parent_hash points to block1 hash
428    header.parent_hash = parent_hash;
429    let block = SealedBlock::seal_parts(header, body);
430
431    (RecoveredBlock::new_sealed(block, vec![Address::new([0x31; 20])]), execution_outcome)
432}
433
434/// Block five that points to block 4
435fn block5(
436    number: BlockNumber,
437    parent_hash: B256,
438    prev_execution_outcome: &ExecutionOutcome,
439) -> (RecoveredBlock<reth_ethereum_primitives::Block>, ExecutionOutcome) {
440    let address_range = 1..=20;
441    let slot_range = 1..=100;
442
443    let mut bundle_state_builder = BundleState::builder(number..=number);
444    for idx in address_range {
445        let address = Address::with_last_byte(idx);
446        // update every even account and recreate every odd only with half of slots
447        bundle_state_builder = bundle_state_builder
448            .state_present_account_info(
449                address,
450                AccountInfo { nonce: 1, balance: U256::from(idx * 2), ..Default::default() },
451            )
452            .state_storage(
453                address,
454                slot_range
455                    .clone()
456                    .take(50)
457                    .map(|slot| (U256::from(slot), (U256::from(slot), U256::from(slot * 4))))
458                    .collect(),
459            );
460        bundle_state_builder = if idx.is_multiple_of(2) {
461            bundle_state_builder
462                .revert_account_info(
463                    number,
464                    address,
465                    Some(Some(AccountInfo {
466                        nonce: 1,
467                        balance: U256::from(idx * 2),
468                        ..Default::default()
469                    })),
470                )
471                .revert_storage(
472                    number,
473                    address,
474                    slot_range
475                        .clone()
476                        .map(|slot| (U256::from(slot), U256::from(slot * 2)))
477                        .collect(),
478                )
479        } else {
480            bundle_state_builder.revert_address(number, address)
481        };
482    }
483    let execution_outcome = ExecutionOutcome::new(
484        bundle_state_builder.build(),
485        vec![vec![Receipt {
486            tx_type: TxType::Eip1559,
487            success: true,
488            cumulative_gas_used: 400,
489            logs: vec![Log::new_unchecked(
490                Address::new([0x61; 20]),
491                vec![B256::with_last_byte(3), B256::with_last_byte(4)],
492                Bytes::default(),
493            )],
494        }]],
495        number,
496        Vec::new(),
497    );
498
499    let mut extended = prev_execution_outcome.clone();
500    extended.extend(execution_outcome.clone());
501    let state_root = bundle_state_root(&extended);
502
503    let (mut header, mut body) = TEST_BLOCK.clone().split_header_body();
504    body.withdrawals = Some(Withdrawals::new(vec![Withdrawal::default()]));
505    header.number = number;
506    header.state_root = state_root;
507    // parent_hash points to block1 hash
508    header.parent_hash = parent_hash;
509    let block = SealedBlock::seal_parts(header, body);
510
511    (RecoveredBlock::new_sealed(block, vec![Address::new([0x31; 20])]), execution_outcome)
512}