@@ -2848,6 +2848,80 @@ impl SortitionDB {
2848
2848
Ok ( ( ) )
2849
2849
}
2850
2850
2851
+ /// Validates given StacksEpochs (will runtime panic if there is any invalid StacksEpoch structuring) and
2852
+ /// replaces them into the SortitionDB's epochs table
2853
+ fn validate_and_replace_epochs (
2854
+ db_tx : & Transaction ,
2855
+ epochs : & [ StacksEpoch ] ,
2856
+ ) -> Result < ( ) , db_error > {
2857
+ let epochs = StacksEpoch :: validate_epochs ( epochs) ;
2858
+ let existing_epochs = Self :: get_stacks_epochs ( db_tx) ?;
2859
+ if existing_epochs == epochs {
2860
+ return Ok ( ( ) ) ;
2861
+ }
2862
+
2863
+ let tip = SortitionDB :: get_canonical_burn_chain_tip ( db_tx) ?;
2864
+ let existing_epoch_idx = StacksEpoch :: find_epoch ( & existing_epochs, tip. block_height )
2865
+ . unwrap_or_else ( || {
2866
+ panic ! (
2867
+ "FATAL: Sortition tip {} has no epoch in its existing epochs table" ,
2868
+ tip. block_height
2869
+ ) ;
2870
+ } ) ;
2871
+
2872
+ let new_epoch_idx =
2873
+ StacksEpoch :: find_epoch ( & epochs, tip. block_height ) . unwrap_or_else ( || {
2874
+ panic ! (
2875
+ "FATAL: Sortition tip {} has no epoch in the configured epochs list" ,
2876
+ tip. block_height
2877
+ ) ;
2878
+ } ) ;
2879
+
2880
+ // can't retcon epochs -- all epochs up to (but excluding) the tip's epoch in both epoch
2881
+ // lists must be the same.
2882
+ for i in 0 ..existing_epoch_idx. min ( new_epoch_idx) {
2883
+ if existing_epochs[ i] != epochs[ i] {
2884
+ panic ! (
2885
+ "FATAL: tried to retcon epoch {:?} into epoch {:?}" ,
2886
+ & existing_epochs[ i] , & epochs[ i]
2887
+ ) ;
2888
+ }
2889
+ }
2890
+
2891
+ // can't change parameters of the current epoch in either epoch list,
2892
+ // except for the end height (and only if it hasn't been reached yet)
2893
+ let mut diff_epoch = existing_epochs[ existing_epoch_idx] . clone ( ) ;
2894
+ diff_epoch. end_height = epochs[ new_epoch_idx] . end_height ;
2895
+
2896
+ if diff_epoch != epochs[ new_epoch_idx] {
2897
+ panic ! (
2898
+ "FATAL: tried to change current epoch {:?} into {:?}" ,
2899
+ & existing_epochs[ existing_epoch_idx] , & epochs[ new_epoch_idx]
2900
+ ) ;
2901
+ }
2902
+
2903
+ if tip. block_height >= epochs[ new_epoch_idx] . end_height {
2904
+ panic ! ( "FATAL: tip has reached or passed the end of the configured epoch" ) ;
2905
+ }
2906
+
2907
+ info ! ( "Replace existing epochs with new epochs" ) ;
2908
+ db_tx. execute ( "DELETE FROM epochs;" , NO_PARAMS ) ?;
2909
+ for epoch in epochs. into_iter ( ) {
2910
+ let args: & [ & dyn ToSql ] = & [
2911
+ & ( epoch. epoch_id as u32 ) ,
2912
+ & u64_to_sql ( epoch. start_height ) ?,
2913
+ & u64_to_sql ( epoch. end_height ) ?,
2914
+ & epoch. block_limit ,
2915
+ & epoch. network_epoch ,
2916
+ ] ;
2917
+ db_tx. execute (
2918
+ "INSERT INTO epochs (epoch_id,start_block_height,end_block_height,block_limit,network_epoch) VALUES (?1,?2,?3,?4,?5)" ,
2919
+ args
2920
+ ) ?;
2921
+ }
2922
+ Ok ( ( ) )
2923
+ }
2924
+
2851
2925
/// Get a block commit by its content-addressed location in a specific sortition.
2852
2926
pub fn get_block_commit (
2853
2927
conn : & Connection ,
@@ -3322,6 +3396,10 @@ impl SortitionDB {
3322
3396
3323
3397
self . apply_schema_8_migration ( migrator. take ( ) ) ?;
3324
3398
} else if version == expected_version {
3399
+ let tx = self . tx_begin ( ) ?;
3400
+ SortitionDB :: validate_and_replace_epochs ( & tx, epochs) ?;
3401
+ tx. commit ( ) ?;
3402
+
3325
3403
return Ok ( ( ) ) ;
3326
3404
} else {
3327
3405
panic ! ( "The schema version of the sortition DB is invalid." )
@@ -10629,4 +10707,51 @@ pub mod tests {
10629
10707
good_ops_2[ 3 ]
10630
10708
) ;
10631
10709
}
10710
+
10711
+ #[ test]
10712
+ fn test_validate_and_replace_epochs ( ) {
10713
+ use crate :: core:: STACKS_EPOCHS_MAINNET ;
10714
+
10715
+ let path_root = "/tmp/test_validate_and_replace_epochs" ;
10716
+ if fs:: metadata ( path_root) . is_ok ( ) {
10717
+ fs:: remove_dir_all ( path_root) . unwrap ( ) ;
10718
+ }
10719
+
10720
+ fs:: create_dir_all ( path_root) . unwrap ( ) ;
10721
+
10722
+ let mut bad_epochs = STACKS_EPOCHS_MAINNET . to_vec ( ) ;
10723
+ let idx = bad_epochs. len ( ) - 2 ;
10724
+ bad_epochs[ idx] . end_height += 1 ;
10725
+ bad_epochs[ idx + 1 ] . start_height += 1 ;
10726
+
10727
+ let sortdb = SortitionDB :: connect (
10728
+ & format ! ( "{}/sortdb.sqlite" , & path_root) ,
10729
+ 0 ,
10730
+ & BurnchainHeaderHash ( [ 0x00 ; 32 ] ) ,
10731
+ 0 ,
10732
+ & bad_epochs,
10733
+ PoxConstants :: mainnet_default ( ) ,
10734
+ None ,
10735
+ true ,
10736
+ )
10737
+ . unwrap ( ) ;
10738
+
10739
+ let db_epochs = SortitionDB :: get_stacks_epochs ( sortdb. conn ( ) ) . unwrap ( ) ;
10740
+ assert_eq ! ( db_epochs, bad_epochs) ;
10741
+
10742
+ let fixed_sortdb = SortitionDB :: connect (
10743
+ & format ! ( "{}/sortdb.sqlite" , & path_root) ,
10744
+ 0 ,
10745
+ & BurnchainHeaderHash ( [ 0x00 ; 32 ] ) ,
10746
+ 0 ,
10747
+ & STACKS_EPOCHS_MAINNET . to_vec ( ) ,
10748
+ PoxConstants :: mainnet_default ( ) ,
10749
+ None ,
10750
+ true ,
10751
+ )
10752
+ . unwrap ( ) ;
10753
+
10754
+ let db_epochs = SortitionDB :: get_stacks_epochs ( sortdb. conn ( ) ) . unwrap ( ) ;
10755
+ assert_eq ! ( db_epochs, STACKS_EPOCHS_MAINNET . to_vec( ) ) ;
10756
+ }
10632
10757
}
0 commit comments