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::SealedBlock;
10use reth_primitives_traits::Block as BlockTrait;
11use std::{io::Write, path::Path};
12use tracing::debug;
13
14pub fn generate_test_blocks(chain_spec: &ChainSpec, count: u64) -> Vec<SealedBlock> {
16 let mut blocks: Vec<SealedBlock> = Vec::new();
17 let genesis_header = chain_spec.sealed_genesis_header();
18 let mut parent_hash = genesis_header.hash();
19 let mut parent_number = genesis_header.number();
20 let mut parent_base_fee = genesis_header.base_fee_per_gas;
21 let mut parent_gas_limit = genesis_header.gas_limit;
22
23 debug!(target: "e2e::import",
24 "Genesis header base fee: {:?}, gas limit: {}, state root: {:?}",
25 parent_base_fee,
26 parent_gas_limit,
27 genesis_header.state_root()
28 );
29
30 for i in 1..=count {
31 let mut header = Header {
33 parent_hash,
34 number: parent_number + 1,
35 gas_limit: parent_gas_limit, gas_used: 0, timestamp: genesis_header.timestamp() + i * 12, beneficiary: Address::ZERO,
39 receipts_root: alloy_consensus::constants::EMPTY_RECEIPTS,
40 logs_bloom: Default::default(),
41 difficulty: U256::from(1), state_root: if i == 1 {
44 genesis_header.state_root()
45 } else {
46 blocks.last().unwrap().state_root
47 },
48 transactions_root: alloy_consensus::constants::EMPTY_TRANSACTIONS,
49 ommers_hash: alloy_consensus::constants::EMPTY_OMMER_ROOT_HASH,
50 mix_hash: B256::ZERO,
51 nonce: B64::from(0u64),
52 extra_data: Default::default(),
53 base_fee_per_gas: None,
54 withdrawals_root: None,
55 blob_gas_used: None,
56 excess_blob_gas: None,
57 parent_beacon_block_root: None,
58 requests_hash: None,
59 };
60
61 if chain_spec.is_london_active_at_block(header.number) {
63 if let Some(parent_fee) = parent_base_fee {
65 let (parent_gas_used, parent_gas_limit) = if i == 1 {
68 (genesis_header.gas_used, genesis_header.gas_limit)
70 } else {
71 let last_block = blocks.last().unwrap();
72 (last_block.gas_used, last_block.gas_limit)
73 };
74 header.base_fee_per_gas = Some(alloy_eips::calc_next_block_base_fee(
75 parent_gas_used,
76 parent_gas_limit,
77 parent_fee,
78 chain_spec.base_fee_params_at_timestamp(header.timestamp),
79 ));
80 debug!(target: "e2e::import", "Block {} calculated base fee: {:?} (parent gas used: {}, parent gas limit: {}, parent base fee: {})",
81 i, header.base_fee_per_gas, parent_gas_used, parent_gas_limit, parent_fee);
82 parent_base_fee = header.base_fee_per_gas;
83 }
84 }
85
86 if chain_spec.is_paris_active_at_block(header.number) {
88 header.difficulty = U256::ZERO;
89 header.nonce = B64::ZERO;
90 }
91
92 if chain_spec.is_shanghai_active_at_timestamp(header.timestamp) {
94 header.withdrawals_root = Some(EMPTY_WITHDRAWALS);
95 }
96
97 if chain_spec.is_cancun_active_at_timestamp(header.timestamp) {
99 header.blob_gas_used = Some(0);
100 header.excess_blob_gas = Some(0);
101 header.parent_beacon_block_root = Some(B256::ZERO);
102 }
103
104 let body = BlockBody {
106 transactions: vec![],
107 ommers: vec![],
108 withdrawals: header.withdrawals_root.is_some().then(Withdrawals::default),
109 };
110
111 let block = Block { header: header.clone(), body: body.clone() };
113 let sealed_block = BlockTrait::seal_slow(block);
114
115 debug!(target: "e2e::import",
116 "Generated block {} with hash {:?}",
117 sealed_block.number(),
118 sealed_block.hash()
119 );
120 debug!(target: "e2e::import",
121 " Body has {} transactions, {} ommers, withdrawals: {}",
122 body.transactions.len(),
123 body.ommers.len(),
124 body.withdrawals.is_some()
125 );
126
127 parent_hash = sealed_block.hash();
129 parent_number = sealed_block.number();
130 parent_gas_limit = sealed_block.gas_limit;
131 if header.base_fee_per_gas.is_some() {
132 parent_base_fee = header.base_fee_per_gas;
133 }
134
135 blocks.push(sealed_block);
136 }
137
138 blocks
139}
140
141pub fn write_blocks_to_rlp(blocks: &[SealedBlock], path: &Path) -> std::io::Result<()> {
143 let mut file = std::fs::File::create(path)?;
144 let mut total_bytes = 0;
145
146 for (i, block) in blocks.iter().enumerate() {
147 let block_for_encoding = block.clone().unseal();
149
150 let mut buf = Vec::new();
151 block_for_encoding.encode(&mut buf);
152 debug!(target: "e2e::import",
153 "Block {} has {} transactions, encoded to {} bytes",
154 i,
155 block.body().transactions.len(),
156 buf.len()
157 );
158
159 debug!(target: "e2e::import", "Block {} encoded to {} bytes", i, buf.len());
161 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) -> 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}