diff --git a/CHANGELOG.md b/CHANGELOG.md index 50ccc946..d6f2b7f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - auto-fund spammer accounts periodically when running with `--forever` to prevent ETH depletion - add `--time-to-inclusion-bucket` flag to configure histogram bucket size in reports ([#498](https://github.com/flashbots/contender/pull/498)) - move default data dir to `$XDG_STATE_HOME/contender` (`~/.local/state/contender`), with automatic migration from legacy `~/.contender` ([#460](https://github.com/flashbots/contender/issues/460)) +- organize `--help` output into logical sections for `spam` and `campaign` flags ([#408](https://github.com/flashbots/contender/issues/408)) ## [0.9.0](https://github.com/flashbots/contender/releases/tag/v0.9.0) - 2026-03-17 @@ -93,4 +94,4 @@ Internal changes: - revamp error handling ([#378](https://github.com/flashbots/contender/pull/378)) - DB schema bumped to `user_version = 6` to record campaign/stage metadata in runs. - - If you see a DB version mismatch, export/reset your DB: `contender db export` (optional backup) then `contender db reset` (or `drop`) to recreate with the new schema. \ No newline at end of file + - If you see a DB version mismatch, export/reset your DB: `contender db export` (optional backup) then `contender db reset` (or `drop`) to recreate with the new schema. diff --git a/crates/cli/CHANGELOG.md b/crates/cli/CHANGELOG.md index 6e0e44f2..f08700e2 100644 --- a/crates/cli/CHANGELOG.md +++ b/crates/cli/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - auto-fund spammer accounts periodically when running with `--forever` to prevent ETH depletion; refund interval is derived from `--min-balance`, `get_max_spam_cost()`, and `--tps`/`--tpb` - add `--time-to-inclusion-bucket` flag to configure histogram bucket size in reports ([#498](https://github.com/flashbots/contender/pull/498/changes)) - move default data dir from `~/.contender` to `$XDG_STATE_HOME/contender` (defaults to `~/.local/state/contender`), with automatic migration of existing data ([#460](https://github.com/flashbots/contender/issues/460/changes)) +- organize `--help` output into logical sections for `spam` and `campaign` flags ([#408](https://github.com/flashbots/contender/issues/408)) ## [0.9.0](https://github.com/flashbots/contender/releases/tag/v0.9.0) - 2026-03-17 diff --git a/crates/cli/src/commands/campaign.rs b/crates/cli/src/commands/campaign.rs index f47c9853..ed589aff 100644 --- a/crates/cli/src/commands/campaign.rs +++ b/crates/cli/src/commands/campaign.rs @@ -3,7 +3,10 @@ use crate::commands::spam::SpamCampaignContext; use crate::commands::GenericDb; use crate::commands::{ self, - common::{ScenarioSendTxsCliArgs, SendTxsCliArgsInner}, + common::{ + ScenarioSendTxsCliArgs, SendTxsCliArgsInner, HELP_HEADING_COMMON, HELP_HEADING_PAYLOAD, + HELP_HEADING_RUNTIME, + }, SpamCliArgs, }; use crate::error::CliError; @@ -24,7 +27,7 @@ use uuid::Uuid; #[derive(Clone, Debug, Args)] pub struct CampaignCliArgs { /// Path to campaign config TOML - #[arg(help = "Path to campaign config TOML")] + #[arg(help = "Path to campaign config TOML", help_heading = HELP_HEADING_COMMON)] pub campaign: String, #[command(flatten)] @@ -36,7 +39,8 @@ pub struct CampaignCliArgs { short, long, long_help = "HTTP JSON-RPC URL to use for bundle spamming (must support `eth_sendBundle`)", - visible_aliases = ["builder", "builder-rpc-url", "builder-rpc"] + visible_aliases = ["builder", "builder-rpc-url", "builder-rpc"], + help_heading = HELP_HEADING_COMMON, )] pub builder_url: Option, @@ -46,7 +50,8 @@ pub struct CampaignCliArgs { long, default_value_t = 12, long_help = "The number of blocks to wait for pending transactions to land. If transactions land within the timeout, it resets.", - visible_aliases = ["wait"] + visible_aliases = ["wait"], + help_heading = HELP_HEADING_RUNTIME, )] pub pending_timeout: u64, @@ -55,7 +60,8 @@ pub struct CampaignCliArgs { long = "rpc-batch-size", value_name = "N", default_value_t = 0, - long_help = "Max number of eth_sendRawTransaction calls to send in a single JSON-RPC batch request. 0 (default) disables batching and sends one eth_sendRawTransaction per tx." + long_help = "Max number of eth_sendRawTransaction calls to send in a single JSON-RPC batch request. 0 (default) disables batching and sends one eth_sendRawTransaction per tx.", + help_heading = HELP_HEADING_PAYLOAD, )] pub rpc_batch_size: u64, @@ -64,7 +70,8 @@ pub struct CampaignCliArgs { long, help = "Ignore transaction receipts.", long_help = "Keep sending transactions without waiting for receipts.", - visible_aliases = ["ir", "no-receipts"] + visible_aliases = ["ir", "no-receipts"], + help_heading = HELP_HEADING_RUNTIME, )] pub ignore_receipts: bool, @@ -72,7 +79,8 @@ pub struct CampaignCliArgs { #[arg( long, help = "Disable nonce synchronization between batches.", - visible_aliases = ["disable-nonce-sync", "fast-nonces"] + visible_aliases = ["disable-nonce-sync", "fast-nonces"], + help_heading = HELP_HEADING_RUNTIME, )] pub optimistic_nonces: bool, @@ -81,7 +89,8 @@ pub struct CampaignCliArgs { global = true, long, long_help = "Generate a report for the spam run(s) after the campaign completes.", - visible_aliases = ["report"] + visible_aliases = ["report"], + help_heading = HELP_HEADING_RUNTIME, )] pub gen_report: bool, @@ -89,7 +98,8 @@ pub struct CampaignCliArgs { #[arg( long, global = true, - long_help = "If set, skip contract deployment & setup transactions when running builtin scenarios. Does nothing when running a scenario file." + long_help = "If set, skip contract deployment & setup transactions when running builtin scenarios. Does nothing when running a scenario file.", + help_heading = HELP_HEADING_RUNTIME, )] pub skip_setup: bool, @@ -98,7 +108,8 @@ pub struct CampaignCliArgs { long = "timeout", long_help = "The time to wait for spammer to recover from failure before stopping contender.", value_parser = parse_duration, - default_value = "5min" + default_value = "5min", + help_heading = HELP_HEADING_RUNTIME, )] pub spam_timeout: Duration, @@ -106,7 +117,8 @@ pub struct CampaignCliArgs { #[arg( long = "send-raw-tx-sync", default_value_t = false, - long_help = "Use eth_sendRawTransactionSync instead of eth_sendRawTransaction. The RPC blocks until the tx is included, giving precise TTI. NOTE: incompatible with --rpc-batch-size." + long_help = "Use eth_sendRawTransactionSync instead of eth_sendRawTransaction. The RPC blocks until the tx is included, giving precise TTI. NOTE: incompatible with --rpc-batch-size.", + help_heading = HELP_HEADING_PAYLOAD, )] pub send_raw_tx_sync: bool, @@ -115,7 +127,8 @@ pub struct CampaignCliArgs { global = true, default_value_t = false, long = "forever", - visible_aliases = ["indefinite", "indefinitely", "infinite"] + visible_aliases = ["indefinite", "indefinitely", "infinite"], + help_heading = HELP_HEADING_RUNTIME, )] pub run_forever: bool, @@ -124,7 +137,8 @@ pub struct CampaignCliArgs { long = "flashblocks-ws-url", value_name = "URL", env = "FLASHBLOCKS_WS_URL", - long_help = "WebSocket URL for subscribing to flashblock pre-confirmations. When set, contender will track sub-block inclusion latency alongside full-block metrics." + long_help = "WebSocket URL for subscribing to flashblock pre-confirmations. When set, contender will track sub-block inclusion latency alongside full-block metrics.", + help_heading = HELP_HEADING_RUNTIME, )] pub flashblocks_ws_url: Option, @@ -132,12 +146,19 @@ pub struct CampaignCliArgs { #[arg( long, global = true, - long_help = "Skip per-transaction debug traces (debug_traceTransaction) when generating the campaign report. This significantly speeds up report generation for large runs at the cost of omitting the storage heatmap and tx gas used charts." + long_help = "Skip per-transaction debug traces (debug_traceTransaction) when generating the campaign report. This significantly speeds up report generation for large runs at the cost of omitting the storage heatmap and tx gas used charts.", + help_heading = HELP_HEADING_RUNTIME, )] pub skip_tx_traces: bool, /// Bucket size in milliseconds for the time-to-inclusion histogram. - #[arg(long, default_value_t = 1000, value_name = "MS", value_parser = clap::value_parser!(u64).range(1..=10000))] + #[arg( + long, + default_value_t = 1000, + value_name = "MS", + value_parser = clap::value_parser!(u64).range(1..=10000), + help_heading = HELP_HEADING_RUNTIME + )] pub time_to_inclusion_bucket: u64, } diff --git a/crates/cli/src/commands/common.rs b/crates/cli/src/commands/common.rs index 1e3e87c2..9a61c373 100644 --- a/crates/cli/src/commands/common.rs +++ b/crates/cli/src/commands/common.rs @@ -21,11 +21,17 @@ use std::str::FromStr; use std::sync::Arc; use tracing::info; +pub const HELP_HEADING_COMMON: &str = "Common"; +pub const HELP_HEADING_PAYLOAD: &str = "Payload Adjustments"; +pub const HELP_HEADING_RUNTIME: &str = "Runtime Adjustments"; +pub const HELP_HEADING_ENGINE: &str = "Engine API Settings"; + #[derive(Clone, Debug, clap::Args)] pub struct ScenarioSendTxsCliArgs { /// The path to the test file to use for spamming/setup. Use default scenarios with "scenario:". /// Default scenarios can be found at https://github.com/flashbots/contender/tree/main/scenarios /// Example: `scenario:simple.toml` or `scenario:precompiles/modexp.toml` + #[arg(help_heading = HELP_HEADING_COMMON)] pub testfile: Option, #[command(flatten)] @@ -34,25 +40,26 @@ pub struct ScenarioSendTxsCliArgs { #[derive(Clone, Debug, clap::Args)] pub struct SendTxsCliArgsInner { - /// RPC URL to send requests. #[arg( - env = "RPC_URL", short, long, - long_help = "RPC URL to send requests from the `eth_` namespace. Set --builder-url or --auth-rpc-url to enable other namespaces.", - default_value = "http://localhost:8545", - visible_aliases = ["el-rpc", "el-rpc-url"] + value_name="KEY=VALUE", + long_help = "Key-value pairs to override the parameters in scenario files.", + value_parser = cli_env_vars_parser, + action = clap::ArgAction::Append, + help_heading = HELP_HEADING_COMMON, )] - pub rpc_url: Url, + pub env: Option>, - /// The seed to use for generating spam transactions. + /// The minimum balance to keep in each spammer EOA. #[arg( - env = "CONTENDER_SEED", - short, long, - long_help = "The seed to use for generating spam transactions" + long_help = "The minimum balance to keep in each spammer EOA, with units.", + default_value = "0.01 ether", + value_parser = parse_value, + help_heading = HELP_HEADING_COMMON, )] - pub seed: Option, + pub min_balance: U256, /// Private key(s) to use for funding agent accounts or signing transactions. #[arg( @@ -60,91 +67,102 @@ pub struct SendTxsCliArgsInner { short, long = "priv-key", long_help = "Add private keys to fund agent accounts. Scenarios with hard-coded `from` addresses may also use these to sign transactions. -Flag may be specified multiple times." +Flag may be specified multiple times.", + help_heading = HELP_HEADING_COMMON, )] pub private_keys: Option>, - /// The minimum balance to keep in each spammer EOA. - #[arg( - long, - long_help = "The minimum balance to keep in each spammer EOA, with units.", - default_value = "0.01 ether", - value_parser = parse_value, - )] - pub min_balance: U256, - - /// Transaction type - #[arg( - short = 't', - long, - long_help = "Transaction type for generated transactions.", - value_enum, - default_value_t = TxTypeCli::Eip1559, - )] - pub tx_type: TxTypeCli, - - /// Bundle type + /// RPC URL to send requests. #[arg( + env = "RPC_URL", + short, long, - long_help = "Bundle type for generated bundles.", - value_enum, - default_value_t = BundleTypeCli::default(), - visible_aliases = ["bt"] + long_help = "RPC URL to send requests from the `eth_` namespace. Set --builder-url or --auth-rpc-url to enable other namespaces.", + default_value = "http://localhost:8545", + visible_aliases = ["el-rpc", "el-rpc-url"], + help_heading = HELP_HEADING_COMMON, )] - pub bundle_type: BundleTypeCli, - - #[command(flatten)] - pub auth_args: AuthCliArgs, + pub rpc_url: Url, - /// Enable block-building. + /// Label to differentiate multiple deployments of the same scenario. #[arg( - long, - long_help = "Enable block-building by calling engine_forkchoiceUpdated on Auth RPC after each spam batch. -Requires --auth-rpc-url and --jwt-secret to be set.", - visible_aliases = ["fcu", "build-blocks"] + long = "scenario-label", + visible_aliases = ["sl"], + long_help = "Label to differentiate multiple deployments of the same scenario. Appends _{label} to contract names in the DB.", + help_heading = HELP_HEADING_COMMON, )] - pub call_forkchoice: bool, + pub scenario_label: Option, + /// The seed to use for generating spam transactions. #[arg( + env = "CONTENDER_SEED", short, long, - value_name="KEY=VALUE", - long_help = "Key-value pairs to override the parameters in scenario files.", - value_parser = cli_env_vars_parser, - action = clap::ArgAction::Append, + long_help = "The seed to use for generating spam transactions", + help_heading = HELP_HEADING_COMMON, )] - pub env: Option>, + pub seed: Option, + /// Bundle type #[arg( long, - long_help = "Override senders to send all transactions from one account." + long_help = "Bundle type for generated bundles.", + value_enum, + default_value_t = BundleTypeCli::default(), + visible_aliases = ["bt"], + help_heading = HELP_HEADING_PAYLOAD, )] - pub override_senders: bool, + pub bundle_type: BundleTypeCli, /// The gas price to use for the spammer. #[arg( long, long_help = "The gas price to use for the spammer, with units, defaults to Wei.", value_parser = parse_value, + help_heading = HELP_HEADING_PAYLOAD, )] pub gas_price: Option, + /// Transaction type + #[arg( + short = 't', + long, + long_help = "Transaction type for generated transactions.", + value_enum, + default_value_t = TxTypeCli::Eip1559, + help_heading = HELP_HEADING_PAYLOAD, + )] + pub tx_type: TxTypeCli, + /// The number of accounts to generate for each agent (`from_pool` in scenario files). /// Defaults to 1 for standalone setup, 10 for spam and campaign. #[arg( short = 'a', long, visible_aliases = ["na", "accounts"], + help_heading = HELP_HEADING_RUNTIME, )] pub accounts_per_agent: Option, - /// Label to differentiate multiple deployments of the same scenario. #[arg( - long = "scenario-label", - visible_aliases = ["sl"], - long_help = "Label to differentiate multiple deployments of the same scenario. Appends _{label} to contract names in the DB.", + long, + long_help = "Override senders to send all transactions from one account.", + help_heading = HELP_HEADING_RUNTIME, )] - pub scenario_label: Option, + pub override_senders: bool, + + /// Enable block-building. + #[arg( + long, + long_help = "Enable block-building by calling engine_forkchoiceUpdated on Auth RPC after each spam batch. +Requires --auth-rpc-url and --jwt-secret to be set.", + visible_aliases = ["fcu", "build-blocks"], + help_heading = HELP_HEADING_ENGINE, + )] + pub call_forkchoice: bool, + + #[command(flatten)] + pub auth_args: AuthCliArgs, } impl SendTxsCliArgsInner { @@ -196,7 +214,8 @@ pub struct AuthCliArgs { env = "AUTH_RPC_URL", long, long_help = "Provide this URL to enable use of engine_ calls.", - visible_aliases = ["auth", "auth-rpc", "auth-url"] + visible_aliases = ["auth", "auth-rpc", "auth-url"], + help_heading = HELP_HEADING_ENGINE, )] pub auth_rpc_url: Option, @@ -206,26 +225,29 @@ pub struct AuthCliArgs { long, long_help = "Path to file containing JWT secret used for `engine_` calls. Required if --auth-rpc-url is set.", - visible_aliases = ["jwt"] + visible_aliases = ["jwt"], + help_heading = HELP_HEADING_ENGINE, )] pub jwt_secret: Option, - /// Use OP engine provider - #[arg( - long = "optimism", - long_help = "Use OP types in the engine provider. Set this flag when targeting an OP node.", - visible_aliases = ["op"] - )] - pub use_op: bool, - /// Engine API Message Version #[arg( long, short, value_enum, - default_value_t = EngineMessageVersion::V4 + default_value_t = EngineMessageVersion::V4, + help_heading = HELP_HEADING_ENGINE, )] message_version: EngineMessageVersion, + + /// Use OP engine provider + #[arg( + long = "optimism", + long_help = "Use OP types in the engine provider. Set this flag when targeting an OP node.", + visible_aliases = ["op"], + help_heading = HELP_HEADING_ENGINE, + )] + pub use_op: bool, } impl Default for AuthCliArgs { @@ -281,19 +303,15 @@ impl AuthCliArgs { #[derive(Clone, Debug, clap::Args)] pub struct SendSpamCliArgs { - /// HTTP JSON-RPC URL to use for bundle spamming (must support `eth_sendBundle`). + /// The number of txs to send per second using the timed spammer. + /// May not be set if `txs_per_block` is set. #[arg( - env = "BUILDER_RPC_URL", - short, + global = true, long, - long_help = "HTTP JSON-RPC URL to use for bundle spamming (must support `eth_sendBundle`)", - visible_aliases = ["builder", "builder-rpc-url", "builder-rpc"] + long_help = "Number of txs to send per second. Must not be set if --txs-per-block is set.", + visible_aliases = ["tps"], + help_heading = HELP_HEADING_COMMON, )] - pub builder_url: Option, - - /// The number of txs to send per second using the timed spammer. - /// May not be set if `txs_per_block` is set. - #[arg(global = true, long, long_help = "Number of txs to send per second. Must not be set if --txs-per-block is set.", visible_aliases = ["tps"])] pub txs_per_second: Option, /// The number of txs to send per block using the blockwise spammer. @@ -304,7 +322,9 @@ pub struct SendSpamCliArgs { long_help = "Number of txs to send per block. Must not be set if --txs-per-second is set. Requires --priv-key to be set for each 'from' address in the given testfile.", - visible_aliases = ["tpb"])] + visible_aliases = ["tpb"], + help_heading = HELP_HEADING_COMMON, + )] pub txs_per_block: Option, /// The duration of the spamming run in seconds or blocks, depending on whether `txs_per_second` or `txs_per_block` is set. @@ -312,17 +332,30 @@ Requires --priv-key to be set for each 'from' address in the given testfile.", short, long, default_value_t = 10, - long_help = "Duration of the spamming run in seconds or blocks, depending on whether --txs-per-second or --txs-per-block is set." + long_help = "Duration of the spamming run in seconds or blocks, depending on whether --txs-per-second or --txs-per-block is set.", + help_heading = HELP_HEADING_COMMON, )] pub duration: u64, // TODO: make a new enum to represent seconds or blocks + /// HTTP JSON-RPC URL to use for bundle spamming (must support `eth_sendBundle`). + #[arg( + env = "BUILDER_RPC_URL", + short, + long, + long_help = "HTTP JSON-RPC URL to use for bundle spamming (must support `eth_sendBundle`)", + visible_aliases = ["builder", "builder-rpc-url", "builder-rpc"], + help_heading = HELP_HEADING_COMMON, + )] + pub builder_url: Option, + /// The time to wait for pending transactions to land, in blocks. #[arg( short = 'w', long, default_value_t = 12, long_help = "The number of blocks to wait for pending transactions to land. If transactions land within the timeout, it resets.", - visible_aliases = ["wait"] + visible_aliases = ["wait"], + help_heading = HELP_HEADING_RUNTIME, )] pub pending_timeout: u64, @@ -331,7 +364,8 @@ Requires --priv-key to be set for each 'from' address in the given testfile.", global = true, default_value_t = false, long = "forever", - visible_aliases = ["indefinite", "indefinitely", "infinite"] + visible_aliases = ["indefinite", "indefinitely", "infinite"], + help_heading = HELP_HEADING_RUNTIME, )] pub run_forever: bool, } diff --git a/crates/cli/src/commands/spam.rs b/crates/cli/src/commands/spam.rs index 0be44fc6..adc7abc1 100644 --- a/crates/cli/src/commands/spam.rs +++ b/crates/cli/src/commands/spam.rs @@ -1,4 +1,6 @@ -use super::common::{ScenarioSendTxsCliArgs, SendSpamCliArgs}; +use super::common::{ + ScenarioSendTxsCliArgs, SendSpamCliArgs, HELP_HEADING_PAYLOAD, HELP_HEADING_RUNTIME, +}; use crate::{ commands::{ common::{EngineParams, SendTxsCliArgsInner, TxTypeCli}, @@ -251,23 +253,25 @@ impl EngineArgs { #[derive(Clone, Debug, clap::Args)] pub struct SpamCliArgs { #[command(flatten)] - pub eth_json_rpc_args: ScenarioSendTxsCliArgs, + pub spam_args: SendSpamCliArgs, #[command(flatten)] - pub spam_args: SendSpamCliArgs, + pub eth_json_rpc_args: ScenarioSendTxsCliArgs, #[arg( long, help = "Ignore transaction receipts.", long_help = "Keep sending transactions without waiting for receipts.", - visible_aliases = ["ir", "no-receipts"] + visible_aliases = ["ir", "no-receipts"], + help_heading = HELP_HEADING_RUNTIME, )] pub ignore_receipts: bool, #[arg( long, help = "Disable nonce synchronization between batches.", - visible_aliases = ["disable-nonce-sync", "fast-nonces"] + visible_aliases = ["disable-nonce-sync", "fast-nonces"], + help_heading = HELP_HEADING_RUNTIME, )] pub optimistic_nonces: bool, @@ -275,7 +279,8 @@ pub struct SpamCliArgs { global = true, long, long_help = "Set this to generate a report for the spam run(s) after spamming.", - visible_aliases = ["report"] + visible_aliases = ["report"], + help_heading = HELP_HEADING_RUNTIME, )] pub gen_report: bool, @@ -283,7 +288,8 @@ pub struct SpamCliArgs { #[arg( long, global = true, - long_help = "If set, skip contract deployment & setup transactions when running builtin scenarios. Does nothing when running a scenario file." + long_help = "If set, skip contract deployment & setup transactions when running builtin scenarios. Does nothing when running a scenario file.", + help_heading = HELP_HEADING_RUNTIME, )] pub skip_setup: bool, @@ -298,7 +304,8 @@ pub struct SpamCliArgs { value_name = "N", default_value_t = 0, long_help = "Max number of eth_sendRawTransaction calls to send in a single JSON-RPC batch request. \ - 0 (default) disables batching and sends one eth_sendRawTransaction per tx." + 0 (default) disables batching and sends one eth_sendRawTransaction per tx.", + help_heading = HELP_HEADING_PAYLOAD, )] pub rpc_batch_size: u64, @@ -306,7 +313,8 @@ pub struct SpamCliArgs { #[arg( long = "send-raw-tx-sync", default_value_t = false, - long_help = "Use eth_sendRawTransactionSync instead of eth_sendRawTransaction. The RPC blocks until the tx is included, giving precise TTI. NOTE: incompatible with --rpc-batch-size." + long_help = "Use eth_sendRawTransactionSync instead of eth_sendRawTransaction. The RPC blocks until the tx is included, giving precise TTI. NOTE: incompatible with --rpc-batch-size.", + help_heading = HELP_HEADING_PAYLOAD, )] pub send_raw_tx_sync: bool, @@ -314,7 +322,8 @@ pub struct SpamCliArgs { long = "timeout", long_help = "The time to wait for spammer to recover from failure before stopping contender. NOTE: this flag is deprecated and currently does nothing. It will be removed in a future release.", value_parser = parse_duration, - default_value = "5min" + default_value = "5min", + help_heading = HELP_HEADING_RUNTIME, )] pub spam_timeout: Duration, @@ -323,7 +332,8 @@ pub struct SpamCliArgs { long = "flashblocks-ws-url", value_name = "URL", env = "FLASHBLOCKS_WS_URL", - long_help = "WebSocket URL for subscribing to flashblock pre-confirmations. When set, contender will track sub-block inclusion latency alongside full-block metrics." + long_help = "WebSocket URL for subscribing to flashblock pre-confirmations. When set, contender will track sub-block inclusion latency alongside full-block metrics.", + help_heading = HELP_HEADING_RUNTIME, )] pub flashblocks_ws_url: Option,