reth_bench/bench/send_invalid_payload/
mod.rs1mod invalidation;
4use invalidation::InvalidationConfig;
5
6use super::helpers::{load_jwt_secret, read_input};
7use alloy_primitives::{Address, B256};
8use alloy_provider::network::AnyRpcBlock;
9use alloy_rpc_types_engine::ExecutionPayload;
10use clap::Parser;
11use eyre::{OptionExt, Result};
12use op_alloy_consensus::OpTxEnvelope;
13use reth_cli_runner::CliContext;
14use std::io::Write;
15
16#[derive(Debug, Parser)]
22pub struct Command {
23 #[arg(short, long, help_heading = "Input Options")]
26 path: Option<String>,
27
28 #[arg(
30 short,
31 long,
32 help_heading = "Input Options",
33 required_if_eq_any([("mode", "execute"), ("mode", "cast")]),
34 required_unless_present("mode")
35 )]
36 rpc_url: Option<String>,
37
38 #[arg(short, long, help_heading = "Input Options")]
41 jwt_secret: Option<String>,
42
43 #[arg(long, default_value_t = 3, help_heading = "Input Options")]
45 new_payload_version: u8,
46
47 #[arg(long, value_enum, default_value = "execute", help_heading = "Input Options")]
49 mode: Mode,
50
51 #[arg(long, value_name = "HASH", help_heading = "Explicit Value Overrides")]
54 parent_hash: Option<B256>,
55
56 #[arg(long, value_name = "ADDR", help_heading = "Explicit Value Overrides")]
58 fee_recipient: Option<Address>,
59
60 #[arg(long, value_name = "HASH", help_heading = "Explicit Value Overrides")]
62 state_root: Option<B256>,
63
64 #[arg(long, value_name = "HASH", help_heading = "Explicit Value Overrides")]
66 receipts_root: Option<B256>,
67
68 #[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
70 block_number: Option<u64>,
71
72 #[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
74 gas_limit: Option<u64>,
75
76 #[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
78 gas_used: Option<u64>,
79
80 #[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
82 timestamp: Option<u64>,
83
84 #[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
86 base_fee_per_gas: Option<u64>,
87
88 #[arg(long, value_name = "HASH", help_heading = "Explicit Value Overrides")]
90 block_hash: Option<B256>,
91
92 #[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
94 blob_gas_used: Option<u64>,
95
96 #[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
98 excess_blob_gas: Option<u64>,
99
100 #[arg(long, value_name = "HASH", help_heading = "Explicit Value Overrides")]
102 parent_beacon_block_root: Option<B256>,
103
104 #[arg(long, value_name = "HASH", help_heading = "Explicit Value Overrides")]
106 requests_hash: Option<B256>,
107
108 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
111 invalidate_parent_hash: bool,
112
113 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
115 invalidate_state_root: bool,
116
117 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
119 invalidate_receipts_root: bool,
120
121 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
123 invalidate_gas_used: bool,
124
125 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
127 invalidate_block_number: bool,
128
129 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
131 invalidate_timestamp: bool,
132
133 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
135 invalidate_base_fee: bool,
136
137 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
139 invalidate_transactions: bool,
140
141 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
143 invalidate_block_hash: bool,
144
145 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
147 invalidate_withdrawals: bool,
148
149 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
151 invalidate_blob_gas_used: bool,
152
153 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
155 invalidate_excess_blob_gas: bool,
156
157 #[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
159 invalidate_requests_hash: bool,
160
161 #[arg(long, default_value_t = false, help_heading = "Meta Flags")]
164 skip_hash_recalc: bool,
165
166 #[arg(long, default_value_t = false, help_heading = "Meta Flags")]
168 dry_run: bool,
169}
170
171#[derive(Debug, Clone, clap::ValueEnum)]
172enum Mode {
173 Execute,
176 Cast,
179 Json,
181}
182
183impl Command {
184 const fn build_invalidation_config(&self) -> InvalidationConfig {
186 InvalidationConfig {
187 parent_hash: self.parent_hash,
188 fee_recipient: self.fee_recipient,
189 state_root: self.state_root,
190 receipts_root: self.receipts_root,
191 logs_bloom: None,
192 prev_randao: None,
193 block_number: self.block_number,
194 gas_limit: self.gas_limit,
195 gas_used: self.gas_used,
196 timestamp: self.timestamp,
197 extra_data: None,
198 base_fee_per_gas: self.base_fee_per_gas,
199 block_hash: self.block_hash,
200 blob_gas_used: self.blob_gas_used,
201 excess_blob_gas: self.excess_blob_gas,
202 invalidate_parent_hash: self.invalidate_parent_hash,
203 invalidate_state_root: self.invalidate_state_root,
204 invalidate_receipts_root: self.invalidate_receipts_root,
205 invalidate_gas_used: self.invalidate_gas_used,
206 invalidate_block_number: self.invalidate_block_number,
207 invalidate_timestamp: self.invalidate_timestamp,
208 invalidate_base_fee: self.invalidate_base_fee,
209 invalidate_transactions: self.invalidate_transactions,
210 invalidate_block_hash: self.invalidate_block_hash,
211 invalidate_withdrawals: self.invalidate_withdrawals,
212 invalidate_blob_gas_used: self.invalidate_blob_gas_used,
213 invalidate_excess_blob_gas: self.invalidate_excess_blob_gas,
214 }
215 }
216
217 pub async fn execute(self, _ctx: CliContext) -> Result<()> {
219 let block_json = read_input(self.path.as_deref())?;
220 let jwt_secret = load_jwt_secret(self.jwt_secret.as_deref())?;
221
222 let block = serde_json::from_str::<AnyRpcBlock>(&block_json)?
223 .into_inner()
224 .map_header(|header| header.map(|h| h.into_header_with_defaults()))
225 .try_map_transactions(|tx| tx.try_into_either::<OpTxEnvelope>())?
226 .into_consensus();
227
228 let config = self.build_invalidation_config();
229
230 let parent_beacon_block_root =
231 self.parent_beacon_block_root.or(block.header.parent_beacon_block_root);
232 let blob_versioned_hashes =
233 block.body.blob_versioned_hashes_iter().copied().collect::<Vec<_>>();
234 let use_v4 = block.header.requests_hash.is_some();
235 let requests_hash = self.requests_hash.or(block.header.requests_hash);
236
237 let mut execution_payload = ExecutionPayload::from_block_slow(&block).0;
238
239 let changes = match &mut execution_payload {
240 ExecutionPayload::V1(p) => config.apply_to_payload_v1(p),
241 ExecutionPayload::V2(p) => config.apply_to_payload_v2(p),
242 ExecutionPayload::V3(p) => config.apply_to_payload_v3(p),
243 };
244
245 let skip_recalc = self.skip_hash_recalc || config.should_skip_hash_recalc();
246 if !skip_recalc {
247 let new_hash = match execution_payload.clone().into_block_raw() {
248 Ok(block) => block.header.hash_slow(),
249 Err(e) => {
250 eprintln!(
251 "Warning: Could not recalculate block hash: {e}. Using original hash."
252 );
253 match &execution_payload {
254 ExecutionPayload::V1(p) => p.block_hash,
255 ExecutionPayload::V2(p) => p.payload_inner.block_hash,
256 ExecutionPayload::V3(p) => p.payload_inner.payload_inner.block_hash,
257 }
258 }
259 };
260
261 match &mut execution_payload {
262 ExecutionPayload::V1(p) => p.block_hash = new_hash,
263 ExecutionPayload::V2(p) => p.payload_inner.block_hash = new_hash,
264 ExecutionPayload::V3(p) => p.payload_inner.payload_inner.block_hash = new_hash,
265 }
266 }
267
268 if self.dry_run {
269 println!("=== Dry Run ===");
270 println!("Changes that would be applied:");
271 for change in &changes {
272 println!(" - {}", change);
273 }
274 if changes.is_empty() {
275 println!(" (no changes)");
276 }
277 if skip_recalc {
278 println!(" - Block hash recalculation: SKIPPED");
279 } else {
280 println!(" - Block hash recalculation: PERFORMED");
281 }
282 println!("\nResulting payload JSON:");
283 let json = serde_json::to_string_pretty(&execution_payload)?;
284 println!("{}", json);
285 return Ok(());
286 }
287
288 let json_request = if use_v4 {
289 serde_json::to_string(&(
290 execution_payload,
291 blob_versioned_hashes,
292 parent_beacon_block_root,
293 requests_hash.unwrap_or_default(),
294 ))?
295 } else {
296 serde_json::to_string(&(
297 execution_payload,
298 blob_versioned_hashes,
299 parent_beacon_block_root,
300 ))?
301 };
302
303 match self.mode {
304 Mode::Execute => {
305 let mut command = std::process::Command::new("cast");
306 let method = if use_v4 { "engine_newPayloadV4" } else { "engine_newPayloadV3" };
307 command.arg("rpc").arg(method).arg("--raw");
308 if let Some(rpc_url) = self.rpc_url {
309 command.arg("--rpc-url").arg(rpc_url);
310 }
311 if let Some(secret) = &jwt_secret {
312 command.arg("--jwt-secret").arg(secret);
313 }
314
315 let mut process = command.stdin(std::process::Stdio::piped()).spawn()?;
316
317 process
318 .stdin
319 .take()
320 .ok_or_eyre("stdin not available")?
321 .write_all(json_request.as_bytes())?;
322
323 process.wait()?;
324 }
325 Mode::Cast => {
326 let mut cmd = format!(
327 "cast rpc engine_newPayloadV{} --raw '{}'",
328 self.new_payload_version, json_request
329 );
330
331 if let Some(rpc_url) = self.rpc_url {
332 cmd += &format!(" --rpc-url {rpc_url}");
333 }
334 if let Some(secret) = &jwt_secret {
335 cmd += &format!(" --jwt-secret {secret}");
336 }
337
338 println!("{cmd}");
339 }
340 Mode::Json => {
341 println!("{json_request}");
342 }
343 }
344
345 Ok(())
346 }
347}