@@ -445,6 +445,206 @@ test_staked_by_vote( void ) {
445
445
fd_stake_ci_delete ( fd_stake_ci_leave ( info ) );
446
446
}
447
447
448
+ static void
449
+ test_dest_update ( void ) {
450
+ fd_stake_ci_t * info = fd_stake_ci_join ( fd_stake_ci_new ( _info , identity_key ) );
451
+
452
+ /* Set up initial state with only staked nodes */
453
+ fd_stake_ci_stake_msg_init ( info , generate_stake_msg ( stake_msg , 0UL , "ABC" ) );
454
+ fd_stake_ci_stake_msg_fini ( info );
455
+ check_destinations ( info , 0UL , "ABC" , "I" );
456
+
457
+ /* Test updating existing staked node */
458
+ fd_pubkey_t pubkey_a ;
459
+ memset ( pubkey_a .uc , 'A' , sizeof (fd_pubkey_t ) );
460
+ fd_stake_ci_dest_update ( info , & pubkey_a , 0x12345678U , 8080 );
461
+
462
+ fd_shred_dest_t * sdest = fd_stake_ci_get_sdest_for_slot ( info , 0UL );
463
+ fd_shred_dest_idx_t idx_a = fd_shred_dest_pubkey_to_idx ( sdest , & pubkey_a );
464
+ FD_TEST ( idx_a != FD_SHRED_DEST_NO_DEST );
465
+ fd_shred_dest_weighted_t * dest_a = fd_shred_dest_idx_to_dest ( sdest , idx_a );
466
+ FD_TEST ( dest_a -> ip4 == 0x12345678U );
467
+ FD_TEST ( dest_a -> port == 8080 );
468
+ FD_TEST ( dest_a -> stake_lamports > 0UL ); /* Should still be staked */
469
+
470
+ /* Test adding new unstaked node via update */
471
+ fd_pubkey_t pubkey_d ;
472
+ memset ( pubkey_d .uc , 'D' , sizeof (fd_pubkey_t ) );
473
+ fd_stake_ci_dest_update ( info , & pubkey_d , 0x87654321U , 9090 );
474
+
475
+ /* D should now be in the unstaked list */
476
+ check_destinations ( info , 0UL , "ABC" , "DI" );
477
+
478
+ fd_shred_dest_idx_t idx_d = fd_shred_dest_pubkey_to_idx ( sdest , & pubkey_d );
479
+ FD_TEST ( idx_d != FD_SHRED_DEST_NO_DEST );
480
+ fd_shred_dest_weighted_t * dest_d = fd_shred_dest_idx_to_dest ( sdest , idx_d );
481
+ FD_TEST ( dest_d -> ip4 == 0x87654321U );
482
+ FD_TEST ( dest_d -> port == 9090 );
483
+ FD_TEST ( dest_d -> stake_lamports == 0UL ); /* Should be unstaked */
484
+
485
+ /* Test adding multiple new unstaked nodes via update */
486
+ fd_pubkey_t pubkey_e , pubkey_f ;
487
+ memset ( pubkey_e .uc , 'E' , sizeof (fd_pubkey_t ) );
488
+ memset ( pubkey_f .uc , 'F' , sizeof (fd_pubkey_t ) );
489
+
490
+ fd_stake_ci_dest_update ( info , & pubkey_e , 0x11111111U , 1111 );
491
+ fd_stake_ci_dest_update ( info , & pubkey_f , 0x22222222U , 2222 );
492
+
493
+ /* Check that E and F were added as unstaked */
494
+ check_destinations ( info , 0UL , "ABC" , "DEFI" );
495
+
496
+ /* Test updating an unstaked node's contact info */
497
+ fd_stake_ci_dest_update ( info , & pubkey_d , 0x99999999U , 9999 );
498
+
499
+ idx_d = fd_shred_dest_pubkey_to_idx ( sdest , & pubkey_d );
500
+ dest_d = fd_shred_dest_idx_to_dest ( sdest , idx_d );
501
+ FD_TEST ( dest_d -> ip4 == 0x99999999U );
502
+ FD_TEST ( dest_d -> port == 9999 );
503
+ FD_TEST ( dest_d -> stake_lamports == 0UL ); /* Should still be unstaked */
504
+
505
+ /* Test that updates apply to both epochs */
506
+ fd_stake_ci_stake_msg_init ( info , generate_stake_msg ( stake_msg , 1UL , "AB" ) );
507
+ fd_stake_ci_stake_msg_fini ( info );
508
+
509
+ /* Update should affect both epoch 0 and epoch 1 */
510
+ fd_pubkey_t pubkey_b ;
511
+ memset ( pubkey_b .uc , 'B' , sizeof (fd_pubkey_t ) );
512
+ fd_stake_ci_dest_update ( info , & pubkey_b , 0x11223344U , 5555 );
513
+
514
+ /* Check epoch 0 */
515
+ fd_shred_dest_t * sdest0 = fd_stake_ci_get_sdest_for_slot ( info , 0UL );
516
+ fd_shred_dest_idx_t idx_b0 = fd_shred_dest_pubkey_to_idx ( sdest0 , & pubkey_b );
517
+ fd_shred_dest_weighted_t * dest_b0 = fd_shred_dest_idx_to_dest ( sdest0 , idx_b0 );
518
+ FD_TEST ( dest_b0 -> ip4 == 0x11223344U );
519
+ FD_TEST ( dest_b0 -> port == 5555 );
520
+
521
+ /* Check epoch 1 */
522
+ fd_shred_dest_t * sdest1 = fd_stake_ci_get_sdest_for_slot ( info , 1000UL );
523
+ fd_shred_dest_idx_t idx_b1 = fd_shred_dest_pubkey_to_idx ( sdest1 , & pubkey_b );
524
+ fd_shred_dest_weighted_t * dest_b1 = fd_shred_dest_idx_to_dest ( sdest1 , idx_b1 );
525
+ FD_TEST ( dest_b1 -> ip4 == 0x11223344U );
526
+ FD_TEST ( dest_b1 -> port == 5555 );
527
+
528
+ /* Test adding a new node that gets added to both epochs */
529
+ fd_pubkey_t pubkey_z ;
530
+ memset ( pubkey_z .uc , 'Z' , sizeof (fd_pubkey_t ) );
531
+ fd_stake_ci_dest_update ( info , & pubkey_z , 0xAABBCCDDU , 7777 );
532
+
533
+ /* Z should be unstaked in both epochs */
534
+ check_destinations ( info , 0UL , "ABC" , "DEFIZ" );
535
+ /* C moved from staked to unstaked in epoch 1,
536
+ and with no contact info entry it should not
537
+ appear in the unstaked list. */
538
+ check_destinations ( info , 1UL , "AB" , "DEFIZ" );
539
+
540
+ fd_pubkey_t pubkey_c ;
541
+ memset ( pubkey_c .uc , 'C' , sizeof (fd_pubkey_t ) );
542
+ fd_stake_ci_dest_update ( info , & pubkey_c , 0xCCCCCCCCU , 8888 );
543
+
544
+ /* C should now be unstaked and present in epoch 1 */
545
+ check_destinations ( info , 1UL , "AB" , "CDEFIZ" );
546
+
547
+
548
+ fd_stake_ci_delete ( fd_stake_ci_leave ( info ) );
549
+ }
550
+
551
+ static void
552
+ test_dest_remove ( void ) {
553
+ fd_stake_ci_t * info = fd_stake_ci_join ( fd_stake_ci_new ( _info , identity_key ) );
554
+
555
+ /* Set up initial state with some staked and unstaked nodes */
556
+ fd_stake_ci_stake_msg_init ( info , generate_stake_msg ( stake_msg , 0UL , "ABC" ) );
557
+ fd_stake_ci_stake_msg_fini ( info );
558
+
559
+ /* Build up destination list using only update operations */
560
+ fd_pubkey_t pubkey_a , pubkey_b , pubkey_c , pubkey_d , pubkey_e , pubkey_f ;
561
+ memset ( pubkey_a .uc , 'A' , sizeof (fd_pubkey_t ) );
562
+ memset ( pubkey_b .uc , 'B' , sizeof (fd_pubkey_t ) );
563
+ memset ( pubkey_c .uc , 'C' , sizeof (fd_pubkey_t ) );
564
+ memset ( pubkey_d .uc , 'D' , sizeof (fd_pubkey_t ) );
565
+ memset ( pubkey_e .uc , 'E' , sizeof (fd_pubkey_t ) );
566
+ memset ( pubkey_f .uc , 'F' , sizeof (fd_pubkey_t ) );
567
+
568
+ /* Update staked nodes A, B, C with contact info */
569
+ fd_stake_ci_dest_update ( info , & pubkey_a , 0x11111111U , 1111 );
570
+ fd_stake_ci_dest_update ( info , & pubkey_b , 0x22222222U , 2222 );
571
+ fd_stake_ci_dest_update ( info , & pubkey_c , 0x33333333U , 3333 );
572
+
573
+ /* Add unstaked nodes D, E, F via update */
574
+ fd_stake_ci_dest_update ( info , & pubkey_d , 0x44444444U , 4444 );
575
+ fd_stake_ci_dest_update ( info , & pubkey_e , 0x55555555U , 5555 );
576
+ fd_stake_ci_dest_update ( info , & pubkey_f , 0x66666666U , 6666 );
577
+
578
+
579
+ check_destinations ( info , 0UL , "ABC" , "DEFI" );
580
+
581
+ /* Test removing unstaked node */
582
+ memset ( pubkey_d .uc , 'D' , sizeof (fd_pubkey_t ) );
583
+ fd_stake_ci_dest_remove ( info , & pubkey_d );
584
+
585
+ /* D should be removed from unstaked list */
586
+ check_destinations ( info , 0UL , "ABC" , "EFI" );
587
+
588
+ fd_shred_dest_t * sdest = fd_stake_ci_get_sdest_for_slot ( info , 0UL );
589
+ fd_shred_dest_idx_t idx_d = fd_shred_dest_pubkey_to_idx ( sdest , & pubkey_d );
590
+ FD_TEST ( idx_d == FD_SHRED_DEST_NO_DEST ); /* Should not be found */
591
+
592
+ /* Test removing staked node (should NOT actually remove it, just clear contact info) */
593
+ memset ( pubkey_a .uc , 'A' , sizeof (fd_pubkey_t ) );
594
+
595
+ /* First update A with some contact info */
596
+ fd_stake_ci_dest_update ( info , & pubkey_a , 0x12345678U , 8080 );
597
+ fd_shred_dest_idx_t idx_a = fd_shred_dest_pubkey_to_idx ( sdest , & pubkey_a );
598
+ fd_shred_dest_weighted_t * dest_a = fd_shred_dest_idx_to_dest ( sdest , idx_a );
599
+ FD_TEST ( dest_a -> ip4 == 0x12345678U );
600
+ FD_TEST ( dest_a -> port == 8080 );
601
+
602
+ /* Now try to remove A - it should stay because it's staked */
603
+ fd_stake_ci_dest_remove ( info , & pubkey_a );
604
+
605
+ /* A should still be in staked list */
606
+ check_destinations ( info , 0UL , "ABC" , "EFI" );
607
+ idx_a = fd_shred_dest_pubkey_to_idx ( sdest , & pubkey_a );
608
+ FD_TEST ( idx_a != FD_SHRED_DEST_NO_DEST ); /* Should still be found */
609
+ dest_a = fd_shred_dest_idx_to_dest ( sdest , idx_a );
610
+ FD_TEST ( dest_a -> stake_lamports > 0UL ); /* Should still be staked */
611
+
612
+ /* Test removing non-existing node (should be no-op) */
613
+ fd_pubkey_t pubkey_z ;
614
+ memset ( pubkey_z .uc , 'Z' , sizeof (fd_pubkey_t ) );
615
+ fd_stake_ci_dest_remove ( info , & pubkey_z );
616
+
617
+ /* Nothing should change */
618
+ check_destinations ( info , 0UL , "ABC" , "EFI" );
619
+
620
+ /* Test that removes apply to both epochs */
621
+ fd_stake_ci_stake_msg_init ( info , generate_stake_msg ( stake_msg , 1UL , "AB" ) );
622
+ fd_stake_ci_stake_msg_fini ( info );
623
+
624
+ /* E should be unstaked in both epochs, remove it */
625
+ memset ( pubkey_e .uc , 'E' , sizeof (fd_pubkey_t ) );
626
+ fd_stake_ci_dest_remove ( info , & pubkey_e );
627
+
628
+ /* Check both epochs - E should be gone from both */
629
+ check_destinations ( info , 0UL , "ABC" , "FI" );
630
+ check_destinations ( info , 1UL , "AB" , "CFI" );
631
+
632
+ /* Verify E is not found in either epoch */
633
+ fd_shred_dest_t * sdest0 = fd_stake_ci_get_sdest_for_slot ( info , 0UL );
634
+ fd_shred_dest_t * sdest1 = fd_stake_ci_get_sdest_for_slot ( info , 1000UL );
635
+ FD_TEST ( fd_shred_dest_pubkey_to_idx ( sdest0 , & pubkey_e ) == FD_SHRED_DEST_NO_DEST );
636
+ FD_TEST ( fd_shred_dest_pubkey_to_idx ( sdest1 , & pubkey_e ) == FD_SHRED_DEST_NO_DEST );
637
+
638
+ /* Test removing multiple unstaked nodes */
639
+ memset ( pubkey_f .uc , 'F' , sizeof (fd_pubkey_t ) );
640
+ fd_stake_ci_dest_remove ( info , & pubkey_f );
641
+
642
+ check_destinations ( info , 0UL , "ABC" , "I" );
643
+ check_destinations ( info , 1UL , "AB" , "CI" );
644
+
645
+ fd_stake_ci_delete ( fd_stake_ci_leave ( info ) );
646
+ }
647
+
448
648
int
449
649
main ( int argc ,
450
650
char * * argv ) {
@@ -473,6 +673,9 @@ main( int argc,
473
673
test_set_identity ();
474
674
test_staked_by_vote ();
475
675
676
+ test_dest_update ();
677
+ test_dest_remove ();
678
+
476
679
FD_LOG_NOTICE (( "pass" ));
477
680
fd_halt ();
478
681
return 0 ;
0 commit comments