diff --git a/src/database/keyvalue.rs b/src/database/keyvalue.rs index f586ebeba..eb8f1633d 100644 --- a/src/database/keyvalue.rs +++ b/src/database/keyvalue.rs @@ -532,4 +532,9 @@ mod test { fn test_check_descriptor_checksum() { crate::database::test::test_check_descriptor_checksum(get_tree()); } + + #[test] + fn test_del_spent_utxos() { + crate::database::test::test_del_spent_utxos(get_tree()); + } } diff --git a/src/database/memory.rs b/src/database/memory.rs index 691e7eb16..2a845e1b2 100644 --- a/src/database/memory.rs +++ b/src/database/memory.rs @@ -687,4 +687,9 @@ mod test { fn test_check_descriptor_checksum() { crate::database::test::test_check_descriptor_checksum(get_tree()); } + + #[test] + fn test_del_spent_utxos() { + crate::database::test::test_del_spent_utxos(get_tree()); + } } diff --git a/src/database/mod.rs b/src/database/mod.rs index e3e2b3310..784760205 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -158,6 +158,33 @@ pub trait Database: BatchOperations { /// /// It should insert and return `0` if not present in the database fn increment_last_index(&mut self, keychain: KeychainKind) -> Result; + + /// Delete a list of spent utxos from database. Delete all spent utxos if list is `None`. + fn del_spent_utxos( + &mut self, + to_delete: Option>, + ) -> Result, Error> { + if let Some(to_delete) = to_delete { + let to_delete = to_delete + .iter() + .filter_map(|out| self.get_utxo(out).transpose()) + .collect::, _>>()?; + let deleted_utxos = to_delete + .iter() + .filter(|utxo| utxo.is_spent) + .filter_map(|out| self.del_utxo(&out.outpoint).transpose()) + .collect::, _>>()?; + Ok(deleted_utxos) + } else { + let deleted_utxos = self + .iter_utxos()? + .iter() + .filter(|utxo| utxo.is_spent) + .filter_map(|out| self.del_utxo(&out.outpoint).transpose()) + .collect::, _>>()?; + Ok(deleted_utxos) + } + } } /// Trait for a database that supports batch operations @@ -653,5 +680,135 @@ pub mod test { assert!(res.is_err()); } + pub fn test_del_spent_utxos(mut db: D) { + // Query database for utxos, to prove that it is empty + assert_eq!(db.iter_utxos().unwrap().len(), 0); + + let (first_utxo, _second_utxo, third_utxo, fourth_utxo) = + setup_del_spent_utxo_test(&mut db); + // test that insertion was successful + assert_eq!(db.iter_utxos().unwrap().len(), 4); + + // call del_spent_utxos with None + let mut res = db.del_spent_utxos(None).unwrap(); + res.sort_by(|a, b| a.txout.value.cmp(&b.txout.value)); + // verify that the spent ones has been deleted + assert_eq!( + res, + vec![first_utxo.clone(), third_utxo.clone(), fourth_utxo.clone()] + ); + assert_eq!(db.iter_utxos().unwrap().len(), 1); + // re-insert the deleted utxo into database + db.set_utxo(&first_utxo).unwrap(); + db.set_utxo(&third_utxo).unwrap(); + db.set_utxo(&fourth_utxo).unwrap(); + + // call del_spent_utxos with vector of utxos + let mut res = db + .del_spent_utxos(Some(vec![first_utxo.outpoint, third_utxo.outpoint])) + .unwrap(); + res.sort_by(|a, b| a.txout.value.cmp(&b.txout.value)); + assert_eq!(res, vec![first_utxo.clone(), third_utxo.clone()]); + let utxos = db.iter_utxos().unwrap(); + assert_eq!(utxos.len(), 2); + assert!(utxos.contains(&fourth_utxo)); + } + + fn setup_del_spent_utxo_test( + db: &mut D, + ) -> (LocalUtxo, LocalUtxo, LocalUtxo, LocalUtxo) { + // insert four utxos into database + let first_outpoint = OutPoint::from_str( + "c1b4e695098210a31fe02abffe9005cffc051bbe86ff33e173155bcbdc5821e3:0", + ) + .unwrap(); + let first_script = Script::from( + Vec::::from_hex("76a914db4d1141d0048b1ed15839d0b7a4c488cd368b0e88ac").unwrap(), + ); + let first_txout = TxOut { + value: 133742, + script_pubkey: first_script, + }; + let first_utxo = LocalUtxo { + txout: first_txout, + outpoint: first_outpoint, + keychain: KeychainKind::External, + is_spent: true, + }; + + db.set_utxo(&first_utxo).unwrap(); + + let second_outpoint = OutPoint::from_str( + "fc9e4f9c334d55c1dc535bd691a1c159b0f7314c54745522257a905e18a56779:1", + ) + .unwrap(); + + let second_script = Script::from( + Vec::::from_hex("76a914824d8a679134215d6d21d25bde3cc63f89ec92eb88ac").unwrap(), + ); + + let second_txout = TxOut { + value: 2257563, + script_pubkey: second_script, + }; + + let second_utxo = LocalUtxo { + txout: second_txout, + outpoint: second_outpoint, + keychain: KeychainKind::External, + is_spent: false, + }; + + db.set_utxo(&second_utxo).unwrap(); + + let third_outpoint = OutPoint::from_str( + "26e14e606105b250e64849cc14a484ce58f0f7f7064e662862001661b726427b:1", + ) + .unwrap(); + + let third_script = Script::from( + Vec::::from_hex("76a9140d5a9a6f7aae31ebb3b72bbfd05935de5765e0e688ac").unwrap(), + ); + + let third_txout = TxOut { + value: 1119096, + script_pubkey: third_script, + }; + + let third_utxo = LocalUtxo { + txout: third_txout, + outpoint: third_outpoint, + keychain: KeychainKind::External, + is_spent: true, + }; + + db.set_utxo(&third_utxo).unwrap(); + + let fourth_outpoint = OutPoint::from_str( + "d27cff02d45817a11ac01d0d93a2e439f2d643b5d8bc57f4f7f56aa571104e8c:0", + ) + .unwrap(); + + let fourth_script = Script::from( + Vec::::from_hex("76a914899490496bfd1228e7ad5b0e156f1fc50a8f6f7688ac").unwrap(), + ); + + let fourth_txout = TxOut { + value: 36662433, + script_pubkey: fourth_script, + }; + + let fourth_utxo = LocalUtxo { + txout: fourth_txout, + outpoint: fourth_outpoint, + keychain: KeychainKind::External, + is_spent: true, + }; + + db.set_utxo(&fourth_utxo).unwrap(); + + (first_utxo, second_utxo, third_utxo, fourth_utxo) + } + // TODO: more tests... } diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs index a8061984f..de6fdc722 100644 --- a/src/database/sqlite.rs +++ b/src/database/sqlite.rs @@ -1096,4 +1096,9 @@ pub mod test { fn test_check_descriptor_checksum() { crate::database::test::test_check_descriptor_checksum(get_database()); } + + #[test] + fn test_del_spent_utxos() { + crate::database::test::test_del_spent_utxos(get_database()); + } }