reth_bench/bench/
gas_limit_ramp.rs1use crate::{
4 authenticated_transport::AuthenticatedTransportConnect,
5 bench::{
6 helpers::{build_payload, parse_gas_limit, prepare_payload_request, rpc_block_to_header},
7 output::GasRampPayloadFile,
8 },
9 valid_payload::{call_forkchoice_updated, call_new_payload, payload_to_new_payload},
10};
11use alloy_eips::BlockNumberOrTag;
12use alloy_provider::{network::AnyNetwork, Provider, RootProvider};
13use alloy_rpc_client::ClientBuilder;
14use alloy_rpc_types_engine::{ExecutionPayload, ForkchoiceState, JwtSecret};
15
16use clap::Parser;
17use reqwest::Url;
18use reth_chainspec::ChainSpec;
19use reth_cli_runner::CliContext;
20use reth_ethereum_primitives::TransactionSigned;
21use reth_primitives_traits::constants::{GAS_LIMIT_BOUND_DIVISOR, MAXIMUM_GAS_LIMIT_BLOCK};
22use std::{path::PathBuf, time::Instant};
23use tracing::info;
24
25#[derive(Debug, Parser)]
27pub struct Command {
28 #[arg(long, value_name = "BLOCKS", conflicts_with = "target_gas_limit")]
30 blocks: Option<u64>,
31
32 #[arg(long, value_name = "TARGET_GAS_LIMIT", conflicts_with = "blocks", value_parser = parse_gas_limit)]
37 target_gas_limit: Option<u64>,
38
39 #[arg(long = "engine-rpc-url", value_name = "ENGINE_RPC_URL")]
41 engine_rpc_url: String,
42
43 #[arg(long = "jwt-secret", value_name = "JWT_SECRET")]
45 jwt_secret: PathBuf,
46
47 #[arg(long, value_name = "OUTPUT")]
49 output: PathBuf,
50}
51
52#[derive(Debug, Clone, Copy)]
54enum RampMode {
55 Blocks(u64),
57 TargetGasLimit(u64),
59}
60
61impl Command {
62 pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> {
64 let mode = match (self.blocks, self.target_gas_limit) {
65 (Some(blocks), None) => {
66 if blocks == 0 {
67 return Err(eyre::eyre!("--blocks must be greater than 0"));
68 }
69 RampMode::Blocks(blocks)
70 }
71 (None, Some(target)) => {
72 if target == 0 {
73 return Err(eyre::eyre!("--target-gas-limit must be greater than 0"));
74 }
75 RampMode::TargetGasLimit(target)
76 }
77 _ => {
78 return Err(eyre::eyre!(
79 "Exactly one of --blocks or --target-gas-limit must be specified"
80 ));
81 }
82 };
83
84 if self.output.is_file() {
86 return Err(eyre::eyre!("Output path must be a directory"));
87 }
88 if !self.output.exists() {
89 std::fs::create_dir_all(&self.output)?;
90 info!("Created output directory: {:?}", self.output);
91 }
92
93 let jwt = std::fs::read_to_string(&self.jwt_secret)?;
95 let jwt = JwtSecret::from_hex(jwt)?;
96 let auth_url = Url::parse(&self.engine_rpc_url)?;
97
98 info!("Connecting to Engine RPC at {}", auth_url);
99 let auth_transport = AuthenticatedTransportConnect::new(auth_url, jwt);
100 let client = ClientBuilder::default().connect_with(auth_transport).await?;
101 let provider = RootProvider::<AnyNetwork>::new(client);
102
103 let chain_id = provider.get_chain_id().await?;
105 let chain_spec = ChainSpec::from_chain_id(chain_id)
106 .ok_or_else(|| eyre::eyre!("Unsupported chain id: {chain_id}"))?;
107
108 let parent_block = provider
110 .get_block_by_number(BlockNumberOrTag::Latest)
111 .full()
112 .await?
113 .ok_or_else(|| eyre::eyre!("Failed to fetch latest block"))?;
114
115 let (mut parent_header, mut parent_hash) = rpc_block_to_header(parent_block);
116
117 let canonical_parent = parent_header.number;
118 let start_block = canonical_parent + 1;
119
120 match mode {
121 RampMode::Blocks(blocks) => {
122 info!(
123 canonical_parent,
124 start_block,
125 end_block = start_block + blocks - 1,
126 "Starting gas limit ramp benchmark (block count mode)"
127 );
128 }
129 RampMode::TargetGasLimit(target) => {
130 info!(
131 canonical_parent,
132 start_block,
133 current_gas_limit = parent_header.gas_limit,
134 target_gas_limit = target,
135 "Starting gas limit ramp benchmark (target gas limit mode)"
136 );
137 }
138 }
139
140 let mut blocks_processed = 0u64;
141 let total_benchmark_duration = Instant::now();
142
143 while !should_stop(mode, blocks_processed, parent_header.gas_limit) {
144 let timestamp = parent_header.timestamp.saturating_add(1);
145
146 let request = prepare_payload_request(&chain_spec, timestamp, parent_hash);
147 let new_payload_version = request.new_payload_version;
148
149 let (payload, sidecar) = build_payload(&provider, request).await?;
150
151 let mut block =
152 payload.clone().try_into_block_with_sidecar::<TransactionSigned>(&sidecar)?;
153
154 let max_increase = max_gas_limit_increase(parent_header.gas_limit);
155 let gas_limit =
156 parent_header.gas_limit.saturating_add(max_increase).min(MAXIMUM_GAS_LIMIT_BLOCK);
157
158 block.header.gas_limit = gas_limit;
159
160 let block_hash = block.header.hash_slow();
161 let (payload, _) = ExecutionPayload::from_block_unchecked(block_hash, &block);
164 let (version, params) = payload_to_new_payload(
165 payload,
166 sidecar,
167 false,
168 block.header.withdrawals_root,
169 Some(new_payload_version),
170 )?;
171
172 let payload_path =
174 self.output.join(format!("payload_block_{}.json", block.header.number));
175 let file =
176 GasRampPayloadFile { version: version as u8, block_hash, params: params.clone() };
177 let payload_json = serde_json::to_string_pretty(&file)?;
178 std::fs::write(&payload_path, &payload_json)?;
179 info!(block_number = block.header.number, path = %payload_path.display(), "Saved payload");
180
181 call_new_payload(&provider, version, params).await?;
182
183 let forkchoice_state = ForkchoiceState {
184 head_block_hash: block_hash,
185 safe_block_hash: block_hash,
186 finalized_block_hash: block_hash,
187 };
188 call_forkchoice_updated(&provider, version, forkchoice_state, None).await?;
189
190 parent_header = block.header;
191 parent_hash = block_hash;
192 blocks_processed += 1;
193 }
194
195 let final_gas_limit = parent_header.gas_limit;
196 info!(
197 total_duration=?total_benchmark_duration.elapsed(),
198 blocks_processed,
199 final_gas_limit,
200 "Benchmark complete"
201 );
202
203 Ok(())
204 }
205}
206
207const fn max_gas_limit_increase(parent_gas_limit: u64) -> u64 {
208 (parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR).saturating_sub(1)
209}
210
211const fn should_stop(mode: RampMode, blocks_processed: u64, current_gas_limit: u64) -> bool {
212 match mode {
213 RampMode::Blocks(target_blocks) => blocks_processed >= target_blocks,
214 RampMode::TargetGasLimit(target) => current_gas_limit >= target,
215 }
216}