@@ -188,18 +188,6 @@ fd_forest_verify( fd_forest_t const * forest ) {
188
188
return 0 ;
189
189
}
190
190
191
- /* query queries for a connected ele keyed by slot. does not return
192
- orphaned ele. */
193
-
194
- static fd_forest_ele_t *
195
- ancestry_frontier_query ( fd_forest_t * forest , ulong slot ) {
196
- fd_forest_ele_t * pool = fd_forest_pool ( forest );
197
- fd_forest_ele_t * ele = NULL ;
198
- ele = fd_forest_ancestry_ele_query ( fd_forest_ancestry ( forest ), & slot , NULL , pool );
199
- ele = fd_ptr_if ( !ele , fd_forest_frontier_ele_query ( fd_forest_frontier ( forest ), & slot , NULL , pool ), ele );
200
- return ele ;
201
- }
202
-
203
191
/* remove removes and returns a connected ele from ancestry or frontier
204
192
maps. does not remove orphaned ele. does not unlink ele. */
205
193
@@ -382,9 +370,6 @@ insert( fd_forest_t * forest, ulong slot, ushort parent_off ) {
382
370
383
371
fd_forest_ele_t *
384
372
fd_forest_query ( fd_forest_t * forest , ulong slot ) {
385
- # if FD_FOREST_USE_HANDHOLDING
386
- FD_TEST ( slot > fd_forest_root_slot ( forest ) ); /* caller error - inval */
387
- # endif
388
373
return query ( forest , slot );
389
374
}
390
375
@@ -433,22 +418,39 @@ fd_forest_publish( fd_forest_t * forest, ulong new_root_slot ) {
433
418
434
419
fd_forest_ancestry_t * ancestry = fd_forest_ancestry ( forest );
435
420
fd_forest_frontier_t * frontier = fd_forest_frontier ( forest );
421
+ fd_forest_orphaned_t * orphaned = fd_forest_orphaned ( forest );
436
422
fd_forest_ele_t * pool = fd_forest_pool ( forest );
437
423
ulong null = fd_forest_pool_idx_null ( pool );
438
424
439
425
fd_forest_ele_t * old_root_ele = fd_forest_pool_ele ( pool , forest -> root );
440
- fd_forest_ele_t * new_root_ele = ancestry_frontier_query ( forest , new_root_slot );
426
+ fd_forest_ele_t * new_root_ele = query ( forest , new_root_slot );
441
427
442
- # if FD_FOREST_USE_HANDHOLDING
443
- FD_TEST ( new_root_ele ); /* caller error - not found */
444
- FD_TEST ( new_root_ele -> slot > old_root_ele -> slot ); /* caller error - inval */
445
- # endif
428
+ #if FD_FOREST_USE_HANDHOLDING
429
+ if ( FD_LIKELY ( new_root_ele ) ) {
430
+ FD_TEST ( new_root_ele -> slot > old_root_ele -> slot ); /* caller error - inval */
431
+ }
432
+ #endif
433
+
434
+ /* Edge case where if we haven't been getting repairs, and we have a
435
+ gap between the root and orphans. we publish forward to a slot that
436
+ we don't have. This only case this should be happening is when we
437
+ load a second incremental and that incremental slot lives in the
438
+ gap. In that case this isn't a bug, but we should be treating this
439
+ new root like the snapshot slot / init root. Should be happening
440
+ very rarely given a well-functioning repair. */
441
+
442
+ if ( FD_UNLIKELY ( !new_root_ele ) ) {
443
+ new_root_ele = acquire ( forest , new_root_slot );
444
+ new_root_ele -> complete_idx = 0 ;
445
+ new_root_ele -> buffered_idx = 0 ;
446
+ fd_forest_frontier_ele_insert ( frontier , new_root_ele , pool );
447
+ }
446
448
447
449
/* First, remove the previous root, and add it to a FIFO prune queue.
448
450
head points to the queue head (initialized with old_root_ele). */
449
451
450
452
fd_forest_ele_t * head = ancestry_frontier_remove ( forest , old_root_ele -> slot );
451
- head -> next = null ;
453
+ head -> prev = null ;
452
454
fd_forest_ele_t * tail = head ;
453
455
454
456
/* Second, BFS down the tree, inserting each ele into the prune queue
@@ -461,22 +463,80 @@ fd_forest_publish( fd_forest_t * forest, ulong new_root_slot ) {
461
463
if ( FD_LIKELY ( child != new_root_ele ) ) { /* do not prune new root or descendants */
462
464
ulong idx = fd_forest_ancestry_idx_remove ( ancestry , & child -> slot , null , pool );
463
465
idx = fd_ulong_if ( idx != null , idx , fd_forest_frontier_idx_remove ( frontier , & child -> slot , null , pool ) );
464
- tail -> next = idx ; /* insert prune queue */
466
+ tail -> prev = idx ; /* insert prune queue */
465
467
# if FD_FOREST_USE_HANDHOLDING
466
- FD_TEST ( tail -> next != null ); /* programming error in BFS */
468
+ FD_TEST ( tail -> prev != null ); /* programming error in BFS */
467
469
# endif
468
- tail = fd_forest_pool_ele ( pool , tail -> next ); /* advance prune queue */
469
- tail -> next = null ;
470
+ tail = fd_forest_pool_ele ( pool , tail -> prev ); /* advance prune queue */
471
+ tail -> prev = null ;
470
472
}
471
473
child = fd_forest_pool_ele ( pool , child -> sibling );
472
474
}
473
- fd_forest_ele_t * next = fd_forest_pool_ele ( pool , head -> next ); /* FIFO pop */
475
+ fd_forest_ele_t * next = fd_forest_pool_ele ( pool , head -> prev ); /* FIFO pop */
474
476
fd_forest_pool_ele_release ( pool , head ); /* free head */
475
477
head = next ;
476
478
}
477
479
480
+ /* If there is nothing on the frontier, we have hit an edge case
481
+ during catching up where all of our frontiers were < the new root.
482
+ In that case we need to continue repairing from the new root, so
483
+ add it to the frontier. */
484
+
485
+ if ( FD_UNLIKELY ( fd_forest_frontier_iter_done ( fd_forest_frontier_iter_init ( frontier , pool ), frontier , pool ) ) ) {
486
+ fd_forest_ele_t * remove = fd_forest_ancestry_ele_remove ( ancestry , & new_root_ele -> slot , NULL , pool );
487
+ if ( FD_UNLIKELY ( !remove ) ) {
488
+ /* Very rare case where during second incremental load we could publish to an orphaned slot */
489
+ remove = fd_forest_orphaned_ele_remove ( orphaned , & new_root_ele -> slot , NULL , pool );
490
+ }
491
+ FD_TEST ( remove == new_root_ele );
492
+ fd_forest_frontier_ele_insert ( frontier , new_root_ele , pool );
493
+ new_root_ele -> complete_idx = 0 ;
494
+ new_root_ele -> buffered_idx = 0 ;
495
+ advance_frontier ( forest , new_root_ele -> slot , 0 );
496
+ }
497
+
478
498
new_root_ele -> parent = null ; /* unlink new root from parent */
479
- forest -> root = fd_forest_ancestry_idx_query ( ancestry , & new_root_slot , null , pool );
499
+ forest -> root = fd_forest_pool_idx ( pool , new_root_ele );
500
+
501
+ /* Lastly, cleanup orphans if there orphan heads < new_root_slot.
502
+ First, add any relevant orphans to the prune queue. */
503
+
504
+ head = NULL ;
505
+ for ( fd_forest_orphaned_iter_t iter = fd_forest_orphaned_iter_init ( orphaned , pool );
506
+ !fd_forest_orphaned_iter_done ( iter , orphaned , pool );
507
+ iter = fd_forest_orphaned_iter_next ( iter , orphaned , pool ) ) {
508
+ fd_forest_ele_t * ele = fd_forest_orphaned_iter_ele ( iter , orphaned , pool );
509
+ if ( FD_UNLIKELY ( ele -> slot < new_root_slot ) ) {
510
+ if ( FD_UNLIKELY ( !head ) ) {
511
+ head = ele ;
512
+ head -> prev = null ;
513
+ tail = ele ;
514
+ } else {
515
+ tail -> prev = iter .ele_idx ;
516
+ tail = fd_forest_pool_ele ( pool , tail -> prev );
517
+ tail -> prev = null ;
518
+ }
519
+ }
520
+ }
521
+
522
+ /* Now BFS and clean up children of these orphan heads */
523
+ while ( head ) {
524
+ fd_forest_ele_t * child = fd_forest_pool_ele ( pool , head -> child );
525
+ while ( FD_LIKELY ( child ) ) {
526
+ if ( FD_LIKELY ( child != new_root_ele ) ) {
527
+ tail -> prev = fd_forest_pool_idx ( pool , child ); /* insert prune queue */
528
+ tail = fd_forest_pool_ele ( pool , tail -> prev ); /* advance prune queue */
529
+ tail -> prev = null ;
530
+ }
531
+ child = fd_forest_pool_ele ( pool , child -> sibling );
532
+ }
533
+ ulong remove = fd_forest_orphaned_idx_remove ( orphaned , & head -> slot , null , pool ); /* remove myself */
534
+ remove = fd_ulong_if ( remove == null , fd_forest_ancestry_idx_remove ( ancestry , & head -> slot , null , pool ), remove );
535
+
536
+ fd_forest_ele_t * next = fd_forest_pool_ele ( pool , head -> prev ); /* FIFO pop */
537
+ fd_forest_pool_ele_release ( pool , head ); /* free head */
538
+ head = next ;
539
+ }
480
540
return new_root_ele ;
481
541
}
482
542
0 commit comments