1use crate::valid_payload::call_forkchoice_updated;
4use eyre::Result;
5use std::{
6 io::{BufReader, Read},
7 time::Duration,
8};
9
10pub(crate) fn read_input(path: Option<&str>) -> Result<String> {
12 Ok(match path {
13 Some(path) => reth_fs_util::read_to_string(path)?,
14 None => String::from_utf8(
15 BufReader::new(std::io::stdin()).bytes().collect::<Result<Vec<_>, _>>()?,
16 )?,
17 })
18}
19
20pub(crate) fn load_jwt_secret(jwt_secret: Option<&str>) -> Result<Option<String>> {
22 match jwt_secret {
23 Some(secret) => {
24 match std::fs::read_to_string(secret) {
26 Ok(contents) => Ok(Some(contents.trim().to_string())),
27 Err(_) => Ok(Some(secret.to_string())),
29 }
30 }
31 None => Ok(None),
32 }
33}
34
35pub(crate) fn parse_gas_limit(s: &str) -> eyre::Result<u64> {
39 let s = s.trim();
40 if s.is_empty() {
41 return Err(eyre::eyre!("empty value"));
42 }
43
44 let (num_str, multiplier) = if let Some(prefix) = s.strip_suffix(['G', 'g']) {
45 (prefix, 1_000_000_000u64)
46 } else if let Some(prefix) = s.strip_suffix(['M', 'm']) {
47 (prefix, 1_000_000u64)
48 } else if let Some(prefix) = s.strip_suffix(['K', 'k']) {
49 (prefix, 1_000u64)
50 } else {
51 (s, 1u64)
52 };
53
54 let base: u64 = num_str.trim().parse()?;
55 base.checked_mul(multiplier).ok_or_else(|| eyre::eyre!("value overflow"))
56}
57
58pub(crate) fn parse_duration(s: &str) -> eyre::Result<Duration> {
63 match humantime::parse_duration(s) {
64 Ok(d) => Ok(d),
65 Err(_) => {
66 let millis: u64 =
67 s.trim().parse().map_err(|_| eyre::eyre!("invalid duration: {s:?}"))?;
68 Ok(Duration::from_millis(millis))
69 }
70 }
71}
72
73use alloy_consensus::Header;
74use alloy_eips::eip4844::kzg_to_versioned_hash;
75use alloy_primitives::{Address, B256};
76use alloy_provider::{ext::EngineApi, network::AnyNetwork, RootProvider};
77use alloy_rpc_types_engine::{
78 CancunPayloadFields, ExecutionPayload, ExecutionPayloadSidecar, ForkchoiceState,
79 PayloadAttributes, PayloadId,
80};
81use eyre::OptionExt;
82use reth_chainspec::{ChainSpec, EthereumHardforks};
83use reth_node_api::EngineApiMessageVersion;
84use tracing::debug;
85
86pub(crate) struct PayloadRequest {
88 pub(crate) attributes: PayloadAttributes,
90 pub(crate) forkchoice_state: ForkchoiceState,
92 pub(crate) fcu_version: EngineApiMessageVersion,
94 pub(crate) get_payload_version: u8,
96 pub(crate) new_payload_version: EngineApiMessageVersion,
98}
99
100pub(crate) fn prepare_payload_request(
102 chain_spec: &ChainSpec,
103 timestamp: u64,
104 parent_hash: B256,
105) -> PayloadRequest {
106 let shanghai_active = chain_spec.is_shanghai_active_at_timestamp(timestamp);
107 let cancun_active = chain_spec.is_cancun_active_at_timestamp(timestamp);
108 let prague_active = chain_spec.is_prague_active_at_timestamp(timestamp);
109 let osaka_active = chain_spec.is_osaka_active_at_timestamp(timestamp);
110
111 let fcu_version = if cancun_active {
113 EngineApiMessageVersion::V3
114 } else if shanghai_active {
115 EngineApiMessageVersion::V2
116 } else {
117 EngineApiMessageVersion::V1
118 };
119
120 let (get_payload_version, new_payload_version) = if osaka_active {
123 (5, EngineApiMessageVersion::V4) } else if prague_active {
125 (4, EngineApiMessageVersion::V4)
126 } else if cancun_active {
127 (3, EngineApiMessageVersion::V3)
128 } else if shanghai_active {
129 (2, EngineApiMessageVersion::V2)
130 } else {
131 (1, EngineApiMessageVersion::V1)
132 };
133
134 PayloadRequest {
135 attributes: PayloadAttributes {
136 timestamp,
137 prev_randao: B256::ZERO,
138 suggested_fee_recipient: Address::ZERO,
139 withdrawals: shanghai_active.then(Vec::new),
140 parent_beacon_block_root: cancun_active.then_some(B256::ZERO),
141 },
142 forkchoice_state: ForkchoiceState {
143 head_block_hash: parent_hash,
144 safe_block_hash: parent_hash,
145 finalized_block_hash: parent_hash,
146 },
147 fcu_version,
148 get_payload_version,
149 new_payload_version,
150 }
151}
152
153pub(crate) async fn build_payload(
158 provider: &RootProvider<AnyNetwork>,
159 request: PayloadRequest,
160) -> eyre::Result<(ExecutionPayload, ExecutionPayloadSidecar)> {
161 let fcu_result = call_forkchoice_updated(
162 provider,
163 request.fcu_version,
164 request.forkchoice_state,
165 Some(request.attributes.clone()),
166 )
167 .await?;
168
169 let payload_id =
170 fcu_result.payload_id.ok_or_eyre("Payload builder did not return a payload id")?;
171
172 get_payload_with_sidecar(
173 provider,
174 request.get_payload_version,
175 payload_id,
176 request.attributes.parent_beacon_block_root,
177 )
178 .await
179}
180
181pub(crate) fn rpc_block_to_header(block: alloy_provider::network::AnyRpcBlock) -> (Header, B256) {
183 let block_hash = block.header.hash;
184 let header = block.header.inner.clone().into_header_with_defaults();
185 (header, block_hash)
186}
187
188fn versioned_hashes_from_commitments(
190 commitments: &[alloy_primitives::FixedBytes<48>],
191) -> Vec<B256> {
192 commitments.iter().map(|c| kzg_to_versioned_hash(c.as_ref())).collect()
193}
194
195pub(crate) async fn get_payload_with_sidecar(
197 provider: &RootProvider<AnyNetwork>,
198 version: u8,
199 payload_id: PayloadId,
200 parent_beacon_block_root: Option<B256>,
201) -> eyre::Result<(ExecutionPayload, ExecutionPayloadSidecar)> {
202 debug!(target: "reth-bench", get_payload_version = ?version, ?payload_id, "Sending getPayload");
203
204 match version {
205 1 => {
206 let payload = provider.get_payload_v1(payload_id).await?;
207 Ok((ExecutionPayload::V1(payload), ExecutionPayloadSidecar::none()))
208 }
209 2 => {
210 let envelope = provider.get_payload_v2(payload_id).await?;
211 let payload = match envelope.execution_payload {
212 alloy_rpc_types_engine::ExecutionPayloadFieldV2::V1(p) => ExecutionPayload::V1(p),
213 alloy_rpc_types_engine::ExecutionPayloadFieldV2::V2(p) => ExecutionPayload::V2(p),
214 };
215 Ok((payload, ExecutionPayloadSidecar::none()))
216 }
217 3 => {
218 let envelope = provider.get_payload_v3(payload_id).await?;
219 let versioned_hashes =
220 versioned_hashes_from_commitments(&envelope.blobs_bundle.commitments);
221 let cancun_fields = CancunPayloadFields {
222 parent_beacon_block_root: parent_beacon_block_root
223 .ok_or_eyre("parent_beacon_block_root required for V3")?,
224 versioned_hashes,
225 };
226 Ok((
227 ExecutionPayload::V3(envelope.execution_payload),
228 ExecutionPayloadSidecar::v3(cancun_fields),
229 ))
230 }
231 4 => {
232 let envelope = provider.get_payload_v4(payload_id).await?;
233 Ok(envelope.into_payload_and_sidecar(
234 parent_beacon_block_root.ok_or_eyre("parent_beacon_block_root required for V4")?,
235 ))
236 }
237 5 => {
238 let envelope = provider.get_payload_v5(payload_id).await?;
239 Ok(envelope.into_payload_and_sidecar(
240 parent_beacon_block_root.ok_or_eyre("parent_beacon_block_root required for V5")?,
241 ))
242 }
243 _ => panic!("This tool does not support getPayload versions past v5"),
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_parse_gas_limit_plain_number() {
253 assert_eq!(parse_gas_limit("30000000").unwrap(), 30_000_000);
254 assert_eq!(parse_gas_limit("1").unwrap(), 1);
255 assert_eq!(parse_gas_limit("0").unwrap(), 0);
256 }
257
258 #[test]
259 fn test_parse_gas_limit_k_suffix() {
260 assert_eq!(parse_gas_limit("1K").unwrap(), 1_000);
261 assert_eq!(parse_gas_limit("30k").unwrap(), 30_000);
262 assert_eq!(parse_gas_limit("100K").unwrap(), 100_000);
263 }
264
265 #[test]
266 fn test_parse_gas_limit_m_suffix() {
267 assert_eq!(parse_gas_limit("1M").unwrap(), 1_000_000);
268 assert_eq!(parse_gas_limit("30m").unwrap(), 30_000_000);
269 assert_eq!(parse_gas_limit("100M").unwrap(), 100_000_000);
270 }
271
272 #[test]
273 fn test_parse_gas_limit_g_suffix() {
274 assert_eq!(parse_gas_limit("1G").unwrap(), 1_000_000_000);
275 assert_eq!(parse_gas_limit("2g").unwrap(), 2_000_000_000);
276 assert_eq!(parse_gas_limit("10G").unwrap(), 10_000_000_000);
277 }
278
279 #[test]
280 fn test_parse_gas_limit_with_whitespace() {
281 assert_eq!(parse_gas_limit(" 1G ").unwrap(), 1_000_000_000);
282 assert_eq!(parse_gas_limit("2 M").unwrap(), 2_000_000);
283 }
284
285 #[test]
286 fn test_parse_gas_limit_errors() {
287 assert!(parse_gas_limit("").is_err());
288 assert!(parse_gas_limit("abc").is_err());
289 assert!(parse_gas_limit("G").is_err());
290 assert!(parse_gas_limit("-1G").is_err());
291 }
292
293 #[test]
294 fn test_parse_duration_with_unit() {
295 assert_eq!(parse_duration("100ms").unwrap(), Duration::from_millis(100));
296 assert_eq!(parse_duration("2s").unwrap(), Duration::from_secs(2));
297 assert_eq!(parse_duration("1m").unwrap(), Duration::from_secs(60));
298 }
299
300 #[test]
301 fn test_parse_duration_bare_millis() {
302 assert_eq!(parse_duration("400").unwrap(), Duration::from_millis(400));
303 assert_eq!(parse_duration("0").unwrap(), Duration::from_millis(0));
304 assert_eq!(parse_duration("1000").unwrap(), Duration::from_millis(1000));
305 }
306
307 #[test]
308 fn test_parse_duration_errors() {
309 assert!(parse_duration("abc").is_err());
310 assert!(parse_duration("").is_err());
311 }
312}