@@ -731,7 +731,7 @@ impl HierarchyModuli {
731731 )
732732 }
733733
734- /// For each layer, returns the closest diff less that or equal to `slot`.
734+ /// For each layer, returns the closest diff less than or equal to `slot`.
735735 pub fn closest_layer_points ( & self , slot : Slot , start_slot : Slot ) -> Vec < Slot > {
736736 self . moduli
737737 . iter ( )
@@ -991,4 +991,80 @@ mod tests {
991991 ]
992992 ) ;
993993 }
994+
995+ // Test that the diffs and snapshots required for storage of split states are retained in the
996+ // hot DB as the split slot advances, if we begin from an initial configuration where this
997+ // invariant holds.
998+ fn test_slots_retained_invariant ( hierarchy : HierarchyModuli , start_slot : u64 , epoch_jump : u64 ) {
999+ let start_slot = Slot :: new ( start_slot) ;
1000+ let mut finalized_slot = start_slot;
1001+
1002+ // Initially we have just one snapshot stored at the `start_slot`. This is what checkpoint
1003+ // sync sets up (or the V24 migration).
1004+ let mut retained_slots = vec ! [ finalized_slot] ;
1005+
1006+ // Iterate until we've reached two snapshots in the future.
1007+ let stop_at = hierarchy
1008+ . next_snapshot_slot ( hierarchy. next_snapshot_slot ( start_slot) . unwrap ( ) + 1 )
1009+ . unwrap ( ) ;
1010+
1011+ while finalized_slot <= stop_at {
1012+ // Jump multiple epocsh at a time because inter-epoch states are not interesting and
1013+ // would take too long to iterate over.
1014+ let new_finalized_slot = finalized_slot + 32 * epoch_jump;
1015+
1016+ let new_retained_slots = hierarchy. closest_layer_points ( new_finalized_slot, start_slot) ;
1017+
1018+ for slot in & new_retained_slots {
1019+ // All new retained slots must either be already stored prior to the old finalized
1020+ // slot, OR newer than the finalized slot (i.e. stored in the hot DB as part of
1021+ // regular state storage).
1022+ assert ! ( retained_slots. contains( slot) || * slot >= finalized_slot) ;
1023+ }
1024+
1025+ retained_slots = new_retained_slots;
1026+ finalized_slot = new_finalized_slot;
1027+ }
1028+ }
1029+
1030+ #[ test]
1031+ fn slots_retained_invariant ( ) {
1032+ let cases = [
1033+ // Default hierarchy with a start_slot between the 2^13 and 2^16 layers.
1034+ (
1035+ HierarchyConfig :: default ( ) . to_moduli ( ) . unwrap ( ) ,
1036+ 2 * ( 1 << 14 ) - 5 * 32 ,
1037+ 1 ,
1038+ ) ,
1039+ // Default hierarchy with a start_slot between the 2^13 and 2^16 layers, with 8 epochs
1040+ // finalizing at a time (should not make any difference).
1041+ (
1042+ HierarchyConfig :: default ( ) . to_moduli ( ) . unwrap ( ) ,
1043+ 2 * ( 1 << 14 ) - 5 * 32 ,
1044+ 8 ,
1045+ ) ,
1046+ // Very dense hierarchy config.
1047+ (
1048+ HierarchyConfig :: from_str ( "5,7" )
1049+ . unwrap ( )
1050+ . to_moduli ( )
1051+ . unwrap ( ) ,
1052+ 32 ,
1053+ 1 ,
1054+ ) ,
1055+ // Very dense hierarchy config that skips a whole snapshot on its first finalization.
1056+ (
1057+ HierarchyConfig :: from_str ( "5,7" )
1058+ . unwrap ( )
1059+ . to_moduli ( )
1060+ . unwrap ( ) ,
1061+ 32 ,
1062+ 1 << 7 ,
1063+ ) ,
1064+ ] ;
1065+
1066+ for ( hierarchy, start_slot, epoch_jump) in cases {
1067+ test_slots_retained_invariant ( hierarchy, start_slot, epoch_jump) ;
1068+ }
1069+ }
9941070}
0 commit comments