reth_bench/bench/
new_payload_only.rs1use crate::{
4 bench::{
5 context::BenchContext,
6 metrics_scraper::MetricsScraper,
7 output::{
8 NewPayloadResult, TotalGasOutput, TotalGasRow, GAS_OUTPUT_SUFFIX,
9 NEW_PAYLOAD_OUTPUT_SUFFIX,
10 },
11 },
12 valid_payload::{block_to_new_payload, call_new_payload_with_reth},
13};
14use alloy_provider::{ext::DebugApi, Provider};
15use clap::Parser;
16use csv::Writer;
17use eyre::{Context, OptionExt};
18use reth_cli_runner::CliContext;
19use reth_node_core::args::BenchmarkArgs;
20use std::time::{Duration, Instant};
21use tracing::{debug, info};
22
23#[derive(Debug, Parser)]
25pub struct Command {
26 #[arg(long, value_name = "RPC_URL", verbatim_doc_comment)]
28 rpc_url: String,
29
30 #[arg(
33 long = "rpc-block-buffer-size",
34 value_name = "RPC_BLOCK_BUFFER_SIZE",
35 default_value = "20",
36 verbatim_doc_comment
37 )]
38 rpc_block_buffer_size: usize,
39
40 #[command(flatten)]
41 benchmark: BenchmarkArgs,
42}
43
44impl Command {
45 pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> {
47 let BenchContext {
48 benchmark_mode,
49 block_provider,
50 auth_provider,
51 mut next_block,
52 is_optimism,
53 use_reth_namespace,
54 rlp_blocks,
55 wait_for_persistence,
56 no_wait_for_caches,
57 } = BenchContext::new(&self.benchmark, self.rpc_url).await?;
58
59 let total_blocks = benchmark_mode.total_blocks();
60
61 let mut metrics_scraper = MetricsScraper::maybe_new(self.benchmark.metrics_url.clone());
62
63 if use_reth_namespace {
64 info!("Using reth_newPayload endpoint");
65 }
66
67 let buffer_size = self.rpc_block_buffer_size;
68
69 let (error_sender, mut error_receiver) = tokio::sync::oneshot::channel();
71 let (sender, mut receiver) = tokio::sync::mpsc::channel(buffer_size);
72
73 tokio::task::spawn(async move {
74 while benchmark_mode.contains(next_block) {
75 let block_res = block_provider
76 .get_block_by_number(next_block.into())
77 .full()
78 .await
79 .wrap_err_with(|| format!("Failed to fetch block by number {next_block}"));
80 let block = match block_res.and_then(|opt| opt.ok_or_eyre("Block not found")) {
81 Ok(block) => block,
82 Err(e) => {
83 tracing::error!(target: "reth-bench", "Failed to fetch block {next_block}: {e}");
84 let _ = error_sender.send(e);
85 break;
86 }
87 };
88
89 let rlp = if rlp_blocks {
90 let Ok(rlp) = block_provider.debug_get_raw_block(next_block.into()).await
91 else {
92 tracing::error!(target: "reth-bench", "Failed to fetch raw block {next_block}");
93 let _ = error_sender
94 .send(eyre::eyre!("Failed to fetch raw block {next_block}"));
95 break;
96 };
97 Some(rlp)
98 } else {
99 None
100 };
101
102 next_block += 1;
103 if let Err(e) = sender.send((block, rlp)).await {
104 tracing::error!(target: "reth-bench", "Failed to send block data: {e}");
105 break;
106 }
107 }
108 });
109
110 let mut results = Vec::new();
111 let mut blocks_processed = 0u64;
112 let total_benchmark_duration = Instant::now();
113 let mut total_wait_time = Duration::ZERO;
114
115 while let Some((block, rlp)) = {
116 let wait_start = Instant::now();
117 let result = receiver.recv().await;
118 total_wait_time += wait_start.elapsed();
119 result
120 } {
121 let block_number = block.header.number;
122 let transaction_count = block.transactions.len() as u64;
123 let gas_used = block.header.gas_used;
124
125 debug!(target: "reth-bench", number=?block.header.number, "Sending payload to engine");
126
127 let (version, params) = block_to_new_payload(
128 block,
129 is_optimism,
130 rlp,
131 use_reth_namespace,
132 wait_for_persistence,
133 no_wait_for_caches,
134 )?;
135
136 let start = Instant::now();
137 let server_timings =
138 call_new_payload_with_reth(&auth_provider, version, params).await?;
139
140 let latency =
141 server_timings.as_ref().map(|t| t.latency).unwrap_or_else(|| start.elapsed());
142 let new_payload_result = NewPayloadResult {
143 gas_used,
144 latency,
145 persistence_wait: server_timings.as_ref().and_then(|t| t.persistence_wait),
146 execution_cache_wait: server_timings
147 .as_ref()
148 .map(|t| t.execution_cache_wait)
149 .unwrap_or_default(),
150 sparse_trie_wait: server_timings
151 .as_ref()
152 .map(|t| t.sparse_trie_wait)
153 .unwrap_or_default(),
154 };
155 blocks_processed += 1;
156 let progress = match total_blocks {
157 Some(total) => format!("{blocks_processed}/{total}"),
158 None => format!("{blocks_processed}"),
159 };
160 info!(target: "reth-bench", progress, %new_payload_result);
161
162 let current_duration = total_benchmark_duration.elapsed() - total_wait_time;
165
166 let row =
168 TotalGasRow { block_number, transaction_count, gas_used, time: current_duration };
169 results.push((row, new_payload_result));
170
171 if let Some(scraper) = metrics_scraper.as_mut() &&
172 let Err(err) = scraper.scrape_after_block(block_number).await
173 {
174 tracing::warn!(target: "reth-bench", %err, block_number, "Failed to scrape metrics");
175 }
176 }
177
178 if let Ok(error) = error_receiver.try_recv() {
180 return Err(error);
181 }
182
183 let (gas_output_results, new_payload_results): (_, Vec<NewPayloadResult>) =
184 results.into_iter().unzip();
185
186 if let Some(path) = self.benchmark.output {
188 let output_path = path.join(NEW_PAYLOAD_OUTPUT_SUFFIX);
190 info!(target: "reth-bench", "Writing newPayload call latency output to file: {:?}", output_path);
191 let mut writer = Writer::from_path(output_path)?;
192 for result in new_payload_results {
193 writer.serialize(result)?;
194 }
195 writer.flush()?;
196
197 let output_path = path.join(GAS_OUTPUT_SUFFIX);
199 info!(target: "reth-bench", "Writing total gas output to file: {:?}", output_path);
200 let mut writer = Writer::from_path(output_path)?;
201 for row in &gas_output_results {
202 writer.serialize(row)?;
203 }
204 writer.flush()?;
205
206 if let Some(scraper) = &metrics_scraper {
207 scraper.write_csv(&path)?;
208 }
209
210 info!(target: "reth-bench", "Finished writing benchmark output files to {:?}.", path);
211 }
212
213 let gas_output = TotalGasOutput::new(gas_output_results)?;
215 info!(
216 target: "reth-bench",
217 total_duration=?gas_output.total_duration,
218 total_gas_used=?gas_output.total_gas_used,
219 blocks_processed=?gas_output.blocks_processed,
220 "Total Ggas/s: {:.4}",
221 gas_output.total_gigagas_per_second()
222 );
223
224 Ok(())
225 }
226}