@@ -523,6 +523,40 @@ DROP TABLE blocks;
523
523
524
524
ALTER TABLE temp_blocks RENAME TO blocks;"# ;
525
525
526
+ // Migration logic necessary to move burn blocks from the old burn blocks table to the new burn blocks table
527
+ // with the correct primary key
528
+ static MIGRATE_BURN_STATE_TABLE_1_TO_TABLE_2 : & str = r#"
529
+ CREATE TABLE IF NOT EXISTS temp_burn_blocks (
530
+ block_hash TEXT NOT NULL,
531
+ block_height INTEGER NOT NULL,
532
+ received_time INTEGER NOT NULL,
533
+ consensus_hash TEXT PRIMARY KEY NOT NULL
534
+ ) STRICT;
535
+
536
+ INSERT INTO temp_burn_blocks (block_hash, block_height, received_time, consensus_hash)
537
+ SELECT block_hash, block_height, received_time, consensus_hash
538
+ FROM (
539
+ SELECT
540
+ block_hash,
541
+ block_height,
542
+ received_time,
543
+ consensus_hash,
544
+ ROW_NUMBER() OVER (
545
+ PARTITION BY consensus_hash
546
+ ORDER BY received_time DESC
547
+ ) AS rn
548
+ FROM burn_blocks
549
+ WHERE consensus_hash IS NOT NULL
550
+ AND consensus_hash <> ''
551
+ ) AS ordered
552
+ WHERE rn = 1;
553
+
554
+ DROP TABLE burn_blocks;
555
+ ALTER TABLE temp_burn_blocks RENAME TO burn_blocks;
556
+
557
+ CREATE INDEX IF NOT EXISTS idx_burn_blocks_block_hash ON burn_blocks(block_hash);
558
+ "# ;
559
+
526
560
static CREATE_BLOCK_VALIDATION_PENDING_TABLE : & str = r#"
527
561
CREATE TABLE IF NOT EXISTS block_validations_pending (
528
562
signer_signature_hash TEXT NOT NULL,
@@ -651,9 +685,14 @@ static SCHEMA_11: &[&str] = &[
651
685
] ;
652
686
653
687
static SCHEMA_12 : & [ & str ] = & [
688
+ MIGRATE_BURN_STATE_TABLE_1_TO_TABLE_2 ,
689
+ "INSERT OR REPLACE INTO db_config (version) VALUES (12);" ,
690
+ ] ;
691
+
692
+ static SCHEMA_13 : & [ & str ] = & [
654
693
ADD_PARENT_BURN_BLOCK_HASH ,
655
694
ADD_PARENT_BURN_BLOCK_HASH_INDEX ,
656
- "INSERT INTO db_config (version) VALUES (12 );" ,
695
+ "INSERT INTO db_config (version) VALUES (13 );" ,
657
696
] ;
658
697
659
698
impl SignerDb {
@@ -856,6 +895,20 @@ impl SignerDb {
856
895
Ok ( ( ) )
857
896
}
858
897
898
+ /// Migrate from schema 12 to schema 13
899
+ fn schema_13_migration ( tx : & Transaction ) -> Result < ( ) , DBError > {
900
+ if Self :: get_schema_version ( tx) ? >= 13 {
901
+ // no migration necessary
902
+ return Ok ( ( ) ) ;
903
+ }
904
+
905
+ for statement in SCHEMA_13 . iter ( ) {
906
+ tx. execute_batch ( statement) ?;
907
+ }
908
+
909
+ Ok ( ( ) )
910
+ }
911
+
859
912
/// Register custom scalar functions used by the database
860
913
fn register_scalar_functions ( & self ) -> Result < ( ) , DBError > {
861
914
// Register helper function for determining if a block is a tenure change transaction
@@ -901,7 +954,8 @@ impl SignerDb {
901
954
9 => Self :: schema_10_migration ( & sql_tx) ?,
902
955
10 => Self :: schema_11_migration ( & sql_tx) ?,
903
956
11 => Self :: schema_12_migration ( & sql_tx) ?,
904
- 12 => break ,
957
+ 12 => Self :: schema_13_migration ( & sql_tx) ?,
958
+ 13 => break ,
905
959
x => return Err ( DBError :: Other ( format ! (
906
960
"Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}" ,
907
961
Self :: SCHEMA_VERSION ,
@@ -2711,4 +2765,79 @@ pub mod tests {
2711
2765
"latency between updates should be 10 second"
2712
2766
) ;
2713
2767
}
2768
+
2769
+ #[ test]
2770
+ fn burn_state_migration_consensus_hash_primary_key ( ) {
2771
+ // Construct the old table
2772
+ let conn = rusqlite:: Connection :: open_in_memory ( ) . expect ( "Failed to create in mem db" ) ;
2773
+ conn. execute_batch ( CREATE_BURN_STATE_TABLE )
2774
+ . expect ( "Failed to create old table" ) ;
2775
+ conn. execute_batch ( ADD_CONSENSUS_HASH )
2776
+ . expect ( "Failed to add consensus hash to old table" ) ;
2777
+ conn. execute_batch ( ADD_CONSENSUS_HASH_INDEX )
2778
+ . expect ( "Failed to add consensus hash index to old table" ) ;
2779
+
2780
+ let consensus_hash = ConsensusHash ( [ 0 ; 20 ] ) ;
2781
+ let total_nmb_rows = 5 ;
2782
+ // Fill with old data with conflicting consensus hashes
2783
+ for i in 0 ..=total_nmb_rows {
2784
+ let now = SystemTime :: now ( ) ;
2785
+ let received_ts = now. duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
2786
+ let burn_hash = BurnchainHeaderHash ( [ i; 32 ] ) ;
2787
+ let burn_height = i;
2788
+ if i % 2 == 0 {
2789
+ // Make sure we have some one empty consensus hash options that will get dropped
2790
+ conn. execute (
2791
+ "INSERT OR REPLACE INTO burn_blocks (block_hash, block_height, received_time) VALUES (?1, ?2, ?3)" ,
2792
+ params ! [
2793
+ burn_hash,
2794
+ u64_to_sql( burn_height. into( ) ) . unwrap( ) ,
2795
+ u64_to_sql( received_ts + i as u64 ) . unwrap( ) , // Ensure increasing received_time
2796
+ ]
2797
+ ) . unwrap ( ) ;
2798
+ } else {
2799
+ conn. execute (
2800
+ "INSERT OR REPLACE INTO burn_blocks (block_hash, consensus_hash, block_height, received_time) VALUES (?1, ?2, ?3, ?4)" ,
2801
+ params ! [
2802
+ burn_hash,
2803
+ consensus_hash,
2804
+ u64_to_sql( burn_height. into( ) ) . unwrap( ) ,
2805
+ u64_to_sql( received_ts + i as u64 ) . unwrap( ) , // Ensure increasing received_time
2806
+ ]
2807
+ ) . unwrap ( ) ;
2808
+ } ;
2809
+ }
2810
+
2811
+ // Migrate the data and make sure that the primary key conflict is resolved by using the last received time
2812
+ // and that the block height and consensus hash of the surviving row is as expected
2813
+ conn. execute_batch ( MIGRATE_BURN_STATE_TABLE_1_TO_TABLE_2 )
2814
+ . expect ( "Failed to migrate data" ) ;
2815
+ let migrated_count: u64 = conn
2816
+ . query_row ( "SELECT COUNT(*) FROM burn_blocks;" , [ ] , |row| row. get ( 0 ) )
2817
+ . expect ( "Failed to get row count" ) ;
2818
+
2819
+ assert_eq ! (
2820
+ migrated_count, 1 ,
2821
+ "Expected exactly one row after migration"
2822
+ ) ;
2823
+
2824
+ let ( block_height, hex_hash) : ( u64 , String ) = conn
2825
+ . query_row (
2826
+ "SELECT block_height, consensus_hash FROM burn_blocks;" ,
2827
+ [ ] ,
2828
+ |row| Ok ( ( row. get ( 0 ) ?, row. get ( 1 ) ?) ) ,
2829
+ )
2830
+ . expect ( "Failed to get block_height and consensus_hash" ) ;
2831
+
2832
+ assert_eq ! (
2833
+ block_height, total_nmb_rows as u64 ,
2834
+ "Expected block_height {total_nmb_rows} to be retained (has the latest received time)"
2835
+ ) ;
2836
+
2837
+ assert_eq ! (
2838
+ hex_hash,
2839
+ consensus_hash. to_hex( ) ,
2840
+ "Expected the surviving row to have the correct consensus_hash"
2841
+ ) ;
2842
+ }
2714
2843
}
0 commit comments