@@ -5,6 +5,7 @@ use std::process;
55use std:: sync:: Arc ;
66
77use clap:: { Parser , ValueEnum } ;
8+ use dash_spv:: chain:: CheckpointManager ;
89use dash_spv:: {
910 ClientConfig , DashSpvClient , DevnetConfig , LevelFilter , LlmqDevnetParams , MempoolStrategy ,
1011 Network , ValidationMode ,
@@ -119,6 +120,13 @@ struct Args {
119120 #[ arg( short, long, value_name = "HEIGHT" ) ]
120121 start_height : Option < String > ,
121122
123+ /// Wallet birth height. Header, filter-header and filter sync anchor at the
124+ /// nearest checkpoint at or before this height instead of genesis. Use 'now'
125+ /// for the latest checkpoint (a brand-new wallet with no prior history).
126+ /// Defaults to genesis (0) so an unspecified wallet still does a full scan.
127+ #[ arg( long, value_name = "HEIGHT" ) ]
128+ birth_height : Option < String > ,
129+
122130 /// Disable log file output (enables console logging as fallback)
123131 #[ arg( long) ]
124132 no_log_file : bool ,
@@ -249,11 +257,14 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
249257 config = config. with_user_agent ( user_agent) ;
250258 }
251259
260+ // Resolve the wallet birth height, mapping 'now' to the latest checkpoint.
261+ let birth_height = resolve_birth_height ( & args. birth_height , config. network ) ?;
262+
252263 // Create the wallet manager
253264 let mut wallet_manager = WalletManager :: < ManagedWalletInfo > :: new ( config. network ) ;
254265 wallet_manager. create_wallet_from_mnemonic (
255266 mnemonic_phrase. as_str ( ) ,
256- 0 ,
267+ birth_height ,
257268 key_wallet:: wallet:: initialization:: WalletAccountCreationOptions :: default ( ) ,
258269 ) ?;
259270 let wallet = Arc :: new ( tokio:: sync:: RwLock :: new ( wallet_manager) ) ;
@@ -279,6 +290,27 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
279290 Ok ( ( ) )
280291}
281292
293+ /// Resolve the `--birth-height` argument into a concrete block height.
294+ ///
295+ /// `None` means genesis (0). `now`/`latest` resolves to the highest bundled
296+ /// checkpoint for the network, which is the right anchor for a brand-new wallet
297+ /// with no prior on-chain history. A numeric value is taken verbatim and gets
298+ /// snapped to the nearest checkpoint at or before it during sync anchoring.
299+ fn resolve_birth_height ( arg : & Option < String > , network : Network ) -> Result < u32 , String > {
300+ let Some ( value) = arg else {
301+ return Ok ( 0 ) ;
302+ } ;
303+
304+ if value == "now" || value == "latest" {
305+ return Ok ( CheckpointManager :: for_network ( network)
306+ . last_checkpoint ( )
307+ . map ( |checkpoint| checkpoint. height )
308+ . unwrap_or ( 0 ) ) ;
309+ }
310+
311+ value. parse :: < u32 > ( ) . map_err ( |e| format ! ( "Invalid birth height '{}': {}" , value, e) )
312+ }
313+
282314fn build_client_config ( args : & Args , data_dir : PathBuf ) -> Result < ClientConfig , String > {
283315 let network: Network = args. network . into ( ) ;
284316 let validation_mode: ValidationMode = args. validation_mode . into ( ) ;
@@ -437,6 +469,36 @@ mod tests {
437469 parse ( & argv) . expect ( "parse" )
438470 }
439471
472+ #[ test]
473+ fn birth_height_resolves_none_now_and_numeric ( ) {
474+ // Unspecified means genesis, preserving the full-scan default.
475+ assert_eq ! ( resolve_birth_height( & None , Network :: Mainnet ) . unwrap( ) , 0 ) ;
476+
477+ // A numeric value is taken verbatim (nearest-checkpoint snapping happens later).
478+ assert_eq ! (
479+ resolve_birth_height( & Some ( "123456" . to_string( ) ) , Network :: Mainnet ) . unwrap( ) ,
480+ 123456
481+ ) ;
482+
483+ // 'now'/'latest' resolve to the highest bundled checkpoint for the network.
484+ let latest = CheckpointManager :: for_network ( Network :: Mainnet )
485+ . last_checkpoint ( )
486+ . expect ( "mainnet has checkpoints" )
487+ . height ;
488+ assert ! ( latest > 0 ) ;
489+ assert_eq ! (
490+ resolve_birth_height( & Some ( "now" . to_string( ) ) , Network :: Mainnet ) . unwrap( ) ,
491+ latest
492+ ) ;
493+ assert_eq ! (
494+ resolve_birth_height( & Some ( "latest" . to_string( ) ) , Network :: Mainnet ) . unwrap( ) ,
495+ latest
496+ ) ;
497+
498+ // Non-numeric junk is rejected.
499+ assert ! ( resolve_birth_height( & Some ( "abc" . to_string( ) ) , Network :: Mainnet ) . is_err( ) ) ;
500+ }
501+
440502 #[ test]
441503 fn devnet_requires_name ( ) {
442504 let args = args ( & [ "--network" , "devnet" ] ) ;
0 commit comments