@@ -22,10 +22,12 @@ use std::{
2222 net:: { IpAddr , SocketAddr } ,
2323 path:: { Path , PathBuf } ,
2424 sync:: Arc ,
25+ time:: SystemTime ,
2526} ;
2627use tokio_util:: sync:: CancellationToken ;
2728
2829use clap:: Parser ;
30+ use ethlambda_blockchain:: MILLISECONDS_PER_SLOT ;
2931use ethlambda_blockchain:: key_manager:: ValidatorKeyPair ;
3032use ethlambda_network_api:: { InitBlockChain , InitP2P , ToBlockChainToP2PRef , ToP2PToBlockChainRef } ;
3133use ethlambda_p2p:: { Bootnode , P2P , PeerId , SwarmConfig , build_swarm, parse_enrs} ;
@@ -36,13 +38,16 @@ use ethlambda_types::{
3638 signature:: ValidatorSecretKey ,
3739 state:: { State , ValidatorPubkeyBytes } ,
3840} ;
41+ use eyre:: WrapErr ;
3942use serde:: Deserialize ;
4043use tracing:: { error, info, warn} ;
4144use tracing_subscriber:: { EnvFilter , Layer , Registry , layer:: SubscriberExt } ;
4245
4346use ethlambda_blockchain:: BlockChain ;
4447use ethlambda_rpc:: RpcConfig ;
45- use ethlambda_storage:: { StorageBackend , Store , backend:: RocksDBBackend } ;
48+ use ethlambda_storage:: {
49+ MAX_RESUMABLE_DB_STATE_AGE , StorageBackend , Store , backend:: RocksDBBackend ,
50+ } ;
4651
4752const ASCII_ART : & str = r#"
4853 _ _ _ _ _
@@ -124,7 +129,8 @@ async fn main() -> eyre::Result<()> {
124129 . with_default_directive ( tracing:: Level :: INFO . into ( ) )
125130 . from_env_lossy ( ) ;
126131 let subscriber = Registry :: default ( ) . with ( tracing_subscriber:: fmt:: layer ( ) . with_filter ( filter) ) ;
127- tracing:: subscriber:: set_global_default ( subscriber) . unwrap ( ) ;
132+ tracing:: subscriber:: set_global_default ( subscriber)
133+ . wrap_err ( "failed to set global tracing subscriber" ) ?;
128134
129135 let options = CliOptions :: parse ( ) ;
130136
@@ -155,7 +161,12 @@ async fn main() -> eyre::Result<()> {
155161 return run_test_driver ( rpc_config) . await ;
156162 }
157163
158- let node_p2p_key = read_hex_file_bytes ( & options. node_key ) ;
164+ let node_p2p_key = read_hex_file_bytes ( & options. node_key ) . wrap_err_with ( || {
165+ format ! (
166+ "failed to load node key from {}" ,
167+ options. node_key. display( )
168+ )
169+ } ) ?;
159170 let p2p_socket = SocketAddr :: new ( IpAddr :: from ( [ 0 , 0 , 0 , 0 ] ) , options. gossipsub_port ) ;
160171
161172 #[ cfg( all( not( target_env = "msvc" ) , not( feature = "zk-alloc" ) ) ) ]
@@ -182,17 +193,27 @@ async fn main() -> eyre::Result<()> {
182193 let validator_config = options. validator_config ;
183194 let validator_keys_dir = options. hash_sig_keys_dir ;
184195
185- let config_yaml = std:: fs:: read_to_string ( & config_path) . expect ( "Failed to read config.yaml" ) ;
196+ let config_yaml = std:: fs:: read_to_string ( & config_path) . wrap_err_with ( || {
197+ format ! (
198+ "failed to read genesis config from {}" ,
199+ config_path. display( )
200+ )
201+ } ) ?;
186202 let genesis_config: GenesisConfig =
187- serde_yaml_ng:: from_str ( & config_yaml) . expect ( "Failed to parse config.yaml" ) ;
203+ serde_yaml_ng:: from_str ( & config_yaml) . wrap_err_with ( || {
204+ format ! (
205+ "failed to parse genesis config from {}" ,
206+ config_path. display( )
207+ )
208+ } ) ?;
188209
189210 info ! (
190211 genesis_time = genesis_config. genesis_time,
191212 validator_count = genesis_config. genesis_validators. len( ) ,
192213 "Loaded genesis configuration"
193214 ) ;
194215
195- let validator_config_file = read_validator_config_file ( & validator_config) ;
216+ let validator_config_file = read_validator_config_file ( & validator_config) ? ;
196217 let node_names = load_node_names ( & validator_config_file) ;
197218
198219 // Resolve attestation_committee_count: CLI flag > validator-config.yaml > 1.
@@ -212,17 +233,22 @@ async fn main() -> eyre::Result<()> {
212233 ) ;
213234 ethlambda_blockchain:: metrics:: set_attestation_committee_count ( attestation_committee_count) ;
214235
215- let bootnodes = read_bootnodes ( & bootnodes_path) ;
236+ let bootnodes = read_bootnodes ( & bootnodes_path) ? ;
216237
217238 let validator_keys =
218239 read_validator_keys ( & validators_path, & validator_keys_dir, & options. node_id )
219- . expect ( "Failed to load validator keys") ;
240+ . wrap_err ( "failed to load validator keys") ? ;
220241
221242 let data_dir =
222243 std:: path:: absolute ( & options. data_dir ) . unwrap_or_else ( |_| options. data_dir . clone ( ) ) ;
223244 info ! ( data_dir = %data_dir. display( ) , "Initializing DB" ) ;
224- std:: fs:: create_dir_all ( & data_dir) . expect ( "Failed to create data directory" ) ;
225- let backend = Arc :: new ( RocksDBBackend :: open ( & data_dir) . expect ( "Failed to open RocksDB" ) ) ;
245+ std:: fs:: create_dir_all ( & data_dir)
246+ . wrap_err_with ( || format ! ( "failed to create data directory {}" , data_dir. display( ) ) ) ?;
247+ let backend = Arc :: new (
248+ RocksDBBackend :: open ( & data_dir)
249+ . map_err ( |err| eyre:: eyre!( "{err}" ) )
250+ . wrap_err_with ( || format ! ( "failed to open RocksDB at {}" , data_dir. display( ) ) ) ?,
251+ ) ;
226252
227253 let store = fetch_initial_state (
228254 options. checkpoint_sync_url . as_deref ( ) ,
@@ -260,7 +286,7 @@ async fn main() -> eyre::Result<()> {
260286 is_aggregator : options. is_aggregator ,
261287 aggregate_subnet_ids : options. aggregate_subnet_ids ,
262288 } )
263- . expect ( "failed to build swarm" ) ;
289+ . wrap_err ( "failed to build swarm" ) ? ;
264290
265291 let p2p = P2P :: spawn ( built, store. clone ( ) , node_names) ;
266292
@@ -383,9 +409,20 @@ struct ValidatorConfigEntry {
383409 privkey : H256 ,
384410}
385411
386- fn read_validator_config_file ( path : impl AsRef < Path > ) -> ValidatorConfigFile {
387- let yaml = std:: fs:: read_to_string ( & path) . expect ( "Failed to read validator config file" ) ;
388- serde_yaml_ng:: from_str ( & yaml) . expect ( "Failed to parse validator config file" )
412+ fn read_validator_config_file ( path : impl AsRef < Path > ) -> eyre:: Result < ValidatorConfigFile > {
413+ let path = path. as_ref ( ) ;
414+ let yaml = std:: fs:: read_to_string ( path) . wrap_err_with ( || {
415+ format ! (
416+ "failed to read validator config file from {}" ,
417+ path. display( )
418+ )
419+ } ) ?;
420+ serde_yaml_ng:: from_str ( & yaml) . wrap_err_with ( || {
421+ format ! (
422+ "failed to parse validator config file from {}" ,
423+ path. display( )
424+ )
425+ } )
389426}
390427
391428fn load_node_names ( file : & ValidatorConfigFile ) -> HashMap < PeerId , String > {
@@ -398,12 +435,21 @@ fn load_node_names(file: &ValidatorConfigFile) -> HashMap<PeerId, String> {
398435 ethlambda_p2p:: derive_peer_ids ( names_and_privkeys)
399436}
400437
401- fn read_bootnodes ( bootnodes_path : impl AsRef < Path > ) -> Vec < Bootnode > {
402- let bootnodes_yaml =
403- std:: fs:: read_to_string ( bootnodes_path) . expect ( "Failed to read bootnodes file" ) ;
404- let enrs: Vec < String > =
405- serde_yaml_ng:: from_str ( & bootnodes_yaml) . expect ( "Failed to parse bootnodes file" ) ;
406- parse_enrs ( enrs)
438+ fn read_bootnodes ( bootnodes_path : impl AsRef < Path > ) -> eyre:: Result < Vec < Bootnode > > {
439+ let bootnodes_path = bootnodes_path. as_ref ( ) ;
440+ let bootnodes_yaml = std:: fs:: read_to_string ( bootnodes_path) . wrap_err_with ( || {
441+ format ! (
442+ "failed to read bootnodes file from {}" ,
443+ bootnodes_path. display( )
444+ )
445+ } ) ?;
446+ let enrs: Vec < String > = serde_yaml_ng:: from_str ( & bootnodes_yaml) . wrap_err_with ( || {
447+ format ! (
448+ "failed to parse bootnodes file from {}" ,
449+ bootnodes_path. display( )
450+ )
451+ } ) ?;
452+ Ok ( parse_enrs ( enrs) )
407453}
408454
409455/// One entry in `annotated_validators.yaml` as emitted by `lean-quickstart`'s
@@ -476,18 +522,26 @@ fn read_validator_keys(
476522 validators_path : impl AsRef < Path > ,
477523 validator_keys_dir : impl AsRef < Path > ,
478524 node_id : & str ,
479- ) -> Result < HashMap < u64 , ValidatorKeyPair > , String > {
525+ ) -> eyre :: Result < HashMap < u64 , ValidatorKeyPair > > {
480526 let validators_path = validators_path. as_ref ( ) ;
481527 let validator_keys_dir = validator_keys_dir. as_ref ( ) ;
482- let validators_yaml = std:: fs:: read_to_string ( validators_path)
483- . map_err ( |err| format ! ( "Failed to read validators file: {err}" ) ) ?;
528+ let validators_yaml = std:: fs:: read_to_string ( validators_path) . wrap_err_with ( || {
529+ format ! (
530+ "failed to read validators file from {}" ,
531+ validators_path. display( )
532+ )
533+ } ) ?;
484534 let validator_infos: BTreeMap < String , Vec < AnnotatedValidator > > =
485- serde_yaml_ng:: from_str ( & validators_yaml)
486- . map_err ( |err| format ! ( "Failed to parse validators file: {err}" ) ) ?;
535+ serde_yaml_ng:: from_str ( & validators_yaml) . wrap_err_with ( || {
536+ format ! (
537+ "failed to parse validators file from {}" ,
538+ validators_path. display( )
539+ )
540+ } ) ?;
487541
488542 let validator_vec = validator_infos
489543 . get ( node_id)
490- . ok_or_else ( || format ! ( "Node ID '{node_id}' not found in validators config" ) ) ?;
544+ . ok_or_else ( || eyre :: eyre !( "node ID '{node_id}' not found in validators config" ) ) ?;
491545
492546 let resolve_path = |file : & Path | -> PathBuf {
493547 if file. is_absolute ( ) {
@@ -500,41 +554,34 @@ fn read_validator_keys(
500554 // Group entries per validator index, routing each to its role slot.
501555 let mut grouped: BTreeMap < u64 , RoleSlots > = BTreeMap :: new ( ) ;
502556 for entry in validator_vec {
503- let role = classify_role ( & entry. privkey_file ) ?;
557+ let role = classify_role ( & entry. privkey_file ) . map_err ( eyre :: Report :: msg ) ?;
504558 let path = resolve_path ( & entry. privkey_file ) ;
505559 let slots = grouped. entry ( entry. index ) . or_default ( ) ;
506560 let target = match role {
507561 ValidatorKeyRole :: Attestation => & mut slots. attestation ,
508562 ValidatorKeyRole :: Proposal => & mut slots. proposal ,
509563 } ;
510564 if target. is_some ( ) {
511- return Err ( format ! (
512- "validator {}: duplicate {role:?} entry" ,
513- entry. index
514- ) ) ;
565+ eyre:: bail!( "validator {}: duplicate {role:?} entry" , entry. index) ;
515566 }
516567 * target = Some ( path) ;
517568 }
518569
519- let load_key = |path : & Path , purpose : & str | -> Result < ValidatorSecretKey , String > {
520- let bytes = std:: fs:: read ( path) . map_err ( |err| {
521- format ! (
522- "Failed to read {purpose} key file {}: {err}" ,
523- path. display( )
524- )
525- } ) ?;
570+ let load_key = |path : & Path , purpose : & str | -> eyre:: Result < ValidatorSecretKey > {
571+ let bytes = std:: fs:: read ( path)
572+ . wrap_err_with ( || format ! ( "failed to read {purpose} key file {}" , path. display( ) ) ) ?;
526573 ValidatorSecretKey :: from_bytes ( & bytes)
527- . map_err ( |err| format ! ( "Failed to parse {purpose} key {}: {err:?}" , path. display( ) ) )
574+ . map_err ( |err| eyre :: eyre !( "failed to parse {purpose} key {}: {err:?}" , path. display( ) ) )
528575 } ;
529576
530577 let mut validator_keys = HashMap :: new ( ) ;
531578 for ( idx, slots) in grouped {
532579 let att_path = slots
533580 . attestation
534- . ok_or_else ( || format ! ( "validator {idx}: missing attester entry" ) ) ?;
581+ . ok_or_else ( || eyre :: eyre !( "validator {idx}: missing attester entry" ) ) ?;
535582 let prop_path = slots
536583 . proposal
537- . ok_or_else ( || format ! ( "validator {idx}: missing proposer entry" ) ) ?;
584+ . ok_or_else ( || eyre :: eyre !( "validator {idx}: missing proposer entry" ) ) ?;
538585
539586 info ! (
540587 %node_id,
@@ -565,20 +612,13 @@ fn read_validator_keys(
565612 Ok ( validator_keys)
566613}
567614
568- fn read_hex_file_bytes ( path : impl AsRef < Path > ) -> Vec < u8 > {
615+ fn read_hex_file_bytes ( path : impl AsRef < Path > ) -> eyre :: Result < Vec < u8 > > {
569616 let path = path. as_ref ( ) ;
570- let Ok ( file_content) = std:: fs:: read_to_string ( path)
571- . inspect_err ( |err| error ! ( file=%path. display( ) , %err, "Failed to read hex file" ) )
572- else {
573- std:: process:: exit ( 1 ) ;
574- } ;
617+ let file_content = std:: fs:: read_to_string ( path)
618+ . wrap_err_with ( || format ! ( "failed to read hex file from {}" , path. display( ) ) ) ?;
575619 let hex_string = file_content. trim ( ) . trim_start_matches ( "0x" ) ;
576- let Ok ( bytes) = hex:: decode ( hex_string)
577- . inspect_err ( |err| error ! ( file=%path. display( ) , %err, "Failed to decode hex file" ) )
578- else {
579- std:: process:: exit ( 1 ) ;
580- } ;
581- bytes
620+ hex:: decode ( hex_string)
621+ . wrap_err_with ( || format ! ( "failed to decode hex file from {}" , path. display( ) ) )
582622}
583623
584624/// Fetch the initial state for the node.
@@ -617,6 +657,30 @@ async fn fetch_initial_state(
617657 } ;
618658
619659 // Checkpoint sync path
660+
661+ // Prefer resuming from a fresh on-disk state to avoid re-downloading what we already have.
662+ if let Some ( store) = Store :: from_db_state ( backend. clone ( ) , genesis. genesis_time ) {
663+ let now_ms = SystemTime :: UNIX_EPOCH
664+ . elapsed ( )
665+ . expect ( "already past the unix epoch" )
666+ . as_millis ( ) as u64 ;
667+ let current_slot =
668+ now_ms. saturating_sub ( genesis. genesis_time * 1000 ) / MILLISECONDS_PER_SLOT ;
669+ let finalized_slot = store. latest_finalized ( ) . slot ;
670+ let gap = current_slot. saturating_sub ( finalized_slot) ;
671+ if gap <= MAX_RESUMABLE_DB_STATE_AGE {
672+ info ! (
673+ finalized_slot,
674+ current_slot, gap, "Resuming from existing DB state"
675+ ) ;
676+ return Ok ( store) ;
677+ }
678+ warn ! (
679+ finalized_slot,
680+ current_slot, gap, "Existing DB state is stale; falling through to checkpoint sync"
681+ ) ;
682+ }
683+
620684 info ! ( %checkpoint_url, "Starting checkpoint sync" ) ;
621685
622686 // The state and block are fetched in parallel; if the peer advances
0 commit comments