1use reth_network_peers::TrustedPeer;
3use reth_network_types::{PeersConfig, SessionsConfig};
4use reth_prune_types::{PruneModes, MINIMUM_UNWIND_SAFE_DISTANCE};
5use reth_stages_types::ExecutionStageThresholds;
6use reth_static_file_types::{StaticFileMap, StaticFileSegment};
7use std::{
8 path::{Path, PathBuf},
9 time::Duration,
10};
11use url::Url;
12
13#[cfg(feature = "serde")]
14const EXTENSION: &str = "toml";
15
16pub const DEFAULT_BLOCK_INTERVAL: usize = 5;
18
19#[derive(Debug, Clone, Default, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(feature = "serde", serde(default))]
23pub struct Config {
24 pub bootnodes: Vec<TrustedPeer>,
28 pub stages: StageConfig,
30 #[cfg_attr(feature = "serde", serde(default))]
32 pub prune: PruneConfig,
33 pub peers: PeersConfig,
35 pub sessions: SessionsConfig,
37 #[cfg_attr(feature = "serde", serde(default))]
39 pub static_files: StaticFilesConfig,
40}
41
42impl Config {
43 pub fn set_prune_config(&mut self, prune_config: PruneConfig) {
45 self.prune = prune_config;
46 }
47}
48
49#[cfg(feature = "serde")]
50impl Config {
51 pub fn from_path(path: impl AsRef<Path>) -> eyre::Result<Self> {
56 let path = path.as_ref();
57 match std::fs::read_to_string(path) {
58 Ok(cfg_string) => {
59 toml::from_str(&cfg_string).map_err(|e| eyre::eyre!("Failed to parse TOML: {e}"))
60 }
61 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
62 if let Some(parent) = path.parent() {
63 std::fs::create_dir_all(parent)
64 .map_err(|e| eyre::eyre!("Failed to create directory: {e}"))?;
65 }
66 let cfg = Self::default();
67 let s = toml::to_string_pretty(&cfg)
68 .map_err(|e| eyre::eyre!("Failed to serialize to TOML: {e}"))?;
69 std::fs::write(path, s)
70 .map_err(|e| eyre::eyre!("Failed to write configuration file: {e}"))?;
71 Ok(cfg)
72 }
73 Err(e) => Err(eyre::eyre!("Failed to load configuration: {e}")),
74 }
75 }
76
77 pub fn peers_config_with_basic_nodes_from_file(
81 &self,
82 peers_file: Option<&Path>,
83 ) -> PeersConfig {
84 self.peers
85 .clone()
86 .with_basic_nodes_from_file(peers_file)
87 .unwrap_or_else(|_| self.peers.clone())
88 }
89
90 pub fn save(&self, path: &Path) -> Result<(), std::io::Error> {
92 if path.extension() != Some(std::ffi::OsStr::new(EXTENSION)) {
93 return Err(std::io::Error::new(
94 std::io::ErrorKind::InvalidInput,
95 format!("reth config file extension must be '{EXTENSION}'"),
96 ));
97 }
98
99 std::fs::write(
100 path,
101 toml::to_string(self)
102 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?,
103 )
104 }
105}
106
107#[derive(Debug, Clone, Default, PartialEq, Eq)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110#[cfg_attr(feature = "serde", serde(default))]
111pub struct StageConfig {
112 pub era: EraConfig,
114 pub headers: HeadersConfig,
116 pub bodies: BodiesConfig,
118 pub sender_recovery: SenderRecoveryConfig,
120 pub execution: ExecutionConfig,
122 pub prune: PruneStageConfig,
124 pub account_hashing: HashingConfig,
126 pub storage_hashing: HashingConfig,
128 pub merkle: MerkleConfig,
130 pub transaction_lookup: TransactionLookupConfig,
132 pub index_account_history: IndexHistoryConfig,
134 pub index_storage_history: IndexHistoryConfig,
136 pub etl: EtlConfig,
138}
139
140impl StageConfig {
141 pub fn execution_external_clean_threshold(&self) -> u64 {
146 self.merkle
147 .incremental_threshold
148 .max(self.account_hashing.clean_threshold)
149 .max(self.storage_hashing.clean_threshold)
150 }
151}
152
153#[derive(Debug, Clone, Default, PartialEq, Eq)]
155#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
156#[cfg_attr(feature = "serde", serde(default))]
157pub struct EraConfig {
158 pub path: Option<PathBuf>,
162 pub url: Option<Url>,
166 pub folder: Option<PathBuf>,
170}
171
172impl EraConfig {
173 pub fn with_datadir(mut self, dir: impl AsRef<Path>) -> Self {
175 self.folder = Some(dir.as_ref().join("era"));
176 self
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
183#[cfg_attr(feature = "serde", serde(default))]
184pub struct HeadersConfig {
185 pub downloader_max_concurrent_requests: usize,
189 pub downloader_min_concurrent_requests: usize,
193 pub downloader_max_buffered_responses: usize,
196 pub downloader_request_limit: u64,
198 pub commit_threshold: u64,
200}
201
202impl Default for HeadersConfig {
203 fn default() -> Self {
204 Self {
205 commit_threshold: 10_000,
206 downloader_request_limit: 1_000,
207 downloader_max_concurrent_requests: 100,
208 downloader_min_concurrent_requests: 5,
209 downloader_max_buffered_responses: 100,
210 }
211 }
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
216#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
217#[cfg_attr(feature = "serde", serde(default))]
218pub struct BodiesConfig {
219 pub downloader_request_limit: u64,
223 pub downloader_stream_batch_size: usize,
227 pub downloader_max_buffered_blocks_size_bytes: usize,
231 pub downloader_min_concurrent_requests: usize,
235 pub downloader_max_concurrent_requests: usize,
240}
241
242impl Default for BodiesConfig {
243 fn default() -> Self {
244 Self {
245 downloader_request_limit: 200,
246 downloader_stream_batch_size: 1_000,
247 downloader_max_buffered_blocks_size_bytes: 2 * 1024 * 1024 * 1024, downloader_min_concurrent_requests: 5,
249 downloader_max_concurrent_requests: 100,
250 }
251 }
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
257#[cfg_attr(feature = "serde", serde(default))]
258pub struct SenderRecoveryConfig {
259 pub commit_threshold: u64,
261}
262
263impl Default for SenderRecoveryConfig {
264 fn default() -> Self {
265 Self { commit_threshold: 5_000_000 }
266 }
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
272#[cfg_attr(feature = "serde", serde(default))]
273pub struct ExecutionConfig {
274 pub max_blocks: Option<u64>,
276 pub max_changes: Option<u64>,
278 pub max_cumulative_gas: Option<u64>,
280 #[cfg_attr(
282 feature = "serde",
283 serde(
284 serialize_with = "humantime_serde::serialize",
285 deserialize_with = "deserialize_duration"
286 )
287 )]
288 pub max_duration: Option<Duration>,
289}
290
291impl Default for ExecutionConfig {
292 fn default() -> Self {
293 Self {
294 max_blocks: Some(500_000),
295 max_changes: Some(5_000_000),
296 max_cumulative_gas: Some(30_000_000 * 50_000),
298 max_duration: Some(Duration::from_secs(10 * 60)),
300 }
301 }
302}
303
304impl From<ExecutionConfig> for ExecutionStageThresholds {
305 fn from(config: ExecutionConfig) -> Self {
306 Self {
307 max_blocks: config.max_blocks,
308 max_changes: config.max_changes,
309 max_cumulative_gas: config.max_cumulative_gas,
310 max_duration: config.max_duration,
311 }
312 }
313}
314
315#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
318#[cfg_attr(feature = "serde", serde(default))]
319pub struct PruneStageConfig {
320 pub commit_threshold: usize,
322}
323
324impl Default for PruneStageConfig {
325 fn default() -> Self {
326 Self { commit_threshold: 1_000_000 }
327 }
328}
329
330#[derive(Debug, Clone, Copy, PartialEq, Eq)]
332#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
333#[cfg_attr(feature = "serde", serde(default))]
334pub struct HashingConfig {
335 pub clean_threshold: u64,
338 pub commit_threshold: u64,
340 pub commit_entries: u64,
344}
345
346impl Default for HashingConfig {
347 fn default() -> Self {
348 Self { clean_threshold: 500_000, commit_threshold: 100_000, commit_entries: 30_000_000 }
349 }
350}
351
352#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
355#[cfg_attr(feature = "serde", serde(default))]
356pub struct MerkleConfig {
357 pub incremental_threshold: u64,
365 pub rebuild_threshold: u64,
368}
369
370impl Default for MerkleConfig {
371 fn default() -> Self {
372 Self { incremental_threshold: 7_000, rebuild_threshold: 100_000 }
373 }
374}
375
376#[derive(Debug, Clone, Copy, PartialEq, Eq)]
378#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
379#[cfg_attr(feature = "serde", serde(default))]
380pub struct TransactionLookupConfig {
381 pub chunk_size: u64,
383}
384
385impl Default for TransactionLookupConfig {
386 fn default() -> Self {
387 Self { chunk_size: 5_000_000 }
388 }
389}
390
391#[derive(Debug, Clone, PartialEq, Eq)]
393#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
394#[cfg_attr(feature = "serde", serde(default))]
395pub struct EtlConfig {
396 pub dir: Option<PathBuf>,
398 pub file_size: usize,
400}
401
402impl Default for EtlConfig {
403 fn default() -> Self {
404 Self { dir: None, file_size: Self::default_file_size() }
405 }
406}
407
408impl EtlConfig {
409 pub const fn new(dir: Option<PathBuf>, file_size: usize) -> Self {
411 Self { dir, file_size }
412 }
413
414 pub fn from_datadir(path: &Path) -> PathBuf {
416 path.join("etl-tmp")
417 }
418
419 pub const fn default_file_size() -> usize {
421 500 * (1024 * 1024)
423 }
424}
425
426#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
428#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
429#[cfg_attr(feature = "serde", serde(default))]
430pub struct StaticFilesConfig {
431 pub blocks_per_file: BlocksPerFileConfig,
433}
434
435#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
437#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
438#[cfg_attr(feature = "serde", serde(default))]
439pub struct BlocksPerFileConfig {
440 pub headers: Option<u64>,
442 pub transactions: Option<u64>,
444 pub receipts: Option<u64>,
446 pub transaction_senders: Option<u64>,
448 pub account_change_sets: Option<u64>,
450 pub storage_change_sets: Option<u64>,
452}
453
454impl StaticFilesConfig {
455 pub fn validate(&self) -> eyre::Result<()> {
459 let BlocksPerFileConfig {
460 headers,
461 transactions,
462 receipts,
463 transaction_senders,
464 account_change_sets,
465 storage_change_sets,
466 } = self.blocks_per_file;
467 eyre::ensure!(headers != Some(0), "Headers segment blocks per file must be greater than 0");
468 eyre::ensure!(
469 transactions != Some(0),
470 "Transactions segment blocks per file must be greater than 0"
471 );
472 eyre::ensure!(
473 receipts != Some(0),
474 "Receipts segment blocks per file must be greater than 0"
475 );
476 eyre::ensure!(
477 transaction_senders != Some(0),
478 "Transaction senders segment blocks per file must be greater than 0"
479 );
480 eyre::ensure!(
481 account_change_sets != Some(0),
482 "Account changesets segment blocks per file must be greater than 0"
483 );
484 eyre::ensure!(
485 storage_change_sets != Some(0),
486 "Storage changesets segment blocks per file must be greater than 0"
487 );
488 Ok(())
489 }
490
491 pub fn as_blocks_per_file_map(&self) -> StaticFileMap<u64> {
493 let BlocksPerFileConfig {
494 headers,
495 transactions,
496 receipts,
497 transaction_senders,
498 account_change_sets,
499 storage_change_sets,
500 } = self.blocks_per_file;
501
502 let mut map = StaticFileMap::default();
503 for segment in StaticFileSegment::iter() {
506 let blocks_per_file = match segment {
507 StaticFileSegment::Headers => headers,
508 StaticFileSegment::Transactions => transactions,
509 StaticFileSegment::Receipts => receipts,
510 StaticFileSegment::TransactionSenders => transaction_senders,
511 StaticFileSegment::AccountChangeSets => account_change_sets,
512 StaticFileSegment::StorageChangeSets => storage_change_sets,
513 };
514
515 if let Some(blocks_per_file) = blocks_per_file {
516 map.insert(segment, blocks_per_file);
517 }
518 }
519 map
520 }
521}
522
523#[derive(Debug, Clone, Copy, PartialEq, Eq)]
525#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
526#[cfg_attr(feature = "serde", serde(default))]
527pub struct IndexHistoryConfig {
528 pub commit_threshold: u64,
530}
531
532impl Default for IndexHistoryConfig {
533 fn default() -> Self {
534 Self { commit_threshold: 100_000 }
535 }
536}
537
538#[derive(Debug, Clone, PartialEq, Eq)]
540#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
541#[cfg_attr(feature = "serde", serde(default))]
542pub struct PruneConfig {
543 pub block_interval: usize,
545 #[cfg_attr(feature = "serde", serde(alias = "parts"))]
547 pub segments: PruneModes,
548 #[cfg_attr(feature = "serde", serde(default = "default_minimum_pruning_distance"))]
551 pub minimum_pruning_distance: u64,
552}
553
554const fn default_minimum_pruning_distance() -> u64 {
556 MINIMUM_UNWIND_SAFE_DISTANCE
557}
558
559impl Default for PruneConfig {
560 fn default() -> Self {
561 Self {
562 block_interval: DEFAULT_BLOCK_INTERVAL,
563 segments: PruneModes::default(),
564 minimum_pruning_distance: MINIMUM_UNWIND_SAFE_DISTANCE,
565 }
566 }
567}
568
569impl PruneConfig {
570 pub fn is_default(&self) -> bool {
572 self == &Self::default()
573 }
574
575 pub fn has_receipts_pruning(&self) -> bool {
577 self.segments.has_receipts_pruning()
578 }
579
580 pub fn merge(&mut self, other: Self) {
586 let Self {
587 block_interval,
588 segments:
589 PruneModes {
590 sender_recovery,
591 transaction_lookup,
592 receipts,
593 account_history,
594 storage_history,
595 bodies_history,
596 receipts_log_filter,
597 },
598 minimum_pruning_distance,
599 } = other;
600
601 if self.block_interval == DEFAULT_BLOCK_INTERVAL {
603 self.block_interval = block_interval;
604 }
605
606 if self.minimum_pruning_distance == MINIMUM_UNWIND_SAFE_DISTANCE {
608 self.minimum_pruning_distance = minimum_pruning_distance;
609 }
610
611 self.segments.sender_recovery = self.segments.sender_recovery.or(sender_recovery);
613 self.segments.transaction_lookup = self.segments.transaction_lookup.or(transaction_lookup);
614 self.segments.receipts = self.segments.receipts.or(receipts);
615 self.segments.account_history = self.segments.account_history.or(account_history);
616 self.segments.storage_history = self.segments.storage_history.or(storage_history);
617 self.segments.bodies_history = self.segments.bodies_history.or(bodies_history);
618
619 if self.segments.receipts_log_filter.0.is_empty() && !receipts_log_filter.0.is_empty() {
620 self.segments.receipts_log_filter = receipts_log_filter;
621 }
622 }
623}
624
625#[cfg(feature = "serde")]
627fn deserialize_duration<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
628where
629 D: serde::de::Deserializer<'de>,
630{
631 #[derive(serde::Deserialize)]
632 #[serde(untagged)]
633 enum AnyDuration {
634 #[serde(deserialize_with = "humantime_serde::deserialize")]
635 Human(Option<Duration>),
636 Duration(Option<Duration>),
637 }
638
639 <AnyDuration as serde::Deserialize>::deserialize(deserializer).map(|d| match d {
640 AnyDuration::Human(duration) | AnyDuration::Duration(duration) => duration,
641 })
642}
643
644#[cfg(all(test, feature = "serde"))]
645mod tests {
646 use super::{Config, EXTENSION};
647 use crate::PruneConfig;
648 use alloy_primitives::Address;
649 use reth_network_peers::TrustedPeer;
650 use reth_prune_types::{
651 PruneMode, PruneModes, ReceiptsLogPruneConfig, MINIMUM_UNWIND_SAFE_DISTANCE,
652 };
653 use std::{collections::BTreeMap, path::Path, str::FromStr, time::Duration};
654
655 fn with_tempdir(filename: &str, proc: fn(&std::path::Path)) {
656 let temp_dir = tempfile::tempdir().unwrap();
657 let config_path = temp_dir.path().join(filename).with_extension(EXTENSION);
658
659 proc(&config_path);
660
661 temp_dir.close().unwrap()
662 }
663
664 fn with_config_path(test_fn: fn(&Path)) {
666 let config_dir = tempfile::tempdir().expect("creating test fixture failed");
668 let config_path =
670 config_dir.path().join("example-app").join("example-config").with_extension("toml");
671 test_fn(&config_path);
673 config_dir.close().expect("removing test fixture failed");
674 }
675
676 #[test]
677 fn test_load_path_works() {
678 with_config_path(|path| {
679 let config = Config::from_path(path).expect("load_path failed");
680 assert_eq!(config, Config::default());
681 })
682 }
683
684 #[test]
685 fn test_load_path_reads_existing_config() {
686 with_config_path(|path| {
687 let config = Config::default();
688
689 if let Some(parent) = path.parent() {
691 std::fs::create_dir_all(parent).expect("Failed to create directories");
692 }
693
694 std::fs::write(path, toml::to_string(&config).unwrap())
696 .expect("Failed to write config");
697
698 let loaded = Config::from_path(path).expect("load_path failed");
700 assert_eq!(config, loaded);
701 })
702 }
703
704 #[test]
705 fn test_load_path_fails_on_invalid_toml() {
706 with_config_path(|path| {
707 let invalid_toml = "invalid toml data";
708
709 if let Some(parent) = path.parent() {
711 std::fs::create_dir_all(parent).expect("Failed to create directories");
712 }
713
714 std::fs::write(path, invalid_toml).expect("Failed to write invalid TOML");
716
717 let result = Config::from_path(path);
719 assert!(result.is_err());
720 })
721 }
722
723 #[test]
724 fn test_load_path_creates_directory_if_not_exists() {
725 with_config_path(|path| {
726 let parent = path.parent().unwrap();
728 assert!(!parent.exists());
729
730 let config = Config::from_path(path).expect("load_path failed");
732 assert_eq!(config, Config::default());
733
734 assert!(parent.exists());
736 assert!(path.exists());
737 });
738 }
739
740 #[test]
741 fn test_store_config() {
742 with_tempdir("config-store-test", |config_path| {
743 let config = Config::default();
744 std::fs::write(
745 config_path,
746 toml::to_string(&config).expect("Failed to serialize config"),
747 )
748 .expect("Failed to write config file");
749 })
750 }
751
752 #[test]
753 fn test_store_config_method() {
754 with_tempdir("config-store-test-method", |config_path| {
755 let config = Config::default();
756 config.save(config_path).expect("Failed to store config");
757 })
758 }
759
760 #[test]
761 fn test_load_config() {
762 with_tempdir("config-load-test", |config_path| {
763 let config = Config::default();
764
765 std::fs::write(
767 config_path,
768 toml::to_string(&config).expect("Failed to serialize config"),
769 )
770 .expect("Failed to write config file");
771
772 let loaded_config = Config::from_path(config_path).unwrap();
774
775 assert_eq!(config, loaded_config);
777 })
778 }
779
780 #[test]
781 fn test_load_execution_stage() {
782 with_tempdir("config-load-test", |config_path| {
783 let mut config = Config::default();
784 config.stages.execution.max_duration = Some(Duration::from_secs(10 * 60));
785
786 std::fs::write(
788 config_path,
789 toml::to_string(&config).expect("Failed to serialize config"),
790 )
791 .expect("Failed to write config file");
792
793 let loaded_config = Config::from_path(config_path).unwrap();
795
796 assert_eq!(config, loaded_config);
798 })
799 }
800
801 #[test]
803 fn test_backwards_compatibility() {
804 let alpha_0_0_8 = r"#
805[stages.headers]
806downloader_max_concurrent_requests = 100
807downloader_min_concurrent_requests = 5
808downloader_max_buffered_responses = 100
809downloader_request_limit = 1000
810commit_threshold = 10000
811
812[stages.bodies]
813downloader_request_limit = 200
814downloader_stream_batch_size = 1000
815downloader_max_buffered_blocks_size_bytes = 2147483648
816downloader_min_concurrent_requests = 5
817downloader_max_concurrent_requests = 100
818
819[stages.sender_recovery]
820commit_threshold = 5000000
821
822[stages.execution]
823max_blocks = 500000
824max_changes = 5000000
825
826[stages.account_hashing]
827clean_threshold = 500000
828commit_threshold = 100000
829
830[stages.storage_hashing]
831clean_threshold = 500000
832commit_threshold = 100000
833
834[stages.merkle]
835clean_threshold = 50000
836
837[stages.transaction_lookup]
838chunk_size = 5000000
839
840[stages.index_account_history]
841commit_threshold = 100000
842
843[stages.index_storage_history]
844commit_threshold = 100000
845
846[peers]
847refill_slots_interval = '1s'
848trusted_nodes = []
849connect_trusted_nodes_only = false
850max_backoff_count = 5
851ban_duration = '12h'
852
853[peers.connection_info]
854max_outbound = 100
855max_inbound = 30
856
857[peers.reputation_weights]
858bad_message = -16384
859bad_block = -16384
860bad_transactions = -16384
861already_seen_transactions = 0
862timeout = -4096
863bad_protocol = -2147483648
864failed_to_connect = -25600
865dropped = -4096
866
867[peers.backoff_durations]
868low = '30s'
869medium = '3m'
870high = '15m'
871max = '1h'
872
873[sessions]
874session_command_buffer = 32
875session_event_buffer = 260
876
877[sessions.limits]
878
879[sessions.initial_internal_request_timeout]
880secs = 20
881nanos = 0
882
883[sessions.protocol_breach_request_timeout]
884secs = 120
885nanos = 0
886
887[prune]
888block_interval = 5
889
890[prune.parts]
891sender_recovery = { distance = 16384 }
892transaction_lookup = 'full'
893receipts = { before = 1920000 }
894account_history = { distance = 16384 }
895storage_history = { distance = 16384 }
896[prune.parts.receipts_log_filter]
897'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' = { before = 17000000 }
898'0xdac17f958d2ee523a2206206994597c13d831ec7' = { distance = 1000 }
899#";
900 let _conf: Config = toml::from_str(alpha_0_0_8).unwrap();
901
902 let alpha_0_0_11 = r"#
903[prune.segments]
904sender_recovery = { distance = 16384 }
905transaction_lookup = 'full'
906receipts = { before = 1920000 }
907account_history = { distance = 16384 }
908storage_history = { distance = 16384 }
909[prune.segments.receipts_log_filter]
910'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' = { before = 17000000 }
911'0xdac17f958d2ee523a2206206994597c13d831ec7' = { distance = 1000 }
912#";
913 let _conf: Config = toml::from_str(alpha_0_0_11).unwrap();
914
915 let alpha_0_0_18 = r"#
916[stages.headers]
917downloader_max_concurrent_requests = 100
918downloader_min_concurrent_requests = 5
919downloader_max_buffered_responses = 100
920downloader_request_limit = 1000
921commit_threshold = 10000
922
923[stages.total_difficulty]
924commit_threshold = 100000
925
926[stages.bodies]
927downloader_request_limit = 200
928downloader_stream_batch_size = 1000
929downloader_max_buffered_blocks_size_bytes = 2147483648
930downloader_min_concurrent_requests = 5
931downloader_max_concurrent_requests = 100
932
933[stages.sender_recovery]
934commit_threshold = 5000000
935
936[stages.execution]
937max_blocks = 500000
938max_changes = 5000000
939max_cumulative_gas = 1500000000000
940[stages.execution.max_duration]
941secs = 600
942nanos = 0
943
944[stages.account_hashing]
945clean_threshold = 500000
946commit_threshold = 100000
947
948[stages.storage_hashing]
949clean_threshold = 500000
950commit_threshold = 100000
951
952[stages.merkle]
953clean_threshold = 50000
954
955[stages.transaction_lookup]
956commit_threshold = 5000000
957
958[stages.index_account_history]
959commit_threshold = 100000
960
961[stages.index_storage_history]
962commit_threshold = 100000
963
964[peers]
965refill_slots_interval = '5s'
966trusted_nodes = []
967connect_trusted_nodes_only = false
968max_backoff_count = 5
969ban_duration = '12h'
970
971[peers.connection_info]
972max_outbound = 100
973max_inbound = 30
974max_concurrent_outbound_dials = 10
975
976[peers.reputation_weights]
977bad_message = -16384
978bad_block = -16384
979bad_transactions = -16384
980already_seen_transactions = 0
981timeout = -4096
982bad_protocol = -2147483648
983failed_to_connect = -25600
984dropped = -4096
985bad_announcement = -1024
986
987[peers.backoff_durations]
988low = '30s'
989medium = '3m'
990high = '15m'
991max = '1h'
992
993[sessions]
994session_command_buffer = 32
995session_event_buffer = 260
996
997[sessions.limits]
998
999[sessions.initial_internal_request_timeout]
1000secs = 20
1001nanos = 0
1002
1003[sessions.protocol_breach_request_timeout]
1004secs = 120
1005nanos = 0
1006#";
1007 let conf: Config = toml::from_str(alpha_0_0_18).unwrap();
1008 assert_eq!(conf.stages.execution.max_duration, Some(Duration::from_secs(10 * 60)));
1009
1010 let alpha_0_0_19 = r"#
1011[stages.headers]
1012downloader_max_concurrent_requests = 100
1013downloader_min_concurrent_requests = 5
1014downloader_max_buffered_responses = 100
1015downloader_request_limit = 1000
1016commit_threshold = 10000
1017
1018[stages.total_difficulty]
1019commit_threshold = 100000
1020
1021[stages.bodies]
1022downloader_request_limit = 200
1023downloader_stream_batch_size = 1000
1024downloader_max_buffered_blocks_size_bytes = 2147483648
1025downloader_min_concurrent_requests = 5
1026downloader_max_concurrent_requests = 100
1027
1028[stages.sender_recovery]
1029commit_threshold = 5000000
1030
1031[stages.execution]
1032max_blocks = 500000
1033max_changes = 5000000
1034max_cumulative_gas = 1500000000000
1035max_duration = '10m'
1036
1037[stages.account_hashing]
1038clean_threshold = 500000
1039commit_threshold = 100000
1040
1041[stages.storage_hashing]
1042clean_threshold = 500000
1043commit_threshold = 100000
1044
1045[stages.merkle]
1046clean_threshold = 50000
1047
1048[stages.transaction_lookup]
1049commit_threshold = 5000000
1050
1051[stages.index_account_history]
1052commit_threshold = 100000
1053
1054[stages.index_storage_history]
1055commit_threshold = 100000
1056
1057[peers]
1058refill_slots_interval = '5s'
1059trusted_nodes = []
1060connect_trusted_nodes_only = false
1061max_backoff_count = 5
1062ban_duration = '12h'
1063
1064[peers.connection_info]
1065max_outbound = 100
1066max_inbound = 30
1067max_concurrent_outbound_dials = 10
1068
1069[peers.reputation_weights]
1070bad_message = -16384
1071bad_block = -16384
1072bad_transactions = -16384
1073already_seen_transactions = 0
1074timeout = -4096
1075bad_protocol = -2147483648
1076failed_to_connect = -25600
1077dropped = -4096
1078bad_announcement = -1024
1079
1080[peers.backoff_durations]
1081low = '30s'
1082medium = '3m'
1083high = '15m'
1084max = '1h'
1085
1086[sessions]
1087session_command_buffer = 32
1088session_event_buffer = 260
1089
1090[sessions.limits]
1091
1092[sessions.initial_internal_request_timeout]
1093secs = 20
1094nanos = 0
1095
1096[sessions.protocol_breach_request_timeout]
1097secs = 120
1098nanos = 0
1099#";
1100 let _conf: Config = toml::from_str(alpha_0_0_19).unwrap();
1101 }
1102
1103 #[test]
1105 fn test_backwards_compatibility_prune_full() {
1106 let s = r"#
1107[prune]
1108block_interval = 5
1109
1110[prune.segments]
1111sender_recovery = { distance = 16384 }
1112transaction_lookup = 'full'
1113receipts = { distance = 16384 }
1114#";
1115 let _conf: Config = toml::from_str(s).unwrap();
1116 }
1117
1118 #[test]
1119 fn test_prune_config_merge() {
1120 let mut config1 = PruneConfig {
1121 block_interval: 5,
1122 minimum_pruning_distance: MINIMUM_UNWIND_SAFE_DISTANCE,
1123 segments: PruneModes {
1124 sender_recovery: Some(PruneMode::Full),
1125 transaction_lookup: None,
1126 receipts: Some(PruneMode::Distance(1000)),
1127 account_history: None,
1128 storage_history: Some(PruneMode::Before(5000)),
1129 bodies_history: None,
1130 receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([(
1131 Address::random(),
1132 PruneMode::Full,
1133 )])),
1134 },
1135 };
1136
1137 let config2 = PruneConfig {
1138 block_interval: 10,
1139 minimum_pruning_distance: MINIMUM_UNWIND_SAFE_DISTANCE,
1140 segments: PruneModes {
1141 sender_recovery: Some(PruneMode::Distance(500)),
1142 transaction_lookup: Some(PruneMode::Full),
1143 receipts: Some(PruneMode::Full),
1144 account_history: Some(PruneMode::Distance(2000)),
1145 storage_history: Some(PruneMode::Distance(3000)),
1146 bodies_history: None,
1147 receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([
1148 (Address::random(), PruneMode::Distance(1000)),
1149 (Address::random(), PruneMode::Before(2000)),
1150 ])),
1151 },
1152 };
1153
1154 let original_filter = config1.segments.receipts_log_filter.clone();
1155 config1.merge(config2);
1156
1157 assert_eq!(config1.block_interval, 10);
1160 assert_eq!(config1.segments.sender_recovery, Some(PruneMode::Full));
1161 assert_eq!(config1.segments.transaction_lookup, Some(PruneMode::Full));
1162 assert_eq!(config1.segments.receipts, Some(PruneMode::Distance(1000)));
1163 assert_eq!(config1.segments.account_history, Some(PruneMode::Distance(2000)));
1164 assert_eq!(config1.segments.storage_history, Some(PruneMode::Before(5000)));
1165 assert_eq!(config1.segments.receipts_log_filter, original_filter);
1166 }
1167
1168 #[test]
1169 fn test_conf_trust_nodes_only() {
1170 let trusted_nodes_only = r"#
1171[peers]
1172trusted_nodes_only = true
1173#";
1174 let conf: Config = toml::from_str(trusted_nodes_only).unwrap();
1175 assert!(conf.peers.trusted_nodes_only);
1176
1177 let trusted_nodes_only = r"#
1178[peers]
1179connect_trusted_nodes_only = true
1180#";
1181 let conf: Config = toml::from_str(trusted_nodes_only).unwrap();
1182 assert!(conf.peers.trusted_nodes_only);
1183 }
1184
1185 #[test]
1186 fn test_can_support_dns_in_trusted_nodes() {
1187 let reth_toml = r#"
1188 [peers]
1189 trusted_nodes = [
1190 "enode://0401e494dbd0c84c5c0f72adac5985d2f2525e08b68d448958aae218f5ac8198a80d1498e0ebec2ce38b1b18d6750f6e61a56b4614c5a6c6cf0981c39aed47dc@34.159.32.127:30303",
1191 "enode://e9675164b5e17b9d9edf0cc2bd79e6b6f487200c74d1331c220abb5b8ee80c2eefbf18213989585e9d0960683e819542e11d4eefb5f2b4019e1e49f9fd8fff18@berav2-bootnode.staketab.org:30303"
1192 ]
1193 "#;
1194
1195 let conf: Config = toml::from_str(reth_toml).unwrap();
1196 assert_eq!(conf.peers.trusted_nodes.len(), 2);
1197
1198 let expected_enodes = vec![
1199 "enode://0401e494dbd0c84c5c0f72adac5985d2f2525e08b68d448958aae218f5ac8198a80d1498e0ebec2ce38b1b18d6750f6e61a56b4614c5a6c6cf0981c39aed47dc@34.159.32.127:30303",
1200 "enode://e9675164b5e17b9d9edf0cc2bd79e6b6f487200c74d1331c220abb5b8ee80c2eefbf18213989585e9d0960683e819542e11d4eefb5f2b4019e1e49f9fd8fff18@berav2-bootnode.staketab.org:30303",
1201 ];
1202
1203 for enode in expected_enodes {
1204 let node = TrustedPeer::from_str(enode).unwrap();
1205 assert!(conf.peers.trusted_nodes.contains(&node));
1206 }
1207 }
1208
1209 #[test]
1210 fn test_bootnodes() {
1211 let reth_toml = r#"
1212 bootnodes = [
1213 "enode://0401e494dbd0c84c5c0f72adac5985d2f2525e08b68d448958aae218f5ac8198a80d1498e0ebec2ce38b1b18d6750f6e61a56b4614c5a6c6cf0981c39aed47dc@34.159.32.127:30303",
1214 "enode://e9675164b5e17b9d9edf0cc2bd79e6b6f487200c74d1331c220abb5b8ee80c2eefbf18213989585e9d0960683e819542e11d4eefb5f2b4019e1e49f9fd8fff18@berav2-bootnode.staketab.org:30303",
1215 "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8"
1216 ]
1217 "#;
1218
1219 let conf: Config = toml::from_str(reth_toml).unwrap();
1220 assert_eq!(conf.bootnodes.len(), 3);
1221 assert_eq!(
1222 conf.bootnodes[1].host,
1223 url::Host::<String>::Domain("berav2-bootnode.staketab.org".to_string())
1224 );
1225 assert_eq!(
1227 conf.bootnodes[2],
1228 TrustedPeer::from_str("enode://ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f@127.0.0.1:30303").unwrap()
1229 );
1230
1231 let serialized = toml::to_string(&conf).unwrap();
1233 assert_eq!(toml::from_str::<Config>(&serialized).unwrap(), conf);
1234 }
1235
1236 #[test]
1237 fn test_bootnodes_default_empty() {
1238 let conf: Config = toml::from_str("").unwrap();
1239 assert!(conf.bootnodes.is_empty());
1240 }
1241}