1616use std:: {
1717 collections:: { BTreeMap , BTreeSet , HashMap , HashSet } ,
1818 net:: SocketAddrV4 ,
19+ path:: PathBuf ,
1920 time:: Duration ,
2021} ;
2122
@@ -27,6 +28,7 @@ use monad_executor_glue::PeerEntry;
2728use monad_types:: { Epoch , NodeId , Round } ;
2829use rand:: { RngCore , seq:: IteratorRandom } ;
2930use rand_chacha:: ChaCha8Rng ;
31+ use serde:: { Deserialize , Serialize } ;
3032use tracing:: { debug, info, trace, warn} ;
3133
3234use crate :: {
@@ -87,13 +89,24 @@ pub enum PeerDiscoveryRole {
8789 FullNodeClient , // full node set as Client in secondary raptorcast
8890}
8991
90- #[ derive( Debug , Clone , PartialEq , Eq ) ]
92+ #[ derive( Debug , Clone , PartialEq , Eq , Deserialize , Serialize ) ]
93+ #[ serde( deny_unknown_fields) ]
94+ #[ serde( bound = "ST: CertificateSignatureRecoverable" ) ]
9195pub struct ConnectionInfo < ST : CertificateSignatureRecoverable > {
9296 pub last_ping : Ping < ST > ,
9397 pub unresponsive_pings : u32 ,
9498 pub name_record : MonadNameRecord < ST > ,
9599}
96100
101+ // Helper struct to deserialize both routing_info and pending_queue from TOML
102+ #[ derive( Debug , Clone , Deserialize , Serialize ) ]
103+ #[ serde( deny_unknown_fields) ]
104+ #[ serde( bound = "ST: CertificateSignatureRecoverable" ) ]
105+ struct PersistedPeers < ST : CertificateSignatureRecoverable > {
106+ routing_info : BTreeMap < NodeId < CertificateSignaturePubKey < ST > > , MonadNameRecord < ST > > ,
107+ pending_queue : BTreeMap < NodeId < CertificateSignaturePubKey < ST > > , ConnectionInfo < ST > > ,
108+ }
109+
97110#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
98111pub enum SecondaryRaptorcastConnectionStatus {
99112 Connected ,
@@ -165,6 +178,7 @@ pub struct PeerDiscovery<ST: CertificateSignatureRecoverable> {
165178 // secondary raptorcast setting: enable client mode when self is a full node
166179 pub enable_client : bool ,
167180 pub rng : ChaCha8Rng ,
181+ pub persisted_peers_path : PathBuf ,
168182}
169183
170184pub struct PeerDiscoveryBuilder < ST : CertificateSignatureRecoverable > {
@@ -186,6 +200,7 @@ pub struct PeerDiscoveryBuilder<ST: CertificateSignatureRecoverable> {
186200 pub enable_publisher : bool ,
187201 pub enable_client : bool ,
188202 pub rng : ChaCha8Rng ,
203+ pub persisted_peers_path : PathBuf ,
189204}
190205
191206impl < ST : CertificateSignatureRecoverable > PeerDiscoveryAlgoBuilder for PeerDiscoveryBuilder < ST > {
@@ -258,6 +273,7 @@ impl<ST: CertificateSignatureRecoverable> PeerDiscoveryAlgoBuilder for PeerDisco
258273 enable_publisher : self . enable_publisher ,
259274 enable_client : self . enable_client ,
260275 rng : self . rng ,
276+ persisted_peers_path : self . persisted_peers_path ,
261277 } ;
262278
263279 let mut cmds = Vec :: new ( ) ;
@@ -269,6 +285,60 @@ impl<ST: CertificateSignatureRecoverable> PeerDiscoveryAlgoBuilder for PeerDisco
269285 }
270286 } ) ;
271287
288+ // Load persisted peers from JSON file (admin-friendly).
289+ if let Ok ( toml_data) = std:: fs:: read_to_string ( & state. persisted_peers_path ) {
290+ match toml:: from_str :: < PersistedPeers < ST > > ( & toml_data) {
291+ Ok ( persisted) => {
292+ debug ! (
293+ path = ?state. persisted_peers_path,
294+ routing_count = ?persisted. routing_info. len( ) ,
295+ pending_count = ?persisted. pending_queue. len( ) ,
296+ "loaded persisted peers from disk"
297+ ) ;
298+
299+ for ( peer_id, name_record) in persisted. routing_info {
300+ match state. insert_peer_to_pending ( peer_id, name_record) {
301+ Ok ( cmds_from_insert) => {
302+ debug ! ( ?peer_id, "Persisted routing peer accepted" ) ;
303+ cmds. extend ( cmds_from_insert) ;
304+ }
305+ Err ( err) => {
306+ debug ! ( ?peer_id, ?err, "Persisted routing peer rejected" ) ;
307+ }
308+ }
309+ }
310+
311+ for ( peer_id, conn_info) in persisted. pending_queue {
312+ match validate_socket_ipv4_address (
313+ & conn_info. name_record . udp_address ( ) ,
314+ & state. self_record . udp_address ( ) ,
315+ ) {
316+ Ok ( _) => {
317+ state. pending_queue . insert ( peer_id, conn_info) ;
318+ debug ! ( ?peer_id, "Loaded persisted pending peer" ) ;
319+ }
320+ Err ( err) => {
321+ debug ! ( ?peer_id, ?err, "Persisted pending peer rejected" ) ;
322+ }
323+ }
324+ }
325+ state. metrics [ GAUGE_PEER_DISC_NUM_PENDING_PEERS ] =
326+ state. pending_queue . len ( ) as u64 ;
327+ }
328+ Err ( err) => {
329+ warn ! (
330+ path = ?state. persisted_peers_path,
331+ ?err,
332+ "failed to deserialize persisted peers as TOML"
333+ ) ;
334+ }
335+ }
336+ } else {
337+ debug ! ( path = ?state. persisted_peers_path, "no persisted peers file found, continuing" ) ;
338+ }
339+
340+ state. persist_peers ( ) ; // Early test before timer cycle
341+
272342 cmds. extend ( state. refresh ( ) ) ;
273343
274344 ( state, cmds)
@@ -1291,6 +1361,8 @@ where
12911361 self . metrics [ GAUGE_PEER_DISC_NUM_PEERS ] = self . routing_info . len ( ) as u64 ;
12921362 self . metrics [ GAUGE_PEER_DISC_NUM_PENDING_PEERS ] = self . pending_queue . len ( ) as u64 ;
12931363
1364+ self . persist_peers ( ) ;
1365+
12941366 // reset timer to schedule for the next refresh
12951367 cmds. extend ( self . reset_refresh_timer ( ) ) ;
12961368
@@ -1302,6 +1374,27 @@ where
13021374 cmds
13031375 }
13041376
1377+ fn persist_peers ( & self ) {
1378+ // Persist both routing_info and pending_queue as PersistedPeers (TOML)
1379+ let persisted = PersistedPeers {
1380+ routing_info : self . routing_info . clone ( ) ,
1381+ pending_queue : self . pending_queue . clone ( ) ,
1382+ } ;
1383+ match toml:: to_string ( & persisted) {
1384+ Ok ( serialized) => {
1385+ if let Err ( e) = std:: fs:: write ( & self . persisted_peers_path , serialized) {
1386+ warn ! ( path = ?self . persisted_peers_path, ?e, "failed to persist peers" ) ;
1387+ } else {
1388+ debug ! ( path = ?self . persisted_peers_path, routing_len =? self . routing_info. len( ) , pending_len =? self . pending_queue. len( ) ,
1389+ "persisted peers to disk" ) ;
1390+ }
1391+ }
1392+ Err ( err) => {
1393+ warn ! ( path = ?self . persisted_peers_path, ?err, "failed to serialize peers" ) ;
1394+ }
1395+ }
1396+ }
1397+
13051398 fn update_current_round (
13061399 & mut self ,
13071400 round : Round ,
@@ -1625,6 +1718,7 @@ mod tests {
16251718 enable_publisher : false ,
16261719 enable_client : false ,
16271720 rng : ChaCha8Rng :: seed_from_u64 ( 123456 ) ,
1721+ persisted_peers_path : Default :: default ( ) ,
16281722 }
16291723 }
16301724
0 commit comments