1use core::fmt;
2use std::net::{IpAddr, SocketAddr};
3use std::str::FromStr;
4use std::time::Duration;
5
6use bytesize::ByteSize;
7use multiaddr::Multiaddr;
8use serde::{Deserialize, Serialize};
9
10mod utils;
11
12#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
13pub struct ProtocolNames {
14 pub consensus: String,
15 pub discovery_kad: String,
16 pub discovery_regres: String,
17 pub sync: String,
18 pub broadcast: String,
19}
20
21impl Default for ProtocolNames {
22 fn default() -> Self {
23 Self {
24 consensus: "/malachitebft-core-consensus/v1beta1".to_string(),
25 discovery_kad: "/malachitebft-discovery/kad/v1beta1".to_string(),
26 discovery_regres: "/malachitebft-discovery/reqres/v1beta1".to_string(),
27 sync: "/malachitebft-sync/v1beta1".to_string(),
28 broadcast: "/malachitebft-broadcast/v1beta1".to_string(),
29 }
30 }
31}
32
33#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
35pub struct P2pConfig {
36 pub listen_addr: Multiaddr,
38
39 pub persistent_peers: Vec<Multiaddr>,
41
42 #[serde(default)]
44 pub persistent_peers_only: bool,
45
46 #[serde(default)]
48 pub discovery: DiscoveryConfig,
49
50 pub protocol: PubSubProtocol,
52
53 pub pubsub_max_size: ByteSize,
55
56 pub rpc_max_size: ByteSize,
58
59 #[serde(default)]
61 pub protocol_names: ProtocolNames,
62}
63
64impl Default for P2pConfig {
65 fn default() -> Self {
66 P2pConfig {
67 listen_addr: Multiaddr::empty(),
68 persistent_peers: vec![],
69 persistent_peers_only: false,
70 discovery: Default::default(),
71 protocol: Default::default(),
72 rpc_max_size: ByteSize::mib(10),
73 pubsub_max_size: ByteSize::mib(4),
74 protocol_names: Default::default(),
75 }
76 }
77}
78
79#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
81pub struct DiscoveryConfig {
82 #[serde(default)]
84 pub enabled: bool,
85
86 #[serde(default)]
88 pub bootstrap_protocol: BootstrapProtocol,
89
90 #[serde(default)]
92 pub selector: Selector,
93
94 #[serde(default = "discovery::default_num_outbound_peers")]
96 pub num_outbound_peers: usize,
97
98 #[serde(default = "discovery::default_num_inbound_peers")]
100 pub num_inbound_peers: usize,
101
102 #[serde(default = "discovery::default_max_connections_per_peer")]
104 pub max_connections_per_peer: usize,
105
106 #[serde(default = "discovery::default_num_inbound_peers")]
110 pub max_connections_per_ip: usize,
111
112 #[serde(default)]
114 #[serde(with = "humantime_serde")]
115 pub ephemeral_connection_timeout: Duration,
116
117 #[serde(default = "discovery::default_dial_max_retries")]
118 pub dial_max_retries: usize,
119
120 #[serde(default = "discovery::default_request_max_retries")]
121 pub request_max_retries: usize,
122
123 #[serde(default = "discovery::default_connect_request_max_retries")]
124 pub connect_request_max_retries: usize,
125}
126
127impl Default for DiscoveryConfig {
128 fn default() -> Self {
129 DiscoveryConfig {
130 enabled: false,
131 bootstrap_protocol: Default::default(),
132 selector: Default::default(),
133 num_outbound_peers: discovery::default_num_outbound_peers(),
134 num_inbound_peers: discovery::default_num_inbound_peers(),
135 max_connections_per_ip: discovery::default_num_inbound_peers(),
136 max_connections_per_peer: discovery::default_max_connections_per_peer(),
137 ephemeral_connection_timeout: Duration::from_secs(60),
138 dial_max_retries: discovery::default_dial_max_retries(),
139 request_max_retries: discovery::default_request_max_retries(),
140 connect_request_max_retries: discovery::default_connect_request_max_retries(),
141 }
142 }
143}
144
145mod discovery {
146 pub fn default_num_outbound_peers() -> usize {
147 50
148 }
149
150 pub fn default_num_inbound_peers() -> usize {
151 50
152 }
153
154 pub fn default_max_connections_per_peer() -> usize {
155 5
156 }
157
158 pub fn default_dial_max_retries() -> usize {
159 5
160 }
161
162 pub fn default_request_max_retries() -> usize {
163 5
164 }
165
166 pub fn default_connect_request_max_retries() -> usize {
167 3
168 }
169}
170
171#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
172#[serde(rename_all = "lowercase")]
173pub enum BootstrapProtocol {
174 #[default]
175 Kademlia,
176 Full,
177}
178
179impl BootstrapProtocol {
180 pub fn name(&self) -> &'static str {
181 match self {
182 Self::Kademlia => "kademlia",
183 Self::Full => "full",
184 }
185 }
186}
187
188impl FromStr for BootstrapProtocol {
189 type Err = String;
190
191 fn from_str(s: &str) -> Result<Self, Self::Err> {
192 match s {
193 "kademlia" => Ok(Self::Kademlia),
194 "full" => Ok(Self::Full),
195 e => Err(format!(
196 "unknown bootstrap protocol: {e}, available: kademlia, full"
197 )),
198 }
199 }
200}
201
202#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
203#[serde(rename_all = "lowercase")]
204pub enum Selector {
205 #[default]
206 Kademlia,
207 Random,
208}
209
210impl Selector {
211 pub fn name(&self) -> &'static str {
212 match self {
213 Self::Kademlia => "kademlia",
214 Self::Random => "random",
215 }
216 }
217}
218
219impl FromStr for Selector {
220 type Err = String;
221
222 fn from_str(s: &str) -> Result<Self, Self::Err> {
223 match s {
224 "kademlia" => Ok(Self::Kademlia),
225 "random" => Ok(Self::Random),
226 e => Err(format!(
227 "unknown selector: {e}, available: kademlia, random"
228 )),
229 }
230 }
231}
232
233#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
234pub enum TransportProtocol {
235 #[default]
236 Tcp,
237 Quic,
238}
239
240impl TransportProtocol {
241 pub fn multiaddr(&self, host: &str, port: usize) -> Multiaddr {
242 match self {
243 Self::Tcp => format!("/ip4/{host}/tcp/{port}").parse().unwrap(),
244 Self::Quic => format!("/ip4/{host}/udp/{port}/quic-v1").parse().unwrap(),
245 }
246 }
247}
248
249impl FromStr for TransportProtocol {
250 type Err = String;
251
252 fn from_str(s: &str) -> Result<Self, Self::Err> {
253 match s {
254 "tcp" => Ok(Self::Tcp),
255 "quic" => Ok(Self::Quic),
256 e => Err(format!(
257 "unknown transport protocol: {e}, available: tcp, quic"
258 )),
259 }
260 }
261}
262
263#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
267#[serde(tag = "type", rename_all = "lowercase")]
268pub enum PubSubProtocol {
269 GossipSub(GossipSubConfig),
270 Broadcast,
271}
272
273impl Default for PubSubProtocol {
274 fn default() -> Self {
275 Self::GossipSub(GossipSubConfig::default())
276 }
277}
278
279#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
281#[serde(from = "gossipsub::RawConfig", default)]
282pub struct GossipSubConfig {
283 mesh_n: usize,
285
286 mesh_n_high: usize,
288
289 mesh_n_low: usize,
291
292 mesh_outbound_min: usize,
297
298 enable_peer_scoring: bool,
300
301 enable_explicit_peering: bool,
306
307 enable_flood_publish: bool,
310}
311
312impl Default for GossipSubConfig {
313 fn default() -> Self {
314 Self::new(6, 12, 4, 2, false, false, true)
316 }
317}
318
319impl GossipSubConfig {
320 pub fn new(
322 mesh_n: usize,
323 mesh_n_high: usize,
324 mesh_n_low: usize,
325 mesh_outbound_min: usize,
326 enable_peer_scoring: bool,
327 enable_explicit_peering: bool,
328 enable_flood_publish: bool,
329 ) -> Self {
330 let mut result = Self {
331 mesh_n,
332 mesh_n_high,
333 mesh_n_low,
334 mesh_outbound_min,
335 enable_peer_scoring,
336 enable_explicit_peering,
337 enable_flood_publish,
338 };
339
340 result.adjust();
341 result
342 }
343
344 pub fn adjust(&mut self) {
346 use std::cmp::{max, min};
347
348 if self.mesh_n == 0 {
349 self.mesh_n = 6;
350 }
351
352 if self.mesh_n_high == 0 || self.mesh_n_high < self.mesh_n {
353 self.mesh_n_high = self.mesh_n * 2;
354 }
355
356 if self.mesh_n_low == 0 || self.mesh_n_low > self.mesh_n {
357 self.mesh_n_low = self.mesh_n * 2 / 3;
358 }
359
360 if self.mesh_outbound_min == 0
361 || self.mesh_outbound_min > self.mesh_n / 2
362 || self.mesh_outbound_min >= self.mesh_n_low
363 {
364 self.mesh_outbound_min = max(1, min(self.mesh_n / 2, self.mesh_n_low - 1));
365 }
366
367 }
372
373 pub fn mesh_n(&self) -> usize {
374 self.mesh_n
375 }
376
377 pub fn mesh_n_high(&self) -> usize {
378 self.mesh_n_high
379 }
380
381 pub fn mesh_n_low(&self) -> usize {
382 self.mesh_n_low
383 }
384
385 pub fn mesh_outbound_min(&self) -> usize {
386 self.mesh_outbound_min
387 }
388
389 pub fn enable_peer_scoring(&self) -> bool {
390 self.enable_peer_scoring
391 }
392
393 pub fn enable_explicit_peering(&self) -> bool {
394 self.enable_explicit_peering
395 }
396
397 pub fn enable_flood_publish(&self) -> bool {
398 self.enable_flood_publish
399 }
400}
401
402mod gossipsub {
403 use super::utils::bool_from_anything;
404
405 fn default_enable_peer_scoring() -> bool {
406 false
407 }
408
409 fn default_enable_explicit_peering() -> bool {
410 false
411 }
412
413 fn default_enable_flood_publish() -> bool {
414 true
415 }
416
417 #[derive(serde::Deserialize)]
418 pub struct RawConfig {
419 #[serde(default)]
420 mesh_n: usize,
421 #[serde(default)]
422 mesh_n_high: usize,
423 #[serde(default)]
424 mesh_n_low: usize,
425 #[serde(default)]
426 mesh_outbound_min: usize,
427 #[serde(
428 default = "default_enable_peer_scoring",
429 deserialize_with = "bool_from_anything"
430 )]
431 enable_peer_scoring: bool,
432 #[serde(
433 default = "default_enable_explicit_peering",
434 deserialize_with = "bool_from_anything"
435 )]
436 enable_explicit_peering: bool,
437 #[serde(
438 default = "default_enable_flood_publish",
439 deserialize_with = "bool_from_anything"
440 )]
441 enable_flood_publish: bool,
442 }
443
444 impl From<RawConfig> for super::GossipSubConfig {
445 fn from(raw: RawConfig) -> Self {
446 super::GossipSubConfig::new(
447 raw.mesh_n,
448 raw.mesh_n_high,
449 raw.mesh_n_low,
450 raw.mesh_outbound_min,
451 raw.enable_peer_scoring,
452 raw.enable_explicit_peering,
453 raw.enable_flood_publish,
454 )
455 }
456 }
457}
458
459#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
460#[serde(tag = "load_type", rename_all = "snake_case")]
461pub enum MempoolLoadType {
462 #[default]
463 NoLoad,
464 UniformLoad(mempool_load::UniformLoadConfig),
465 NonUniformLoad(mempool_load::NonUniformLoadConfig),
466}
467
468pub mod mempool_load {
469 use super::*;
470
471 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
472 pub struct NonUniformLoadConfig {
473 pub base_count: i32,
475
476 pub base_size: i32,
478
479 pub count_variation: std::ops::Range<i32>,
481
482 pub size_variation: std::ops::Range<i32>,
484
485 pub spike_probability: f64,
488
489 pub spike_multiplier: usize,
492
493 pub sleep_interval: std::ops::Range<u64>,
495 }
496
497 impl Default for NonUniformLoadConfig {
498 fn default() -> Self {
499 Self {
500 base_count: 100,
501 base_size: 256,
502 count_variation: -100..200,
503 size_variation: -64..128,
504 spike_probability: 0.10,
505 spike_multiplier: 2,
506 sleep_interval: 1000..5000,
507 }
508 }
509 }
510
511 #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
512 pub struct UniformLoadConfig {
513 #[serde(with = "humantime_serde")]
515 pub interval: Duration,
516
517 pub count: usize,
519
520 pub size: ByteSize,
522 }
523
524 impl Default for UniformLoadConfig {
525 fn default() -> Self {
526 Self {
527 interval: Duration::from_secs(1),
528 count: 1000,
529 size: ByteSize::b(256),
530 }
531 }
532 }
533}
534
535#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
537pub struct MempoolLoadConfig {
538 #[serde(flatten)]
540 pub load_type: MempoolLoadType,
541}
542
543#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
545pub struct MempoolConfig {
546 pub p2p: P2pConfig,
548
549 pub max_tx_count: usize,
551
552 pub gossip_batch_size: usize,
554
555 pub load: MempoolLoadConfig,
557}
558
559#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
561pub struct ValueSyncConfig {
562 pub enabled: bool,
564
565 #[serde(with = "humantime_serde")]
567 pub status_update_interval: Duration,
568
569 #[serde(with = "humantime_serde")]
571 pub request_timeout: Duration,
572
573 pub max_request_size: ByteSize,
575
576 pub max_response_size: ByteSize,
578
579 pub parallel_requests: usize,
581
582 #[serde(default)]
584 pub scoring_strategy: ScoringStrategy,
585
586 #[serde(with = "humantime_serde")]
588 pub inactive_threshold: Duration,
589
590 pub batch_size: usize,
592}
593
594impl Default for ValueSyncConfig {
595 fn default() -> Self {
596 Self {
597 enabled: true,
598 status_update_interval: Duration::from_secs(10),
599 request_timeout: Duration::from_secs(10),
600 max_request_size: ByteSize::mib(1),
601 max_response_size: ByteSize::mib(10),
602 parallel_requests: 5,
603 scoring_strategy: ScoringStrategy::default(),
604 inactive_threshold: Duration::from_secs(60),
605 batch_size: 5,
606 }
607 }
608}
609
610#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
611#[serde(rename_all = "lowercase")]
612pub enum ScoringStrategy {
613 #[default]
614 Ema,
615}
616
617impl ScoringStrategy {
618 pub fn name(&self) -> &'static str {
619 match self {
620 Self::Ema => "ema",
621 }
622 }
623}
624
625impl FromStr for ScoringStrategy {
626 type Err = String;
627
628 fn from_str(s: &str) -> Result<Self, Self::Err> {
629 match s {
630 "ema" => Ok(Self::Ema),
631 e => Err(format!("unknown scoring strategy: {e}, available: ema")),
632 }
633 }
634}
635
636fn default_consensus_enabled() -> bool {
637 true
638}
639
640fn default_queue_capacity() -> usize {
641 10
642}
643
644#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
646pub struct ConsensusConfig {
647 #[serde(default = "default_consensus_enabled")]
652 pub enabled: bool,
653
654 pub p2p: P2pConfig,
656
657 pub value_payload: ValuePayload,
659
660 #[serde(default = "default_queue_capacity")]
665 pub queue_capacity: usize,
666}
667
668impl Default for ConsensusConfig {
669 fn default() -> Self {
670 Self {
671 enabled: true,
672 p2p: P2pConfig::default(),
673 value_payload: ValuePayload::default(),
674 queue_capacity: default_queue_capacity(),
675 }
676 }
677}
678
679#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
681#[serde(rename_all = "kebab-case")]
682pub enum ValuePayload {
683 #[default]
684 PartsOnly,
685 ProposalOnly, ProposalAndParts,
687}
688
689impl ValuePayload {
690 pub fn include_parts(&self) -> bool {
691 match self {
692 Self::ProposalOnly => false,
693 Self::PartsOnly | Self::ProposalAndParts => true,
694 }
695 }
696
697 pub fn include_proposal(&self) -> bool {
698 match self {
699 Self::PartsOnly => false,
700 Self::ProposalOnly | Self::ProposalAndParts => true,
701 }
702 }
703}
704
705#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
706pub struct MetricsConfig {
707 pub enabled: bool,
709
710 pub listen_addr: SocketAddr,
712}
713
714impl Default for MetricsConfig {
715 fn default() -> Self {
716 MetricsConfig {
717 enabled: false,
718 listen_addr: SocketAddr::new(IpAddr::from([127, 0, 0, 1]), 9000),
719 }
720 }
721}
722
723#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
724#[serde(tag = "flavor", rename_all = "snake_case")]
725pub enum RuntimeConfig {
726 #[default]
728 SingleThreaded,
729
730 MultiThreaded {
732 worker_threads: usize,
734 },
735}
736
737impl RuntimeConfig {
738 pub fn single_threaded() -> Self {
739 Self::SingleThreaded
740 }
741
742 pub fn multi_threaded(worker_threads: usize) -> Self {
743 Self::MultiThreaded { worker_threads }
744 }
745}
746
747#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
748pub struct VoteExtensionsConfig {
749 pub enabled: bool,
750 pub size: ByteSize,
751}
752
753#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
754pub struct TestConfig {
755 pub max_block_size: ByteSize,
756 pub txs_per_part: usize,
757 pub time_allowance_factor: f32,
758 #[serde(with = "humantime_serde")]
759 pub exec_time_per_tx: Duration,
760 pub max_retain_blocks: usize,
761 #[serde(default)]
762 pub vote_extensions: VoteExtensionsConfig,
763 #[serde(default)]
764 pub stable_block_times: bool,
765 #[serde(default, with = "humantime_serde")]
766 pub target_time: Option<Duration>,
767}
768
769impl Default for TestConfig {
770 fn default() -> Self {
771 Self {
772 max_block_size: ByteSize::mib(1),
773 txs_per_part: 256,
774 time_allowance_factor: 0.5,
775 exec_time_per_tx: Duration::from_millis(1),
776 max_retain_blocks: 1000,
777 vote_extensions: VoteExtensionsConfig::default(),
778 stable_block_times: false,
779 target_time: None,
780 }
781 }
782}
783
784#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
785pub struct LoggingConfig {
786 pub log_level: LogLevel,
787 pub log_format: LogFormat,
788}
789
790#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
791#[serde(rename_all = "lowercase")]
792pub enum LogLevel {
793 Trace,
794 #[default]
795 Debug,
796 Warn,
797 Info,
798 Error,
799}
800
801impl FromStr for LogLevel {
802 type Err = String;
803
804 fn from_str(s: &str) -> Result<Self, Self::Err> {
805 match s {
806 "trace" => Ok(LogLevel::Trace),
807 "debug" => Ok(LogLevel::Debug),
808 "warn" => Ok(LogLevel::Warn),
809 "info" => Ok(LogLevel::Info),
810 "error" => Ok(LogLevel::Error),
811 e => Err(format!("Invalid log level: {e}")),
812 }
813 }
814}
815
816impl fmt::Display for LogLevel {
817 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
818 match self {
819 LogLevel::Trace => write!(f, "trace"),
820 LogLevel::Debug => write!(f, "debug"),
821 LogLevel::Warn => write!(f, "warn"),
822 LogLevel::Info => write!(f, "info"),
823 LogLevel::Error => write!(f, "error"),
824 }
825 }
826}
827
828#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
829#[serde(rename_all = "lowercase")]
830pub enum LogFormat {
831 #[default]
832 Plaintext,
833 Json,
834}
835
836impl FromStr for LogFormat {
837 type Err = String;
838
839 fn from_str(s: &str) -> Result<Self, Self::Err> {
840 match s {
841 "plaintext" => Ok(LogFormat::Plaintext),
842 "json" => Ok(LogFormat::Json),
843 e => Err(format!("Invalid log format: {e}")),
844 }
845 }
846}
847
848impl fmt::Display for LogFormat {
849 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
850 match self {
851 LogFormat::Plaintext => write!(f, "plaintext"),
852 LogFormat::Json => write!(f, "json"),
853 }
854 }
855}
856
857#[cfg(test)]
858mod tests {
859 use super::*;
860
861 #[test]
862 fn log_format() {
863 assert_eq!(
864 LogFormat::from_str("yaml"),
865 Err("Invalid log format: yaml".to_string())
866 )
867 }
868
869 #[test]
870 fn runtime_multi_threaded() {
871 assert_eq!(
872 RuntimeConfig::multi_threaded(5),
873 RuntimeConfig::MultiThreaded { worker_threads: 5 }
874 );
875 }
876
877 #[test]
878 fn log_formatting() {
879 assert_eq!(
880 format!(
881 "{} {} {} {} {}",
882 LogLevel::Trace,
883 LogLevel::Debug,
884 LogLevel::Warn,
885 LogLevel::Info,
886 LogLevel::Error
887 ),
888 "trace debug warn info error"
889 );
890
891 assert_eq!(
892 format!("{} {}", LogFormat::Plaintext, LogFormat::Json),
893 "plaintext json"
894 );
895 }
896
897 #[test]
898 fn protocol_names_default() {
899 let protocol_names = ProtocolNames::default();
900 assert_eq!(
901 protocol_names.consensus,
902 "/malachitebft-core-consensus/v1beta1"
903 );
904 assert_eq!(
905 protocol_names.discovery_kad,
906 "/malachitebft-discovery/kad/v1beta1"
907 );
908 assert_eq!(
909 protocol_names.discovery_regres,
910 "/malachitebft-discovery/reqres/v1beta1"
911 );
912 assert_eq!(protocol_names.sync, "/malachitebft-sync/v1beta1");
913 }
914
915 #[test]
916 fn protocol_names_serde() {
917 use serde_json;
918
919 let protocol_names = ProtocolNames {
921 consensus: "/custom-consensus/v1".to_string(),
922 discovery_kad: "/custom-discovery/kad/v1".to_string(),
923 discovery_regres: "/custom-discovery/reqres/v1".to_string(),
924 sync: "/custom-sync/v1".to_string(),
925 broadcast: "/custom-broadcast/v1".to_string(),
926 };
927
928 let json = serde_json::to_string(&protocol_names).unwrap();
929
930 let deserialized: ProtocolNames = serde_json::from_str(&json).unwrap();
932 assert_eq!(protocol_names, deserialized);
933 }
934
935 #[test]
936 fn p2p_config_with_protocol_names() {
937 let config = P2pConfig::default();
938
939 assert_eq!(config.protocol_names, ProtocolNames::default());
941
942 let custom_protocol_names = ProtocolNames {
944 consensus: "/test-network/consensus/v1".to_string(),
945 discovery_kad: "/test-network/discovery/kad/v1".to_string(),
946 discovery_regres: "/test-network/discovery/reqres/v1".to_string(),
947 sync: "/test-network/sync/v1".to_string(),
948 broadcast: "/test-network/broadcast/v1".to_string(),
949 };
950
951 let config_with_custom = P2pConfig {
952 protocol_names: custom_protocol_names.clone(),
953 ..Default::default()
954 };
955
956 assert_eq!(config_with_custom.protocol_names, custom_protocol_names);
957 }
958
959 #[test]
960 fn protocol_names_toml_deserialization() {
961 let toml_content = r#"
962 timeout_propose = "3s"
963 timeout_propose_delta = "500ms"
964 timeout_prevote = "1s"
965 timeout_prevote_delta = "500ms"
966 timeout_precommit = "1s"
967 timeout_precommit_delta = "500ms"
968 timeout_rebroadcast = "5s"
969 value_payload = "parts-only"
970
971 [p2p]
972 listen_addr = "/ip4/0.0.0.0/tcp/0"
973 persistent_peers = []
974 pubsub_max_size = "4 MiB"
975 rpc_max_size = "10 MiB"
976
977 [p2p.protocol_names]
978 consensus = "/custom-network/consensus/v2"
979 discovery_kad = "/custom-network/discovery/kad/v2"
980 discovery_regres = "/custom-network/discovery/reqres/v2"
981 sync = "/custom-network/sync/v2"
982 broadcast = "/custom-network/broadcast/v2"
983
984 [p2p.protocol]
985 type = "gossipsub"
986 "#;
987
988 let config: ConsensusConfig = toml::from_str(toml_content).unwrap();
989
990 assert_eq!(
991 config.p2p.protocol_names.consensus,
992 "/custom-network/consensus/v2"
993 );
994 assert_eq!(
995 config.p2p.protocol_names.discovery_kad,
996 "/custom-network/discovery/kad/v2"
997 );
998 assert_eq!(
999 config.p2p.protocol_names.discovery_regres,
1000 "/custom-network/discovery/reqres/v2"
1001 );
1002 assert_eq!(config.p2p.protocol_names.sync, "/custom-network/sync/v2");
1003 assert_eq!(
1004 config.p2p.protocol_names.broadcast,
1005 "/custom-network/broadcast/v2"
1006 );
1007 }
1008
1009 #[test]
1010 fn protocol_names_toml_defaults_when_missing() {
1011 let toml_content = r#"
1012 timeout_propose = "3s"
1013 timeout_propose_delta = "500ms"
1014 timeout_prevote = "1s"
1015 timeout_prevote_delta = "500ms"
1016 timeout_precommit = "1s"
1017 timeout_precommit_delta = "500ms"
1018 timeout_rebroadcast = "5s"
1019 value_payload = "parts-only"
1020
1021 [p2p]
1022 listen_addr = "/ip4/0.0.0.0/tcp/0"
1023 persistent_peers = []
1024 pubsub_max_size = "4 MiB"
1025 rpc_max_size = "10 MiB"
1026
1027 [p2p.protocol]
1028 type = "gossipsub"
1029 "#;
1030
1031 let config: ConsensusConfig = toml::from_str(toml_content).unwrap();
1032
1033 assert_eq!(config.p2p.protocol_names, ProtocolNames::default());
1035 }
1036
1037 #[test]
1038 fn p2p_config_persistent_peers_only_default() {
1039 let config = P2pConfig::default();
1040 assert!(
1041 !config.persistent_peers_only,
1042 "persistent_peers_only should default to false"
1043 );
1044 }
1045
1046 #[test]
1047 fn p2p_config_persistent_peers_only_toml() {
1048 let toml_content = r#"
1049 timeout_propose = "3s"
1050 timeout_propose_delta = "500ms"
1051 timeout_prevote = "1s"
1052 timeout_prevote_delta = "500ms"
1053 timeout_precommit = "1s"
1054 timeout_precommit_delta = "500ms"
1055 timeout_rebroadcast = "5s"
1056 value_payload = "parts-only"
1057
1058 [p2p]
1059 listen_addr = "/ip4/0.0.0.0/tcp/0"
1060 persistent_peers = []
1061 persistent_peers_only = true
1062 pubsub_max_size = "4 MiB"
1063 rpc_max_size = "10 MiB"
1064
1065 [p2p.protocol]
1066 type = "gossipsub"
1067 "#;
1068
1069 let config: ConsensusConfig = toml::from_str(toml_content).unwrap();
1070 assert!(
1071 config.p2p.persistent_peers_only,
1072 "persistent_peers_only should be true when set in TOML"
1073 );
1074 }
1075
1076 #[test]
1077 fn gossipsub_config_default_disables_peer_scoring() {
1078 let config = GossipSubConfig::default();
1079 assert!(!config.enable_peer_scoring());
1080 }
1081
1082 #[test]
1083 fn gossipsub_enable_peer_scoring_deserialization() {
1084 struct TestCase {
1085 name: &'static str,
1086 toml: &'static str,
1087 expected: bool,
1088 }
1089
1090 let cases = [
1091 TestCase {
1092 name: "missing field defaults to false",
1093 toml: r#"
1094 [p2p.protocol]
1095 type = "gossipsub"
1096 "#,
1097 expected: false,
1098 },
1099 TestCase {
1100 name: "explicit true",
1101 toml: r#"
1102 [p2p.protocol]
1103 type = "gossipsub"
1104 enable_peer_scoring = true
1105 "#,
1106 expected: true,
1107 },
1108 TestCase {
1109 name: "explicit false",
1110 toml: r#"
1111 [p2p.protocol]
1112 type = "gossipsub"
1113 enable_peer_scoring = false
1114 "#,
1115 expected: false,
1116 },
1117 TestCase {
1118 name: "string true",
1119 toml: r#"
1120 [p2p.protocol]
1121 type = "gossipsub"
1122 enable_peer_scoring = "true"
1123 "#,
1124 expected: true,
1125 },
1126 TestCase {
1127 name: "string false",
1128 toml: r#"
1129 [p2p.protocol]
1130 type = "gossipsub"
1131 enable_peer_scoring = "false"
1132 "#,
1133 expected: false,
1134 },
1135 ];
1136
1137 for case in cases {
1138 let toml_content = format!(
1139 r#"
1140 timeout_propose = "3s"
1141 timeout_propose_delta = "500ms"
1142 timeout_prevote = "1s"
1143 timeout_prevote_delta = "500ms"
1144 timeout_precommit = "1s"
1145 timeout_precommit_delta = "500ms"
1146 timeout_rebroadcast = "5s"
1147 value_payload = "parts-only"
1148
1149 [p2p]
1150 listen_addr = "/ip4/0.0.0.0/tcp/0"
1151 persistent_peers = []
1152 pubsub_max_size = "4 MiB"
1153 rpc_max_size = "10 MiB"
1154 {}
1155 "#,
1156 case.toml
1157 );
1158
1159 let config: ConsensusConfig = toml::from_str(&toml_content)
1160 .unwrap_or_else(|e| panic!("Failed to parse {}: {}", case.name, e));
1161
1162 let PubSubProtocol::GossipSub(gossipsub) = config.p2p.protocol else {
1163 panic!("{}: expected GossipSub protocol", case.name);
1164 };
1165
1166 assert_eq!(
1167 gossipsub.enable_peer_scoring(),
1168 case.expected,
1169 "{}: expected enable_peer_scoring = {}",
1170 case.name,
1171 case.expected
1172 );
1173 }
1174 }
1175}