diff --git a/src/database/sqlite/mod.rs b/src/database/sqlite/mod.rs index fb3b2e1b..7fbafb3c 100644 --- a/src/database/sqlite/mod.rs +++ b/src/database/sqlite/mod.rs @@ -43,7 +43,7 @@ use miniscript::bitcoin::{ secp256k1, }; -const DB_VERSION: i64 = 5; +const DB_VERSION: i64 = 6; /// Last database version for which Bitcoin transactions were not stored in database. In practice /// this meant we relied on the bitcoind watchonly wallet to store them for us. @@ -371,6 +371,20 @@ impl SqliteConn { .expect("Database must be available"); } + // Sqlite supports i64 integers so we use u32 for the timestamp. + /// Set the last poll timestamp, where `timestamp` is seconds since UNIX epoch. + pub fn set_wallet_last_poll_timestamp(&mut self, timestamp: u32) -> Result<(), SqliteDbError> { + db_exec(&mut self.conn, |db_tx| { + db_tx + .execute( + "UPDATE wallets SET last_poll_timestamp = (?1) WHERE id = (?2)", + rusqlite::params![timestamp, WALLET_ID], + ) + .map(|_| ()) + }) + .map_err(SqliteDbError::Rusqlite) + } + /// Get all the coins from DB, optionally filtered by coin status and/or outpoint. pub fn coins( &mut self, @@ -2384,7 +2398,7 @@ CREATE TABLE labels ( } #[test] - fn v0_to_v5_migration() { + fn v0_to_v6_migration() { let secp = secp256k1::Secp256k1::verification_only(); // Create a database with version 0, using the old schema. @@ -2490,7 +2504,7 @@ CREATE TABLE labels ( { let mut conn = db.connection().unwrap(); let version = conn.db_version(); - assert_eq!(version, 5); + assert_eq!(version, 6); } // We should now be able to insert another PSBT, to query both, and the first PSBT must // have no associated timestamp. @@ -2552,11 +2566,19 @@ CREATE TABLE labels ( assert_eq!(db_labels[0].value, "hello"); } + // In v6, we can get and set the last poll timestamp. + { + let mut conn = db.connection().unwrap(); + assert!(conn.db_wallet().last_poll_timestamp.is_none()); + conn.set_wallet_last_poll_timestamp(1234567).unwrap(); + assert_eq!(conn.db_wallet().last_poll_timestamp, Some(1234567)); + } + fs::remove_dir_all(tmp_dir).unwrap(); } #[test] - fn v3_to_v5_migration() { + fn v3_to_v6_migration() { let secp = secp256k1::Secp256k1::verification_only(); // Create a database with version 3, using the old schema. @@ -2718,10 +2740,10 @@ CREATE TABLE labels ( // Migrate the DB. maybe_apply_migration(&db_path, &bitcoin_txs).unwrap(); - assert_eq!(conn.db_version(), 5); + assert_eq!(conn.db_version(), 6); // Migrating twice will be a no-op. No need to pass `bitcoin_txs` second time. maybe_apply_migration(&db_path, &[]).unwrap(); - assert!(conn.db_version() == 5); + assert!(conn.db_version() == 6); let coins_post = conn.coins(&[], &[]); assert_eq!(coins_pre, coins_post); } diff --git a/src/database/sqlite/schema.rs b/src/database/sqlite/schema.rs index 456e45cb..7a61e307 100644 --- a/src/database/sqlite/schema.rs +++ b/src/database/sqlite/schema.rs @@ -30,7 +30,8 @@ CREATE TABLE wallets ( main_descriptor TEXT NOT NULL, deposit_derivation_index INTEGER NOT NULL, change_derivation_index INTEGER NOT NULL, - rescan_timestamp INTEGER + rescan_timestamp INTEGER, + last_poll_timestamp INTEGER ); /* Our (U)TxOs. @@ -140,6 +141,7 @@ pub struct DbWallet { pub deposit_derivation_index: bip32::ChildNumber, pub change_derivation_index: bip32::ChildNumber, pub rescan_timestamp: Option, + pub last_poll_timestamp: Option, } impl TryFrom<&rusqlite::Row<'_>> for DbWallet { @@ -159,6 +161,7 @@ impl TryFrom<&rusqlite::Row<'_>> for DbWallet { let change_derivation_index = bip32::ChildNumber::from(der_idx); let rescan_timestamp = row.get(5)?; + let last_poll_timestamp = row.get(6)?; Ok(DbWallet { id, @@ -167,6 +170,7 @@ impl TryFrom<&rusqlite::Row<'_>> for DbWallet { deposit_derivation_index, change_derivation_index, rescan_timestamp, + last_poll_timestamp, }) } } diff --git a/src/database/sqlite/utils.rs b/src/database/sqlite/utils.rs index c3426cc1..95fc984b 100644 --- a/src/database/sqlite/utils.rs +++ b/src/database/sqlite/utils.rs @@ -294,6 +294,19 @@ fn migrate_v4_to_v5( Ok(()) } +fn migrate_v5_to_v6(conn: &mut rusqlite::Connection) -> Result<(), SqliteDbError> { + db_exec(conn, |tx| { + tx.execute( + "ALTER TABLE wallets ADD COLUMN last_poll_timestamp INTEGER", + rusqlite::params![], + )?; + tx.execute("UPDATE version SET version = 6", rusqlite::params![])?; + Ok(()) + })?; + + Ok(()) +} + /// Check the database version and if necessary apply the migrations to upgrade it to the current /// one. The `bitcoin_txs` parameter is here for the migration from versions 4 and earlier, which /// did not store the Bitcoin transactions in database, to versions 5 and later, which do. For a @@ -342,6 +355,11 @@ pub fn maybe_apply_migration( migrate_v4_to_v5(&mut conn, bitcoin_txs)?; log::warn!("Migration from database version 4 to version 5 successful."); } + 5 => { + log::warn!("Upgrading database from version 5 to version 6."); + migrate_v5_to_v6(&mut conn)?; + log::warn!("Migration from database version 5 to version 6 successful."); + } _ => return Err(SqliteDbError::UnsupportedVersion(version)), } }