1use std::{
4 collections::BTreeMap,
5 ffi::OsString,
6 path::{Path, PathBuf},
7 time::Duration,
8};
9
10use thruput_bundle_identity::BundleIdentity;
11use thruput_json_rpc_types::bootstrap_manifest::{
12 BootstrapManifest, BootstrapManifestAllocation, BootstrapManifestStorageCostModel,
13};
14use thruput_json_rpc_types::{Ed25519PublicKey, NetworkHashText};
15use thruput_tx_validation::ConfigParamName;
16
17pub const DEFAULT_RPC_LISTEN_ADDRESS: &str = "127.0.0.1:9545";
19
20pub const DEFAULT_RPC_MAX_CONNECTIONS: u32 = 2_000;
22
23pub const DEFAULT_HOGD_DEV_VIEW_TIMEOUT: Duration = Duration::from_secs(30);
25
26pub const DEFAULT_BLOCK_DRIVER_INTERVAL: Duration = Duration::from_millis(500);
28
29pub const DEFAULT_MANUAL_BLOCK_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
31
32pub const DEFAULT_HOGD_DEV_LISTEN_HOST: &str = "127.0.0.1";
34
35pub const DEFAULT_HOGD_DEV_SHUTDOWN_GRACE: Duration = Duration::from_secs(5);
37
38pub const DEFAULT_HOGD_DEV_PERSISTENT_SHUTDOWN_GRACE: Duration = Duration::from_secs(30);
40
41pub const DEFAULT_HOGD_DEV_STARTUP_TIMEOUT: Duration = Duration::from_secs(10);
43
44pub const LOCAL_FAUCET_STORAGE_TOKENS: u64 = 10_000_000_000_000;
46
47pub const DEFAULT_LOCAL_ACCOUNT_TOKENS: u64 = 1_000_000_000;
49
50pub const HOGD_DEV_BOOTSTRAP_MANIFEST_KIND: &str = "groundhog-bootstrap";
52
53pub const DEFAULT_HOGD_DEV_BOOTSTRAP_PROFILE: &str = "local-dev";
55
56pub const DEFAULT_HOGD_DEV_STORAGE_COST_MODEL_PROFILE: &str = "hogd-default";
58
59pub const DEFAULT_LOCAL_AIRDROP_RAW_AMOUNT: u128 = 10 * thruput_signing::NATIVE_TOKEN_SCALE;
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64#[non_exhaustive]
65pub struct LocalAirdropConfig {
66 pub raw_amount: u128,
68 pub attempt_capacity: usize,
70 pub max_concurrency: usize,
72 pub request_timeout: Duration,
74}
75
76impl Default for LocalAirdropConfig {
77 fn default() -> Self {
78 Self {
79 raw_amount: DEFAULT_LOCAL_AIRDROP_RAW_AMOUNT,
80 attempt_capacity: 1_024,
81 max_concurrency: 16,
82 request_timeout: Duration::from_secs(30),
83 }
84 }
85}
86
87impl LocalAirdropConfig {
88 pub(crate) fn validate(self) -> Result<Self, &'static str> {
89 if self.raw_amount == 0 {
90 return Err("raw amount must be greater than zero");
91 }
92 if self.attempt_capacity == 0 {
93 return Err("attempt capacity must be greater than zero");
94 }
95 if self.max_concurrency == 0 {
96 return Err("maximum concurrency must be greater than zero");
97 }
98 if self.request_timeout.is_zero() {
99 return Err("request timeout must be greater than zero");
100 }
101 Ok(self)
102 }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
107#[non_exhaustive]
108pub struct NodeSupervisorConfig {
109 pub rpc_listen_address: String,
111 pub rpc_max_connections: u32,
113 pub rpc_batch_concurrency: thruput_json_rpc_server::batch::RpcBatchConcurrency,
115 pub hogd_dev: HogdDevConfig,
117 pub hogd_protocol_override: Option<String>,
123 pub view_gas_limit: u64,
125 pub view_timeout: Duration,
127 pub view_batch: thruput_json_rpc_server::ViewBatchConfig,
129 pub block_driver: BlockDriverConfig,
131 pub local_airdrop: LocalAirdropConfig,
133 pub bundle_identity: Option<BundleIdentity>,
135}
136
137impl Default for NodeSupervisorConfig {
138 fn default() -> Self {
139 Self {
140 rpc_listen_address: DEFAULT_RPC_LISTEN_ADDRESS.to_string(),
141 rpc_max_connections: DEFAULT_RPC_MAX_CONNECTIONS,
142 rpc_batch_concurrency: thruput_json_rpc_server::batch::RpcBatchConcurrency::default(),
143 hogd_dev: HogdDevConfig::default(),
144 hogd_protocol_override: None,
145 view_gas_limit: thruput_json_rpc_server::DEFAULT_VIEW_GAS_LIMIT,
146 view_timeout: DEFAULT_HOGD_DEV_VIEW_TIMEOUT,
147 view_batch: thruput_json_rpc_server::ViewBatchConfig::default(),
148 block_driver: BlockDriverConfig::default(),
149 local_airdrop: LocalAirdropConfig::default(),
150 bundle_identity: None,
151 }
152 }
153}
154
155impl NodeSupervisorConfig {
156 #[must_use]
158 pub const fn with_view_batch(
159 mut self,
160 limits: thruput_json_rpc_server::ViewBatchConfig,
161 ) -> Self {
162 self.view_batch = limits;
163 self
164 }
165 #[must_use]
167 pub const fn with_rpc_batch_concurrency(
168 mut self,
169 concurrency: thruput_json_rpc_server::batch::RpcBatchConcurrency,
170 ) -> Self {
171 self.rpc_batch_concurrency = concurrency;
172 self
173 }
174 #[must_use]
176 pub fn with_rpc_listen_address(mut self, listen_address: impl Into<String>) -> Self {
177 self.rpc_listen_address = listen_address.into();
178 self
179 }
180
181 #[must_use]
183 pub const fn with_rpc_max_connections(mut self, max_connections: u32) -> Self {
184 self.rpc_max_connections = max_connections;
185 self
186 }
187
188 #[must_use]
190 pub fn with_hogd_dev_binary(mut self, binary_path: impl Into<PathBuf>) -> Self {
191 self.hogd_dev.binary_path = Some(binary_path.into());
192 self
193 }
194
195 #[must_use]
197 pub fn with_hogd_dev(mut self, hogd_dev: HogdDevConfig) -> Self {
198 self.hogd_dev = hogd_dev;
199 self
200 }
201
202 #[must_use]
208 pub fn with_hogd_protocol_override(mut self, protocol: impl Into<String>) -> Self {
209 self.hogd_protocol_override = Some(protocol.into());
210 self
211 }
212
213 #[must_use]
215 pub fn with_view_gas_limit(mut self, view_gas_limit: u64) -> Self {
216 self.view_gas_limit = view_gas_limit;
217 self
218 }
219
220 #[must_use]
222 pub fn with_view_timeout(mut self, view_timeout: Duration) -> Self {
223 self.view_timeout = view_timeout;
224 self
225 }
226
227 #[must_use]
229 pub fn with_block_driver(mut self, block_driver: BlockDriverConfig) -> Self {
230 self.block_driver = block_driver;
231 self
232 }
233
234 #[must_use]
236 pub const fn with_local_airdrop(mut self, local_airdrop: LocalAirdropConfig) -> Self {
237 self.local_airdrop = local_airdrop;
238 self
239 }
240
241 #[must_use]
243 pub fn with_bundle_identity(mut self, bundle_identity: BundleIdentity) -> Self {
244 self.bundle_identity = Some(bundle_identity);
245 self
246 }
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251#[non_exhaustive]
252pub struct BlockDriverConfig {
253 pub mode: BlockDriverMode,
255 pub interval: Duration,
257 pub manual_request_timeout: Duration,
261}
262
263impl BlockDriverConfig {
264 #[must_use]
266 pub const fn instant() -> Self {
267 Self {
268 mode: BlockDriverMode::Instant,
269 interval: DEFAULT_BLOCK_DRIVER_INTERVAL,
270 manual_request_timeout: DEFAULT_MANUAL_BLOCK_REQUEST_TIMEOUT,
271 }
272 }
273
274 #[must_use]
276 pub const fn interval(interval: Duration) -> Self {
277 Self {
278 mode: BlockDriverMode::Interval,
279 interval,
280 manual_request_timeout: DEFAULT_MANUAL_BLOCK_REQUEST_TIMEOUT,
281 }
282 }
283
284 #[must_use]
286 pub const fn manual() -> Self {
287 Self {
288 mode: BlockDriverMode::Manual,
289 interval: DEFAULT_BLOCK_DRIVER_INTERVAL,
290 manual_request_timeout: DEFAULT_MANUAL_BLOCK_REQUEST_TIMEOUT,
291 }
292 }
293
294 #[must_use]
296 pub const fn with_mode(mut self, mode: BlockDriverMode) -> Self {
297 self.mode = mode;
298 self
299 }
300
301 #[must_use]
303 pub const fn with_interval(mut self, interval: Duration) -> Self {
304 self.interval = interval;
305 self
306 }
307
308 #[must_use]
310 pub const fn with_manual_request_timeout(mut self, timeout: Duration) -> Self {
311 self.manual_request_timeout = timeout;
312 self
313 }
314
315 pub(crate) fn effective_interval(self) -> Duration {
316 if self.interval.is_zero() {
317 DEFAULT_BLOCK_DRIVER_INTERVAL
318 } else {
319 self.interval
320 }
321 }
322
323 pub(crate) fn effective_manual_request_timeout(self) -> Duration {
324 if self.manual_request_timeout.is_zero() {
325 DEFAULT_MANUAL_BLOCK_REQUEST_TIMEOUT
326 } else {
327 self.manual_request_timeout
328 }
329 }
330}
331
332impl Default for BlockDriverConfig {
333 fn default() -> Self {
334 Self::instant()
335 }
336}
337
338#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
340#[non_exhaustive]
341pub enum BlockDriverMode {
342 #[default]
344 Instant,
345 Interval,
347 Manual,
349}
350
351#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
353pub enum LogLevel {
354 Critical,
355 Error,
356 #[default]
357 Warn,
358 Info,
359 Debug,
360 Trace,
361}
362
363impl LogLevel {
364 pub fn as_str(&self) -> &str {
365 match self {
366 LogLevel::Critical => "critical",
367 LogLevel::Error => "error",
368 LogLevel::Warn => "warn",
369 LogLevel::Info => "info",
370 LogLevel::Debug => "debug",
371 LogLevel::Trace => "trace",
372 }
373 }
374}
375
376#[derive(Debug, Clone, PartialEq, Eq)]
378#[non_exhaustive]
379pub struct HogdDevConfig {
380 pub binary_path: Option<PathBuf>,
385 pub contract_artifact_directories: Vec<PathBuf>,
387 pub data_dir: Option<PathBuf>,
391 pub log_level: Option<LogLevel>,
393 pub listen_host: String,
395 pub shutdown_grace: Duration,
400 pub startup_timeout: Duration,
402 pub bootstrap_accounts: Vec<BootstrapManifestAllocation>,
406 pub ramdb_bytes: Option<u64>,
410 pub network_config_overrides: BTreeMap<ConfigParamName, u64>,
414 pub extra_args: Vec<OsString>,
417 pub init_network_hash: Option<NetworkHashText>,
422 pub pid_file: Option<PathBuf>,
424 #[cfg(test)]
427 pub(crate) test_child_env: Vec<(OsString, OsString)>,
428}
429
430impl Default for HogdDevConfig {
431 fn default() -> Self {
432 Self {
433 binary_path: None,
434 contract_artifact_directories: Vec::new(),
435 data_dir: None,
436 log_level: None,
437 listen_host: DEFAULT_HOGD_DEV_LISTEN_HOST.to_string(),
438 shutdown_grace: DEFAULT_HOGD_DEV_SHUTDOWN_GRACE,
439 startup_timeout: DEFAULT_HOGD_DEV_STARTUP_TIMEOUT,
440 bootstrap_accounts: Vec::new(),
441 ramdb_bytes: None,
442 network_config_overrides: BTreeMap::new(),
443 extra_args: Vec::new(),
444 init_network_hash: None,
445 pid_file: None,
446 #[cfg(test)]
447 test_child_env: Vec::new(),
448 }
449 }
450}
451
452impl HogdDevConfig {
453 #[cfg(test)]
455 #[must_use]
456 pub(crate) fn with_test_child_env(
457 mut self,
458 key: impl Into<OsString>,
459 value: impl Into<OsString>,
460 ) -> Self {
461 self.test_child_env.push((key.into(), value.into()));
462 self
463 }
464
465 #[must_use]
467 pub fn with_init_network_hash(mut self, network_hash: NetworkHashText) -> Self {
468 self.init_network_hash = Some(network_hash);
469 self
470 }
471
472 #[must_use]
474 pub fn with_network_config_overrides(
475 mut self,
476 overrides: BTreeMap<ConfigParamName, u64>,
477 ) -> Self {
478 self.network_config_overrides = overrides;
479 self
480 }
481
482 #[must_use]
484 pub fn with_binary_path(mut self, binary_path: impl Into<PathBuf>) -> Self {
485 self.binary_path = Some(binary_path.into());
486 self
487 }
488
489 #[must_use]
491 pub fn with_contract_artifact_directories<I, P>(mut self, directories: I) -> Self
492 where
493 I: IntoIterator<Item = P>,
494 P: Into<PathBuf>,
495 {
496 self.contract_artifact_directories = directories
497 .into_iter()
498 .map(Into::into)
499 .map(resolve_path)
500 .collect();
501 self
502 }
503
504 #[must_use]
506 pub fn with_data_dir(mut self, data_dir: impl Into<PathBuf>) -> Self {
507 self.data_dir = Some(resolve_path(data_dir.into()));
508 self
509 }
510
511 #[must_use]
513 pub fn with_log_level(mut self, log_level: LogLevel) -> Self {
514 self.log_level = Some(log_level);
515 self
516 }
517
518 #[must_use]
520 pub fn with_listen_host(mut self, listen_host: impl Into<String>) -> Self {
521 self.listen_host = listen_host.into();
522 self
523 }
524
525 #[must_use]
527 pub fn with_shutdown_grace(mut self, shutdown_grace: Duration) -> Self {
528 self.shutdown_grace = shutdown_grace;
529 self
530 }
531
532 #[must_use]
534 pub fn with_startup_timeout(mut self, startup_timeout: Duration) -> Self {
535 self.startup_timeout = startup_timeout;
536 self
537 }
538
539 #[must_use]
541 pub fn with_bootstrap_accounts(mut self, accounts: Vec<BootstrapManifestAllocation>) -> Self {
542 self.bootstrap_accounts = accounts;
543 self
544 }
545
546 #[must_use]
548 pub fn with_ramdb_bytes(mut self, ramdb_bytes: u64) -> Self {
549 self.ramdb_bytes = Some(ramdb_bytes);
550 self
551 }
552
553 #[must_use]
555 pub fn with_extra_args<I, A>(mut self, args: I) -> Self
556 where
557 I: IntoIterator<Item = A>,
558 A: Into<OsString>,
559 {
560 self.extra_args = args.into_iter().map(Into::into).collect();
561 self
562 }
563
564 #[must_use]
566 pub fn with_pid_file(mut self, pid_file: impl Into<PathBuf>) -> Self {
567 self.pid_file = Some(pid_file.into());
568 self
569 }
570
571 pub fn to_bootstrap_manifest(
573 &self,
574 network_hash: NetworkHashText,
575 faucet_public_key: Ed25519PublicKey,
576 ) -> BootstrapManifest {
577 BootstrapManifest::new(
578 HOGD_DEV_BOOTSTRAP_MANIFEST_KIND,
579 network_hash,
580 DEFAULT_HOGD_DEV_BOOTSTRAP_PROFILE,
581 BootstrapManifestAllocation::new(
582 faucet_public_key,
583 LOCAL_FAUCET_STORAGE_TOKENS.to_string(),
584 ),
585 self.bootstrap_accounts.clone(),
586 BootstrapManifestStorageCostModel::profile(DEFAULT_HOGD_DEV_STORAGE_COST_MODEL_PROFILE),
587 )
588 }
589}
590
591fn resolve_path(path: PathBuf) -> PathBuf {
592 let expanded = match path.strip_prefix(Path::new("~")) {
593 Ok(rest) => std::env::var_os("HOME")
594 .map(PathBuf::from)
595 .map(|home| home.join(rest))
596 .unwrap_or(path),
597 Err(_) => path,
598 };
599 std::path::absolute(&expanded).unwrap_or(expanded)
600}
601
602#[cfg(test)]
603mod tests {
604 use super::LocalAirdropConfig;
605
606 #[test]
607 fn local_airdrop_default_is_valid() {
608 assert!(LocalAirdropConfig::default().validate().is_ok());
609 }
610
611 #[test]
612 fn local_airdrop_rejects_zero_resource_bounds() {
613 let default = LocalAirdropConfig::default();
614 let invalid = [
615 LocalAirdropConfig {
616 raw_amount: 0,
617 ..default
618 },
619 LocalAirdropConfig {
620 attempt_capacity: 0,
621 ..default
622 },
623 LocalAirdropConfig {
624 max_concurrency: 0,
625 ..default
626 },
627 LocalAirdropConfig {
628 request_timeout: std::time::Duration::ZERO,
629 ..default
630 },
631 ];
632
633 assert!(invalid.into_iter().all(|config| config.validate().is_err()));
634 }
635}