fix(cli): store extradata as Bytes, decode hex in parser (#22344)

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Georgios Konstantopoulos
2026-02-18 20:38:16 -08:00
committed by GitHub
parent 8fa539225b
commit 8529da976f
6 changed files with 78 additions and 22 deletions

View File

@@ -55,7 +55,7 @@ where
EthereumBuilderConfig::new()
.with_gas_limit(gas_limit)
.with_max_blobs_per_block(conf.max_blobs_per_block())
.with_extra_data(conf.extra_data_bytes()),
.with_extra_data(conf.extra_data()),
))
}
}

View File

@@ -1,5 +1,6 @@
use crate::{cli::config::PayloadBuilderConfig, version::default_extra_data};
use alloy_consensus::constants::MAXIMUM_EXTRA_DATA_SIZE;
use alloy_primitives::Bytes;
use clap::{
builder::{RangedU64ValueParser, TypedValueParser},
Arg, Args, Command,
@@ -8,7 +9,7 @@ use reth_cli_util::{
parse_duration_from_secs, parse_duration_from_secs_or_ms,
parsers::format_duration_as_secs_or_ms,
};
use std::{borrow::Cow, ffi::OsStr, sync::OnceLock, time::Duration};
use std::{ffi::OsStr, sync::OnceLock, time::Duration};
/// Global static payload builder defaults
static PAYLOAD_BUILDER_DEFAULTS: OnceLock<DefaultPayloadBuilderValues> = OnceLock::new();
@@ -80,12 +81,15 @@ impl Default for DefaultPayloadBuilderValues {
#[command(next_help_heading = "Builder")]
pub struct PayloadBuilderArgs {
/// Block extra data set by the payload builder.
///
/// If the value is a `0x`-prefixed hex string, it is decoded into raw bytes. Otherwise, the
/// raw UTF-8 bytes of the string are used.
#[arg(
long = "builder.extradata",
value_parser = ExtraDataValueParser::default(),
default_value_t = DefaultPayloadBuilderValues::get_global().extra_data.clone()
default_value = DefaultPayloadBuilderValues::get_global().extra_data.as_str()
)]
pub extra_data: String,
pub extra_data: Bytes,
/// Target gas limit for built blocks.
#[arg(long = "builder.gaslimit", alias = "miner.gaslimit", value_name = "GAS_LIMIT")]
@@ -130,7 +134,7 @@ impl Default for PayloadBuilderArgs {
fn default() -> Self {
let defaults = DefaultPayloadBuilderValues::get_global();
Self {
extra_data: defaults.extra_data.clone(),
extra_data: Bytes::from(defaults.extra_data.as_bytes().to_vec()),
interval: parse_duration_from_secs_or_ms(defaults.interval.as_str()).unwrap(),
gas_limit: None,
deadline: Duration::from_secs(defaults.deadline.parse().unwrap()),
@@ -141,8 +145,8 @@ impl Default for PayloadBuilderArgs {
}
impl PayloadBuilderConfig for PayloadBuilderArgs {
fn extra_data(&self) -> Cow<'_, str> {
self.extra_data.as_str().into()
fn extra_data(&self) -> Bytes {
self.extra_data.clone()
}
fn interval(&self) -> Duration {
@@ -171,7 +175,7 @@ impl PayloadBuilderConfig for PayloadBuilderArgs {
struct ExtraDataValueParser;
impl TypedValueParser for ExtraDataValueParser {
type Value = String;
type Value = Bytes;
fn parse_ref(
&self,
@@ -181,7 +185,19 @@ impl TypedValueParser for ExtraDataValueParser {
) -> Result<Self::Value, clap::Error> {
let val =
value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
if val.len() > MAXIMUM_EXTRA_DATA_SIZE {
let bytes = if let Some(hex) = val.strip_prefix("0x") {
alloy_primitives::hex::decode(hex).map_err(|e| {
clap::Error::raw(
clap::error::ErrorKind::InvalidValue,
format!("Invalid hex in extradata: {e}"),
)
})?
} else {
val.as_bytes().to_vec()
};
if bytes.len() > MAXIMUM_EXTRA_DATA_SIZE {
return Err(clap::Error::raw(
clap::error::ErrorKind::InvalidValue,
format!(
@@ -189,7 +205,8 @@ impl TypedValueParser for ExtraDataValueParser {
),
))
}
Ok(val.to_string())
Ok(bytes.into())
}
}
@@ -232,7 +249,7 @@ mod tests {
extra_data.as_str(),
])
.args;
assert_eq!(args.extra_data, extra_data);
assert_eq!(args.extra_data.as_ref(), extra_data.as_bytes());
}
#[test]
@@ -246,6 +263,49 @@ mod tests {
assert!(args.is_err());
}
#[test]
fn test_valid_hex_extra_data() {
let hex = format!("0x{}", "ab".repeat(MAXIMUM_EXTRA_DATA_SIZE));
let args = CommandParser::<PayloadBuilderArgs>::parse_from([
"reth",
"--builder.extradata",
hex.as_str(),
])
.args;
assert_eq!(args.extra_data.as_ref(), vec![0xab; MAXIMUM_EXTRA_DATA_SIZE].as_slice());
}
#[test]
fn test_oversized_hex_extra_data() {
let hex = format!("0x{}", "ab".repeat(MAXIMUM_EXTRA_DATA_SIZE + 1));
assert!(CommandParser::<PayloadBuilderArgs>::try_parse_from([
"reth",
"--builder.extradata",
hex.as_str(),
])
.is_err());
}
#[test]
fn test_invalid_hex_extra_data() {
assert!(CommandParser::<PayloadBuilderArgs>::try_parse_from([
"reth",
"--builder.extradata",
"0xZZZZ",
])
.is_err());
}
#[test]
fn test_odd_length_hex_extra_data() {
assert!(CommandParser::<PayloadBuilderArgs>::try_parse_from([
"reth",
"--builder.extradata",
"0xabc",
])
.is_err());
}
#[test]
fn payload_builder_args_default_sanity_check() {
let default_args = PayloadBuilderArgs::default();

View File

@@ -5,7 +5,7 @@ use alloy_primitives::Bytes;
use reth_chainspec::{Chain, ChainKind, NamedChain};
use reth_network::{protocol::IntoRlpxSubProtocol, NetworkPrimitives};
use reth_transaction_pool::PoolConfig;
use std::{borrow::Cow, time::Duration};
use std::time::Duration;
/// 60M gas limit
const ETHEREUM_BLOCK_GAS_LIMIT_60M: u64 = 60_000_000;
@@ -15,13 +15,8 @@ const ETHEREUM_BLOCK_GAS_LIMIT_60M: u64 = 60_000_000;
/// This provides all basic payload builder settings and is implemented by the
/// [`PayloadBuilderArgs`](crate::args::PayloadBuilderArgs) type.
pub trait PayloadBuilderConfig {
/// Block extra data set by the payload builder.
fn extra_data(&self) -> Cow<'_, str>;
/// Returns the extra data as bytes.
fn extra_data_bytes(&self) -> Bytes {
self.extra_data().as_bytes().to_vec().into()
}
fn extra_data(&self) -> Bytes;
/// The interval at which the job should build a new payload after the last.
fn interval(&self) -> Duration;

View File

@@ -685,7 +685,9 @@ TxPool:
Builder:
--builder.extradata <EXTRA_DATA>
Block extra data set by the payload builder
Block extra data set by the payload builder.
If the value is a `0x`-prefixed hex string, it is decoded into raw bytes. Otherwise, the raw UTF-8 bytes of the string are used.
[default: reth/<VERSION>/<OS>]

View File

@@ -339,7 +339,7 @@ where
pool,
evm_config,
EthereumBuilderConfig::new()
.with_extra_data(ctx.payload_builder_config().extra_data_bytes()),
.with_extra_data(ctx.payload_builder_config().extra_data()),
),
};
Ok(payload_builder)

View File

@@ -62,8 +62,7 @@ where
ctx.provider().clone(),
pool,
evm_config,
EthereumBuilderConfig::new()
.with_extra_data(ctx.payload_builder_config().extra_data_bytes()),
EthereumBuilderConfig::new().with_extra_data(ctx.payload_builder_config().extra_data()),
);
let conf = ctx.payload_builder_config();