diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs index a8061984f..9bcfca832 100644 --- a/src/database/sqlite.rs +++ b/src/database/sqlite.rs @@ -57,7 +57,17 @@ 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 + "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 @@ -88,7 +98,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, @@ -1096,4 +1106,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 + ); + } + } }