1use core::fmt;
2use std::net::{IpAddr, SocketAddr};
3use std::str::FromStr;
4use std::time::Duration;
5
6use bytesize::ByteSize;
7use malachitebft_core_types::TimeoutKind;
8use multiaddr::Multiaddr;
9use serde::{Deserialize, Serialize};
10
11#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
13pub struct P2pConfig {
14 pub listen_addr: Multiaddr,
16
17 pub persistent_peers: Vec<Multiaddr>,
19
20 #[serde(default)]
22 pub discovery: DiscoveryConfig,
23
24 pub protocol: PubSubProtocol,
26
27 pub pubsub_max_size: ByteSize,
29
30 pub rpc_max_size: ByteSize,
32}
33
34impl Default for P2pConfig {
35 fn default() -> Self {
36 P2pConfig {
37 listen_addr: Multiaddr::empty(),
38 persistent_peers: vec![],
39 discovery: Default::default(),
40 protocol: Default::default(),
41 rpc_max_size: ByteSize::mib(10),
42 pubsub_max_size: ByteSize::mib(4),
43 }
44 }
45}
46
47#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
49pub struct DiscoveryConfig {
50 #[serde(default)]
52 pub enabled: bool,
53
54 #[serde(default)]
56 pub bootstrap_protocol: BootstrapProtocol,
57
58 #[serde(default)]
60 pub selector: Selector,
61
62 #[serde(default)]
64 pub num_outbound_peers: usize,
65
66 #[serde(default)]
68 pub num_inbound_peers: usize,
69
70 #[serde(default)]
72 pub max_connections_per_peer: usize,
73
74 #[serde(default)]
76 #[serde(with = "humantime_serde")]
77 pub ephemeral_connection_timeout: Duration,
78}
79
80impl Default for DiscoveryConfig {
81 fn default() -> Self {
82 DiscoveryConfig {
83 enabled: false,
84 bootstrap_protocol: Default::default(),
85 selector: Default::default(),
86 num_outbound_peers: 0,
87 num_inbound_peers: 20,
88 max_connections_per_peer: 5,
89 ephemeral_connection_timeout: Default::default(),
90 }
91 }
92}
93
94#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
95#[serde(rename_all = "lowercase")]
96pub enum BootstrapProtocol {
97 #[default]
98 Kademlia,
99 Full,
100}
101
102impl BootstrapProtocol {
103 pub fn name(&self) -> &'static str {
104 match self {
105 Self::Kademlia => "kademlia",
106 Self::Full => "full",
107 }
108 }
109}
110
111impl FromStr for BootstrapProtocol {
112 type Err = String;
113
114 fn from_str(s: &str) -> Result<Self, Self::Err> {
115 match s {
116 "kademlia" => Ok(Self::Kademlia),
117 "full" => Ok(Self::Full),
118 e => Err(format!(
119 "unknown bootstrap protocol: {e}, available: kademlia, full"
120 )),
121 }
122 }
123}
124
125#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
126#[serde(rename_all = "lowercase")]
127pub enum Selector {
128 #[default]
129 Kademlia,
130 Random,
131}
132
133impl Selector {
134 pub fn name(&self) -> &'static str {
135 match self {
136 Self::Kademlia => "kademlia",
137 Self::Random => "random",
138 }
139 }
140}
141
142impl FromStr for Selector {
143 type Err = String;
144
145 fn from_str(s: &str) -> Result<Self, Self::Err> {
146 match s {
147 "kademlia" => Ok(Self::Kademlia),
148 "random" => Ok(Self::Random),
149 e => Err(format!(
150 "unknown selector: {e}, available: kademlia, random"
151 )),
152 }
153 }
154}
155
156#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
157pub enum TransportProtocol {
158 #[default]
159 Tcp,
160 Quic,
161}
162
163impl TransportProtocol {
164 pub fn multiaddr(&self, host: &str, port: usize) -> Multiaddr {
165 match self {
166 Self::Tcp => format!("/ip4/{host}/tcp/{port}").parse().unwrap(),
167 Self::Quic => format!("/ip4/{host}/udp/{port}/quic-v1").parse().unwrap(),
168 }
169 }
170}
171
172impl FromStr for TransportProtocol {
173 type Err = String;
174
175 fn from_str(s: &str) -> Result<Self, Self::Err> {
176 match s {
177 "tcp" => Ok(Self::Tcp),
178 "quic" => Ok(Self::Quic),
179 e => Err(format!(
180 "unknown transport protocol: {e}, available: tcp, quic"
181 )),
182 }
183 }
184}
185
186#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
190#[serde(tag = "type", rename_all = "lowercase")]
191pub enum PubSubProtocol {
192 GossipSub(GossipSubConfig),
193 Broadcast,
194}
195
196impl Default for PubSubProtocol {
197 fn default() -> Self {
198 Self::GossipSub(GossipSubConfig::default())
199 }
200}
201
202#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
204#[serde(from = "gossipsub::RawConfig", default)]
205pub struct GossipSubConfig {
206 mesh_n: usize,
208
209 mesh_n_high: usize,
211
212 mesh_n_low: usize,
214
215 mesh_outbound_min: usize,
220}
221
222impl Default for GossipSubConfig {
223 fn default() -> Self {
224 Self::new(6, 12, 4, 2)
225 }
226}
227
228impl GossipSubConfig {
229 pub fn new(
231 mesh_n: usize,
232 mesh_n_high: usize,
233 mesh_n_low: usize,
234 mesh_outbound_min: usize,
235 ) -> Self {
236 let mut result = Self {
237 mesh_n,
238 mesh_n_high,
239 mesh_n_low,
240 mesh_outbound_min,
241 };
242
243 result.adjust();
244 result
245 }
246
247 pub fn adjust(&mut self) {
249 use std::cmp::{max, min};
250
251 if self.mesh_n == 0 {
252 self.mesh_n = 6;
253 }
254
255 if self.mesh_n_high == 0 || self.mesh_n_high < self.mesh_n {
256 self.mesh_n_high = self.mesh_n * 2;
257 }
258
259 if self.mesh_n_low == 0 || self.mesh_n_low > self.mesh_n {
260 self.mesh_n_low = self.mesh_n * 2 / 3;
261 }
262
263 if self.mesh_outbound_min == 0
264 || self.mesh_outbound_min > self.mesh_n / 2
265 || self.mesh_outbound_min >= self.mesh_n_low
266 {
267 self.mesh_outbound_min = max(1, min(self.mesh_n / 2, self.mesh_n_low - 1));
268 }
269 }
270
271 pub fn mesh_n(&self) -> usize {
272 self.mesh_n
273 }
274
275 pub fn mesh_n_high(&self) -> usize {
276 self.mesh_n_high
277 }
278
279 pub fn mesh_n_low(&self) -> usize {
280 self.mesh_n_low
281 }
282
283 pub fn mesh_outbound_min(&self) -> usize {
284 self.mesh_outbound_min
285 }
286}
287
288mod gossipsub {
289 #[derive(serde::Deserialize)]
290 pub struct RawConfig {
291 #[serde(default)]
292 mesh_n: usize,
293 #[serde(default)]
294 mesh_n_high: usize,
295 #[serde(default)]
296 mesh_n_low: usize,
297 #[serde(default)]
298 mesh_outbound_min: usize,
299 }
300
301 impl From<RawConfig> for super::GossipSubConfig {
302 fn from(raw: RawConfig) -> Self {
303 super::GossipSubConfig::new(
304 raw.mesh_n,
305 raw.mesh_n_high,
306 raw.mesh_n_low,
307 raw.mesh_outbound_min,
308 )
309 }
310 }
311}
312
313#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
314#[serde(tag = "load_type", rename_all = "snake_case")]
315pub enum MempoolLoadType {
316 NoLoad,
317 UniformLoad(mempool_load::UniformLoadConfig),
318 NonUniformLoad(mempool_load::NonUniformLoadConfig),
319}
320
321impl Default for MempoolLoadType {
322 fn default() -> Self {
323 Self::NoLoad
324 }
325}
326
327pub mod mempool_load {
328 use super::*;
329
330 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
331 pub struct NonUniformLoadConfig {
332 pub base_count: i32,
334
335 pub base_size: i32,
337
338 pub count_variation: std::ops::Range<i32>,
340
341 pub size_variation: std::ops::Range<i32>,
343
344 pub spike_probability: f64,
347
348 pub spike_multiplier: usize,
351
352 pub sleep_interval: std::ops::Range<u64>,
354 }
355
356 impl Default for NonUniformLoadConfig {
357 fn default() -> Self {
358 Self {
359 base_count: 100,
360 base_size: 256,
361 count_variation: -100..200,
362 size_variation: -64..128,
363 spike_probability: 0.10,
364 spike_multiplier: 2,
365 sleep_interval: 1000..5000,
366 }
367 }
368 }
369
370 #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
371 pub struct UniformLoadConfig {
372 #[serde(with = "humantime_serde")]
374 pub interval: Duration,
375
376 pub count: usize,
378
379 pub size: ByteSize,
381 }
382
383 impl Default for UniformLoadConfig {
384 fn default() -> Self {
385 Self {
386 interval: Duration::from_secs(1),
387 count: 1000,
388 size: ByteSize::b(256),
389 }
390 }
391 }
392}
393
394#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
396pub struct MempoolLoadConfig {
397 #[serde(flatten)]
399 pub load_type: MempoolLoadType,
400}
401
402#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
404pub struct MempoolConfig {
405 pub p2p: P2pConfig,
407
408 pub max_tx_count: usize,
410
411 pub gossip_batch_size: usize,
413
414 pub load: MempoolLoadConfig,
416}
417
418#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
420pub struct ValueSyncConfig {
421 pub enabled: bool,
423
424 #[serde(with = "humantime_serde")]
426 pub status_update_interval: Duration,
427
428 #[serde(with = "humantime_serde")]
430 pub request_timeout: Duration,
431
432 pub max_request_size: ByteSize,
434
435 pub max_response_size: ByteSize,
437
438 pub parallel_requests: usize,
440
441 #[serde(default)]
443 pub scoring_strategy: ScoringStrategy,
444
445 #[serde(with = "humantime_serde")]
447 pub inactive_threshold: Duration,
448}
449
450impl Default for ValueSyncConfig {
451 fn default() -> Self {
452 Self {
453 enabled: true,
454 status_update_interval: Duration::from_secs(10),
455 request_timeout: Duration::from_secs(10),
456 max_request_size: ByteSize::mib(1),
457 max_response_size: ByteSize::mib(512),
458 parallel_requests: 5,
459 scoring_strategy: ScoringStrategy::default(),
460 inactive_threshold: Duration::from_secs(60),
461 }
462 }
463}
464
465#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
466#[serde(rename_all = "lowercase")]
467pub enum ScoringStrategy {
468 #[default]
469 Ema,
470}
471
472impl ScoringStrategy {
473 pub fn name(&self) -> &'static str {
474 match self {
475 Self::Ema => "ema",
476 }
477 }
478}
479
480impl FromStr for ScoringStrategy {
481 type Err = String;
482
483 fn from_str(s: &str) -> Result<Self, Self::Err> {
484 match s {
485 "ema" => Ok(Self::Ema),
486 e => Err(format!("unknown scoring strategy: {e}, available: ema")),
487 }
488 }
489}
490
491#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
493pub struct ConsensusConfig {
494 #[serde(flatten)]
496 pub timeouts: TimeoutConfig,
497
498 pub p2p: P2pConfig,
500
501 pub value_payload: ValuePayload,
503
504 #[serde(default)]
510 pub queue_capacity: usize,
511}
512
513#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
515#[serde(rename_all = "kebab-case")]
516pub enum ValuePayload {
517 #[default]
518 PartsOnly,
519 ProposalOnly, ProposalAndParts,
521}
522
523impl ValuePayload {
524 pub fn include_parts(&self) -> bool {
525 match self {
526 Self::ProposalOnly => false,
527 Self::PartsOnly | Self::ProposalAndParts => true,
528 }
529 }
530
531 pub fn include_proposal(&self) -> bool {
532 match self {
533 Self::PartsOnly => false,
534 Self::ProposalOnly | Self::ProposalAndParts => true,
535 }
536 }
537}
538
539#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
541pub struct TimeoutConfig {
542 #[serde(with = "humantime_serde")]
544 pub timeout_propose: Duration,
545
546 #[serde(with = "humantime_serde")]
548 pub timeout_propose_delta: Duration,
549
550 #[serde(with = "humantime_serde")]
552 pub timeout_prevote: Duration,
553
554 #[serde(with = "humantime_serde")]
556 pub timeout_prevote_delta: Duration,
557
558 #[serde(with = "humantime_serde")]
560 pub timeout_precommit: Duration,
561
562 #[serde(with = "humantime_serde")]
564 pub timeout_precommit_delta: Duration,
565
566 #[serde(with = "humantime_serde")]
569 pub timeout_rebroadcast: Duration,
570}
571
572impl TimeoutConfig {
573 pub fn timeout_duration(&self, step: TimeoutKind) -> Duration {
574 match step {
575 TimeoutKind::Propose => self.timeout_propose,
576 TimeoutKind::Prevote => self.timeout_prevote,
577 TimeoutKind::Precommit => self.timeout_precommit,
578 TimeoutKind::Rebroadcast => {
579 self.timeout_propose + self.timeout_prevote + self.timeout_precommit
580 }
581 }
582 }
583
584 pub fn delta_duration(&self, step: TimeoutKind) -> Option<Duration> {
585 match step {
586 TimeoutKind::Propose => Some(self.timeout_propose_delta),
587 TimeoutKind::Prevote => Some(self.timeout_prevote_delta),
588 TimeoutKind::Precommit => Some(self.timeout_precommit_delta),
589 TimeoutKind::Rebroadcast => None,
590 }
591 }
592}
593
594impl Default for TimeoutConfig {
595 fn default() -> Self {
596 let timeout_propose = Duration::from_secs(3);
597 let timeout_prevote = Duration::from_secs(1);
598 let timeout_precommit = Duration::from_secs(1);
599 let timeout_rebroadcast = timeout_propose + timeout_prevote + timeout_precommit;
600
601 Self {
602 timeout_propose,
603 timeout_propose_delta: Duration::from_millis(500),
604 timeout_prevote,
605 timeout_prevote_delta: Duration::from_millis(500),
606 timeout_precommit,
607 timeout_precommit_delta: Duration::from_millis(500),
608 timeout_rebroadcast,
609 }
610 }
611}
612
613#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
614pub struct MetricsConfig {
615 pub enabled: bool,
617
618 pub listen_addr: SocketAddr,
620}
621
622impl Default for MetricsConfig {
623 fn default() -> Self {
624 MetricsConfig {
625 enabled: false,
626 listen_addr: SocketAddr::new(IpAddr::from([127, 0, 0, 1]), 9000),
627 }
628 }
629}
630
631#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
632#[serde(tag = "flavor", rename_all = "snake_case")]
633pub enum RuntimeConfig {
634 #[default]
636 SingleThreaded,
637
638 MultiThreaded {
640 worker_threads: usize,
642 },
643}
644
645impl RuntimeConfig {
646 pub fn single_threaded() -> Self {
647 Self::SingleThreaded
648 }
649
650 pub fn multi_threaded(worker_threads: usize) -> Self {
651 Self::MultiThreaded { worker_threads }
652 }
653}
654
655#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
656pub struct VoteExtensionsConfig {
657 pub enabled: bool,
658 pub size: ByteSize,
659}
660
661#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
662pub struct TestConfig {
663 pub max_block_size: ByteSize,
664 pub txs_per_part: usize,
665 pub time_allowance_factor: f32,
666 #[serde(with = "humantime_serde")]
667 pub exec_time_per_tx: Duration,
668 pub max_retain_blocks: usize,
669 #[serde(default)]
670 pub vote_extensions: VoteExtensionsConfig,
671 #[serde(default)]
672 pub stable_block_times: bool,
673}
674
675impl Default for TestConfig {
676 fn default() -> Self {
677 Self {
678 max_block_size: ByteSize::mib(1),
679 txs_per_part: 256,
680 time_allowance_factor: 0.5,
681 exec_time_per_tx: Duration::from_millis(1),
682 max_retain_blocks: 1000,
683 vote_extensions: VoteExtensionsConfig::default(),
684 stable_block_times: false,
685 }
686 }
687}
688
689#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
690pub struct LoggingConfig {
691 pub log_level: LogLevel,
692 pub log_format: LogFormat,
693}
694
695#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
696#[serde(rename_all = "lowercase")]
697pub enum LogLevel {
698 Trace,
699 #[default]
700 Debug,
701 Warn,
702 Info,
703 Error,
704}
705
706impl FromStr for LogLevel {
707 type Err = String;
708
709 fn from_str(s: &str) -> Result<Self, Self::Err> {
710 match s {
711 "trace" => Ok(LogLevel::Trace),
712 "debug" => Ok(LogLevel::Debug),
713 "warn" => Ok(LogLevel::Warn),
714 "info" => Ok(LogLevel::Info),
715 "error" => Ok(LogLevel::Error),
716 e => Err(format!("Invalid log level: {e}")),
717 }
718 }
719}
720
721impl fmt::Display for LogLevel {
722 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
723 match self {
724 LogLevel::Trace => write!(f, "trace"),
725 LogLevel::Debug => write!(f, "debug"),
726 LogLevel::Warn => write!(f, "warn"),
727 LogLevel::Info => write!(f, "info"),
728 LogLevel::Error => write!(f, "error"),
729 }
730 }
731}
732
733#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
734#[serde(rename_all = "lowercase")]
735pub enum LogFormat {
736 #[default]
737 Plaintext,
738 Json,
739}
740
741impl FromStr for LogFormat {
742 type Err = String;
743
744 fn from_str(s: &str) -> Result<Self, Self::Err> {
745 match s {
746 "plaintext" => Ok(LogFormat::Plaintext),
747 "json" => Ok(LogFormat::Json),
748 e => Err(format!("Invalid log format: {e}")),
749 }
750 }
751}
752
753impl fmt::Display for LogFormat {
754 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
755 match self {
756 LogFormat::Plaintext => write!(f, "plaintext"),
757 LogFormat::Json => write!(f, "json"),
758 }
759 }
760}
761
762#[cfg(test)]
763mod tests {
764 use super::*;
765
766 #[test]
767 fn log_format() {
768 assert_eq!(
769 LogFormat::from_str("yaml"),
770 Err("Invalid log format: yaml".to_string())
771 )
772 }
773
774 #[test]
775 fn timeout_durations() {
776 let t = TimeoutConfig::default();
777 assert_eq!(t.timeout_duration(TimeoutKind::Propose), t.timeout_propose);
778 assert_eq!(t.timeout_duration(TimeoutKind::Prevote), t.timeout_prevote);
779 assert_eq!(
780 t.timeout_duration(TimeoutKind::Precommit),
781 t.timeout_precommit
782 );
783 }
784
785 #[test]
786 fn runtime_multi_threaded() {
787 assert_eq!(
788 RuntimeConfig::multi_threaded(5),
789 RuntimeConfig::MultiThreaded { worker_threads: 5 }
790 );
791 }
792
793 #[test]
794 fn log_formatting() {
795 assert_eq!(
796 format!(
797 "{} {} {} {} {}",
798 LogLevel::Trace,
799 LogLevel::Debug,
800 LogLevel::Warn,
801 LogLevel::Info,
802 LogLevel::Error
803 ),
804 "trace debug warn info error"
805 );
806
807 assert_eq!(
808 format!("{} {}", LogFormat::Plaintext, LogFormat::Json),
809 "plaintext json"
810 );
811 }
812}