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 };
59
60 if chain_spec.is_london_active_at_block(header.number) {
62 if let Some(parent_fee) = parent_base_fee {
64 let (parent_gas_used, parent_gas_limit) = if i == 1 {
67 (genesis_header.gas_used, genesis_header.gas_limit)
69 } else {
70 let last_block = blocks.last().unwrap();
71 (last_block.gas_used, last_block.gas_limit)
72 };
73 header.base_fee_per_gas = Some(alloy_eips::calc_next_block_base_fee(
74 parent_gas_used,
75 parent_gas_limit,
76 parent_fee,
77 chain_spec.base_fee_params_at_timestamp(header.timestamp),
78 ));
79 debug!(target: "e2e::import", "Block {} calculated base fee: {:?} (parent gas used: {}, parent gas limit: {}, parent base fee: {})",
80 i, header.base_fee_per_gas, parent_gas_used, parent_gas_limit, parent_fee);
81 parent_base_fee = header.base_fee_per_gas;
82 }
83 }
84
85 if chain_spec.is_paris_active_at_block(header.number) {
87 header.difficulty = U256::ZERO;
88 header.nonce = B64::ZERO;
89 }
90
91 if chain_spec.is_shanghai_active_at_timestamp(header.timestamp) {
93 header.withdrawals_root = Some(EMPTY_WITHDRAWALS);
94 }
95
96 if chain_spec.is_cancun_active_at_timestamp(header.timestamp) {
98 header.blob_gas_used = Some(0);
99 header.excess_blob_gas = Some(0);
100 header.parent_beacon_block_root = Some(B256::ZERO);
101 }
102
103 let body = BlockBody {
105 transactions: vec![],
106 ommers: vec![],
107 withdrawals: header.withdrawals_root.is_some().then(Withdrawals::default),
108 };
109
110 let block = Block { header: header.clone(), body: body.clone() };
112 let sealed_block = BlockTrait::seal_slow(block);
113
114 debug!(target: "e2e::import",
115 "Generated block {} with hash {:?}",
116 sealed_block.number(),
117 sealed_block.hash()
118 );
119 debug!(target: "e2e::import",
120 " Body has {} transactions, {} ommers, withdrawals: {}",
121 body.transactions.len(),
122 body.ommers.len(),
123 body.withdrawals.is_some()
124 );
125
126 parent_hash = sealed_block.hash();
128 parent_number = sealed_block.number();
129 parent_gas_limit = sealed_block.gas_limit;
130 if header.base_fee_per_gas.is_some() {
131 parent_base_fee = header.base_fee_per_gas;
132 }
133
134 blocks.push(sealed_block);
135 }
136
137 blocks
138}
139
140pub fn write_blocks_to_rlp(blocks: &[SealedBlock<Block>], path: &Path) -> std::io::Result<()> {
142 let mut file = std::fs::File::create(path)?;
143 let mut total_bytes = 0;
144
145 for (i, block) in blocks.iter().enumerate() {
146 let block_for_encoding = block.clone().unseal();
148
149 let mut buf = Vec::new();
150 block_for_encoding.encode(&mut buf);
151 debug!(target: "e2e::import",
152 "Block {} has {} transactions, encoded to {} bytes",
153 i,
154 block.body().transactions.len(),
155 buf.len()
156 );
157
158 if buf.len() < 20 {
160 debug!(target: "e2e::import", " Raw bytes: {:?}", &buf);
161 } else {
162 debug!(target: "e2e::import", " First 20 bytes: {:?}", &buf[..20]);
163 }
164
165 total_bytes += buf.len();
166 file.write_all(&buf)?;
167 }
168
169 file.flush()?;
170 debug!(target: "e2e::import", "Total RLP bytes written: {total_bytes}");
171 Ok(())
172}
173
174pub fn create_fcu_json(tip: &SealedBlock<Block>) -> serde_json::Value {
176 serde_json::json!({
177 "params": [{
178 "headBlockHash": format!("0x{:x}", tip.hash()),
179 "safeBlockHash": format!("0x{:x}", tip.hash()),
180 "finalizedBlockHash": format!("0x{:x}", tip.hash()),
181 }]
182 })
183}