@@ -523,6 +523,40 @@ DROP TABLE blocks;
523523
524524ALTER TABLE temp_blocks RENAME TO blocks;"# ;
525525
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+
526560static CREATE_BLOCK_VALIDATION_PENDING_TABLE : & str = r#"
527561CREATE TABLE IF NOT EXISTS block_validations_pending (
528562 signer_signature_hash TEXT NOT NULL,
@@ -651,9 +685,14 @@ static SCHEMA_11: &[&str] = &[
651685] ;
652686
653687static 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 ] = & [
654693 ADD_PARENT_BURN_BLOCK_HASH ,
655694 ADD_PARENT_BURN_BLOCK_HASH_INDEX ,
656- "INSERT INTO db_config (version) VALUES (12 );" ,
695+ "INSERT INTO db_config (version) VALUES (13 );" ,
657696] ;
658697
659698impl SignerDb {
@@ -856,6 +895,20 @@ impl SignerDb {
856895 Ok ( ( ) )
857896 }
858897
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+
859912 /// Register custom scalar functions used by the database
860913 fn register_scalar_functions ( & self ) -> Result < ( ) , DBError > {
861914 // Register helper function for determining if a block is a tenure change transaction
@@ -901,7 +954,8 @@ impl SignerDb {
901954 9 => Self :: schema_10_migration ( & sql_tx) ?,
902955 10 => Self :: schema_11_migration ( & sql_tx) ?,
903956 11 => Self :: schema_12_migration ( & sql_tx) ?,
904- 12 => break ,
957+ 12 => Self :: schema_13_migration ( & sql_tx) ?,
958+ 13 => break ,
905959 x => return Err ( DBError :: Other ( format ! (
906960 "Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}" ,
907961 Self :: SCHEMA_VERSION ,
@@ -2711,4 +2765,79 @@ pub mod tests {
27112765 "latency between updates should be 10 second"
27122766 ) ;
27132767 }
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+ }
27142843}
0 commit comments