@@ -495,6 +495,40 @@ DROP TABLE blocks;
495495
496496ALTER TABLE temp_blocks RENAME TO blocks;"# ;
497497
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+
498532static CREATE_BLOCK_VALIDATION_PENDING_TABLE : & str = r#"
499533CREATE TABLE IF NOT EXISTS block_validations_pending (
500534 signer_signature_hash TEXT NOT NULL,
@@ -613,9 +647,14 @@ static SCHEMA_11: &[&str] = &[
613647 "INSERT INTO db_config (version) VALUES (11);" ,
614648] ;
615649
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+
616655impl SignerDb {
617656 /// 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 ;
619658
620659 /// Create a new `SignerState` instance.
621660 /// This will create a new SQLite database at the given path
@@ -799,6 +838,20 @@ impl SignerDb {
799838 Ok ( ( ) )
800839 }
801840
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+
802855 /// Register custom scalar functions used by the database
803856 fn register_scalar_functions ( & self ) -> Result < ( ) , DBError > {
804857 // Register helper function for determining if a block is a tenure change transaction
@@ -843,7 +896,8 @@ impl SignerDb {
843896 8 => Self :: schema_9_migration ( & sql_tx) ?,
844897 9 => Self :: schema_10_migration ( & sql_tx) ?,
845898 10 => Self :: schema_11_migration ( & sql_tx) ?,
846- 11 => break ,
899+ 11 => Self :: schema_12_migration ( & sql_tx) ?,
900+ 12 => break ,
847901 x => return Err ( DBError :: Other ( format ! (
848902 "Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}" ,
849903 Self :: SCHEMA_VERSION ,
@@ -2619,4 +2673,79 @@ pub mod tests {
26192673 "latency between updates should be 10 second"
26202674 ) ;
26212675 }
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+ }
26222751}
0 commit comments