Skip to content

Commit 745ce55

Browse files
committed
Merge remote-tracking branch 'origin/develop' into feat/fork-detection-state-machine
2 parents e8b50f7 + 0ca28a0 commit 745ce55

File tree

5 files changed

+517
-6
lines changed

5 files changed

+517
-6
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to the versioning scheme outlined in the [README.md](README.md).
77

8-
## [Unreleased]
8+
## [3.1.0.0.9]
99

1010
### Added
1111

stacks-signer/CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to the versioning scheme outlined in the [README.md](README.md).
77

8-
## [Unreleased]
8+
## [3.1.0.0.9.0]
99

1010
### Changed
1111

stacks-signer/src/signerdb.rs

+131-2
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,40 @@ DROP TABLE blocks;
523523
524524
ALTER 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+
526560
static CREATE_BLOCK_VALIDATION_PENDING_TABLE: &str = r#"
527561
CREATE TABLE IF NOT EXISTS block_validations_pending (
528562
signer_signature_hash TEXT NOT NULL,
@@ -651,9 +685,14 @@ static SCHEMA_11: &[&str] = &[
651685
];
652686

653687
static 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

659698
impl 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

Comments
 (0)