@@ -495,6 +495,40 @@ 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
+ block_hash,
513
+ block_height,
514
+ received_time,
515
+ consensus_hash,
516
+ ROW_NUMBER() OVER (
517
+ PARTITION BY consensus_hash
518
+ ORDER BY received_time DESC
519
+ ) AS rn
520
+ FROM burn_blocks
521
+ WHERE consensus_hash IS NOT NULL
522
+ AND consensus_hash <> ''
523
+ ) AS ordered
524
+ WHERE rn = 1;
525
+
526
+ DROP TABLE burn_blocks;
527
+ ALTER TABLE temp_burn_blocks RENAME TO burn_blocks;
528
+
529
+ CREATE INDEX IF NOT EXISTS idx_burn_blocks_block_hash ON burn_blocks(block_hash);
530
+ "# ;
531
+
498
532
static CREATE_BLOCK_VALIDATION_PENDING_TABLE : & str = r#"
499
533
CREATE TABLE IF NOT EXISTS block_validations_pending (
500
534
signer_signature_hash TEXT NOT NULL,
@@ -613,9 +647,14 @@ static SCHEMA_11: &[&str] = &[
613
647
"INSERT INTO db_config (version) VALUES (11);" ,
614
648
] ;
615
649
650
+ static SCHEMA_12 : & [ & str ] = & [
651
+ MIGRATE_BURN_STATE_TABLE_1_TO_TABLE_2 ,
652
+ "INSERT OR REPLACE INTO db_config (version) VALUES (12);" ,
653
+ ] ;
654
+
616
655
impl SignerDb {
617
656
/// The current schema version used in this build of the signer binary.
618
- pub const SCHEMA_VERSION : u32 = 11 ;
657
+ pub const SCHEMA_VERSION : u32 = 12 ;
619
658
620
659
/// Create a new `SignerState` instance.
621
660
/// This will create a new SQLite database at the given path
@@ -799,6 +838,20 @@ impl SignerDb {
799
838
Ok ( ( ) )
800
839
}
801
840
841
+ /// Migrate from schema 11 to schema 12
842
+ fn schema_12_migration ( tx : & Transaction ) -> Result < ( ) , DBError > {
843
+ if Self :: get_schema_version ( tx) ? >= 12 {
844
+ // no migration necessary
845
+ return Ok ( ( ) ) ;
846
+ }
847
+
848
+ for statement in SCHEMA_12 . iter ( ) {
849
+ tx. execute_batch ( statement) ?;
850
+ }
851
+
852
+ Ok ( ( ) )
853
+ }
854
+
802
855
/// Register custom scalar functions used by the database
803
856
fn register_scalar_functions ( & self ) -> Result < ( ) , DBError > {
804
857
// Register helper function for determining if a block is a tenure change transaction
@@ -843,7 +896,8 @@ impl SignerDb {
843
896
8 => Self :: schema_9_migration ( & sql_tx) ?,
844
897
9 => Self :: schema_10_migration ( & sql_tx) ?,
845
898
10 => Self :: schema_11_migration ( & sql_tx) ?,
846
- 11 => break ,
899
+ 11 => Self :: schema_12_migration ( & sql_tx) ?,
900
+ 12 => break ,
847
901
x => return Err ( DBError :: Other ( format ! (
848
902
"Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}" ,
849
903
Self :: SCHEMA_VERSION ,
@@ -2619,4 +2673,79 @@ pub mod tests {
2619
2673
"latency between updates should be 10 second"
2620
2674
) ;
2621
2675
}
2676
+
2677
+ #[ test]
2678
+ fn burn_state_migration_consensus_hash_primary_key ( ) {
2679
+ // Construct the old table
2680
+ let conn = rusqlite:: Connection :: open_in_memory ( ) . expect ( "Failed to create in mem db" ) ;
2681
+ conn. execute_batch ( CREATE_BURN_STATE_TABLE )
2682
+ . expect ( "Failed to create old table" ) ;
2683
+ conn. execute_batch ( ADD_CONSENSUS_HASH )
2684
+ . expect ( "Failed to add consensus hash to old table" ) ;
2685
+ conn. execute_batch ( ADD_CONSENSUS_HASH_INDEX )
2686
+ . expect ( "Failed to add consensus hash index to old table" ) ;
2687
+
2688
+ let consensus_hash = ConsensusHash ( [ 0 ; 20 ] ) ;
2689
+ let total_nmb_rows = 5 ;
2690
+ // Fill with old data with conflicting consensus hashes
2691
+ for i in 0 ..=total_nmb_rows {
2692
+ let now = SystemTime :: now ( ) ;
2693
+ let received_ts = now. duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
2694
+ let burn_hash = BurnchainHeaderHash ( [ i; 32 ] ) ;
2695
+ let burn_height = i;
2696
+ if i % 2 == 0 {
2697
+ // Make sure we have some one empty consensus hash options that will get dropped
2698
+ conn. execute (
2699
+ "INSERT OR REPLACE INTO burn_blocks (block_hash, block_height, received_time) VALUES (?1, ?2, ?3)" ,
2700
+ params ! [
2701
+ burn_hash,
2702
+ u64_to_sql( burn_height. into( ) ) . unwrap( ) ,
2703
+ u64_to_sql( received_ts + i as u64 ) . unwrap( ) , // Ensure increasing received_time
2704
+ ]
2705
+ ) . unwrap ( ) ;
2706
+ } else {
2707
+ conn. execute (
2708
+ "INSERT OR REPLACE INTO burn_blocks (block_hash, consensus_hash, block_height, received_time) VALUES (?1, ?2, ?3, ?4)" ,
2709
+ params ! [
2710
+ burn_hash,
2711
+ consensus_hash,
2712
+ u64_to_sql( burn_height. into( ) ) . unwrap( ) ,
2713
+ u64_to_sql( received_ts + i as u64 ) . unwrap( ) , // Ensure increasing received_time
2714
+ ]
2715
+ ) . unwrap ( ) ;
2716
+ } ;
2717
+ }
2718
+
2719
+ // Migrate the data and make sure that the primary key conflict is resolved by using the last received time
2720
+ // and that the block height and consensus hash of the surviving row is as expected
2721
+ conn. execute_batch ( MIGRATE_BURN_STATE_TABLE_1_TO_TABLE_2 )
2722
+ . expect ( "Failed to migrate data" ) ;
2723
+ let migrated_count: u64 = conn
2724
+ . query_row ( "SELECT COUNT(*) FROM burn_blocks;" , [ ] , |row| row. get ( 0 ) )
2725
+ . expect ( "Failed to get row count" ) ;
2726
+
2727
+ assert_eq ! (
2728
+ migrated_count, 1 ,
2729
+ "Expected exactly one row after migration"
2730
+ ) ;
2731
+
2732
+ let ( block_height, hex_hash) : ( u64 , String ) = conn
2733
+ . query_row (
2734
+ "SELECT block_height, consensus_hash FROM burn_blocks;" ,
2735
+ [ ] ,
2736
+ |row| Ok ( ( row. get ( 0 ) ?, row. get ( 1 ) ?) ) ,
2737
+ )
2738
+ . expect ( "Failed to get block_height and consensus_hash" ) ;
2739
+
2740
+ assert_eq ! (
2741
+ block_height, total_nmb_rows as u64 ,
2742
+ "Expected block_height {total_nmb_rows} to be retained (has the latest received time)"
2743
+ ) ;
2744
+
2745
+ assert_eq ! (
2746
+ hex_hash,
2747
+ consensus_hash. to_hex( ) ,
2748
+ "Expected the surviving row to have the correct consensus_hash"
2749
+ ) ;
2750
+ }
2622
2751
}
0 commit comments