Skip to content

Commit d47c4ab

Browse files
authored
Merge branch 'develop' into feat/nakamoto-mempool-sync
2 parents 8182591 + 2e0c984 commit d47c4ab

File tree

1 file changed

+76
-19
lines changed

1 file changed

+76
-19
lines changed

stacks-signer/src/signerdb.rs

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,17 @@ use std::time::SystemTime;
1919

2020
use blockstack_lib::chainstate::nakamoto::{NakamotoBlock, NakamotoBlockVote};
2121
use blockstack_lib::util_lib::db::{
22-
query_row, sqlite_open, table_exists, u64_to_sql, Error as DBError,
22+
query_row, sqlite_open, table_exists, tx_begin_immediate, u64_to_sql, Error as DBError,
2323
};
2424
use clarity::types::chainstate::BurnchainHeaderHash;
2525
use clarity::util::get_epoch_time_secs;
2626
use libsigner::BlockProposal;
27-
use rusqlite::{params, Connection, Error as SqliteError, OpenFlags};
27+
use rusqlite::{
28+
params, Connection, Error as SqliteError, OpenFlags, OptionalExtension, Transaction,
29+
};
2830
use serde::{Deserialize, Serialize};
2931
use slog::{slog_debug, slog_error};
3032
use stacks_common::types::chainstate::ConsensusHash;
31-
use stacks_common::types::sqlite::NO_PARAMS;
3233
use stacks_common::util::hash::Sha512Trunc256Sum;
3334
use stacks_common::{debug, error};
3435
use wsts::net::NonceRequest;
@@ -107,7 +108,7 @@ pub struct SignerDb {
107108
db: Connection,
108109
}
109110

