reth_e2e_test_utils/
test_rlp_utils.rs1use alloy_consensus::{constants::EMPTY_WITHDRAWALS, BlockHeader, Header};
4use alloy_eips::eip4895::Withdrawals;
5use alloy_primitives::{Address, B256, B64, U256};
6use alloy_rlp::Encodable;
7use reth_chainspec::{ChainSpec, EthereumHardforks};
8use reth_ethereum_primitives::{Block, BlockBody};
9use reth_primitives_traits::{Block as BlockTrait, SealedBlock};
10use std::{io::Write, path::Path};
11use tracing::debug;
12
13pub fn generate_test_blocks(chain_spec: &ChainSpec, count: u64) -> Vec<SealedBlock<Block>> {
15 let mut blocks: Vec<SealedBlock<Block>> = Vec::new();
16 let genesis_header = chain_spec.sealed_genesis_header();
17 let mut parent_hash = genesis_header.hash();
18 let mut parent_number = genesis_header.number();
19 let mut parent_base_fee = genesis_header.base_fee_per_gas;
20 let mut parent_gas_limit = genesis_header.gas_limit;
21
22 debug!(target: "e2e::import",
23 "Genesis header base fee: {:?}, gas limit: {}, state root: {:?}",
24 parent_base_fee,
25 parent_gas_limit,
26 genesis_header.state_root()
27 );
28
29 for i in 1..=count {
30 let mut header = Header {
32 parent_hash,
33 number: parent_number + 1,
34 gas_limit: parent_gas_limit, gas_used: 0, timestamp: genesis_header.timestamp() + i * 12, beneficiary: Address::ZERO,
38 receipts_root: alloy_consensus::constants::EMPTY_RECEIPTS,
39 logs_bloom: Default::default(),
40 difficulty: U256::from(1), state_root: if i == 1 {
43 genesis_header.state_root()
44 } else {
45 blocks.last().unwrap().state_root
46 },
47 transactions_root: alloy_consensus::constants::EMPTY_TRANSACTIONS,
48 ommers_hash: alloy_consensus::constants::EMPTY_OMMER_ROOT_HASH,
49 mix_hash: B256::ZERO,
50 nonce: B64::from(0u64),
51 extra_data: Default::default(),
52 base_fee_per_gas: None,
53 withdrawals_root: None,
54 blob_gas_used: None,
55 excess_blob_gas: None,
56 parent_beacon_block_root: None,
57 requests_hash: None,
58 block_access_list_hash: None,
59 slot_number: None,
60 };
61
62 if chain_spec.is_london_active_at_block(header.number) {
64 if let Some(parent_fee) = parent_base_fee {
66 let (parent_gas_used, parent_gas_limit) = if i == 1 {
69 (genesis_header.gas_used, genesis_header.gas_limit)
71 } else {
72 let last_block = blocks.last().unwrap();
73 (last_block.gas_used, last_block.gas_limit)
74 };
75 header.base_fee_per_gas = Some(alloy_eips::calc_next_block_base_fee(
76 parent_gas_used,
77 parent_gas_limit,
78 parent_fee,
79 chain_spec.base_fee_params_at_timestamp(header.timestamp),
80 ));
81 debug!(target: "e2e::import", "Block {} calculated base fee: {:?} (parent gas used: {}, parent gas limit: {}, parent base fee: {})",
82 i, header.base_fee_per_gas, parent_gas_used, parent_gas_limit, parent_fee);
83 parent_base_fee = header.base_fee_per_gas;
84 }
85 }
86
87 if chain_spec.is_paris_active_at_block(header.number) {
89 header.difficulty = U256::ZERO;
90 header.nonce = B64::ZERO;
91 }
92
93 if chain_spec.is_shanghai_active_at_timestamp(header.timestamp) {
95 header.withdrawals_root = Some(EMPTY_WITHDRAWALS);
96 }
97
98 if chain_spec.is_cancun_active_at_timestamp(header.timestamp) {
100 header.blob_gas_used = Some(0);
101 header.excess_blob_gas = Some(0);
102 header.parent_beacon_block_root = Some(B256::ZERO);
103 }
104
105 let body = BlockBody {
107 transactions: vec![],
108 ommers: vec![],
109 withdrawals: header.withdrawals_root.is_some().then(Withdrawals::default),
110 };
111
112 let block = Block { header: header.clone(), body: body.clone() };
114 let sealed_block = BlockTrait::seal_slow(block);
115
116 debug!(target: "e2e::import",
117 "Generated block {} with hash {:?}",
118 sealed_block.number(),
119 sealed_block.hash()
120 );
121 debug!(target: "e2e::import",
122 " Body has {} transactions, {} ommers, withdrawals: {}",
123 body.transactions.len(),
124 body.ommers.len(),
125 body.withdrawals.is_some()
126 );
127
128 parent_hash = sealed_block.hash();
130 parent_number = sealed_block.number();
131 parent_gas_limit = sealed_block.gas_limit;
132 if header.base_fee_per_gas.is_some() {
133 parent_base_fee = header.base_fee_per_gas;
134 }
135
136 blocks.push(sealed_block);
137 }
138
139 blocks
140}
141
142pub fn write_blocks_to_rlp(blocks: &[SealedBlock<Block>], path: &Path) -> std::io::Result<()> {
144 let mut file = std::fs::File::create(path)?;
145 let mut total_bytes = 0;
146
147 for (i, block) in blocks.iter().enumerate() {
148 let block_for_encoding = block.clone().unseal();
150
151 let mut buf = Vec::new();
152 block_for_encoding.encode(&mut buf);
153 debug!(target: "e2e::import",
154 "Block {} has {} transactions, encoded to {} bytes",
155 i,
156 block.body().transactions.len(),
157 buf.len()
158 );
159
160 if buf.len() < 20 {
162 debug!(target: "e2e::import", " Raw bytes: {:?}", &buf);
163 } else {
164 debug!(target: "e2e::import", " First 20 bytes: {:?}", &buf[..20]);
165 }
166
167 total_bytes += buf.len();
168 file.write_all(&buf)?;
169 }
170
171 file.flush()?;
172 debug!(target: "e2e::import", "Total RLP bytes written: {total_bytes}");
173 Ok(())
174}
175
176pub fn create_fcu_json(tip: &SealedBlock<Block>) -> serde_json::Value {
178 serde_json::json!({
179 "params": [{
180 "headBlockHash": format!("0x{:x}", tip.hash()),
181 "safeBlockHash": format!("0x{:x}", tip.hash()),
182 "finalizedBlockHash": format!("0x{:x}", tip.hash()),
183 }]
184 })
185}