@@ -495,6 +495,29 @@ DROP TABLE blocks;
495
495
496
496
ALTER TABLE temp_blocks RENAME TO blocks;"# ;
497
497
498
+ // Migration logic necessary to move burn blocks from the old burn blocks table to the new burn blocks table
499
+ // with the correct primary key
500
+ static MIGRATE_BURN_STATE_TABLE_1_TO_TABLE_2 : & str = r#"
501
+ CREATE TABLE IF NOT EXISTS temp_burn_blocks (
502
+ block_hash TEXT NOT NULL,
503
+ block_height INTEGER NOT NULL,
504
+ received_time INTEGER NOT NULL,
505
+ consensus_hash TEXT PRIMARY KEY NOT NULL
506
+ ) STRICT;
507
+
508
+ INSERT INTO temp_burn_blocks (block_hash, block_height, received_time, consensus_hash)
509
+ SELECT block_hash, block_height, received_time, consensus_hash
510
+ FROM (
511
+ SELECT *,
512
+ ROW_NUMBER() OVER (PARTITION BY consensus_hash ORDER BY received_time DESC) as rn
513
+ FROM burn_blocks
514
+ ) AS ordered
515
+ WHERE rn = 1;
516
+
517
+ DROP TABLE burn_blocks;
518
+ ALTER TABLE temp_burn_blocks RENAME TO burn_blocks;
519
+ "# ;
520
+
498
521
static CREATE_BLOCK_VALIDATION_PENDING_TABLE : & str = r#"
499
522
CREATE TABLE IF NOT EXISTS block_validations_pending (
500
523
signer_signature_hash TEXT NOT NULL,
@@ -613,9 +636,14 @@ static SCHEMA_11: &[&str] = &[
613
636
"INSERT INTO db_config (version) VALUES (11);" ,
614
637
] ;
615
638
639
+ static SCHEMA_12 : & [ & str ] = & [
640
+ MIGRATE_BURN_STATE_TABLE_1_TO_TABLE_2 ,
641
+ "INSERT OR REPLACE INTO db_config (version) VALUES (12);" ,
642
+ ] ;
643
+
616
644
impl SignerDb {
617
645
/// The current schema version used in this build of the signer binary.
618
- pub const SCHEMA_VERSION : u32 = 11 ;
646
+ pub const SCHEMA_VERSION : u32 = 12 ;
619
647
620
648
/// Create a new `SignerState` instance.
621
649
/// This will create a new SQLite database at the given path
@@ -799,6 +827,20 @@ impl SignerDb {
799
827
Ok ( ( ) )
800
828
}
801
829
830
+ /// Migrate from schema 11 to schema 12
831
+ fn schema_12_migration ( tx : & Transaction ) -> Result < ( ) , DBError > {
832
+ if Self :: get_schema_version ( tx) ? >= 12 {
833
+ // no migration necessary
834
+ return Ok ( ( ) ) ;
835
+ }
836
+
837
+ for statement in SCHEMA_12 . iter ( ) {
838
+ tx. execute_batch ( statement) ?;
839
+ }
840
+
841
+ Ok ( ( ) )
842
+ }
843
+
802
844
/// Register custom scalar functions used by the database
803
845
fn register_scalar_functions ( & self ) -> Result < ( ) , DBError > {
804
846
// Register helper function for determining if a block is a tenure change transaction
@@ -843,7 +885,8 @@ impl SignerDb {
843
885
8 => Self :: schema_9_migration ( & sql_tx) ?,
844
886
9 => Self :: schema_10_migration ( & sql_tx) ?,
845
887
10 => Self :: schema_11_migration ( & sql_tx) ?,
846
- 11 => break ,
888
+ 11 => Self :: schema_12_migration ( & sql_tx) ?,
889
+ 12 => break ,
847
890
x => return Err ( DBError :: Other ( format ! (
848
891
"Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}" ,
849
892
Self :: SCHEMA_VERSION ,
@@ -2619,4 +2662,57 @@ pub mod tests {
2619
2662
"latency between updates should be 10 second"
2620
2663
) ;
2621
2664
}
2665
+
2666
+ #[ test]
2667
+ fn burn_state_migration_consensus_hash_primary_key ( ) {
2668
+ // Construct the old table
2669
+ let conn = rusqlite:: Connection :: open_in_memory ( ) . expect ( "Failed to create in mem db" ) ;
2670
+ conn. execute_batch ( CREATE_BURN_STATE_TABLE )
2671
+ . expect ( "Failed to create old table" ) ;
2672
+ conn. execute_batch ( ADD_CONSENSUS_HASH )
2673
+ . expect ( "Failed to add consensus hash" ) ;
2674
+ conn. execute_batch ( ADD_CONSENSUS_HASH_INDEX )
2675
+ . expect ( "Failed to add consensus hash index" ) ;
2676
+
2677
+ // Fill with old data with conflicting consensus hashes
2678
+ for i in 0 ..3 {
2679
+ let now = SystemTime :: now ( ) ;
2680
+ let received_ts = now. duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
2681
+ let burn_hash = BurnchainHeaderHash ( [ i; 32 ] ) ;
2682
+ let consensus_hash = ConsensusHash ( [ 0 ; 20 ] ) ; // Same consensus hash for all
2683
+ let burn_height = i;
2684
+ conn. execute (
2685
+ "INSERT OR REPLACE INTO burn_blocks (block_hash, consensus_hash, block_height, received_time) VALUES (?1, ?2, ?3, ?4)" ,
2686
+ params ! [
2687
+ burn_hash,
2688
+ consensus_hash,
2689
+ u64_to_sql( burn_height. into( ) ) . unwrap( ) ,
2690
+ u64_to_sql( received_ts + i as u64 ) . unwrap( ) , // Ensure increasing received_time
2691
+ ]
2692
+ ) . unwrap ( ) ;
2693
+ }
2694
+
2695
+ // Migrate the data and make sure that the primary key conflict is resolved by using the last received time
2696
+ conn. execute_batch ( MIGRATE_BURN_STATE_TABLE_1_TO_TABLE_2 )
2697
+ . expect ( "Failed to migrate data" ) ;
2698
+ let migrated_count: i64 = conn
2699
+ . query_row ( "SELECT COUNT(*) FROM burn_blocks;" , [ ] , |row| row. get ( 0 ) )
2700
+ . expect ( "Failed to get row count" ) ;
2701
+
2702
+ assert_eq ! (
2703
+ migrated_count, 1 ,
2704
+ "Expected exactly one row after migration"
2705
+ ) ;
2706
+
2707
+ let block_height: i64 = conn
2708
+ . query_row ( "SELECT block_height FROM burn_blocks;" , [ ] , |row| {
2709
+ row. get ( 0 )
2710
+ } )
2711
+ . expect ( "Failed to get block height" ) ;
2712
+
2713
+ assert_eq ! (
2714
+ block_height, 2 ,
2715
+ "Expected block_height 2 to be retained (has the latest received time)"
2716
+ ) ;
2717
+ }
2622
2718
}
0 commit comments