db: add a new 'updated_at' column to Spend transactions

Since this is our first modification to the database schema since the
first release of the software this also introduces migration logic for
existing databases.
This commit is contained in:
Antoine Poinsot 2023-03-29 14:57:41 +02:00
parent a402f85101
commit 29ae0a4a5e
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
3 changed files with 76 additions and 18 deletions

View File

@ -14,7 +14,10 @@ use crate::{
database::{
sqlite::{
schema::{DbAddress, DbCoin, DbSpendTransaction, DbTip, DbWallet},
utils::{create_fresh_db, db_exec, db_query, db_tx_query, LOOK_AHEAD_LIMIT},
utils::{
create_fresh_db, db_exec, db_query, db_tx_query, db_version, maybe_apply_migration,
LOOK_AHEAD_LIMIT,
},
},
Coin, CoinType,
},
@ -31,7 +34,7 @@ use miniscript::bitcoin::{
util::{bip32, psbt::PartiallySignedTransaction as Psbt},
};
const DB_VERSION: i64 = 0;
const DB_VERSION: i64 = 1;
#[derive(Debug)]
pub enum SqliteDbError {
@ -108,6 +111,9 @@ impl SqliteDb {
return Err(SqliteDbError::FileNotFound(db_path));
}
log::info!("Checking if the database needs upgrading.");
maybe_apply_migration(&db_path)?;
Ok(SqliteDb { db_path })
}
@ -126,8 +132,7 @@ impl SqliteDb {
) -> Result<(), SqliteDbError> {
let mut conn = self.connection()?;
// Check if there database isn't from the future.
// NOTE: we'll do migration there eventually. Until then be strict on the check.
// At this point any migration must have been applied.
let db_version = conn.db_version();
if db_version != DB_VERSION {
return Err(SqliteDbError::UnsupportedVersion(db_version));
@ -160,18 +165,7 @@ pub struct SqliteConn {
impl SqliteConn {
pub fn db_version(&mut self) -> i64 {
db_query(
&mut self.conn,
"SELECT version FROM version",
rusqlite::params![],
|row| {
let version: i64 = row.get(0)?;
Ok(version)
},
)
.expect("db must not fail")
.pop()
.expect("There is always a row in the version table")
db_version(&mut self.conn).expect("db must not fail")
}
/// Get the network tip.

View File

@ -74,7 +74,8 @@ CREATE TABLE addresses (
CREATE TABLE spend_transactions (
id INTEGER PRIMARY KEY NOT NULL,
psbt BLOB UNIQUE NOT NULL,
txid BLOB UNIQUE NOT NULL
txid BLOB UNIQUE NOT NULL,
updated_at INTEGER
);
";
@ -253,6 +254,7 @@ pub struct DbSpendTransaction {
pub id: i64,
pub psbt: Psbt,
pub txid: bitcoin::Txid,
pub updated_at: Option<u32>,
}
impl TryFrom<&rusqlite::Row<'_>> for DbSpendTransaction {
@ -268,6 +270,13 @@ impl TryFrom<&rusqlite::Row<'_>> for DbSpendTransaction {
let txid: bitcoin::Txid = encode::deserialize(&txid).expect("We only store valid txids");
assert_eq!(txid, psbt.unsigned_tx.txid());
Ok(DbSpendTransaction { id, psbt, txid })
let updated_at = row.get(3)?;
Ok(DbSpendTransaction {
id,
psbt,
txid,
updated_at,
})
}
}

View File

@ -134,3 +134,58 @@ pub fn create_fresh_db(
Ok(())
}
pub fn db_version(conn: &mut rusqlite::Connection) -> Result<i64, SqliteDbError> {
Ok(db_query(
conn,
"SELECT version FROM version",
rusqlite::params![],
|row| {
let version: i64 = row.get(0)?;
Ok(version)
},
)?
.pop()
.expect("There is always a row in the version table"))
}
// In Liana 0.4 we upgraded the schema to hold a timestamp for transaction drafts. Existing
// transaction drafts are not set any timestamp on purpose.
fn migrate_v0_to_v1(conn: &mut rusqlite::Connection) -> Result<(), SqliteDbError> {
db_exec(conn, |tx| {
tx.execute(
"ALTER TABLE spend_transactions ADD COLUMN updated_at",
rusqlite::params![],
)?;
tx.execute(
"UPDATE version SET version = 1",
rusqlite::params![],
)?;
Ok(())
})?;
Ok(())
}
/// Check the database version and if necessary apply the migrations to upgrade it to the current
/// one.
pub fn maybe_apply_migration(db_path: &path::Path) -> Result<(), SqliteDbError> {
let mut conn = rusqlite::Connection::open(db_path)?;
// Iteratively apply the database migrations necessary.
loop {
let version = db_version(&mut conn)?;
match version {
DB_VERSION => {
log::info!("Database is up to date.");
return Ok(());
}
0 => {
log::warn!("Upgrading database from version 0 to version 1.");
migrate_v0_to_v1(&mut conn)?;
log::warn!("Migration from database version 0 to version 1 successful.");
}
_ => return Err(SqliteDbError::UnsupportedVersion(version)),
}
}
}