Skip to main content

reth_bench/bench/
send_payload.rs

1use super::helpers::{load_jwt_secret, read_input};
2use alloy_consensus::TxEnvelope;
3use alloy_provider::network::AnyRpcBlock;
4use alloy_rpc_types_engine::ExecutionPayload;
5use clap::Parser;
6use eyre::{OptionExt, Result};
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| -> eyre::Result<TxEnvelope> {
68                tx.try_into().map_err(|_| eyre::eyre!("unsupported tx type"))
69            })?
70            .into_consensus();
71
72        // Extract parent beacon block root
73        let parent_beacon_block_root = block.header.parent_beacon_block_root;
74
75        // Extract blob versioned hashes
76        let blob_versioned_hashes =
77            block.body.blob_versioned_hashes_iter().copied().collect::<Vec<_>>();
78
79        // Convert to execution payload
80        let execution_payload = ExecutionPayload::from_block_slow(&block).0;
81
82        let use_v4 = block.header.requests_hash.is_some();
83
84        // Create JSON request data
85        let json_request = if use_v4 {
86            serde_json::to_string(&(
87                execution_payload,
88                blob_versioned_hashes,
89                parent_beacon_block_root,
90                block.header.requests_hash.unwrap_or_default(),
91            ))?
92        } else {
93            serde_json::to_string(&(
94                execution_payload,
95                blob_versioned_hashes,
96                parent_beacon_block_root,
97            ))?
98        };
99
100        // Print output or execute command
101        match self.mode {
102            Mode::Execute => {
103                // Create cast command
104                let mut command = std::process::Command::new("cast");
105                let method = if use_v4 { "engine_newPayloadV4" } else { "engine_newPayloadV3" };
106                command.arg("rpc").arg(method).arg("--raw");
107                if let Some(rpc_url) = self.rpc_url {
108                    command.arg("--rpc-url").arg(rpc_url);
109                }
110                if let Some(secret) = &jwt_secret {
111                    command.arg("--jwt-secret").arg(secret);
112                }
113
114                // Start cast process
115                let mut process = command.stdin(std::process::Stdio::piped()).spawn()?;
116
117                // Write to cast's stdin
118                process
119                    .stdin
120                    .take()
121                    .ok_or_eyre("stdin not available")?
122                    .write_all(json_request.as_bytes())?;
123
124                // Wait for cast to finish
125                process.wait()?;
126            }
127            Mode::Cast => {
128                let mut cmd = format!(
129                    "cast rpc engine_newPayloadV{} --raw '{}'",
130                    self.new_payload_version, json_request
131                );
132
133                if let Some(rpc_url) = self.rpc_url {
134                    cmd += &format!(" --rpc-url {rpc_url}");
135                }
136                if let Some(secret) = &jwt_secret {
137                    cmd += &format!(" --jwt-secret {secret}");
138                }
139
140                println!("{cmd}");
141            }
142            Mode::Json => {
143                println!("{json_request}");
144            }
145        }
146
147        Ok(())
148    }
149}