@@ -120,9 +120,7 @@ impl ValidatorRegistrations {
120120 let effective_epoch =
121121 ( current_slot + effective_delay_slots) . epoch ( E :: slots_per_epoch ( ) ) + 1 ;
122122 self . epoch_validator_custody_requirements
123- . entry ( effective_epoch)
124- . and_modify ( |old_custody| * old_custody = validator_custody_requirement)
125- . or_insert ( validator_custody_requirement) ;
123+ . insert ( effective_epoch, validator_custody_requirement) ;
126124 Some ( ( effective_epoch, validator_custody_requirement) )
127125 } else {
128126 None
@@ -134,8 +132,17 @@ impl ValidatorRegistrations {
134132 ///
135133 /// This is done by pruning all values on/after `effective_epoch` and updating the map to store
136134 /// the latest validator custody requirements for the `effective_epoch`.
137- pub fn backfill_validator_custody_requirements ( & mut self , effective_epoch : Epoch ) {
135+ pub fn backfill_validator_custody_requirements (
136+ & mut self ,
137+ effective_epoch : Epoch ,
138+ expected_cgc : u64 ,
139+ ) {
138140 if let Some ( latest_validator_custody) = self . latest_validator_custody_requirement ( ) {
141+ // If the expected cgc isn't equal to the latest validator custody a very recent cgc change may have occurred.
142+ // We should not update the mapping.
143+ if expected_cgc != latest_validator_custody {
144+ return ;
145+ }
139146 // Delete records if
140147 // 1. The epoch is greater than or equal than `effective_epoch`
141148 // 2. the cgc requirements match the latest validator custody requirements
@@ -145,11 +152,25 @@ impl ValidatorRegistrations {
145152 } ) ;
146153
147154 self . epoch_validator_custody_requirements
148- . entry ( effective_epoch)
149- . and_modify ( |old_custody| * old_custody = latest_validator_custody)
150- . or_insert ( latest_validator_custody) ;
155+ . insert ( effective_epoch, latest_validator_custody) ;
151156 }
152157 }
158+
159+ /// Updates the `epoch -> cgc` map by pruning records before `effective_epoch`
160+ /// while setting the `cgc` at `effective_epoch` to the latest validator custody requirement.
161+ ///
162+ /// This is used to restart custody backfill sync at `effective_epoch`
163+ pub fn reset_validator_custody_requirements ( & mut self , effective_epoch : Epoch ) {
164+ if let Some ( latest_validator_custody_requirements) =
165+ self . latest_validator_custody_requirement ( )
166+ {
167+ self . epoch_validator_custody_requirements
168+ . retain ( |& epoch, _| epoch >= effective_epoch) ;
169+
170+ self . epoch_validator_custody_requirements
171+ . insert ( effective_epoch, latest_validator_custody_requirements) ;
172+ } ;
173+ }
153174}
154175
155176/// Given the `validator_custody_units`, return the custody requirement based on
@@ -517,10 +538,22 @@ impl<E: EthSpec> CustodyContext<E> {
517538
518539 /// The node has completed backfill for this epoch. Update the internal records so the function
519540 /// [`Self::custody_columns_for_epoch()`] returns up-to-date results.
520- pub fn update_and_backfill_custody_count_at_epoch ( & self , effective_epoch : Epoch ) {
541+ pub fn update_and_backfill_custody_count_at_epoch (
542+ & self ,
543+ effective_epoch : Epoch ,
544+ expected_cgc : u64 ,
545+ ) {
521546 self . validator_registrations
522547 . write ( )
523- . backfill_validator_custody_requirements ( effective_epoch) ;
548+ . backfill_validator_custody_requirements ( effective_epoch, expected_cgc) ;
549+ }
550+
551+ /// The node is attempting to restart custody backfill. Update the internal records so that
552+ /// custody backfill can start backfilling at `effective_epoch`.
553+ pub fn reset_validator_custody_requirements ( & self , effective_epoch : Epoch ) {
554+ self . validator_registrations
555+ . write ( )
556+ . reset_validator_custody_requirements ( effective_epoch) ;
524557 }
525558}
526559
@@ -604,11 +637,13 @@ mod tests {
604637 custody_context : & CustodyContext < E > ,
605638 start_epoch : Epoch ,
606639 end_epoch : Epoch ,
640+ expected_cgc : u64 ,
607641 ) {
608642 assert ! ( start_epoch >= end_epoch) ;
609643 // Call from end_epoch down to start_epoch (inclusive), simulating backfill
610644 for epoch in ( end_epoch. as_u64 ( ) ..=start_epoch. as_u64 ( ) ) . rev ( ) {
611- custody_context. update_and_backfill_custody_count_at_epoch ( Epoch :: new ( epoch) ) ;
645+ custody_context
646+ . update_and_backfill_custody_count_at_epoch ( Epoch :: new ( epoch) , expected_cgc) ;
612647 }
613648 }
614649
@@ -1368,7 +1403,7 @@ mod tests {
13681403 ) ;
13691404
13701405 // Backfill from epoch 20 down to 15 (simulating backfill)
1371- complete_backfill_for_epochs ( & custody_context, head_epoch, Epoch :: new ( 15 ) ) ;
1406+ complete_backfill_for_epochs ( & custody_context, head_epoch, Epoch :: new ( 15 ) , final_cgc ) ;
13721407
13731408 // After backfilling to epoch 15, it should use latest CGC (32)
13741409 assert_eq ! (
@@ -1406,7 +1441,43 @@ mod tests {
14061441 let custody_context = setup_custody_context ( & spec, head_epoch, epoch_and_cgc_tuples) ;
14071442
14081443 // Backfill to epoch 15 (between the two CGC increases)
1409- complete_backfill_for_epochs ( & custody_context, Epoch :: new ( 20 ) , Epoch :: new ( 15 ) ) ;
1444+ complete_backfill_for_epochs ( & custody_context, Epoch :: new ( 20 ) , Epoch :: new ( 15 ) , final_cgc) ;
1445+
1446+ // Verify epochs 15 - 20 return latest CGC (32)
1447+ for epoch in 15 ..=20 {
1448+ assert_eq ! (
1449+ custody_context. custody_group_count_at_epoch( Epoch :: new( epoch) , & spec) ,
1450+ final_cgc,
1451+ ) ;
1452+ }
1453+
1454+ // Verify epochs 10-14 still return mid_cgc (16)
1455+ for epoch in 10 ..14 {
1456+ assert_eq ! (
1457+ custody_context. custody_group_count_at_epoch( Epoch :: new( epoch) , & spec) ,
1458+ mid_cgc,
1459+ ) ;
1460+ }
1461+ }
1462+
1463+ #[ test]
1464+ fn attempt_backfill_with_invalid_cgc ( ) {
1465+ let spec = E :: default_spec ( ) ;
1466+ let initial_cgc = 8u64 ;
1467+ let mid_cgc = 16u64 ;
1468+ let final_cgc = 32u64 ;
1469+
1470+ // Setup: Node restart after multiple validator registrations causing CGC increases
1471+ let head_epoch = Epoch :: new ( 20 ) ;
1472+ let epoch_and_cgc_tuples = vec ! [
1473+ ( Epoch :: new( 0 ) , initial_cgc) ,
1474+ ( Epoch :: new( 10 ) , mid_cgc) ,
1475+ ( head_epoch, final_cgc) ,
1476+ ] ;
1477+ let custody_context = setup_custody_context ( & spec, head_epoch, epoch_and_cgc_tuples) ;
1478+
1479+ // Backfill to epoch 15 (between the two CGC increases)
1480+ complete_backfill_for_epochs ( & custody_context, Epoch :: new ( 20 ) , Epoch :: new ( 15 ) , final_cgc) ;
14101481
14111482 // Verify epochs 15 - 20 return latest CGC (32)
14121483 for epoch in 15 ..=20 {
@@ -1416,6 +1487,22 @@ mod tests {
14161487 ) ;
14171488 }
14181489
1490+ // Attempt backfill with an incorrect cgc value
1491+ complete_backfill_for_epochs (
1492+ & custody_context,
1493+ Epoch :: new ( 20 ) ,
1494+ Epoch :: new ( 15 ) ,
1495+ initial_cgc,
1496+ ) ;
1497+
1498+ // Verify epochs 15 - 20 still return latest CGC (32)
1499+ for epoch in 15 ..=20 {
1500+ assert_eq ! (
1501+ custody_context. custody_group_count_at_epoch( Epoch :: new( epoch) , & spec) ,
1502+ final_cgc,
1503+ ) ;
1504+ }
1505+
14191506 // Verify epochs 10-14 still return mid_cgc (16)
14201507 for epoch in 10 ..14 {
14211508 assert_eq ! (
@@ -1424,4 +1511,53 @@ mod tests {
14241511 ) ;
14251512 }
14261513 }
1514+
1515+ #[ test]
1516+ fn reset_validator_custody_requirements ( ) {
1517+ let spec = E :: default_spec ( ) ;
1518+ let minimum_cgc = 4u64 ;
1519+ let initial_cgc = 8u64 ;
1520+ let mid_cgc = 16u64 ;
1521+ let final_cgc = 32u64 ;
1522+
1523+ // Setup: Node restart after multiple validator registrations causing CGC increases
1524+ let head_epoch = Epoch :: new ( 20 ) ;
1525+ let epoch_and_cgc_tuples = vec ! [
1526+ ( Epoch :: new( 0 ) , initial_cgc) ,
1527+ ( Epoch :: new( 10 ) , mid_cgc) ,
1528+ ( head_epoch, final_cgc) ,
1529+ ] ;
1530+ let custody_context = setup_custody_context ( & spec, head_epoch, epoch_and_cgc_tuples) ;
1531+
1532+ // Backfill from epoch 20 to 9
1533+ complete_backfill_for_epochs ( & custody_context, Epoch :: new ( 20 ) , Epoch :: new ( 9 ) , final_cgc) ;
1534+
1535+ // Reset validator custody requirements to the latest cgc requirements at `head_epoch` up to the boundary epoch
1536+ custody_context. reset_validator_custody_requirements ( head_epoch) ;
1537+
1538+ // Verify epochs 0 - 19 return the minimum cgc requirement because of the validator custody requirement reset
1539+ for epoch in 0 ..=19 {
1540+ assert_eq ! (
1541+ custody_context. custody_group_count_at_epoch( Epoch :: new( epoch) , & spec) ,
1542+ minimum_cgc,
1543+ ) ;
1544+ }
1545+
1546+ // Verify epoch 20 returns a CGC of 32
1547+ assert_eq ! (
1548+ custody_context. custody_group_count_at_epoch( head_epoch, & spec) ,
1549+ final_cgc
1550+ ) ;
1551+
1552+ // Rerun Backfill to epoch 20
1553+ complete_backfill_for_epochs ( & custody_context, Epoch :: new ( 20 ) , Epoch :: new ( 0 ) , final_cgc) ;
1554+
1555+ // Verify epochs 0 - 20 return the final cgc requirements
1556+ for epoch in 0 ..=20 {
1557+ assert_eq ! (
1558+ custody_context. custody_group_count_at_epoch( Epoch :: new( epoch) , & spec) ,
1559+ final_cgc,
1560+ ) ;
1561+ }
1562+ }
14271563}
0 commit comments