From c51d544932a666851c39f79a517e2e0a1e783b05 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Sat, 26 Nov 2022 14:52:49 +0100 Subject: [PATCH 1/3] [wip] Ensure there are no duplicated script_pubkeys in sqlite Add a `UNIQUE` constraint on the script_pubkeys table so that it doesn't grow constantly when caching new addresses. Fixes #801 --- src/database/sqlite.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs index a8061984f..68285605f 100644 --- a/src/database/sqlite.rs +++ b/src/database/sqlite.rs @@ -57,7 +57,10 @@ static MIGRATIONS: &[&str] = &[ "CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB, is_spent BOOLEAN DEFAULT 0);", "INSERT INTO utxos SELECT value, keychain, vout, txid, script, is_spent FROM utxos_old;", "DROP TABLE utxos_old;", - "CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);" + "CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);", + // Fix issue https://github.com/bitcoindevkit/bdk/issues/801: drop duplicated script_pubkeys + // TODO "", + "CREATE UNIQUE INDEX idx_script_pks_unique ON script_pubkeys(keychain, child);", ]; /// Sqlite database stored on filesystem @@ -88,7 +91,7 @@ impl SqliteDatabase { child: u32, script: &[u8], ) -> Result { - let mut statement = self.connection.prepare_cached("INSERT INTO script_pubkeys (keychain, child, script) VALUES (:keychain, :child, :script)")?; + let mut statement = self.connection.prepare_cached("INSERT OR REPLACE INTO script_pubkeys (keychain, child, script) VALUES (:keychain, :child, :script)")?; statement.execute(named_params! { ":keychain": keychain, ":child": child, From 21c96c9c811337ba04a0f85b48a7dec58ba8b416 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Sat, 26 Nov 2022 15:11:09 +0100 Subject: [PATCH 2/3] Add test for issue #801 --- src/database/sqlite.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs index 68285605f..e837b6c4c 100644 --- a/src/database/sqlite.rs +++ b/src/database/sqlite.rs @@ -1099,4 +1099,44 @@ pub mod test { fn test_check_descriptor_checksum() { crate::database::test::test_check_descriptor_checksum(get_database()); } + + // Issue 801: https://github.com/bitcoindevkit/bdk/issues/801 + #[test] + fn test_unique_spks() { + use crate::bitcoin::hashes::hex::FromHex; + use crate::database::*; + + let mut db = get_database(); + + let script = Script::from( + Vec::::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(), + ); + let path = 42; + let keychain = KeychainKind::External; + + for _ in 0..100 { + db.set_script_pubkey(&script, keychain, path).unwrap(); + } + + let mut statement = db + .connection + .prepare_cached( + "select keychain,child,count(child) from script_pubkeys group by keychain,child;", + ) + .unwrap(); + let mut rows = statement.query([]).unwrap(); + while let Some(row) = rows.next().unwrap() { + let keychain: String = row.get(0).unwrap(); + let child: u32 = row.get(1).unwrap(); + let count: usize = row.get(2).unwrap(); + + assert!( + count == 1, + "keychain={}, child={}, count={}", + keychain, + child, + count + ); + } + } } From b5fcddcf1ab795b96481f3e46da9356e4f9c8b41 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 29 Nov 2022 07:20:49 -0800 Subject: [PATCH 3/3] Add sqlite migration to drop duplicated script_pubkeys rows --- src/database/sqlite.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs index e837b6c4c..9bcfca832 100644 --- a/src/database/sqlite.rs +++ b/src/database/sqlite.rs @@ -59,8 +59,15 @@ static MIGRATIONS: &[&str] = &[ "DROP TABLE utxos_old;", "CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);", // Fix issue https://github.com/bitcoindevkit/bdk/issues/801: drop duplicated script_pubkeys - // TODO "", + "ALTER TABLE script_pubkeys RENAME TO script_pubkeys_old;", + "DROP INDEX idx_keychain_child;", + "DROP INDEX idx_script;", + "CREATE TABLE script_pubkeys (keychain TEXT, child INTEGER, script BLOB);", + "CREATE INDEX idx_keychain_child ON script_pubkeys(keychain, child);", + "CREATE INDEX idx_script ON script_pubkeys(script);", "CREATE UNIQUE INDEX idx_script_pks_unique ON script_pubkeys(keychain, child);", + "INSERT OR REPLACE INTO script_pubkeys SELECT keychain, child, script FROM script_pubkeys_old;", + "DROP TABLE script_pubkeys_old;" ]; /// Sqlite database stored on filesystem