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