From c317d5f19129cd6846ebe05cbe907a0c1300222c Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 13 Feb 2026 16:07:53 -0800 Subject: [PATCH 1/8] cleanup report charts - better heatmap labels - less cluttered time-to-inclusion x-axis labels --- crates/report/src/template.html.handlebars | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/report/src/template.html.handlebars b/crates/report/src/template.html.handlebars index 235912e8..6f64d188 100644 --- a/crates/report/src/template.html.handlebars +++ b/crates/report/src/template.html.handlebars @@ -337,10 +337,20 @@ .map(slot => `0x${slot.slice(0,4)}...${slot.slice(-4)}`); const data = {{data.chart_data.heatmap.matrix}}; + // Pre-compute total writes per slot (y-axis index) + const slotTotals = new Array(storageSlots.length).fill(0); + data.forEach(function(entry) { + slotTotals[entry[1]] += entry[2]; + }); + // Specify the configuration items and data for the chart var option = { tooltip: { - position: 'top' + position: 'top', + formatter: function(params) { + const slotIdx = params.value[1]; + return `${storageSlots[slotIdx]}
Block: ${blocks[params.value[0]]}
Writes: ${params.value[2]}
Total slot writes: ${slotTotals[slotIdx]}`; + } }, grid: { height: '70%', @@ -380,10 +390,7 @@ type: 'heatmap', data: data, label: { - show: true, - formatter: function (params) { - return params.value[2] > 0 ? params.value[2] : '-'; - } + show: false }, emphasis: { itemStyle: { @@ -466,9 +473,9 @@ data: buckets, nameGap: 2, axisLabel: { - interval: 0, + interval: buckets.length > 20 ? Math.floor(buckets.length / 20) - 1 : 0, rotate: 45 - } + }, }, yAxis: { type: 'value', From f88f5c53eaaae7ba22fc3dabc15510b4c23b5243 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 13 Feb 2026 16:09:25 -0800 Subject: [PATCH 2/8] bugfix: unchecked error in reports (if txs are not found) --- crates/report/src/block_trace.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/report/src/block_trace.rs b/crates/report/src/block_trace.rs index 4d302929..a1754490 100644 --- a/crates/report/src/block_trace.rs +++ b/crates/report/src/block_trace.rs @@ -139,12 +139,11 @@ pub async fn get_block_traces( ) -> Result> { // get tx traces for all txs in all_blocks let mut all_traces = vec![]; - if full_blocks.is_empty() { + let total_txs: usize = full_blocks.iter().map(|b| b.transactions.len()).sum(); + if full_blocks.is_empty() || total_txs == 0 { return Ok(all_traces); } - let (sender, mut receiver) = tokio::sync::mpsc::channel::( - full_blocks.iter().map(|b| b.transactions.len()).sum(), - ); + let (sender, mut receiver) = tokio::sync::mpsc::channel::(total_txs); for block in full_blocks { let mut tx_tasks = vec![]; From b644442b06e2a73e20e6957e3ca406b3cd41e6c5 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 13 Feb 2026 16:15:57 -0800 Subject: [PATCH 3/8] bugfix: handle bad params in fill_block to prevent panic --- crates/cli/src/default_scenarios/fill_block.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/cli/src/default_scenarios/fill_block.rs b/crates/cli/src/default_scenarios/fill_block.rs index b5173e52..8620ef6d 100644 --- a/crates/cli/src/default_scenarios/fill_block.rs +++ b/crates/cli/src/default_scenarios/fill_block.rs @@ -51,7 +51,16 @@ pub async fn fill_block( block_gas_limit.unwrap_or(30_000_000) }; - let num_txs = txs_per_block.unwrap_or(txs_per_second.unwrap_or_default()); + let num_txs = match (txs_per_block, txs_per_second) { + (Some(0), _) | (_, Some(0)) => { + return Err(CliError::Args(crate::commands::error::ArgsError::SpamRateNotFound)); + } + (Some(n), _) => n, + (_, Some(n)) => n, + (None, None) => { + return Err(CliError::Args(crate::commands::error::ArgsError::SpamRateNotFound)); + } + }; let gas_per_tx = gas_limit / num_txs; info!("Attempting to fill blocks with {gas_limit} gas; sending {num_txs} txs, each with gas limit {gas_per_tx}."); From 44cbbd013b803e7b686e3cf89652a29b5733e491 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 13 Feb 2026 17:14:13 -0800 Subject: [PATCH 4/8] collect metrics for batched eth_sendRawTransaction calls --- crates/core/src/test_scenario.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/core/src/test_scenario.rs b/crates/core/src/test_scenario.rs index 56fcd735..0eaa2f80 100644 --- a/crates/core/src/test_scenario.rs +++ b/crates/core/src/test_scenario.rs @@ -1123,6 +1123,7 @@ where }) .collect(); + let hist = self.prometheus.hist.get(); tasks.push(tokio::task::spawn(async move { // Build json-rpc batch payload with multiple eth_sendRawTransaction requests let mut requests = Vec::with_capacity(signed_chunk.len()); @@ -1138,9 +1139,19 @@ where })); } + // === PROMETHEUS LATENCY METRICS === + let mut timer = hist.as_ref().map(|h| { + h.with_label_values(&["eth_sendRawTransaction"]) + .start_timer() + }); + let resp = match http_client.post(rpc_url).json(&requests).send().await { - Ok(r) => r, + Ok(r) => { + timer.take().map(|t| t.observe_duration()); + r + } Err(e) => { + timer.take().map(|t| t.observe_duration()); warn!("failed to send JSON-RPC batch: {e:?}"); return; } From 4677423697ebb1a037c00ef1fea6bb3be703867e Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 13 Feb 2026 17:25:04 -0800 Subject: [PATCH 5/8] chore: fmt --- crates/cli/src/default_scenarios/fill_block.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/cli/src/default_scenarios/fill_block.rs b/crates/cli/src/default_scenarios/fill_block.rs index 8620ef6d..0713d0f4 100644 --- a/crates/cli/src/default_scenarios/fill_block.rs +++ b/crates/cli/src/default_scenarios/fill_block.rs @@ -53,12 +53,16 @@ pub async fn fill_block( let num_txs = match (txs_per_block, txs_per_second) { (Some(0), _) | (_, Some(0)) => { - return Err(CliError::Args(crate::commands::error::ArgsError::SpamRateNotFound)); + return Err(CliError::Args( + crate::commands::error::ArgsError::SpamRateNotFound, + )); } (Some(n), _) => n, (_, Some(n)) => n, (None, None) => { - return Err(CliError::Args(crate::commands::error::ArgsError::SpamRateNotFound)); + return Err(CliError::Args( + crate::commands::error::ArgsError::SpamRateNotFound, + )); } }; let gas_per_tx = gas_limit / num_txs; From 35e4127bfd2146989bbf9f425e4daa1c5d2a4362 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 13 Feb 2026 17:25:45 -0800 Subject: [PATCH 6/8] chore: clippy --- crates/core/src/test_scenario.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/core/src/test_scenario.rs b/crates/core/src/test_scenario.rs index 0eaa2f80..fe5ab2de 100644 --- a/crates/core/src/test_scenario.rs +++ b/crates/core/src/test_scenario.rs @@ -1147,11 +1147,11 @@ where let resp = match http_client.post(rpc_url).json(&requests).send().await { Ok(r) => { - timer.take().map(|t| t.observe_duration()); + if let Some(t) = timer.take() { t.observe_duration() } r } Err(e) => { - timer.take().map(|t| t.observe_duration()); + if let Some(t) = timer.take() { t.observe_duration() } warn!("failed to send JSON-RPC batch: {e:?}"); return; } From eef4e934ee05043e061ca249816091b895f78c38 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Fri, 13 Feb 2026 19:22:28 -0800 Subject: [PATCH 7/8] chore: fmt --- crates/core/src/test_scenario.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/core/src/test_scenario.rs b/crates/core/src/test_scenario.rs index fe5ab2de..538f12c8 100644 --- a/crates/core/src/test_scenario.rs +++ b/crates/core/src/test_scenario.rs @@ -1147,11 +1147,15 @@ where let resp = match http_client.post(rpc_url).json(&requests).send().await { Ok(r) => { - if let Some(t) = timer.take() { t.observe_duration() } + if let Some(t) = timer.take() { + t.observe_duration() + } r } Err(e) => { - if let Some(t) = timer.take() { t.observe_duration() } + if let Some(t) = timer.take() { + t.observe_duration() + } warn!("failed to send JSON-RPC batch: {e:?}"); return; } From c4a110dc62c42b57433dc2b301e26a712a52a4b8 Mon Sep 17 00:00:00 2001 From: zeroXbrock <2791467+zeroXbrock@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:11:03 -0800 Subject: [PATCH 8/8] chore: update changelogs --- CHANGELOG.md | 1 + crates/report/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 671b8312..9fc88c0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - added contender version to bottom of reports ([#452](https://github.com/flashbots/contender/pull/452/changes)) - enable custom data dir at runtime ([453](https://github.com/flashbots/contender/pull/453/changes)) +- clean up html report UI, support batched `eth_sendRawTransaction` latency metrics ([#455](https://github.com/flashbots/contender/pull/455)) ## [0.8.1](https://github.com/flashbots/contender/releases/tag/v0.8.1) - 2026-02-09 diff --git a/crates/report/CHANGELOG.md b/crates/report/CHANGELOG.md index 7b1cbaeb..9892e20c 100644 --- a/crates/report/CHANGELOG.md +++ b/crates/report/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - added contender version to bottom of reports ([#452](https://github.com/flashbots/contender/pull/452/changes)) - use `std::path::Path` instead of `str` where applicable ([453](https://github.com/flashbots/contender/pull/453/changes)) +- clean up html report UI, support batched `eth_sendRawTransaction` latency metrics ([#455](https://github.com/flashbots/contender/pull/455/changes)) ## [0.8.1](https://github.com/flashbots/contender/releases/tag/v0.8.1) - 2026-02-09