diff --git a/src/database/any.rs b/src/database/any.rs index 5186452c..ba06e79a 100644 --- a/src/database/any.rs +++ b/src/database/any.rs @@ -144,6 +144,9 @@ impl BatchOperations for AnyDatabase { fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> { impl_inner_method!(AnyDatabase, self, set_last_index, keychain, value) } + fn set_last_sync_time(&mut self, last_sync_time: ConfirmationTime) -> Result<(), Error> { + impl_inner_method!(AnyDatabase, self, set_last_sync_time, last_sync_time) + } fn del_script_pubkey_from_path( &mut self, @@ -180,6 +183,9 @@ impl BatchOperations for AnyDatabase { fn del_last_index(&mut self, keychain: KeychainKind) -> Result, Error> { impl_inner_method!(AnyDatabase, self, del_last_index, keychain) } + fn del_last_sync_time(&mut self) -> Result, Error> { + impl_inner_method!(AnyDatabase, self, del_last_sync_time) + } } impl Database for AnyDatabase { @@ -241,6 +247,9 @@ impl Database for AnyDatabase { fn get_last_index(&self, keychain: KeychainKind) -> Result, Error> { impl_inner_method!(AnyDatabase, self, get_last_index, keychain) } + fn get_last_sync_time(&self) -> Result, Error> { + impl_inner_method!(AnyDatabase, self, get_last_sync_time) + } fn increment_last_index(&mut self, keychain: KeychainKind) -> Result { impl_inner_method!(AnyDatabase, self, increment_last_index, keychain) @@ -272,6 +281,9 @@ impl BatchOperations for AnyBatch { fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> { impl_inner_method!(AnyBatch, self, set_last_index, keychain, value) } + fn set_last_sync_time(&mut self, last_sync_time: ConfirmationTime) -> Result<(), Error> { + impl_inner_method!(AnyBatch, self, set_last_sync_time, last_sync_time) + } fn del_script_pubkey_from_path( &mut self, @@ -302,6 +314,9 @@ impl BatchOperations for AnyBatch { fn del_last_index(&mut self, keychain: KeychainKind) -> Result, Error> { impl_inner_method!(AnyBatch, self, del_last_index, keychain) } + fn del_last_sync_time(&mut self) -> Result, Error> { + impl_inner_method!(AnyBatch, self, del_last_sync_time) + } } impl BatchDatabase for AnyDatabase { diff --git a/src/database/keyvalue.rs b/src/database/keyvalue.rs index 2da92f22..b856b5c5 100644 --- a/src/database/keyvalue.rs +++ b/src/database/keyvalue.rs @@ -82,6 +82,13 @@ macro_rules! impl_batch_operations { Ok(()) } + fn set_last_sync_time(&mut self, ct: ConfirmationTime) -> Result<(), Error> { + let key = MapKey::LastSyncTime.as_map_key(); + self.insert(key, serde_json::to_vec(&ct)?)$($after_insert)*; + + Ok(()) + } + fn del_script_pubkey_from_path(&mut self, keychain: KeychainKind, path: u32) -> Result, Error> { let key = MapKey::Path((Some(keychain), Some(path))).as_map_key(); let res = self.remove(key); @@ -168,6 +175,14 @@ macro_rules! impl_batch_operations { } } } + + fn del_last_sync_time(&mut self) -> Result, Error> { + let key = MapKey::LastSyncTime.as_map_key(); + let res = self.remove(key); + let res = $process_delete!(res); + + Ok(res.map(|b| serde_json::from_slice(&b)).transpose()?) + } } } @@ -342,6 +357,14 @@ impl Database for Tree { .transpose() } + fn get_last_sync_time(&self) -> Result, Error> { + let key = MapKey::LastSyncTime.as_map_key(); + Ok(self + .get(key)? + .map(|b| serde_json::from_slice(&b)) + .transpose()?) + } + // inserts 0 if not present fn increment_last_index(&mut self, keychain: KeychainKind) -> Result { let key = MapKey::LastIndex(keychain).as_map_key(); @@ -470,4 +493,9 @@ mod test { fn test_last_index() { crate::database::test::test_last_index(get_tree()); } + + #[test] + fn test_last_sync_time() { + crate::database::test::test_last_sync_time(get_tree()); + } } diff --git a/src/database/memory.rs b/src/database/memory.rs index 78cc031d..21f00ed9 100644 --- a/src/database/memory.rs +++ b/src/database/memory.rs @@ -33,6 +33,7 @@ use crate::types::*; // transactions t -> tx details // deriv indexes c{i,e} -> u32 // descriptor checksum d{i,e} -> vec +// last sync time l -> { height, timestamp } pub(crate) enum MapKey<'a> { Path((Option, Option)), @@ -41,6 +42,7 @@ pub(crate) enum MapKey<'a> { RawTx(Option<&'a Txid>), Transaction(Option<&'a Txid>), LastIndex(KeychainKind), + LastSyncTime, DescriptorChecksum(KeychainKind), } @@ -59,6 +61,7 @@ impl MapKey<'_> { MapKey::RawTx(_) => b"r".to_vec(), MapKey::Transaction(_) => b"t".to_vec(), MapKey::LastIndex(st) => [b"c", st.as_ref()].concat(), + MapKey::LastSyncTime => b"l".to_vec(), MapKey::DescriptorChecksum(st) => [b"d", st.as_ref()].concat(), } } @@ -180,6 +183,12 @@ impl BatchOperations for MemoryDatabase { Ok(()) } + fn set_last_sync_time(&mut self, ct: ConfirmationTime) -> Result<(), Error> { + let key = MapKey::LastSyncTime.as_map_key(); + self.map.insert(key, Box::new(ct)); + + Ok(()) + } fn del_script_pubkey_from_path( &mut self, @@ -270,6 +279,13 @@ impl BatchOperations for MemoryDatabase { Some(b) => Ok(Some(*b.downcast_ref().unwrap())), } } + fn del_last_sync_time(&mut self) -> Result, Error> { + let key = MapKey::LastSyncTime.as_map_key(); + let res = self.map.remove(&key); + self.deleted_keys.push(key); + + Ok(res.map(|b| b.downcast_ref().cloned().unwrap())) + } } impl Database for MemoryDatabase { @@ -407,6 +423,14 @@ impl Database for MemoryDatabase { Ok(self.map.get(&key).map(|b| *b.downcast_ref().unwrap())) } + fn get_last_sync_time(&self) -> Result, Error> { + let key = MapKey::LastSyncTime.as_map_key(); + Ok(self + .map + .get(&key) + .map(|b| b.downcast_ref().cloned().unwrap())) + } + // inserts 0 if not present fn increment_last_index(&mut self, keychain: KeychainKind) -> Result { let key = MapKey::LastIndex(keychain).as_map_key(); @@ -590,4 +614,9 @@ mod test { fn test_last_index() { crate::database::test::test_last_index(get_tree()); } + + #[test] + fn test_last_sync_time() { + crate::database::test::test_last_sync_time(get_tree()); + } } diff --git a/src/database/mod.rs b/src/database/mod.rs index 4a3936f5..310dd192 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -64,6 +64,8 @@ pub trait BatchOperations { fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error>; /// Store the last derivation index for a given keychain. fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error>; + /// Store the sync time in terms of block height and timestamp + fn set_last_sync_time(&mut self, last_sync_time: ConfirmationTime) -> Result<(), Error>; /// Delete a script_pubkey given the keychain and its child number. fn del_script_pubkey_from_path( @@ -89,6 +91,10 @@ pub trait BatchOperations { ) -> Result, Error>; /// Delete the last derivation index for a keychain. fn del_last_index(&mut self, keychain: KeychainKind) -> Result, Error>; + /// Reset the last sync time to `None` + /// + /// Returns the removed value + fn del_last_sync_time(&mut self) -> Result, Error>; } /// Trait for reading data from a database @@ -134,6 +140,8 @@ pub trait Database: BatchOperations { fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result, Error>; /// Return the last defivation index for a keychain. fn get_last_index(&self, keychain: KeychainKind) -> Result, Error>; + /// Return the last sync time, if present + fn get_last_sync_time(&self) -> Result, Error>; /// Increment the last derivation index for a keychain and return it /// @@ -377,5 +385,23 @@ pub mod test { ); } + pub fn test_last_sync_time(mut tree: D) { + assert!(tree.get_last_sync_time().unwrap().is_none()); + + tree.set_last_sync_time(ConfirmationTime { + height: 100, + timestamp: 1000, + }) + .unwrap(); + + let extracted = tree.get_last_sync_time().unwrap(); + assert!(extracted.is_some()); + assert_eq!(extracted.as_ref().unwrap().height, 100); + assert_eq!(extracted.as_ref().unwrap().timestamp, 1000); + + tree.del_last_sync_time().unwrap(); + assert!(tree.get_last_sync_time().unwrap().is_none()); + } + // TODO: more tests... } diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs index 0dbaed44..813a8f9c 100644 --- a/src/database/sqlite.rs +++ b/src/database/sqlite.rs @@ -35,6 +35,7 @@ static MIGRATIONS: &[&str] = &[ "CREATE UNIQUE INDEX idx_indices_keychain ON last_derivation_indices(keychain);", "CREATE TABLE checksums (keychain TEXT, checksum BLOB);", "CREATE INDEX idx_checksums_keychain ON checksums(keychain);", + "CREATE TABLE last_sync_time (id INTEGER PRIMARY KEY, height INTEGER, timestamp INTEGER);" ]; /// Sqlite database stored on filesystem @@ -205,6 +206,19 @@ impl SqliteDatabase { Ok(()) } + fn update_last_sync_time(&self, ct: ConfirmationTime) -> Result { + let mut statement = self.connection.prepare_cached( + "INSERT INTO last_sync_time (id, height, timestamp) VALUES (0, :height, :timestamp) ON CONFLICT(id) DO UPDATE SET height=:height, timestamp=:timestamp WHERE id = 0", + )?; + + statement.execute(named_params! { + ":height": ct.height, + ":timestamp": ct.timestamp, + })?; + + Ok(self.connection.last_insert_rowid()) + } + fn select_script_pubkeys(&self) -> Result, Error> { let mut statement = self .connection @@ -487,6 +501,20 @@ impl SqliteDatabase { } } + fn select_last_sync_time(&self) -> Result, Error> { + let mut statement = self + .connection + .prepare_cached("SELECT height, timestamp FROM last_sync_time WHERE id = 0")?; + let mut rows = statement.query_map([], |row| { + Ok(ConfirmationTime { + height: row.get(0)?, + timestamp: row.get(1)?, + }) + })?; + + Ok(rows.next().transpose()?) + } + fn select_checksum_by_keychain(&self, keychain: String) -> Result>, Error> { let mut statement = self .connection @@ -563,6 +591,14 @@ impl SqliteDatabase { Ok(()) } + + fn delete_last_sync_time(&self) -> Result<(), Error> { + let mut statement = self + .connection + .prepare_cached("DELETE FROM last_sync_time WHERE id = 0")?; + statement.execute([])?; + Ok(()) + } } impl BatchOperations for SqliteDatabase { @@ -622,6 +658,11 @@ impl BatchOperations for SqliteDatabase { Ok(()) } + fn set_last_sync_time(&mut self, ct: ConfirmationTime) -> Result<(), Error> { + self.update_last_sync_time(ct)?; + Ok(()) + } + fn del_script_pubkey_from_path( &mut self, keychain: KeychainKind, @@ -707,6 +748,17 @@ impl BatchOperations for SqliteDatabase { None => Ok(None), } } + + fn del_last_sync_time(&mut self) -> Result, Error> { + match self.select_last_sync_time()? { + Some(value) => { + self.delete_last_sync_time()?; + + Ok(Some(value)) + } + None => Ok(None), + } + } } impl Database for SqliteDatabase { @@ -818,6 +870,10 @@ impl Database for SqliteDatabase { Ok(value) } + fn get_last_sync_time(&self) -> Result, Error> { + self.select_last_sync_time() + } + fn increment_last_index(&mut self, keychain: KeychainKind) -> Result { let keychain_string = serde_json::to_string(&keychain)?; match self.get_last_index(keychain)? { @@ -965,4 +1021,9 @@ pub mod test { fn test_last_index() { crate::database::test::test_last_index(get_database()); } + + #[test] + fn test_last_sync_time() { + crate::database::test::test_last_sync_time(get_database()); + } }