db: the interface to store the state of an ongoing rescan
We'll need to store in persistent storage if a rescan was requested by a user, and if so from what date. For the SQLite implementation we introduce a rescan_timestamp to the wallet table.
This commit is contained in:
parent
bd4de0b87a
commit
7e83bfad55
@ -53,6 +53,15 @@ pub trait DatabaseConnection {
|
||||
|
||||
fn increment_change_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>);
|
||||
|
||||
/// Get the timestamp at which to start rescaning from, if any.
|
||||
fn rescan_timestamp(&mut self) -> Option<u32>;
|
||||
|
||||
/// Set a timestamp at which to start rescaning the block chain from.
|
||||
fn set_rescan(&mut self, timestamp: u32);
|
||||
|
||||
/// Mark the rescan as complete.
|
||||
fn complete_rescan(&mut self);
|
||||
|
||||
/// Get the derivation index for this address, as well as whether this address is change.
|
||||
fn derivation_index_by_address(
|
||||
&mut self,
|
||||
@ -134,6 +143,18 @@ impl DatabaseConnection for SqliteConn {
|
||||
self.increment_change_index(secp)
|
||||
}
|
||||
|
||||
fn rescan_timestamp(&mut self) -> Option<u32> {
|
||||
self.db_wallet().rescan_timestamp
|
||||
}
|
||||
|
||||
fn set_rescan(&mut self, timestamp: u32) {
|
||||
self.set_wallet_rescan_timestamp(timestamp)
|
||||
}
|
||||
|
||||
fn complete_rescan(&mut self) {
|
||||
self.complete_wallet_rescan()
|
||||
}
|
||||
|
||||
fn coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin> {
|
||||
self.coins()
|
||||
.into_iter()
|
||||
|
||||
@ -21,7 +21,7 @@ use crate::{
|
||||
descriptors::MultipathDescriptor,
|
||||
};
|
||||
|
||||
use std::{convert::TryInto, fmt, io, path};
|
||||
use std::{cmp, convert::TryInto, fmt, io, path};
|
||||
|
||||
use miniscript::bitcoin::{
|
||||
self, consensus::encode, hashes::hex::ToHex, secp256k1,
|
||||
@ -272,6 +272,43 @@ impl SqliteConn {
|
||||
.expect("Database must be available")
|
||||
}
|
||||
|
||||
pub fn set_wallet_rescan_timestamp(&mut self, timestamp: u32) {
|
||||
db_exec(&mut self.conn, |db_tx| {
|
||||
// NOTE: this will need to be updated if we ever implement multi-wallet support
|
||||
db_tx
|
||||
.execute(
|
||||
"UPDATE wallets SET rescan_timestamp = (?1)",
|
||||
rusqlite::params![timestamp],
|
||||
)
|
||||
.map(|_| ())
|
||||
})
|
||||
.expect("Database must be available")
|
||||
}
|
||||
|
||||
/// Drop the rescan timestamp, and set it as the wallet creation timestamp if it
|
||||
/// predates it.
|
||||
///
|
||||
/// # Panics
|
||||
/// - If called while rescan_timestamp is not set
|
||||
pub fn complete_wallet_rescan(&mut self) {
|
||||
let db_wallet = self.db_wallet();
|
||||
let new_timestamp = cmp::min(
|
||||
db_wallet.rescan_timestamp.expect("Must be set"),
|
||||
db_wallet.timestamp,
|
||||
);
|
||||
|
||||
db_exec(&mut self.conn, |db_tx| {
|
||||
// NOTE: this will need to be updated if we ever implement multi-wallet support
|
||||
db_tx
|
||||
.execute(
|
||||
"UPDATE wallets SET timestamp = (?1), rescan_timestamp = NULL",
|
||||
rusqlite::params![new_timestamp],
|
||||
)
|
||||
.map(|_| ())
|
||||
})
|
||||
.expect("Database must be available");
|
||||
}
|
||||
|
||||
/// Get all the coins from DB.
|
||||
pub fn coins(&mut self) -> Vec<DbCoin> {
|
||||
db_query(
|
||||
@ -998,4 +1035,42 @@ mod tests {
|
||||
|
||||
fs::remove_dir_all(&tmp_dir).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn db_rescan() {
|
||||
let (tmp_dir, _, _, db) = dummy_db();
|
||||
|
||||
{
|
||||
let mut conn = db.connection().unwrap();
|
||||
|
||||
// At first no rescan is ongoing
|
||||
let dummy_timestamp = 1_001;
|
||||
let db_wallet = conn.db_wallet();
|
||||
assert!(db_wallet.rescan_timestamp.is_none());
|
||||
assert!(db_wallet.timestamp > dummy_timestamp);
|
||||
|
||||
// But if we set one there'll be
|
||||
conn.set_wallet_rescan_timestamp(dummy_timestamp);
|
||||
assert_eq!(conn.db_wallet().rescan_timestamp, Some(dummy_timestamp));
|
||||
|
||||
// Once it's done the rescan timestamp will be erased, and the
|
||||
// wallet timestamp will be set to the dummy timestamp since it's
|
||||
// lower.
|
||||
conn.complete_wallet_rescan();
|
||||
let db_wallet = conn.db_wallet();
|
||||
assert!(db_wallet.rescan_timestamp.is_none());
|
||||
assert_eq!(db_wallet.timestamp, dummy_timestamp);
|
||||
|
||||
// If we rescan from a later timestamp, we'll keep the existing
|
||||
// wallet timestamp afterward.
|
||||
conn.set_wallet_rescan_timestamp(dummy_timestamp + 1);
|
||||
assert_eq!(conn.db_wallet().rescan_timestamp, Some(dummy_timestamp + 1));
|
||||
conn.complete_wallet_rescan();
|
||||
let db_wallet = conn.db_wallet();
|
||||
assert!(db_wallet.rescan_timestamp.is_none());
|
||||
assert_eq!(db_wallet.timestamp, dummy_timestamp);
|
||||
}
|
||||
|
||||
fs::remove_dir_all(&tmp_dir).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,13 +22,19 @@ CREATE TABLE tip (
|
||||
|
||||
/* This stores metadata about our wallet. We only support single wallet for
|
||||
* now (and the foreseeable future).
|
||||
*
|
||||
* The 'timestamp' field is the creation date of the wallet. We guarantee to have seen all
|
||||
* information related to our descriptor(s) that occured after this date.
|
||||
* The optional 'rescan_timestamp' field is a the timestamp we need to rescan the chain
|
||||
* for events related to our descriptor(s) from.
|
||||
*/
|
||||
CREATE TABLE wallets (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
main_descriptor TEXT NOT NULL,
|
||||
deposit_derivation_index INTEGER NOT NULL,
|
||||
change_derivation_index INTEGER NOT NULL
|
||||
change_derivation_index INTEGER NOT NULL,
|
||||
rescan_timestamp INTEGER
|
||||
);
|
||||
|
||||
/* Our (U)TxOs.
|
||||
@ -109,6 +115,7 @@ pub struct DbWallet {
|
||||
pub main_descriptor: MultipathDescriptor,
|
||||
pub deposit_derivation_index: bip32::ChildNumber,
|
||||
pub change_derivation_index: bip32::ChildNumber,
|
||||
pub rescan_timestamp: Option<u32>,
|
||||
}
|
||||
|
||||
impl TryFrom<&rusqlite::Row<'_>> for DbWallet {
|
||||
@ -127,12 +134,15 @@ impl TryFrom<&rusqlite::Row<'_>> for DbWallet {
|
||||
let der_idx: u32 = row.get(4)?;
|
||||
let change_derivation_index = bip32::ChildNumber::from(der_idx);
|
||||
|
||||
let rescan_timestamp = row.get(5)?;
|
||||
|
||||
Ok(DbWallet {
|
||||
id,
|
||||
timestamp,
|
||||
main_descriptor,
|
||||
deposit_derivation_index,
|
||||
change_derivation_index,
|
||||
rescan_timestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,6 +266,18 @@ impl DatabaseConnection for DummyDbConn {
|
||||
fn rollback_tip(&mut self, _: &BlockChainTip) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn rescan_timestamp(&mut self) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
|
||||
fn set_rescan(&mut self, _: u32) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn complete_rescan(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DummyMinisafe {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user