Skip to main content

reth_bench/bench/
context.rs

1//! This contains the [`BenchContext`], which is information that all replay-based benchmarks need.
2//! The initialization code is also the same, so this can be shared across benchmark commands.
3
4use crate::{authenticated_transport::AuthenticatedTransportConnect, bench_mode::BenchMode};
5use alloy_eips::BlockNumberOrTag;
6use alloy_primitives::address;
7use alloy_provider::{network::AnyNetwork, Provider, RootProvider};
8use alloy_rpc_client::ClientBuilder;
9use alloy_rpc_types_engine::JwtSecret;
10use alloy_transport::layers::{RateLimitRetryPolicy, RetryBackoffLayer};
11use reqwest::Url;
12use reth_node_core::args::{BenchmarkArgs, WaitForPersistence};
13use tracing::info;
14
15/// This is intended to be used by benchmarks that replay blocks from an RPC.
16///
17/// It contains an authenticated provider for engine API queries, a block provider for block
18/// queries, a [`BenchMode`] to determine whether the benchmark should run for a closed or open
19/// range of blocks, and the next block to fetch.
20pub(crate) struct BenchContext {
21    /// The auth provider is used for engine API queries.
22    pub(crate) auth_provider: RootProvider<AnyNetwork>,
23    /// The block provider is used for block queries.
24    pub(crate) block_provider: RootProvider<AnyNetwork>,
25    /// The benchmark mode, which defines whether the benchmark should run for a closed or open
26    /// range of blocks.
27    pub(crate) benchmark_mode: BenchMode,
28    /// The next block to fetch.
29    pub(crate) next_block: u64,
30    /// Whether the chain is an OP rollup.
31    pub(crate) is_optimism: bool,
32    /// Whether to use `reth_newPayload` endpoint instead of `engine_newPayload*`.
33    pub(crate) use_reth_namespace: bool,
34    /// Whether to fetch and replay RLP-encoded blocks.
35    pub(crate) rlp_blocks: bool,
36    /// Controls when `reth_newPayload` waits for persistence.
37    pub(crate) wait_for_persistence: WaitForPersistence,
38    /// Whether to skip waiting for caches (pass `wait_for_caches: false`).
39    pub(crate) no_wait_for_caches: bool,
40}
41
42impl BenchContext {
43    /// This is the initialization code for most benchmarks, taking in a [`BenchmarkArgs`] and
44    /// returning the providers needed to run a benchmark.
45    pub(crate) async fn new(bench_args: &BenchmarkArgs, rpc_url: String) -> eyre::Result<Self> {
46        info!(target: "reth-bench", "Running benchmark using data from RPC URL: {}", rpc_url);
47
48        // Ensure that output directory exists and is a directory
49        if let Some(output) = &bench_args.output {
50            if output.is_file() {
51                return Err(eyre::eyre!("Output path must be a directory"));
52            }
53            // Create the directory if it doesn't exist
54            if !output.exists() {
55                std::fs::create_dir_all(output)?;
56                info!(target: "reth-bench", "Created output directory: {:?}", output);
57            }
58        }
59
60        // set up alloy client for blocks, retrying on 429/503 (default) and 502
61        let retry_policy =
62            RateLimitRetryPolicy::default().or(|err: &alloy_transport::TransportError| -> bool {
63                err.as_transport_err()
64                    .and_then(|t| t.as_http_error())
65                    .is_some_and(|e| e.status == 502)
66            });
67        let max_retries = bench_args.rpc_block_fetch_retries.as_max_retries();
68        let client = ClientBuilder::default()
69            .layer(RetryBackoffLayer::new_with_policy(max_retries, 800, u64::MAX, retry_policy))
70            .http(rpc_url.parse()?);
71        let block_provider = RootProvider::<AnyNetwork>::new(client);
72
73        // Check if this is an OP chain by checking code at a predeploy address.
74        let is_optimism = !block_provider
75            .get_code_at(address!("0x420000000000000000000000000000000000000F"))
76            .await?
77            .is_empty();
78
79        // construct the authenticated provider
80        let auth_jwt = bench_args
81            .auth_jwtsecret
82            .clone()
83            .ok_or_else(|| eyre::eyre!("--jwt-secret must be provided for authenticated RPC"))?;
84
85        // fetch jwt from file
86        //
87        // the jwt is hex encoded so we will decode it after
88        let jwt = std::fs::read_to_string(auth_jwt)?;
89        let jwt = JwtSecret::from_hex(jwt)?;
90
91        // get engine url
92        let auth_url = Url::parse(&bench_args.engine_rpc_url)?;
93
94        // construct the authed transport
95        info!(target: "reth-bench", "Connecting to Engine RPC at {} for replay", auth_url);
96        let auth_transport = AuthenticatedTransportConnect::new(auth_url, jwt);
97        let client = ClientBuilder::default().connect_with(auth_transport).await?;
98        let auth_provider = RootProvider::<AnyNetwork>::new(client);
99
100        // Computes the block range for the benchmark.
101        //
102        // - If `--advance` is provided, fetches the latest block from the engine and sets:
103        //     - `from = head + 1`
104        //     - `to = head + advance`
105        // - If only `--to` is provided, fetches the latest block from the engine and sets:
106        //     - `from = head`
107        // - Otherwise, uses the values from `--from` and `--to`.
108        let (from, to) = if let Some(advance) = bench_args.advance {
109            if advance == 0 {
110                return Err(eyre::eyre!("--advance must be greater than 0"));
111            }
112
113            let head_block = auth_provider
114                .get_block_by_number(BlockNumberOrTag::Latest)
115                .await?
116                .ok_or_else(|| eyre::eyre!("Failed to fetch latest block for --advance"))?;
117            let head_number = head_block.header.number;
118            (Some(head_number), Some(head_number + advance))
119        } else if bench_args.from.is_none() && bench_args.to.is_some() {
120            let head_block = auth_provider
121                .get_block_by_number(BlockNumberOrTag::Latest)
122                .await?
123                .ok_or_else(|| eyre::eyre!("Failed to fetch latest block from engine"))?;
124            let head_number = head_block.header.number;
125            info!(target: "reth-bench", "No --from provided, derived from engine head: {}", head_number);
126            (Some(head_number), bench_args.to)
127        } else {
128            (bench_args.from, bench_args.to)
129        };
130
131        // If `--to` are not provided, we will run the benchmark continuously,
132        // starting at the latest block.
133        let latest_block = block_provider
134            .get_block_by_number(BlockNumberOrTag::Latest)
135            .full()
136            .await?
137            .ok_or_else(|| eyre::eyre!("Failed to fetch latest block from RPC"))?;
138        let mut benchmark_mode = BenchMode::new(from, to, latest_block.into_inner().number());
139
140        let first_block = match benchmark_mode {
141            BenchMode::Continuous(start) => {
142                block_provider.get_block_by_number(start.into()).full().await?.ok_or_else(|| {
143                    eyre::eyre!("Failed to fetch block {} from RPC for continuous mode", start)
144                })?
145            }
146            BenchMode::Range(ref mut range) => {
147                match range.next() {
148                    Some(block_number) => {
149                        // fetch first block in range
150                        block_provider
151                            .get_block_by_number(block_number.into())
152                            .full()
153                            .await?
154                            .ok_or_else(|| {
155                                eyre::eyre!("Failed to fetch block {} from RPC", block_number)
156                            })?
157                    }
158                    None => {
159                        return Err(eyre::eyre!(
160                            "Benchmark mode range is empty, please provide a larger range"
161                        ));
162                    }
163                }
164            }
165        };
166
167        let next_block = first_block.header.number + 1;
168        let rlp_blocks = bench_args.rlp_blocks;
169        let wait_for_persistence =
170            bench_args.wait_for_persistence.unwrap_or(WaitForPersistence::Never);
171        let use_reth_namespace = bench_args.reth_new_payload || rlp_blocks;
172        let no_wait_for_caches = bench_args.no_wait_for_caches;
173        Ok(Self {
174            auth_provider,
175            block_provider,
176            benchmark_mode,
177            next_block,
178            is_optimism,
179            use_reth_namespace,
180            rlp_blocks,
181            wait_for_persistence,
182            no_wait_for_caches,
183        })
184    }
185}