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