diff --git a/src/database/sqlite/mod.rs b/src/database/sqlite/mod.rs index 60eca4e0..3c074625 100644 --- a/src/database/sqlite/mod.rs +++ b/src/database/sqlite/mod.rs @@ -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. diff --git a/src/database/sqlite/schema.rs b/src/database/sqlite/schema.rs index 9a5d6354..597d8114 100644 --- a/src/database/sqlite/schema.rs +++ b/src/database/sqlite/schema.rs @@ -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, } 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, + }) } } diff --git a/src/database/sqlite/utils.rs b/src/database/sqlite/utils.rs index 87b6555d..b808812f 100644 --- a/src/database/sqlite/utils.rs +++ b/src/database/sqlite/utils.rs @@ -134,3 +134,58 @@ pub fn create_fresh_db( Ok(()) } + +pub fn db_version(conn: &mut rusqlite::Connection) -> Result { + 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)), + } + } +}