Skip to main content

reth_provider/test_utils/
blocks.rs

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