@@ -98,6 +98,24 @@ pub async fn validate_entries<N: NetworkZone>(
98
98
99
99
let mut hashes_stop_diff_last_height = last_height - hashes_stop_height;
100
100
101
+ // get the hashes we are missing to create the first fast-sync hash.
102
+ let BlockchainResponse :: BlockHashInRange ( starting_hashes) = blockchain_read_handle
103
+ . ready ( )
104
+ . await ?
105
+ . call ( BlockchainReadRequest :: BlockHashInRange (
106
+ hashes_start_height..start_height,
107
+ Chain :: Main ,
108
+ ) )
109
+ . await ?
110
+ else {
111
+ unreachable ! ( )
112
+ } ;
113
+
114
+ // If we don't have enough hashes to make up a batch we can't validate any.
115
+ if amount_of_hashes + starting_hashes. len ( ) < FAST_SYNC_BATCH_LEN {
116
+ return Ok ( ( VecDeque :: new ( ) , entries) ) ;
117
+ }
118
+
101
119
let mut unknown = VecDeque :: new ( ) ;
102
120
103
121
// start moving from the back of the batches taking enough hashes out so we are only left with hashes
@@ -125,23 +143,10 @@ pub async fn validate_entries<N: NetworkZone>(
125
143
unknown. push_front ( back) ;
126
144
}
127
145
128
- // get the hashes we are missing to create the first fast-sync hash.
129
- let BlockchainResponse :: BlockHashInRange ( hashes) = blockchain_read_handle
130
- . ready ( )
131
- . await ?
132
- . call ( BlockchainReadRequest :: BlockHashInRange (
133
- hashes_start_height..start_height,
134
- Chain :: Main ,
135
- ) )
136
- . await ?
137
- else {
138
- unreachable ! ( )
139
- } ;
140
-
141
146
// Start verifying the hashes.
142
147
let mut hasher = Hasher :: default ( ) ;
143
148
let mut last_i = 1 ;
144
- for ( i, hash) in hashes
149
+ for ( i, hash) in starting_hashes
145
150
. iter ( )
146
151
. chain ( entries. iter ( ) . flat_map ( |e| e. ids . iter ( ) ) )
147
152
. enumerate ( )
@@ -245,3 +250,148 @@ pub fn block_to_verified_block_information(
245
250
block,
246
251
}
247
252
}
253
+
254
+ #[ cfg( test) ]
255
+ mod tests {
256
+ use std:: { collections:: VecDeque , slice, sync:: LazyLock } ;
257
+
258
+ use proptest:: proptest;
259
+
260
+ use cuprate_p2p:: block_downloader:: ChainEntry ;
261
+ use cuprate_p2p_core:: { client:: InternalPeerID , handles:: HandleBuilder , ClearNet } ;
262
+
263
+ use crate :: {
264
+ fast_sync_stop_height, set_fast_sync_hashes, validate_entries, FAST_SYNC_BATCH_LEN ,
265
+ } ;
266
+
267
+ static HASHES : LazyLock < & [ [ u8 ; 32 ] ] > = LazyLock :: new ( || {
268
+ let hashes = ( 0 ..FAST_SYNC_BATCH_LEN * 2000 )
269
+ . map ( |i| {
270
+ let mut ret = [ 0 ; 32 ] ;
271
+ ret[ ..8 ] . copy_from_slice ( & i. to_le_bytes ( ) ) ;
272
+ ret
273
+ } )
274
+ . collect :: < Vec < _ > > ( ) ;
275
+
276
+ let hashes = hashes. leak ( ) ;
277
+
278
+ let fast_sync_hashes = hashes
279
+ . chunks ( FAST_SYNC_BATCH_LEN )
280
+ . map ( |chunk| {
281
+ let len = chunk. len ( ) * 32 ;
282
+ let bytes = chunk. as_ptr ( ) . cast :: < u8 > ( ) ;
283
+
284
+ // SAFETY:
285
+ // We are casting a valid [[u8; 32]] to a [u8], no alignment requirements and we are using it
286
+ // within the [[u8; 32]]'s lifetime.
287
+ unsafe { blake3:: hash ( slice:: from_raw_parts ( bytes, len) ) . into ( ) }
288
+ } )
289
+ . collect :: < Vec < _ > > ( ) ;
290
+
291
+ set_fast_sync_hashes ( fast_sync_hashes. leak ( ) ) ;
292
+
293
+ hashes
294
+ } ) ;
295
+
296
+ proptest ! {
297
+ #[ test]
298
+ fn valid_entry( len in 0_usize ..1_500_000 ) {
299
+ let mut ids = HASHES . to_vec( ) ;
300
+ ids. resize( len, [ 0_u8 ; 32 ] ) ;
301
+
302
+ let handle = HandleBuilder :: new( ) . build( ) ;
303
+
304
+ let entry = ChainEntry {
305
+ ids,
306
+ peer: InternalPeerID :: Unknown ( 1 ) ,
307
+ handle: handle. 1
308
+ } ;
309
+
310
+ let data_dir = tempfile:: tempdir( ) . unwrap( ) ;
311
+
312
+ tokio_test:: block_on( async move {
313
+ let blockchain_config = cuprate_blockchain:: config:: ConfigBuilder :: new( )
314
+ . data_directory( data_dir. path( ) . to_path_buf( ) )
315
+ . build( ) ;
316
+
317
+ let ( mut blockchain_read_handle, _, _) =
318
+ cuprate_blockchain:: service:: init( blockchain_config) . unwrap( ) ;
319
+
320
+
321
+ let ret = validate_entries:: <ClearNet >( VecDeque :: from( [ entry] ) , 0 , & mut blockchain_read_handle) . await . unwrap( ) ;
322
+
323
+ let len_left = ret. 0 . iter( ) . map( |e| e. ids. len( ) ) . sum:: <usize >( ) ;
324
+ let len_right = ret. 1 . iter( ) . map( |e| e. ids. len( ) ) . sum:: <usize >( ) ;
325
+
326
+ assert_eq!( len_left + len_right, len) ;
327
+ assert!( len_left <= fast_sync_stop_height( ) ) ;
328
+ assert!( len_right < FAST_SYNC_BATCH_LEN || len > fast_sync_stop_height( ) ) ;
329
+ } ) ;
330
+ }
331
+
332
+ #[ test]
333
+ fn single_hash_entries( len in 0_usize ..1_500_000 ) {
334
+ let handle = HandleBuilder :: new( ) . build( ) ;
335
+ let entries = ( 0 ..len) . map( |i| {
336
+ ChainEntry {
337
+ ids: vec![ HASHES . get( i) . copied( ) . unwrap_or_default( ) ] ,
338
+ peer: InternalPeerID :: Unknown ( 1 ) ,
339
+ handle: handle. 1 . clone( )
340
+ }
341
+ } ) . collect( ) ;
342
+
343
+ let data_dir = tempfile:: tempdir( ) . unwrap( ) ;
344
+
345
+ tokio_test:: block_on( async move {
346
+ let blockchain_config = cuprate_blockchain:: config:: ConfigBuilder :: new( )
347
+ . data_directory( data_dir. path( ) . to_path_buf( ) )
348
+ . build( ) ;
349
+
350
+ let ( mut blockchain_read_handle, _, _) =
351
+ cuprate_blockchain:: service:: init( blockchain_config) . unwrap( ) ;
352
+
353
+
354
+ let ret = validate_entries:: <ClearNet >( entries, 0 , & mut blockchain_read_handle) . await . unwrap( ) ;
355
+
356
+ let len_left = ret. 0 . iter( ) . map( |e| e. ids. len( ) ) . sum:: <usize >( ) ;
357
+ let len_right = ret. 1 . iter( ) . map( |e| e. ids. len( ) ) . sum:: <usize >( ) ;
358
+
359
+ assert_eq!( len_left + len_right, len) ;
360
+ assert!( len_left <= fast_sync_stop_height( ) ) ;
361
+ assert!( len_right < FAST_SYNC_BATCH_LEN || len > fast_sync_stop_height( ) ) ;
362
+ } ) ;
363
+ }
364
+
365
+ #[ test]
366
+ fn not_enough_hashes( len in 0_usize ..FAST_SYNC_BATCH_LEN ) {
367
+ let hashes_start_height = FAST_SYNC_BATCH_LEN * 1234 ;
368
+
369
+ let handle = HandleBuilder :: new( ) . build( ) ;
370
+ let entry = ChainEntry {
371
+ ids: HASHES [ hashes_start_height..( hashes_start_height + len) ] . to_vec( ) ,
372
+ peer: InternalPeerID :: Unknown ( 1 ) ,
373
+ handle: handle. 1
374
+ } ;
375
+
376
+ let data_dir = tempfile:: tempdir( ) . unwrap( ) ;
377
+
378
+ tokio_test:: block_on( async move {
379
+ let blockchain_config = cuprate_blockchain:: config:: ConfigBuilder :: new( )
380
+ . data_directory( data_dir. path( ) . to_path_buf( ) )
381
+ . build( ) ;
382
+
383
+ let ( mut blockchain_read_handle, _, _) =
384
+ cuprate_blockchain:: service:: init( blockchain_config) . unwrap( ) ;
385
+
386
+
387
+ let ret = validate_entries:: <ClearNet >( VecDeque :: from( [ entry] ) , 0 , & mut blockchain_read_handle) . await . unwrap( ) ;
388
+
389
+ let len_left = ret. 0 . iter( ) . map( |e| e. ids. len( ) ) . sum:: <usize >( ) ;
390
+ let len_right = ret. 1 . iter( ) . map( |e| e. ids. len( ) ) . sum:: <usize >( ) ;
391
+
392
+ assert_eq!( len_right, len) ;
393
+ assert_eq!( len_left, 0 ) ;
394
+ } ) ;
395
+ }
396
+ }
397
+ }
0 commit comments