110-
const CREATE_BLOCKS_TABLE: &str = "
111+
static CREATE_BLOCKS_TABLE: &str = "
111112
CREATE TABLE IF NOT EXISTS blocks (
112113
reward_cycle INTEGER NOT NULL,
113114
signer_signature_hash TEXT NOT NULL,
@@ -119,55 +120,111 @@ CREATE TABLE IF NOT EXISTS blocks (
119120
PRIMARY KEY (reward_cycle, signer_signature_hash)
120121
) STRICT";
121122

122-
const CREATE_INDEXES: &str = "
123+
static CREATE_INDEXES: &str = "
123124
CREATE INDEX IF NOT EXISTS blocks_signed_over ON blocks (signed_over);
124125
CREATE INDEX IF NOT EXISTS blocks_consensus_hash ON blocks (consensus_hash);
125126
CREATE INDEX IF NOT EXISTS blocks_valid ON blocks ((json_extract(block_info, '$.valid')));
126127
CREATE INDEX IF NOT EXISTS burn_blocks_height ON burn_blocks (block_height);
127128
";
128129

129-
const CREATE_SIGNER_STATE_TABLE: &str = "
130+
static CREATE_SIGNER_STATE_TABLE: &str = "
130131
CREATE TABLE IF NOT EXISTS signer_states (
131132
reward_cycle INTEGER PRIMARY KEY,
132133
encrypted_state BLOB NOT NULL
133134
) STRICT";
134135

135-
const CREATE_BURN_STATE_TABLE: &str = "
136+
static CREATE_BURN_STATE_TABLE: &str = "
136137
CREATE TABLE IF NOT EXISTS burn_blocks (
137138
block_hash TEXT PRIMARY KEY,
138139
block_height INTEGER NOT NULL,
139140
received_time INTEGER NOT NULL
140141
) STRICT";
141142

143+
static CREATE_DB_CONFIG: &str = "
144+
CREATE TABLE db_config(
145+
version INTEGER NOT NULL
146+
) STRICT
147+
";
148+
149+
static DROP_SCHEMA_0: &str = "
150+
DROP TABLE IF EXISTS burn_blocks;
151+
DROP TABLE IF EXISTS signer_states;
152+
DROP TABLE IF EXISTS blocks;
153+
DROP TABLE IF EXISTS db_config;";
154+
155+
static SCHEMA_1: &[&str] = &[
156+
DROP_SCHEMA_0,
157+
CREATE_DB_CONFIG,
158+
CREATE_BURN_STATE_TABLE,
159+
CREATE_BLOCKS_TABLE,
160+
CREATE_SIGNER_STATE_TABLE,
161+
CREATE_INDEXES,
162+
"INSERT INTO db_config (version) VALUES (1);",
163+
];
164+
142165
impl SignerDb {
166+
/// The current schema version used in this build of the signer binary.
167+
pub const SCHEMA_VERSION: u32 = 1;
168+
143169
/// Create a new `SignerState` instance.
144170
/// This will create a new SQLite database at the given path
145171
/// or an in-memory database if the path is ":memory:"
146172
pub fn new(db_path: impl AsRef<Path>) -> Result<Self, DBError> {
147173
let connection = Self::connect(db_path)?;
148174

149-
let signer_db = Self { db: connection };
150-
151-
signer_db.instantiate_db()?;
175+
let mut signer_db = Self { db: connection };
176+
signer_db.create_or_migrate()?;
152177

153178
Ok(signer_db)
154179
}
155180

156-
fn instantiate_db(&self) -> Result<(), DBError> {
157-
if !table_exists(&self.db, "blocks")? {
158-
self.db.execute(CREATE_BLOCKS_TABLE, NO_PARAMS)?;
181+
/// Returns the schema version of the database
182+
fn get_schema_version(conn: &Connection) -> Result<u32, DBError> {
183+
if !table_exists(conn, "db_config")? {
184+
return Ok(0);
159185
}
186+
let result = conn
187+
.query_row("SELECT version FROM db_config LIMIT 1", [], |row| {
188+
row.get(0)
189+
})
190+
.optional();
191+
match result {
192+
Ok(x) => Ok(x.unwrap_or_else(|| 0)),
193+
Err(e) => Err(DBError::from(e)),
194+
}
195+
}
160196

161-
if !table_exists(&self.db, "signer_states")? {
162-
self.db.execute(CREATE_SIGNER_STATE_TABLE, NO_PARAMS)?;
197+
/// Migrate from schema 0 to schema 1
198+
fn schema_1_migration(tx: &Transaction) -> Result<(), DBError> {
199+
if Self::get_schema_version(tx)? >= 1 {
200+
// no migration necessary
201+
return Ok(());
163202
}
164203

165-
if !table_exists(&self.db, "burn_blocks")? {
166-
self.db.execute(CREATE_BURN_STATE_TABLE, NO_PARAMS)?;
204+
for statement in SCHEMA_1.iter() {
205+
tx.execute_batch(statement)?;
167206
}
168207

169-
self.db.execute_batch(CREATE_INDEXES)?;
208+
Ok(())
209+
}
170210

211+
/// Either instantiate a new database, or migrate an existing one
212+
/// If the detected version of the existing database is 0 (i.e., a pre-migration
213+
/// logic DB, the DB will be dropped).
214+
fn create_or_migrate(&mut self) -> Result<(), DBError> {
215+
let sql_tx = tx_begin_immediate(&mut self.db)?;
216+
loop {
217+
let version = Self::get_schema_version(&sql_tx)?;
218+
match version {
219+
0 => Self::schema_1_migration(&sql_tx)?,
220+
1 => break,
221+
x => return Err(DBError::Other(format!(
222+
"Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}",
223+
Self::SCHEMA_VERSION,
224+
))),
225+
}
226+
}
227+
sql_tx.commit()?;
171228
Ok(())
172229
}
173230

@@ -597,7 +654,7 @@ mod tests {
597654
let db_path = tmp_db_path();
598655
let db = SignerDb::new(db_path).expect("Failed to create signer db");
599656
assert_eq!(
600-
query_row(&db.db, "SELECT sqlite_version()", NO_PARAMS).unwrap(),
657+
query_row(&db.db, "SELECT sqlite_version()", []).unwrap(),
601658
Some("3.45.0".to_string())
602659
);
603660
}

0 commit comments

Comments
 (0)