Skip to main content

reth_bench/bench/
send_payload.rs

1use super::helpers::{load_jwt_secret, read_input};
2use alloy_provider::network::AnyRpcBlock;
3use alloy_rpc_types_engine::ExecutionPayload;
4use clap::Parser;
5use eyre::{OptionExt, Result};
6use op_alloy_consensus::OpTxEnvelope;
7use reth_cli_runner::CliContext;
8use std::io::Write;
9
10/// Command for generating and sending an `engine_newPayload` request constructed from an RPC
11/// block.
12#[derive(Debug, Parser)]
13pub struct Command {
14    /// Path to the json file to parse. If not specified, stdin will be used.
15    #[arg(short, long)]
16    path: Option<String>,
17
18    /// The engine RPC url to use.
19    #[arg(
20        short,
21        long,
22        // Required if `mode` is `execute` or `cast`.
23        required_if_eq_any([("mode", "execute"), ("mode", "cast")]),
24        // If `mode` is not specified, then `execute` is used, so we need to require it.
25        required_unless_present("mode")
26    )]
27    rpc_url: Option<String>,
28
29    /// The JWT secret to use. Can be either a path to a file containing the secret or the secret
30    /// itself.
31    #[arg(short, long)]
32    jwt_secret: Option<String>,
33
34    #[arg(long, default_value_t = 3)]
35    new_payload_version: u8,
36
37    /// The mode to use.
38    #[arg(long, value_enum, default_value = "execute")]
39    mode: Mode,
40}
41
42#[derive(Debug, Clone, clap::ValueEnum)]
43enum Mode {
44    /// Execute the `cast` command. This works with blocks of any size, because it pipes the
45    /// payload into the `cast` command.
46    Execute,
47    /// Print the `cast` command. Caution: this may not work with large blocks because of the
48    /// command length limit.
49    Cast,
50    /// Print the JSON payload. Can be piped into `cast` command if the block is small enough.
51    Json,
52}
53
54impl Command {
55    /// Execute the generate payload command
56    pub async fn execute(self, _ctx: CliContext) -> Result<()> {
57        // Load block
58        let block_json = read_input(self.path.as_deref())?;
59
60        // Load JWT secret
61        let jwt_secret = load_jwt_secret(self.jwt_secret.as_deref())?;
62
63        // Parse the block
64        let block = serde_json::from_str::<AnyRpcBlock>(&block_json)?
65            .into_inner()
66            .map_header(|header| header.map(|h| h.into_header_with_defaults()))
67            .try_map_transactions(|tx| {
68                // try to convert unknowns into op type so that we can also support optimism
69                tx.try_into_either::<OpTxEnvelope>()
70            })?
71            .into_consensus();
72
73        // Extract parent beacon block root
74        let parent_beacon_block_root = block.header.parent_beacon_block_root;
75
76        // Extract blob versioned hashes
77        let blob_versioned_hashes =
78            block.body.blob_versioned_hashes_iter().copied().collect::<Vec<_>>();
79
80        // Convert to execution payload
81        let execution_payload = ExecutionPayload::from_block_slow(&block).0;
82
83        let use_v4 = block.header.requests_hash.is_some();
84
85        // Create JSON request data
86        let json_request = if use_v4 {
87            serde_json::to_string(&(
88                execution_payload,
89                blob_versioned_hashes,
90                parent_beacon_block_root,
91                block.header.requests_hash.unwrap_or_default(),
92            ))?
93        } else {
94            serde_json::to_string(&(
95                execution_payload,
96                blob_versioned_hashes,
97                parent_beacon_block_root,
98            ))?
99        };
100
101        // Print output or execute command
102        match self.mode {
103            Mode::Execute => {
104                // Create cast command
105                let mut command = std::process::Command::new("cast");
106                let method = if use_v4 { "engine_newPayloadV4" } else { "engine_newPayloadV3" };
107                command.arg("rpc").arg(method).arg("--raw");
108                if let Some(rpc_url) = self.rpc_url {
109                    command.arg("--rpc-url").arg(rpc_url);
110                }
111                if let Some(secret) = &jwt_secret {
112                    command.arg("--jwt-secret").arg(secret);
113                }
114
115                // Start cast process
116                let mut process = command.stdin(std::process::Stdio::piped()).spawn()?;
117
118                // Write to cast's stdin
119                process
120                    .stdin
121                    .take()
122                    .ok_or_eyre("stdin not available")?
123                    .write_all(json_request.as_bytes())?;
124
125                // Wait for cast to finish
126                process.wait()?;
127            }
128            Mode::Cast => {
129                let mut cmd = format!(
130                    "cast rpc engine_newPayloadV{} --raw '{}'",
131                    self.new_payload_version, json_request
132                );
133
134                if let Some(rpc_url) = self.rpc_url {
135                    cmd += &format!(" --rpc-url {rpc_url}");
136                }
137                if let Some(secret) = &jwt_secret {
138                    cmd += &format!(" --jwt-secret {secret}");
139                }
140
141                println!("{cmd}");
142            }
143            Mode::Json => {
144                println!("{json_request}");
145            }
146        }
147
148        Ok(())
149    }
150}