@@ -7,9 +7,10 @@ use reth_chainspec::{EthChainSpec, EthereumHardforks};
77use reth_cli:: chainspec:: ChainSpecParser ;
88use reth_fs_util as fs;
99use std:: {
10+ borrow:: Cow ,
1011 io:: { self , Read , Write } ,
1112 path:: Path ,
12- sync:: Arc ,
13+ sync:: { Arc , OnceLock } ,
1314 time:: { Duration , Instant } ,
1415} ;
1516use tar:: Archive ;
@@ -22,24 +23,109 @@ const MERKLE_BASE_URL: &str = "https://downloads.merkle.io";
2223const EXTENSION_TAR_LZ4 : & str = ".tar.lz4" ;
2324const EXTENSION_TAR_ZSTD : & str = ".tar.zst" ;
2425
26+ /// Global static download defaults
27+ static DOWNLOAD_DEFAULTS : OnceLock < DownloadDefaults > = OnceLock :: new ( ) ;
28+
29+ /// Download configuration defaults
30+ ///
31+ /// Global defaults can be set via [`DownloadDefaults::try_init`].
32+ #[ derive( Debug , Clone ) ]
33+ pub struct DownloadDefaults {
34+ /// List of available snapshot sources
35+ pub available_snapshots : Vec < Cow < ' static , str > > ,
36+ /// Default base URL for snapshots
37+ pub default_base_url : Cow < ' static , str > ,
38+ /// Optional custom long help text that overrides the generated help
39+ pub long_help : Option < String > ,
40+ }
41+
42+ impl DownloadDefaults {
43+ /// Initialize the global download defaults with this configuration
44+ pub fn try_init ( self ) -> Result < ( ) , Self > {
45+ DOWNLOAD_DEFAULTS . set ( self )
46+ }
47+
48+ /// Get a reference to the global download defaults
49+ pub fn get_global ( ) -> & ' static DownloadDefaults {
50+ DOWNLOAD_DEFAULTS . get_or_init ( DownloadDefaults :: default_download_defaults)
51+ }
52+
53+ /// Default download configuration with defaults from merkle.io and publicnode
54+ pub fn default_download_defaults ( ) -> Self {
55+ Self {
56+ available_snapshots : vec ! [
57+ Cow :: Borrowed ( "https://www.merkle.io/snapshots (default, mainnet archive)" ) ,
58+ Cow :: Borrowed ( "https://publicnode.com/snapshots (full nodes & testnets)" ) ,
59+ ] ,
60+ default_base_url : Cow :: Borrowed ( MERKLE_BASE_URL ) ,
61+ long_help : None ,
62+ }
63+ }
64+
65+ /// Generates the long help text for the download URL argument using these defaults.
66+ ///
67+ /// If a custom long_help is set, it will be returned. Otherwise, help text is generated
68+ /// from the available_snapshots list.
69+ pub fn long_help ( & self ) -> String {
70+ if let Some ( ref custom_help) = self . long_help {
71+ return custom_help. clone ( ) ;
72+ }
73+
74+ let mut help = String :: from (
75+ "Specify a snapshot URL or let the command propose a default one.\n \n Available snapshot sources:\n " ,
76+ ) ;
77+
78+ for source in & self . available_snapshots {
79+ help. push_str ( "- " ) ;
80+ help. push_str ( source) ;
81+ help. push ( '\n' ) ;
82+ }
83+
84+ help. push_str (
85+ "\n If no URL is provided, the latest mainnet archive snapshot\n will be proposed for download from " ,
86+ ) ;
87+ help. push_str ( self . default_base_url . as_ref ( ) ) ;
88+ help
89+ }
90+
91+ /// Add a snapshot source to the list
92+ pub fn with_snapshot ( mut self , source : impl Into < Cow < ' static , str > > ) -> Self {
93+ self . available_snapshots . push ( source. into ( ) ) ;
94+ self
95+ }
96+
97+ /// Replace all snapshot sources
98+ pub fn with_snapshots ( mut self , sources : Vec < Cow < ' static , str > > ) -> Self {
99+ self . available_snapshots = sources;
100+ self
101+ }
102+
103+ /// Set the default base URL, e.g. `https://downloads.merkle.io`.
104+ pub fn with_base_url ( mut self , url : impl Into < Cow < ' static , str > > ) -> Self {
105+ self . default_base_url = url. into ( ) ;
106+ self
107+ }
108+
109+ /// Builder: Set custom long help text, overriding the generated help
110+ pub fn with_long_help ( mut self , help : impl Into < String > ) -> Self {
111+ self . long_help = Some ( help. into ( ) ) ;
112+ self
113+ }
114+ }
115+
116+ impl Default for DownloadDefaults {
117+ fn default ( ) -> Self {
118+ Self :: default_download_defaults ( )
119+ }
120+ }
121+
25122#[ derive( Debug , Parser ) ]
26123pub struct DownloadCommand < C : ChainSpecParser > {
27124 #[ command( flatten) ]
28125 env : EnvironmentArgs < C > ,
29126
30- #[ arg(
31- long,
32- short,
33- help = "Custom URL to download the snapshot from" ,
34- long_help = "Specify a snapshot URL or let the command propose a default one.\n \
35- \n \
36- Available snapshot sources:\n \
37- - https://www.merkle.io/snapshots (default, mainnet archive)\n \
38- - https://publicnode.com/snapshots (full nodes & testnets)\n \
39- \n \
40- If no URL is provided, the latest mainnet archive snapshot\n \
41- will be proposed for download from merkle.io"
42- ) ]
127+ /// Custom URL to download the snapshot from
128+ #[ arg( long, short, long_help = DownloadDefaults :: get_global( ) . long_help( ) ) ]
43129 url : Option < String > ,
44130}
45131
@@ -207,9 +293,10 @@ async fn stream_and_extract(url: &str, target_dir: &Path) -> Result<()> {
207293 Ok ( ( ) )
208294}
209295
210- // Builds default URL for latest mainnet archive snapshot
296+ // Builds default URL for latest mainnet archive snapshot using configured defaults
211297async fn get_latest_snapshot_url ( ) -> Result < String > {
212- let latest_url = format ! ( "{MERKLE_BASE_URL}/latest.txt" ) ;
298+ let base_url = & DownloadDefaults :: get_global ( ) . default_base_url ;
299+ let latest_url = format ! ( "{base_url}/latest.txt" ) ;
213300 let filename = Client :: new ( )
214301 . get ( latest_url)
215302 . send ( )
@@ -220,5 +307,64 @@ async fn get_latest_snapshot_url() -> Result<String> {
220307 . trim ( )
221308 . to_string ( ) ;
222309
223- Ok ( format ! ( "{MERKLE_BASE_URL}/{filename}" ) )
310+ Ok ( format ! ( "{base_url}/{filename}" ) )
311+ }
312+
313+ #[ cfg( test) ]
314+ mod tests {
315+ use super :: * ;
316+
317+ #[ test]
318+ fn test_download_defaults_builder ( ) {
319+ let defaults = DownloadDefaults :: default ( )
320+ . with_snapshot ( "https://example.com/snapshots (example)" )
321+ . with_base_url ( "https://example.com" ) ;
322+
323+ assert_eq ! ( defaults. default_base_url, "https://example.com" ) ;
324+ assert_eq ! ( defaults. available_snapshots. len( ) , 3 ) ; // 2 defaults + 1 added
325+ }
326+
327+ #[ test]
328+ fn test_download_defaults_replace_snapshots ( ) {
329+ let defaults = DownloadDefaults :: default ( ) . with_snapshots ( vec ! [
330+ Cow :: Borrowed ( "https://custom1.com" ) ,
331+ Cow :: Borrowed ( "https://custom2.com" ) ,
332+ ] ) ;
333+
334+ assert_eq ! ( defaults. available_snapshots. len( ) , 2 ) ;
335+ assert_eq ! ( defaults. available_snapshots[ 0 ] , "https://custom1.com" ) ;
336+ }
337+
338+ #[ test]
339+ fn test_long_help_generation ( ) {
340+ let defaults = DownloadDefaults :: default ( ) ;
341+ let help = defaults. long_help ( ) ;
342+
343+ assert ! ( help. contains( "Available snapshot sources:" ) ) ;
344+ assert ! ( help. contains( "merkle.io" ) ) ;
345+ assert ! ( help. contains( "publicnode.com" ) ) ;
346+ }
347+
348+ #[ test]
349+ fn test_long_help_override ( ) {
350+ let custom_help = "This is custom help text for downloading snapshots." ;
351+ let defaults = DownloadDefaults :: default ( ) . with_long_help ( custom_help) ;
352+
353+ let help = defaults. long_help ( ) ;
354+ assert_eq ! ( help, custom_help) ;
355+ assert ! ( !help. contains( "Available snapshot sources:" ) ) ;
356+ }
357+
358+ #[ test]
359+ fn test_builder_chaining ( ) {
360+ let defaults = DownloadDefaults :: default ( )
361+ . with_base_url ( "https://custom.example.com" )
362+ . with_snapshot ( "https://snapshot1.com" )
363+ . with_snapshot ( "https://snapshot2.com" )
364+ . with_long_help ( "Custom help for snapshots" ) ;
365+
366+ assert_eq ! ( defaults. default_base_url, "https://custom.example.com" ) ;
367+ assert_eq ! ( defaults. available_snapshots. len( ) , 4 ) ; // 2 defaults + 2 added
368+ assert_eq ! ( defaults. long_help, Some ( "Custom help for snapshots" . to_string( ) ) ) ;
369+ }
224370}
0 commit comments