1use crate::{cli::config::PayloadBuilderConfig, version::default_extra_data};
2use alloy_consensus::constants::MAXIMUM_EXTRA_DATA_SIZE;
3use clap::{
4 builder::{RangedU64ValueParser, TypedValueParser},
5 Arg, Args, Command,
6};
7use reth_cli_util::{
8 parse_duration_from_secs, parse_duration_from_secs_or_ms,
9 parsers::format_duration_as_secs_or_ms,
10};
11use std::{borrow::Cow, ffi::OsStr, sync::OnceLock, time::Duration};
12
13static PAYLOAD_BUILDER_DEFAULTS: OnceLock<DefaultPayloadBuilderValues> = OnceLock::new();
15
16#[derive(Debug, Clone)]
20pub struct DefaultPayloadBuilderValues {
21 extra_data: String,
23 interval: String,
25 deadline: String,
27 max_payload_tasks: usize,
29}
30
31impl DefaultPayloadBuilderValues {
32 pub fn try_init(self) -> Result<(), Self> {
34 PAYLOAD_BUILDER_DEFAULTS.set(self)
35 }
36
37 pub fn get_global() -> &'static Self {
39 PAYLOAD_BUILDER_DEFAULTS.get_or_init(Self::default)
40 }
41
42 pub fn with_extra_data(mut self, v: impl Into<String>) -> Self {
44 self.extra_data = v.into();
45 self
46 }
47
48 pub fn with_interval(mut self, v: Duration) -> Self {
50 self.interval = format_duration_as_secs_or_ms(v);
51 self
52 }
53
54 pub fn with_deadline(mut self, v: u64) -> Self {
56 self.deadline = format!("{}", v);
57 self
58 }
59
60 pub const fn with_max_payload_tasks(mut self, v: usize) -> Self {
62 self.max_payload_tasks = v;
63 self
64 }
65}
66
67impl Default for DefaultPayloadBuilderValues {
68 fn default() -> Self {
69 Self {
70 extra_data: default_extra_data(),
71 interval: "1".to_string(),
72 deadline: "12".to_string(),
73 max_payload_tasks: 3,
74 }
75 }
76}
77
78#[derive(Debug, Clone, Args, PartialEq, Eq)]
80#[command(next_help_heading = "Builder")]
81pub struct PayloadBuilderArgs {
82 #[arg(
84 long = "builder.extradata",
85 value_parser = ExtraDataValueParser::default(),
86 default_value_t = DefaultPayloadBuilderValues::get_global().extra_data.clone()
87 )]
88 pub extra_data: String,
89
90 #[arg(long = "builder.gaslimit", alias = "miner.gaslimit", value_name = "GAS_LIMIT")]
92 pub gas_limit: Option<u64>,
93
94 #[arg(
100 long = "builder.interval",
101 value_parser = parse_duration_from_secs_or_ms,
102 default_value = DefaultPayloadBuilderValues::get_global().interval.as_str(),
103 value_name = "DURATION"
104 )]
105 pub interval: Duration,
106
107 #[arg(
109 long = "builder.deadline",
110 value_parser = parse_duration_from_secs,
111 default_value = DefaultPayloadBuilderValues::get_global().deadline.as_str(),
112 value_name = "SECONDS"
113 )]
114 pub deadline: Duration,
115
116 #[arg(
118 long = "builder.max-tasks",
119 value_parser = RangedU64ValueParser::<usize>::new().range(1..),
120 default_value_t = DefaultPayloadBuilderValues::get_global().max_payload_tasks
121 )]
122 pub max_payload_tasks: usize,
123
124 #[arg(long = "builder.max-blobs", value_name = "COUNT")]
126 pub max_blobs_per_block: Option<u64>,
127}
128
129impl Default for PayloadBuilderArgs {
130 fn default() -> Self {
131 let defaults = DefaultPayloadBuilderValues::get_global();
132 Self {
133 extra_data: defaults.extra_data.clone(),
134 interval: parse_duration_from_secs_or_ms(defaults.interval.as_str()).unwrap(),
135 gas_limit: None,
136 deadline: Duration::from_secs(defaults.deadline.parse().unwrap()),
137 max_payload_tasks: defaults.max_payload_tasks,
138 max_blobs_per_block: None,
139 }
140 }
141}
142
143impl PayloadBuilderConfig for PayloadBuilderArgs {
144 fn extra_data(&self) -> Cow<'_, str> {
145 self.extra_data.as_str().into()
146 }
147
148 fn interval(&self) -> Duration {
149 self.interval
150 }
151
152 fn deadline(&self) -> Duration {
153 self.deadline
154 }
155
156 fn gas_limit(&self) -> Option<u64> {
157 self.gas_limit
158 }
159
160 fn max_payload_tasks(&self) -> usize {
161 self.max_payload_tasks
162 }
163
164 fn max_blobs_per_block(&self) -> Option<u64> {
165 self.max_blobs_per_block
166 }
167}
168
169#[derive(Clone, Debug, Default)]
170#[non_exhaustive]
171struct ExtraDataValueParser;
172
173impl TypedValueParser for ExtraDataValueParser {
174 type Value = String;
175
176 fn parse_ref(
177 &self,
178 _cmd: &Command,
179 _arg: Option<&Arg>,
180 value: &OsStr,
181 ) -> Result<Self::Value, clap::Error> {
182 let val =
183 value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
184 if val.len() > MAXIMUM_EXTRA_DATA_SIZE {
185 return Err(clap::Error::raw(
186 clap::error::ErrorKind::InvalidValue,
187 format!(
188 "Payload builder extradata size exceeds {MAXIMUM_EXTRA_DATA_SIZE}-byte limit"
189 ),
190 ))
191 }
192 Ok(val.to_string())
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use clap::Parser;
200
201 #[derive(Parser)]
203 struct CommandParser<T: Args> {
204 #[command(flatten)]
205 args: T,
206 }
207
208 #[test]
209 fn test_args_with_valid_max_tasks() {
210 let args =
211 CommandParser::<PayloadBuilderArgs>::parse_from(["reth", "--builder.max-tasks", "1"])
212 .args;
213 assert_eq!(args.max_payload_tasks, 1)
214 }
215
216 #[test]
217 fn test_args_with_invalid_max_tasks() {
218 assert!(CommandParser::<PayloadBuilderArgs>::try_parse_from([
219 "reth",
220 "--builder.max-tasks",
221 "0"
222 ])
223 .is_err());
224 }
225
226 #[test]
227 fn test_default_extra_data() {
228 let extra_data = default_extra_data();
229 let args = CommandParser::<PayloadBuilderArgs>::parse_from([
230 "reth",
231 "--builder.extradata",
232 extra_data.as_str(),
233 ])
234 .args;
235 assert_eq!(args.extra_data, extra_data);
236 }
237
238 #[test]
239 fn test_invalid_extra_data() {
240 let extra_data = "x".repeat(MAXIMUM_EXTRA_DATA_SIZE + 1);
241 let args = CommandParser::<PayloadBuilderArgs>::try_parse_from([
242 "reth",
243 "--builder.extradata",
244 extra_data.as_str(),
245 ]);
246 assert!(args.is_err());
247 }
248
249 #[test]
250 fn payload_builder_args_default_sanity_check() {
251 let default_args = PayloadBuilderArgs::default();
252 let args = CommandParser::<PayloadBuilderArgs>::parse_from(["reth"]).args;
253 assert_eq!(args, default_args);
254 }
255
256 #[test]
257 fn test_args_with_s_interval() {
258 let args =
259 CommandParser::<PayloadBuilderArgs>::parse_from(["reth", "--builder.interval", "50"])
260 .args;
261 assert_eq!(args.interval, Duration::from_secs(50));
262 }
263
264 #[test]
265 fn test_args_with_ms_interval() {
266 let args =
267 CommandParser::<PayloadBuilderArgs>::parse_from(["reth", "--builder.interval", "50ms"])
268 .args;
269 assert_eq!(args.interval, Duration::from_millis(50));
270 }
271}