11//! A node that downloads and validates the blockchain, but skips utreexo proofs as they aren't
22//! needed to validate the UTXO set with the SwiftSync method.
33
4- use std:: collections:: BTreeMap ;
54use std:: collections:: HashSet ;
65use std:: fs:: File ;
7- use std:: io:: Read ;
8- use std:: io:: Seek ;
9- use std:: io:: SeekFrom ;
106use std:: sync:: Arc ;
117use std:: time:: Duration ;
128use std:: time:: Instant ;
@@ -23,6 +19,7 @@ use floresta_chain::BlockValidationErrors;
2319use floresta_chain:: BlockchainError ;
2420use floresta_chain:: ThreadSafeChain ;
2521use floresta_common:: service_flags;
22+ use hintsfile:: Hintsfile ;
2623use rand:: rngs:: OsRng ;
2724use rand:: RngCore ;
2825use rustreexo:: accumulator:: stump:: Stump ;
@@ -67,6 +64,9 @@ pub struct SwiftSync {
6764 /// equal than the theoretical supply limit at that height.
6865 supply : Amount ,
6966
67+ /// The target height for the currently used SwiftSync hints.
68+ stop_height : u32 ,
69+
7070 /// Height at which SwiftSync was aborted, if any.
7171 ///
7272 /// We abort when either the hints are found to be invalid or the current chain is invalid (we
@@ -100,12 +100,12 @@ where
100100 Chain : ThreadSafeChain ,
101101 WireError : From < Chain :: Error > ,
102102{
103- /// Parses the SwiftSync hints file and returns the [`Hints `] struct .
104- fn parse_hints_file ( datadir : & str , network : Network ) -> Hints {
103+ /// Parses the SwiftSync hints file and returns an in-memory [`Hintsfile `] representation .
104+ fn parse_hints_file ( datadir : & str , network : Network ) -> Hintsfile {
105105 let path = format ! ( "{datadir}/{network}.hints" ) ;
106106
107- let hints_file = File :: open ( path) . expect ( "invalid hints file path" ) ;
108- Hints :: from_file ( hints_file )
107+ let mut file = File :: open ( path) . expect ( "invalid hints file path" ) ;
108+ Hintsfile :: from_reader ( & mut file ) . expect ( "couldn't read hints file" )
109109 }
110110
111111 /// Generates a random salt for this SwiftSync session.
@@ -127,7 +127,7 @@ where
127127
128128 /// Computes the next blocks to request, and sends a GETDATA request, advancing
129129 /// `last_block_request` up to the SwiftSync hints `stop_height`.
130- fn get_blocks_to_download ( & mut self , stop_height : u32 ) {
130+ fn get_blocks_to_download ( & mut self ) {
131131 // If this request would make our inflight queue too long, postpone it
132132 if !self . can_request_more_blocks ( ) || self . was_aborted ( ) {
133133 return ;
@@ -138,7 +138,8 @@ where
138138
139139 for _ in 0 ..SwiftSync :: BLOCKS_PER_GETDATA {
140140 let next_height = self . last_block_request + 1 ;
141- if next_height > stop_height {
141+
142+ if next_height > self . context . stop_height {
142143 // We need to reach it but not exceed it
143144 break ;
144145 }
@@ -162,7 +163,7 @@ where
162163 }
163164
164165 /// Starts SwiftSync processing for up to `MAX_PARALLEL_WORKERS` pending blocks.
165- fn pump_swiftsync ( & mut self , hints : & mut Hints ) -> Result < ( ) , WireError > {
166+ fn pump_swiftsync ( & mut self , hints : & mut Hintsfile ) -> Result < ( ) , WireError > {
166167 let processing = self
167168 . blocks
168169 . values ( )
@@ -202,7 +203,7 @@ where
202203 & mut self ,
203204 block_hash : BlockHash ,
204205 block_height : u32 ,
205- hints : & mut Hints ,
206+ hints : & mut Hintsfile ,
206207 ) -> Result < ( ) , WireError > {
207208 debug ! ( "processing block {block_hash}" ) ;
208209 let entry = self
@@ -213,7 +214,12 @@ where
213214 if entry. processing_since . is_some ( ) {
214215 return Ok ( ( ) ) ; // already being processed
215216 }
216- let unspent_indexes: HashSet < u64 > = hints. get_indexes ( block_height) . into_iter ( ) . collect ( ) ;
217+
218+ let Some ( block_hints) = hints. take_indices ( block_height) else {
219+ error ! ( "We tried processing block {block_height} but its hints are missing" ) ;
220+ return Ok ( ( ) ) ;
221+ } ;
222+ let unspent_indexes: HashSet < u32 > = block_hints. into_iter ( ) . collect ( ) ;
217223
218224 // Start the processing timer
219225 entry. processing_since = Some ( Instant :: now ( ) ) ;
@@ -260,10 +266,10 @@ where
260266 // Parse the hints file and randomly fill the SwiftSync salt for this session
261267 let mut hints = Self :: parse_hints_file ( & self . datadir , self . network ) ;
262268
263- // Generate the random salt
269+ self . context . stop_height = hints . stop_height ( ) ;
264270 self . context . salt = Self :: generate_salt ( ) ;
265271
266- info ! ( "Performing SwiftSync up to height {}" , hints. stop_height) ;
272+ info ! ( "Performing SwiftSync up to height {}" , hints. stop_height( ) ) ;
267273
268274 let mut ticker = time:: interval ( SwiftSync :: MAINTENANCE_TICK ) ;
269275 // If we fall behind, don't "catch up" by running maintenance repeatedly
@@ -308,7 +314,7 @@ where
308314 /// Returns `LoopControl::Break` if we need to break the main `SwiftSync` loop, which may
309315 /// happen if the kill signal was set, we successfully finished SwiftSync, or we need to abort
310316 /// operation due to a validation error.
311- async fn maintenance_tick ( & mut self , hints : & mut Hints ) -> LoopControl {
317+ async fn maintenance_tick ( & mut self , hints : & mut Hintsfile ) -> LoopControl {
312318 if * self . kill_signal . read ( ) . await {
313319 return LoopControl :: Break ;
314320 }
@@ -322,8 +328,10 @@ where
322328
323329 // If we have reached the SwiftSync stop height, we aren't waiting for inflight requested
324330 // blocks, and there's no in-memory block being processed, we have finished.
325- if self . last_block_request == hints. stop_height && self . unprocessed_blocks ( ) == 0 {
326- self . handle_stop_height_reached ( hints. stop_height ) ;
331+ let finished_requesting = self . last_block_request == self . context . stop_height ;
332+
333+ if finished_requesting && self . unprocessed_blocks ( ) == 0 {
334+ self . handle_stop_height_reached ( ) ;
327335 return LoopControl :: Break ;
328336 }
329337
@@ -355,15 +363,16 @@ where
355363
356364 try_and_log ! ( self . pump_swiftsync( hints) ) ;
357365
358- self . get_blocks_to_download ( hints . stop_height ) ;
366+ self . get_blocks_to_download ( ) ;
359367 LoopControl :: Continue
360368 }
361369
362370 /// Called when we process the last SwiftSync block. Verifies that the produced aggregator is
363371 /// zero and supply is correct. On success marks the chain assumed and exits IBD.
364372 ///
365373 /// If one of the two invariants fails, it sets the `abort_height` field.
366- fn handle_stop_height_reached ( & mut self , stop_height : u32 ) {
374+ fn handle_stop_height_reached ( & mut self ) {
375+ let stop_height = self . context . stop_height ;
367376 let final_agg = self . context . agg ;
368377 let final_supply = self . context . supply ;
369378
@@ -395,7 +404,7 @@ where
395404 async fn handle_message (
396405 & mut self ,
397406 msg : NodeNotification ,
398- hints : & mut Hints ,
407+ hints : & mut Hintsfile ,
399408 ) -> Result < ( ) , WireError > {
400409 match msg {
401410 NodeNotification :: FromUser ( request, responder) => {
@@ -446,7 +455,7 @@ where
446455 self . blocks . insert ( hash, inflight_block) ;
447456
448457 self . pump_swiftsync ( hints) ?;
449- self . get_blocks_to_download ( hints . stop_height ) ;
458+ self . get_blocks_to_download ( ) ;
450459 }
451460
452461 PeerMessages :: Ready ( version) => {
@@ -479,7 +488,7 @@ where
479488 result : WorkerResult ,
480489 block_hash : BlockHash ,
481490 height : u32 ,
482- hints : & mut Hints ,
491+ hints : & mut Hintsfile ,
483492 ) -> Result < ( ) , WireError > {
484493 // This block has already been processed: open space for a new worker
485494 let block = self
@@ -602,91 +611,3 @@ where
602611 }
603612 }
604613}
605-
606- #[ derive( Debug ) ]
607- pub struct Hints {
608- pub ( crate ) map : BTreeMap < u32 , u64 > ,
609- pub ( crate ) file : File ,
610- pub ( crate ) stop_height : u32 ,
611- }
612-
613- impl Hints {
614- // # Panics
615- //
616- // Panics when expected data is not present, or the hintfile overflows the maximum blockheight
617- pub fn from_file ( mut file : File ) -> Self {
618- let mut map = BTreeMap :: new ( ) ;
619- let mut magic = [ 0 ; 4 ] ;
620- file. read_exact ( & mut magic) . unwrap ( ) ;
621- assert_eq ! ( magic, [ 0x55 , 0x54 , 0x58 , 0x4f ] ) ;
622- let mut ver = [ 0 ; 1 ] ;
623- file. read_exact ( & mut ver) . unwrap ( ) ;
624- if u8:: from_le_bytes ( ver) != 0x00 {
625- core:: panic!( "Unsupported file version." ) ;
626- }
627- let mut stop_height = [ 0 ; 4 ] ;
628- file. read_exact ( & mut stop_height) . expect ( "empty file" ) ;
629- let stop_height = u32:: from_le_bytes ( stop_height) ;
630- for _ in 1 ..=stop_height {
631- let mut height = [ 0 ; 4 ] ;
632- file. read_exact ( & mut height)
633- . expect ( "expected kv pair does not exist." ) ;
634- let height = u32:: from_le_bytes ( height) ;
635- let mut file_pos = [ 0 ; 8 ] ;
636- file. read_exact ( & mut file_pos)
637- . expect ( "expected kv pair does not exist." ) ;
638- let file_pos = u64:: from_le_bytes ( file_pos) ;
639- map. insert ( height, file_pos) ;
640- }
641- Self {
642- map,
643- file,
644- stop_height,
645- }
646- }
647-
648- /// Get the stop height of the hint file.
649- pub fn stop_height ( & self ) -> u32 {
650- self . stop_height
651- }
652-
653- /// # Panics
654- ///
655- /// If there are no offset present at that height, aka an overflow, or the entry has already
656- /// been fetched.
657- pub fn get_indexes ( & mut self , height : u32 ) -> Vec < u64 > {
658- let file_pos = self
659- . map
660- . get ( & height)
661- . cloned ( )
662- . expect ( "block height overflow" ) ;
663-
664- // Move the file cursor to the correct byte offset
665- self . file
666- . seek ( SeekFrom :: Start ( file_pos) )
667- . expect ( "missing file position." ) ;
668-
669- // Read the next 4 bytes (little-endian) which store how many bits follow
670- let mut bits_arr = [ 0 ; 4 ] ;
671- self . file . read_exact ( & mut bits_arr) . unwrap ( ) ;
672- let num_bits = u32:: from_le_bytes ( bits_arr) ;
673-
674- let mut unspents = Vec :: new ( ) ;
675-
676- let mut curr_byte: u8 = 0 ;
677- for bit_pos in 0 ..num_bits {
678- let leftovers = bit_pos % 8 ;
679- if leftovers == 0 {
680- let mut single_byte_arr = [ 0 ; 1 ] ;
681- self . file . read_exact ( & mut single_byte_arr) . unwrap ( ) ;
682- curr_byte = u8:: from_le_bytes ( single_byte_arr) ;
683- }
684-
685- // Check current bit in curr_byte; if it's 1, push this txout index
686- if ( ( curr_byte >> leftovers) & 0x01 ) == 0x01 {
687- unspents. push ( bit_pos as u64 ) ;
688- }
689- }
690- unspents
691- }
692- }
0 commit comments