reth_bench/bench/send_invalid_payload/
mod.rs1mod invalidation;
4use invalidation::InvalidationConfig;
5
6use super::helpers::{load_jwt_secret, read_input};
7use alloy_consensus::TxEnvelope;
8use alloy_primitives::{Address, B256};
9use alloy_provider::network::AnyRpcBlock;
10use alloy_rpc_types_engine::ExecutionPayload;
11use clap::Parser;
12use eyre::{OptionExt, Result};
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| -> eyre::Result<TxEnvelope> {
226 tx.try_into().map_err(|_| eyre::eyre!("unsupported tx type"))
227 })?
228 .into_consensus();
229
230 let config = self.build_invalidation_config();
231
232 let parent_beacon_block_root =
233 self.parent_beacon_block_root.or(block.header.parent_beacon_block_root);
234 let blob_versioned_hashes =
235 block.body.blob_versioned_hashes_iter().copied().collect::<Vec<_>>();
236 let use_v4 = block.header.requests_hash.is_some();
237 let requests_hash = self.requests_hash.or(block.header.requests_hash);
238
239 let mut execution_payload = ExecutionPayload::from_block_slow(&block).0;
240
241 let changes = match &mut execution_payload {
242 ExecutionPayload::V1(p) => config.apply_to_payload_v1(p),
243 ExecutionPayload::V2(p) => config.apply_to_payload_v2(p),
244 ExecutionPayload::V3(p) => config.apply_to_payload_v3(p),
245 ExecutionPayload::V4(p) => config.apply_to_payload_v3(&mut p.payload_inner),
246 };
247
248 let skip_recalc = self.skip_hash_recalc || config.should_skip_hash_recalc();
249 if !skip_recalc {
250 let new_hash = match execution_payload.clone().into_block_raw() {
251 Ok(block) => block.header.hash_slow(),
252 Err(e) => {
253 eprintln!(
254 "Warning: Could not recalculate block hash: {e}. Using original hash."
255 );
256 match &execution_payload {
257 ExecutionPayload::V1(p) => p.block_hash,
258 ExecutionPayload::V2(p) => p.payload_inner.block_hash,
259 ExecutionPayload::V3(p) => p.payload_inner.payload_inner.block_hash,
260 ExecutionPayload::V4(p) => {
261 p.payload_inner.payload_inner.payload_inner.block_hash
262 }
263 }
264 }
265 };
266
267 match &mut execution_payload {
268 ExecutionPayload::V1(p) => p.block_hash = new_hash,
269 ExecutionPayload::V2(p) => p.payload_inner.block_hash = new_hash,
270 ExecutionPayload::V3(p) => p.payload_inner.payload_inner.block_hash = new_hash,
271 ExecutionPayload::V4(p) => {
272 p.payload_inner.payload_inner.payload_inner.block_hash = new_hash
273 }
274 }
275 }
276
277 if self.dry_run {
278 println!("=== Dry Run ===");
279 println!("Changes that would be applied:");
280 for change in &changes {
281 println!(" - {}", change);
282 }
283 if changes.is_empty() {
284 println!(" (no changes)");
285 }
286 if skip_recalc {
287 println!(" - Block hash recalculation: SKIPPED");
288 } else {
289 println!(" - Block hash recalculation: PERFORMED");
290 }
291 println!("\nResulting payload JSON:");
292 let json = serde_json::to_string_pretty(&execution_payload)?;
293 println!("{}", json);
294 return Ok(());
295 }
296
297 let json_request = if use_v4 {
298 serde_json::to_string(&(
299 execution_payload,
300 blob_versioned_hashes,
301 parent_beacon_block_root,
302 requests_hash.unwrap_or_default(),
303 ))?
304 } else {
305 serde_json::to_string(&(
306 execution_payload,
307 blob_versioned_hashes,
308 parent_beacon_block_root,
309 ))?
310 };
311
312 match self.mode {
313 Mode::Execute => {
314 let mut command = std::process::Command::new("cast");
315 let method = if use_v4 { "engine_newPayloadV4" } else { "engine_newPayloadV3" };
316 command.arg("rpc").arg(method).arg("--raw");
317 if let Some(rpc_url) = self.rpc_url {
318 command.arg("--rpc-url").arg(rpc_url);
319 }
320 if let Some(secret) = &jwt_secret {
321 command.arg("--jwt-secret").arg(secret);
322 }
323
324 let mut process = command.stdin(std::process::Stdio::piped()).spawn()?;
325
326 process
327 .stdin
328 .take()
329 .ok_or_eyre("stdin not available")?
330 .write_all(json_request.as_bytes())?;
331
332 process.wait()?;
333 }
334 Mode::Cast => {
335 let mut cmd = format!(
336 "cast rpc engine_newPayloadV{} --raw '{}'",
337 self.new_payload_version, json_request
338 );
339
340 if let Some(rpc_url) = self.rpc_url {
341 cmd += &format!(" --rpc-url {rpc_url}");
342 }
343 if let Some(secret) = &jwt_secret {
344 cmd += &format!(" --jwt-secret {secret}");
345 }
346
347 println!("{cmd}");
348 }
349 Mode::Json => {
350 println!("{json_request}");
351 }
352 }
353
354 Ok(())
355 }
356}