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