Skip to content

Commit 0ca28a0

Browse files
authored
Merge pull request #6095 from jferrant/bug/burn-blocks-table-primary-key
Update burn state table to use consensus hash as its primary key
2 parents 5b95fb7 + fa3b83e commit 0ca28a0

File tree

1 file changed

+131
-2
lines changed

1 file changed

+131
-2
lines changed

stacks-signer/src/signerdb.rs

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,40 @@ 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+
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+
498532
static CREATE_BLOCK_VALIDATION_PENDING_TABLE: &str = r#"
499533
CREATE 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+
616655
impl 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

Comments
 (0)