fix: migrate SPV schema to include the block hash alongside the block headers

This commit is contained in:
Jude Nelson
2023-01-24 00:44:50 -05:00
parent 5c0e0ac09e
commit de62bfca9d

View File

@@ -70,7 +70,7 @@ pub const BITCOIN_GENESIS_BLOCK_HASH_REGTEST: &'static str =
pub const BLOCK_DIFFICULTY_CHUNK_SIZE: u64 = 2016;
const BLOCK_DIFFICULTY_INTERVAL: u32 = 14 * 24 * 60 * 60; // two weeks, in seconds
pub const SPV_DB_VERSION: &'static str = "2";
pub const SPV_DB_VERSION: &'static str = "3";
const SPV_INITIAL_SCHEMA: &[&'static str] = &[
r#"
@@ -98,6 +98,31 @@ const SPV_SCHEMA_2: &[&'static str] = &[r#"
);
"#];
// force the node to go and store the burnchain block header hash as well
const SPV_SCHEMA_3: &[&'static str] = &[
r#"
DROP TABLE headers;
"#,
r#"
DELETE FROM chain_work;
"#,
r#"
CREATE TABLE headers(
version INTEGER NOT NULL,
prev_blockhash TEXT NOT NULL,
merkle_root TEXT NOT NULL,
time INTEGER NOT NULL,
bits INTEGER NOT NULL,
nonce INTEGER NOT NULL,
height INTEGER PRIMARY KEY NOT NULL, -- not part of BlockHeader, but used by us internally
hash TEXT NOT NULL -- not part of BlockHeader, but derived from the data that is
);
"#,
r#"
CREATE INDEX index_headers_by_hash ON headers(hash);
"#,
];
pub struct SpvClient {
pub headers_path: String,
pub start_block_height: u64,
@@ -159,7 +184,8 @@ impl SpvClient {
check_txcount: true,
};
if readwrite && !exists {
let empty = client.is_empty()?;
if readwrite && (!exists || empty) {
client.init_block_headers(true)?;
}
@@ -228,6 +254,9 @@ impl SpvClient {
for row_text in SPV_SCHEMA_2 {
tx.execute_batch(row_text).map_err(db_error::SqliteError)?;
}
for row_text in SPV_SCHEMA_3 {
tx.execute_batch(row_text).map_err(db_error::SqliteError)?;
}
tx.execute(
"INSERT INTO db_config (version) VALUES (?1)",
@@ -278,6 +307,16 @@ impl SpvClient {
SpvClient::db_set_version(&tx, "2")?;
tx.commit().map_err(db_error::SqliteError)?;
}
"2" => {
debug!("Migrate SPV DB from schema 2 to 3");
let tx = tx_begin_immediate(conn)?;
for row_text in SPV_SCHEMA_3 {
tx.execute_batch(row_text).map_err(db_error::SqliteError)?;
}
SpvClient::db_set_version(&tx, "2")?;
tx.commit().map_err(db_error::SqliteError)?;
}
SPV_DB_VERSION => {
break;
}
@@ -629,6 +668,18 @@ impl SpvClient {
}
}
/// Is the DB devoid of headers? Used during migrations
pub fn is_empty(&self) -> Result<bool, btc_error> {
match query_row::<BlockHeader, _>(
&self.headers_db,
"SELECT * FROM headers LIMIT 1",
NO_PARAMS,
)? {
Some(_) => Ok(true),
None => Ok(false),
}
}
/// Read the block header at a particular height
/// Returns None if the requested block height is beyond the end of the headers file
pub fn read_block_header(
@@ -646,6 +697,19 @@ impl SpvClient {
}))
}
/// Find a block header height with a given burnchain header hash, if it is present
pub fn find_block_header_height(
&self,
burn_header_hash: &BurnchainHeaderHash,
) -> Result<Option<u64>, btc_error> {
query_row(
&self.headers_db,
"SELECT height FROM headers WHERE hash = ?1",
&[burn_header_hash],
)
.map_err(|e| e.into())
}
/// Get a range of block headers from a file.
/// If the range falls of the end of the headers file, then the returned array will be
/// truncated to not include them (note that this method can return an empty list of the
@@ -700,8 +764,8 @@ impl SpvClient {
height: u64,
) -> Result<(), btc_error> {
let sql = "INSERT OR REPLACE INTO headers
(version, prev_blockhash, merkle_root, time, bits, nonce, height)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)";
(version, prev_blockhash, merkle_root, time, bits, nonce, height, hash)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)";
let args: &[&dyn ToSql] = &[
&header.version,
&header.prev_blockhash,
@@ -710,6 +774,7 @@ impl SpvClient {
&header.bits,
&header.nonce,
&u64_to_sql(height)?,
&BurnchainHeaderHash::from_bitcoin_hash(&header.bitcoin_hash()),
];
tx.execute(sql, args)