Skip to main content

reth_bench/bench/
helpers.rs

1//! Common helpers for reth-bench commands.
2
3use alloy_eips::{eip7928::BlockAccessList, BlockNumberOrTag};
4use alloy_provider::{network::AnyNetwork, Provider, RootProvider};
5use eyre::Result;
6use std::{
7    io::{BufReader, Read},
8    time::Duration,
9};
10
11/// Read input from either a file path or stdin.
12pub(crate) fn read_input(path: Option<&str>) -> Result<String> {
13    Ok(match path {
14        Some(path) => reth_fs_util::read_to_string(path)?,
15        None => String::from_utf8(
16            BufReader::new(std::io::stdin()).bytes().collect::<Result<Vec<_>, _>>()?,
17        )?,
18    })
19}
20
21/// Load JWT secret from either a file or use the provided string directly.
22pub(crate) fn load_jwt_secret(jwt_secret: Option<&str>) -> Result<Option<String>> {
23    match jwt_secret {
24        Some(secret) => {
25            // Try to read as file first
26            match std::fs::read_to_string(secret) {
27                Ok(contents) => Ok(Some(contents.trim().to_string())),
28                // If file read fails, use the string directly
29                Err(_) => Ok(Some(secret.to_string())),
30            }
31        }
32        None => Ok(None),
33    }
34}
35
36/// Parses a gas limit value with optional suffix: K for thousand, M for million, G for billion.
37///
38/// Examples: "30000000", "30M", "1G", "2G"
39pub(crate) fn parse_gas_limit(s: &str) -> eyre::Result<u64> {
40    let s = s.trim();
41    if s.is_empty() {
42        return Err(eyre::eyre!("empty value"));
43    }
44
45    let (num_str, multiplier) = if let Some(prefix) = s.strip_suffix(['G', 'g']) {
46        (prefix, 1_000_000_000u64)
47    } else if let Some(prefix) = s.strip_suffix(['M', 'm']) {
48        (prefix, 1_000_000u64)
49    } else if let Some(prefix) = s.strip_suffix(['K', 'k']) {
50        (prefix, 1_000u64)
51    } else {
52        (s, 1u64)
53    };
54
55    let base: u64 = num_str.trim().parse()?;
56    base.checked_mul(multiplier).ok_or_else(|| eyre::eyre!("value overflow"))
57}
58
59/// Parses a duration string, treating bare integers as milliseconds.
60///
61/// Accepts either a `humantime` duration string (e.g. `"100ms"`, `"2s"`) or a plain
62/// integer which is interpreted as milliseconds (e.g. `"400"` → 400ms).
63pub(crate) fn parse_duration(s: &str) -> eyre::Result<Duration> {
64    match humantime::parse_duration(s) {
65        Ok(d) => Ok(d),
66        Err(_) => {
67            let millis: u64 =
68                s.trim().parse().map_err(|_| eyre::eyre!("invalid duration: {s:?}"))?;
69            Ok(Duration::from_millis(millis))
70        }
71    }
72}
73
74/// Fetches the block access list for a given block number using the provided provider.
75pub(crate) async fn fetch_block_access_list(
76    provider: &RootProvider<AnyNetwork>,
77    block_number: u64,
78) -> eyre::Result<BlockAccessList> {
79    provider
80        .client()
81        .request("eth_getBlockAccessListByBlockNumber", (BlockNumberOrTag::Number(block_number),))
82        .await
83        .map_err(Into::into)
84        .and_then(|block_access_list: Option<BlockAccessList>| {
85            block_access_list.ok_or_else(|| eyre::eyre!("BAL not found for block {block_number}"))
86        })
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_parse_gas_limit_plain_number() {
95        assert_eq!(parse_gas_limit("30000000").unwrap(), 30_000_000);
96        assert_eq!(parse_gas_limit("1").unwrap(), 1);
97        assert_eq!(parse_gas_limit("0").unwrap(), 0);
98    }
99
100    #[test]
101    fn test_parse_gas_limit_k_suffix() {
102        assert_eq!(parse_gas_limit("1K").unwrap(), 1_000);
103        assert_eq!(parse_gas_limit("30k").unwrap(), 30_000);
104        assert_eq!(parse_gas_limit("100K").unwrap(), 100_000);
105    }
106
107    #[test]
108    fn test_parse_gas_limit_m_suffix() {
109        assert_eq!(parse_gas_limit("1M").unwrap(), 1_000_000);
110        assert_eq!(parse_gas_limit("30m").unwrap(), 30_000_000);
111        assert_eq!(parse_gas_limit("100M").unwrap(), 100_000_000);
112    }
113
114    #[test]
115    fn test_parse_gas_limit_g_suffix() {
116        assert_eq!(parse_gas_limit("1G").unwrap(), 1_000_000_000);
117        assert_eq!(parse_gas_limit("2g").unwrap(), 2_000_000_000);
118        assert_eq!(parse_gas_limit("10G").unwrap(), 10_000_000_000);
119    }
120
121    #[test]
122    fn test_parse_gas_limit_with_whitespace() {
123        assert_eq!(parse_gas_limit(" 1G ").unwrap(), 1_000_000_000);
124        assert_eq!(parse_gas_limit("2 M").unwrap(), 2_000_000);
125    }
126
127    #[test]
128    fn test_parse_gas_limit_errors() {
129        assert!(parse_gas_limit("").is_err());
130        assert!(parse_gas_limit("abc").is_err());
131        assert!(parse_gas_limit("G").is_err());
132        assert!(parse_gas_limit("-1G").is_err());
133    }
134
135    #[test]
136    fn test_parse_duration_with_unit() {
137        assert_eq!(parse_duration("100ms").unwrap(), Duration::from_millis(100));
138        assert_eq!(parse_duration("2s").unwrap(), Duration::from_secs(2));
139        assert_eq!(parse_duration("1m").unwrap(), Duration::from_secs(60));
140    }
141
142    #[test]
143    fn test_parse_duration_bare_millis() {
144        assert_eq!(parse_duration("400").unwrap(), Duration::from_millis(400));
145        assert_eq!(parse_duration("0").unwrap(), Duration::from_millis(0));
146        assert_eq!(parse_duration("1000").unwrap(), Duration::from_millis(1000));
147    }
148
149    #[test]
150    fn test_parse_duration_errors() {
151        assert!(parse_duration("abc").is_err());
152        assert!(parse_duration("").is_err());
153    }
154}