Skip to content

Commit 420e869

Browse files
committed
Update burn state table to use consensus hash as its primary key
Signed-off-by: Jacinta Ferrant <jacinta.ferrant@gmail.com>
1 parent e45867c commit 420e869

File tree

1 file changed

+98
-2
lines changed

1 file changed

+98
-2
lines changed

stacks-signer/src/signerdb.rs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,29 @@ DROP TABLE blocks;
495495
496496
ALTER 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+
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+
498521
static CREATE_BLOCK_VALIDATION_PENDING_TABLE: &str = r#"
499522
CREATE TABLE IF NOT EXISTS block_validations_pending (
500523
signer_signature_hash TEXT NOT NULL,
@@ -613,9 +636,14 @@ static SCHEMA_11: &[&str] = &[
613636
"INSERT INTO db_config (version) VALUES (11);",
614637
];
615638

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+
616644
impl SignerDb {
617645
/// 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;
619647

620648
/// Create a new `SignerState` instance.
621649
/// This will create a new SQLite database at the given path
@@ -799,6 +827,20 @@ impl SignerDb {
799827
Ok(())
800828
}
801829

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+
802844
/// Register custom scalar functions used by the database
803845
fn register_scalar_functions(&self) -> Result<(), DBError> {
804846
// Register helper function for determining if a block is a tenure change transaction
@@ -843,7 +885,8 @@ impl SignerDb {
843885
8 => Self::schema_9_migration(&sql_tx)?,
844886
9 => Self::schema_10_migration(&sql_tx)?,
845887
10 => Self::schema_11_migration(&sql_tx)?,
846-
11 => break,
888+
11 => Self::schema_12_migration(&sql_tx)?,
889+
12 => break,
847890
x => return Err(DBError::Other(format!(
848891
"Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}",
849892
Self::SCHEMA_VERSION,
@@ -2619,4 +2662,57 @@ pub mod tests {
26192662
"latency between updates should be 10 second"
26202663
);
26212664
}
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+
}
26222718
}

0 commit comments

Comments
 (0)