@@ -126,8 +126,11 @@ impl ValidatorRegistrations {
126126 }
127127 }
128128
129- /// Updates the `epoch_validator_custody_requirements` map by pruning all values on/after `effective_epoch`
130- /// and updating the map to store the latest validator custody requirements for the `effective_epoch`.
129+ /// Updates the `epoch -> cgc` map after backfill has been completed for
130+ /// the specified epoch.
131+ ///
132+ /// This is done by pruning all values on/after `effective_epoch` and updating the map to store
133+ /// the latest validator custody requirements for the `effective_epoch`.
131134 pub fn backfill_validator_custody_requirements ( & mut self , effective_epoch : Epoch ) {
132135 if let Some ( latest_validator_custody) = self . latest_validator_custody_requirement ( ) {
133136 // Delete records if
@@ -261,6 +264,11 @@ impl<E: EthSpec> CustodyContext<E> {
261264 if let Some ( cgc_from_cli) = cgc_override
262265 && cgc_from_cli > ssz_context. validator_custody_at_head
263266 {
267+ /*
268+ TODO:
269+ * when increased, add (next_epoch, 64) into the registration map AND update data column custody info with the next epoch to trigger backfill
270+ * when reduced, log a warning, and preserve existing value?
271+ */
264272 warn ! (
265273 info = "node will continue to run with the current custody count" ,
266274 current_custody_count = ssz_context. validator_custody_at_head,
@@ -457,6 +465,8 @@ impl<E: EthSpec> CustodyContext<E> {
457465 & all_columns_ordered[ ..custody_group_count]
458466 }
459467
468+ /// The node has completed backfill for this epoch. Update the internal records so the function
469+ /// [`Self::custody_columns_for_epoch()`] returns up-to-date results.
460470 pub fn update_and_backfill_custody_count_at_epoch ( & self , effective_epoch : Epoch ) {
461471 self . validator_registrations
462472 . write ( )
@@ -509,6 +519,42 @@ mod tests {
509519
510520 type E = MainnetEthSpec ;
511521
522+ fn setup_custody_context (
523+ spec : & ChainSpec ,
524+ epoch_and_cgc_tuples : Vec < ( Epoch , u64 ) > ,
525+ ) -> CustodyContext < E > {
526+ let cgc_at_head = epoch_and_cgc_tuples. last ( ) . unwrap ( ) . 1 ;
527+ let ssz_context = CustodyContextSsz {
528+ validator_custody_at_head : cgc_at_head,
529+ persisted_is_supernode : false ,
530+ epoch_validator_custody_requirements : epoch_and_cgc_tuples,
531+ } ;
532+
533+ let custody_context = CustodyContext :: < E > :: new_from_persisted_custody_context (
534+ ssz_context,
535+ NodeCustodyType :: Fullnode ,
536+ spec,
537+ ) ;
538+
539+ let all_custody_groups_ordered = ( 0 ..spec. number_of_custody_groups ) . collect :: < Vec < _ > > ( ) ;
540+ custody_context
541+ . init_ordered_data_columns_from_custody_groups ( all_custody_groups_ordered, spec)
542+ . expect ( "should initialise ordered data columns" ) ;
543+ custody_context
544+ }
545+
546+ fn complete_backfill_for_epochs (
547+ custody_context : & CustodyContext < E > ,
548+ start_epoch : Epoch ,
549+ end_epoch : Epoch ,
550+ ) {
551+ assert ! ( start_epoch >= end_epoch) ;
552+ // Call from end_epoch down to start_epoch (inclusive), simulating backfill
553+ for epoch in ( end_epoch. as_u64 ( ) ..=start_epoch. as_u64 ( ) ) . rev ( ) {
554+ custody_context. update_and_backfill_custody_count_at_epoch ( Epoch :: new ( epoch) ) ;
555+ }
556+ }
557+
512558 #[ test]
513559 fn no_validators_supernode_default ( ) {
514560 let spec = E :: default_spec ( ) ;
@@ -1033,4 +1079,75 @@ mod tests {
10331079 "sampling at epoch 25 should match final cgc"
10341080 ) ;
10351081 }
1082+
1083+ #[ test]
1084+ fn backfill_single_cgc_increase_updates_past_epochs ( ) {
1085+ let spec = E :: default_spec ( ) ;
1086+ let final_cgc = 32u64 ;
1087+ let default_cgc = spec. custody_requirement ;
1088+
1089+ // Setup: Node restart after validators were registered, causing CGC increase to 32 at epoch 20
1090+ let epoch_and_cgc_tuples = vec ! [ ( Epoch :: new( 20 ) , final_cgc) ] ;
1091+ let custody_context = setup_custody_context ( & spec, epoch_and_cgc_tuples) ;
1092+ assert_eq ! (
1093+ custody_context. custody_group_count_at_epoch( Epoch :: new( 15 ) , & spec) ,
1094+ default_cgc,
1095+ ) ;
1096+
1097+ // Backfill from epoch 20 down to 15 (simulating backfill)
1098+ complete_backfill_for_epochs ( & custody_context, Epoch :: new ( 20 ) , Epoch :: new ( 15 ) ) ;
1099+
1100+ // After backfilling to epoch 15, it should use latest CGC (32)
1101+ assert_eq ! (
1102+ custody_context. custody_group_count_at_epoch( Epoch :: new( 15 ) , & spec) ,
1103+ final_cgc,
1104+ ) ;
1105+ assert_eq ! (
1106+ custody_context
1107+ . custody_columns_for_epoch( Some ( Epoch :: new( 15 ) ) , & spec)
1108+ . len( ) ,
1109+ final_cgc as usize ,
1110+ ) ;
1111+
1112+ // Prior epoch should still return the original CGC
1113+ assert_eq ! (
1114+ custody_context. custody_group_count_at_epoch( Epoch :: new( 14 ) , & spec) ,
1115+ default_cgc,
1116+ ) ;
1117+ }
1118+
1119+ #[ test]
1120+ fn backfill_with_multiple_cgc_increases_prunes_map_correctly ( ) {
1121+ let spec = E :: default_spec ( ) ;
1122+ let initial_cgc = 8u64 ;
1123+ let mid_cgc = 16u64 ;
1124+ let final_cgc = 32u64 ;
1125+
1126+ // Setup: Node restart after multiple validator registrations causing CGC increases
1127+ let epoch_and_cgc_tuples = vec ! [
1128+ ( Epoch :: new( 0 ) , initial_cgc) ,
1129+ ( Epoch :: new( 10 ) , mid_cgc) ,
1130+ ( Epoch :: new( 20 ) , final_cgc) ,
1131+ ] ;
1132+ let custody_context = setup_custody_context ( & spec, epoch_and_cgc_tuples) ;
1133+
1134+ // Backfill to epoch 15 (between the two CGC increases)
1135+ complete_backfill_for_epochs ( & custody_context, Epoch :: new ( 20 ) , Epoch :: new ( 15 ) ) ;
1136+
1137+ // Verify epochs 15 - 20 return latest CGC (32)
1138+ for epoch in 15 ..=20 {
1139+ assert_eq ! (
1140+ custody_context. custody_group_count_at_epoch( Epoch :: new( epoch) , & spec) ,
1141+ final_cgc,
1142+ ) ;
1143+ }
1144+
1145+ // Verify epochs 10-14 still return mid_cgc (16)
1146+ for epoch in 10 ..14 {
1147+ assert_eq ! (
1148+ custody_context. custody_group_count_at_epoch( Epoch :: new( epoch) , & spec) ,
1149+ mid_cgc,
1150+ ) ;
1151+ }
1152+ }
10361153}
0 commit comments