]> Untitled Git - bdk/commitdiff
[wallet] Allow limiting the use of internal utxos in TxBuilder
authorAlekos Filini <alekos.filini@gmail.com>
Fri, 7 Aug 2020 17:40:13 +0000 (19:40 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Mon, 10 Aug 2020 15:18:13 +0000 (17:18 +0200)
src/blockchain/utils.rs
src/database/keyvalue.rs
src/database/memory.rs
src/database/mod.rs
src/types.rs
src/wallet/coin_selection.rs
src/wallet/mod.rs
src/wallet/tx_builder.rs

index f45ec03789cbec8d6e2d522c07deb6e1213d8136..6e52ed93f4f25ea65917700421f30b284fd86567 100644 (file)
@@ -236,6 +236,7 @@ pub trait ElectrumLikeSync {
                 updates.set_utxo(&UTXO {
                     outpoint: OutPoint::new(tx.txid(), i as u32),
                     txout: output.clone(),
+                    is_internal: script_type.is_internal(),
                 })?;
                 incoming += output.value;
 
index 06c55a108e9f79bb743ba6d78b40a161546b4469..d79c0d8e641055f5cbe0ca3f82b7f15b7c2fb9b2 100644 (file)
@@ -29,8 +29,11 @@ macro_rules! impl_batch_operations {
 
         fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> {
             let key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key();
-            let value = serialize(&utxo.txout);
-            self.insert(key, value)$($after_insert)*;
+            let value = json!({
+                "t": utxo.txout,
+                "i": utxo.is_internal,
+            });
+            self.insert(key, serde_json::to_vec(&value)?)$($after_insert)*;
 
             Ok(())
         }
@@ -101,8 +104,11 @@ macro_rules! impl_batch_operations {
             match res {
                 None => Ok(None),
                 Some(b) => {
-                    let txout = deserialize(&b)?;
-                    Ok(Some(UTXO { outpoint: outpoint.clone(), txout }))
+                    let mut val: serde_json::Value = serde_json::from_slice(&b)?;
+                    let txout = serde_json::from_value(val["t"].take())?;
+                    let is_internal = serde_json::from_value(val["i"].take())?;
+
+                    Ok(Some(UTXO { outpoint: outpoint.clone(), txout, is_internal }))
                 }
             }
         }
@@ -210,8 +216,16 @@ impl Database for Tree {
             .map(|x| -> Result<_, Error> {
                 let (k, v) = x?;
                 let outpoint = deserialize(&k[1..])?;
-                let txout = deserialize(&v)?;
-                Ok(UTXO { outpoint, txout })
+
+                let mut val: serde_json::Value = serde_json::from_slice(&v)?;
+                let txout = serde_json::from_value(val["t"].take())?;
+                let is_internal = serde_json::from_value(val["i"].take())?;
+
+                Ok(UTXO {
+                    outpoint,
+                    txout,
+                    is_internal,
+                })
             })
             .collect()
     }
@@ -271,10 +285,14 @@ impl Database for Tree {
         let key = MapKey::UTXO(Some(outpoint)).as_map_key();
         self.get(key)?
             .map(|b| -> Result<_, Error> {
-                let txout = deserialize(&b)?;
+                let mut val: serde_json::Value = serde_json::from_slice(&b)?;
+                let txout = serde_json::from_value(val["t"].take())?;
+                let is_internal = serde_json::from_value(val["i"].take())?;
+
                 Ok(UTXO {
                     outpoint: outpoint.clone(),
                     txout,
+                    is_internal,
                 })
             })
             .transpose()
@@ -354,18 +372,11 @@ impl BatchDatabase for Tree {
 
 #[cfg(test)]
 mod test {
-    use std::str::FromStr;
     use std::sync::{Arc, Condvar, Mutex, Once};
     use std::time::{SystemTime, UNIX_EPOCH};
 
     use sled::{Db, Tree};
 
-    use bitcoin::consensus::encode::deserialize;
-    use bitcoin::hashes::hex::*;
-    use bitcoin::*;
-
-    use crate::database::*;
-
     static mut COUNT: usize = 0;
 
     lazy_static! {
@@ -406,185 +417,41 @@ mod test {
 
     #[test]
     fn test_script_pubkey() {
-        let mut tree = get_tree();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let script_type = ScriptType::External;
-
-        tree.set_script_pubkey(&script, script_type, path).unwrap();
-
-        assert_eq!(
-            tree.get_script_pubkey_from_path(script_type, path).unwrap(),
-            Some(script.clone())
-        );
-        assert_eq!(
-            tree.get_path_from_script_pubkey(&script).unwrap(),
-            Some((script_type, path.clone()))
-        );
+        crate::database::test::test_script_pubkey(get_tree());
     }
 
     #[test]
     fn test_batch_script_pubkey() {
-        let mut tree = get_tree();
-        let mut batch = tree.begin_batch();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let script_type = ScriptType::External;
-
-        batch.set_script_pubkey(&script, script_type, path).unwrap();
-
-        assert_eq!(
-            tree.get_script_pubkey_from_path(script_type, path).unwrap(),
-            None
-        );
-        assert_eq!(tree.get_path_from_script_pubkey(&script).unwrap(), None);
-
-        tree.commit_batch(batch).unwrap();
-
-        assert_eq!(
-            tree.get_script_pubkey_from_path(script_type, path).unwrap(),
-            Some(script.clone())
-        );
-        assert_eq!(
-            tree.get_path_from_script_pubkey(&script).unwrap(),
-            Some((script_type, path.clone()))
-        );
+        crate::database::test::test_batch_script_pubkey(get_tree());
     }
 
     #[test]
     fn test_iter_script_pubkey() {
-        let mut tree = get_tree();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let script_type = ScriptType::External;
-
-        tree.set_script_pubkey(&script, script_type, path).unwrap();
-
-        assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
+        crate::database::test::test_iter_script_pubkey(get_tree());
     }
 
     #[test]
     fn test_del_script_pubkey() {
-        let mut tree = get_tree();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let script_type = ScriptType::External;
-
-        tree.set_script_pubkey(&script, script_type, path).unwrap();
-        assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
-
-        tree.del_script_pubkey_from_path(script_type, path).unwrap();
-        assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 0);
+        crate::database::test::test_del_script_pubkey(get_tree());
     }
 
     #[test]
     fn test_utxo() {
-        let mut tree = get_tree();
-
-        let outpoint = OutPoint::from_str(
-            "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:0",
-        )
-        .unwrap();
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let txout = TxOut {
-            value: 133742,
-            script_pubkey: script,
-        };
-        let utxo = UTXO { txout, outpoint };
-
-        tree.set_utxo(&utxo).unwrap();
-
-        assert_eq!(tree.get_utxo(&outpoint).unwrap(), Some(utxo));
+        crate::database::test::test_utxo(get_tree());
     }
 
     #[test]
     fn test_raw_tx() {
-        let mut tree = get_tree();
-
-        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
-        let tx: Transaction = deserialize(&hex_tx).unwrap();
-
-        tree.set_raw_tx(&tx).unwrap();
-
-        let txid = tx.txid();
-
-        assert_eq!(tree.get_raw_tx(&txid).unwrap(), Some(tx));
+        crate::database::test::test_raw_tx(get_tree());
     }
 
     #[test]
     fn test_tx() {
-        let mut tree = get_tree();
-
-        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
-        let tx: Transaction = deserialize(&hex_tx).unwrap();
-        let txid = tx.txid();
-        let mut tx_details = TransactionDetails {
-            transaction: Some(tx),
-            txid,
-            timestamp: 123456,
-            received: 1337,
-            sent: 420420,
-            height: Some(1000),
-        };
-
-        tree.set_tx(&tx_details).unwrap();
-
-        // get with raw tx too
-        assert_eq!(
-            tree.get_tx(&tx_details.txid, true).unwrap(),
-            Some(tx_details.clone())
-        );
-        // get only raw_tx
-        assert_eq!(
-            tree.get_raw_tx(&tx_details.txid).unwrap(),
-            tx_details.transaction
-        );
-
-        // now get without raw_tx
-        tx_details.transaction = None;
-        assert_eq!(
-            tree.get_tx(&tx_details.txid, false).unwrap(),
-            Some(tx_details)
-        );
+        crate::database::test::test_tx(get_tree());
     }
 
     #[test]
     fn test_last_index() {
-        let mut tree = get_tree();
-
-        tree.set_last_index(ScriptType::External, 1337).unwrap();
-
-        assert_eq!(
-            tree.get_last_index(ScriptType::External).unwrap(),
-            Some(1337)
-        );
-        assert_eq!(tree.get_last_index(ScriptType::Internal).unwrap(), None);
-
-        let res = tree.increment_last_index(ScriptType::External).unwrap();
-        assert_eq!(res, 1338);
-        let res = tree.increment_last_index(ScriptType::Internal).unwrap();
-        assert_eq!(res, 0);
-
-        assert_eq!(
-            tree.get_last_index(ScriptType::External).unwrap(),
-            Some(1338)
-        );
-        assert_eq!(tree.get_last_index(ScriptType::Internal).unwrap(), Some(0));
+        crate::database::test::test_last_index(get_tree());
     }
-
-    // TODO: more tests...
 }
index b17bd54991ff20977c9cc4aeba514d720a8eed3b..987aa7b68421fd9283510139e5e9f3ff19ba8e25 100644 (file)
@@ -113,7 +113,8 @@ impl BatchOperations for MemoryDatabase {
 
     fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> {
         let key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key();
-        self.map.insert(key, Box::new(utxo.txout.clone()));
+        self.map
+            .insert(key, Box::new((utxo.txout.clone(), utxo.is_internal)));
 
         Ok(())
     }
@@ -184,10 +185,11 @@ impl BatchOperations for MemoryDatabase {
         match res {
             None => Ok(None),
             Some(b) => {
-                let txout = b.downcast_ref().cloned().unwrap();
+                let (txout, is_internal) = b.downcast_ref().cloned().unwrap();
                 Ok(Some(UTXO {
                     outpoint: outpoint.clone(),
                     txout,
+                    is_internal,
                 }))
             }
         }
@@ -274,8 +276,12 @@ impl Database for MemoryDatabase {
             .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key))))
             .map(|(k, v)| {
                 let outpoint = deserialize(&k[1..]).unwrap();
-                let txout = v.downcast_ref().cloned().unwrap();
-                Ok(UTXO { outpoint, txout })
+                let (txout, is_internal) = v.downcast_ref().cloned().unwrap();
+                Ok(UTXO {
+                    outpoint,
+                    txout,
+                    is_internal,
+                })
             })
             .collect()
     }
@@ -333,10 +339,11 @@ impl Database for MemoryDatabase {
     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> {
         let key = MapKey::UTXO(Some(outpoint)).as_map_key();
         Ok(self.map.get(&key).map(|b| {
-            let txout = b.downcast_ref().cloned().unwrap();
+            let (txout, is_internal) = b.downcast_ref().cloned().unwrap();
             UTXO {
                 outpoint: outpoint.clone(),
                 txout,
+                is_internal,
             }
         }))
     }
@@ -399,14 +406,7 @@ impl BatchDatabase for MemoryDatabase {
 
 #[cfg(test)]
 mod test {
-    use std::str::FromStr;
-
-    use bitcoin::consensus::encode::deserialize;
-    use bitcoin::hashes::hex::*;
-    use bitcoin::*;
-
-    use super::*;
-    use crate::database::*;
+    use super::MemoryDatabase;
 
     fn get_tree() -> MemoryDatabase {
         MemoryDatabase::new()
@@ -414,209 +414,41 @@ mod test {
 
     #[test]
     fn test_script_pubkey() {
-        let mut tree = get_tree();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let script_type = ScriptType::External;
-
-        tree.set_script_pubkey(&script, script_type, path).unwrap();
-
-        assert_eq!(
-            tree.get_script_pubkey_from_path(script_type, path).unwrap(),
-            Some(script.clone())
-        );
-        assert_eq!(
-            tree.get_path_from_script_pubkey(&script).unwrap(),
-            Some((script_type, path.clone()))
-        );
+        crate::database::test::test_script_pubkey(get_tree());
     }
 
     #[test]
     fn test_batch_script_pubkey() {
-        let mut tree = get_tree();
-        let mut batch = tree.begin_batch();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let script_type = ScriptType::External;
-
-        batch.set_script_pubkey(&script, script_type, path).unwrap();
-
-        assert_eq!(
-            tree.get_script_pubkey_from_path(script_type, path).unwrap(),
-            None
-        );
-        assert_eq!(tree.get_path_from_script_pubkey(&script).unwrap(), None);
-
-        tree.commit_batch(batch).unwrap();
-
-        assert_eq!(
-            tree.get_script_pubkey_from_path(script_type, path).unwrap(),
-            Some(script.clone())
-        );
-        assert_eq!(
-            tree.get_path_from_script_pubkey(&script).unwrap(),
-            Some((script_type, path))
-        );
+        crate::database::test::test_batch_script_pubkey(get_tree());
     }
 
     #[test]
     fn test_iter_script_pubkey() {
-        let mut tree = get_tree();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let script_type = ScriptType::External;
-
-        tree.set_script_pubkey(&script, script_type, path).unwrap();
-
-        assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
+        crate::database::test::test_iter_script_pubkey(get_tree());
     }
 
     #[test]
     fn test_del_script_pubkey() {
-        let mut tree = get_tree();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let script_type = ScriptType::External;
-
-        tree.set_script_pubkey(&script, script_type, path).unwrap();
-        assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
-
-        tree.del_script_pubkey_from_path(script_type, path).unwrap();
-        assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 0);
-    }
-
-    #[test]
-    fn test_del_script_pubkey_batch() {
-        let mut tree = get_tree();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let script_type = ScriptType::External;
-
-        tree.set_script_pubkey(&script, script_type, path).unwrap();
-        assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
-
-        let mut batch = tree.begin_batch();
-        batch
-            .del_script_pubkey_from_path(script_type, path)
-            .unwrap();
-
-        assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
-
-        tree.commit_batch(batch).unwrap();
-        assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 0);
+        crate::database::test::test_del_script_pubkey(get_tree());
     }
 
     #[test]
     fn test_utxo() {
-        let mut tree = get_tree();
-
-        let outpoint = OutPoint::from_str(
-            "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:0",
-        )
-        .unwrap();
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let txout = TxOut {
-            value: 133742,
-            script_pubkey: script,
-        };
-        let utxo = UTXO { txout, outpoint };
-
-        tree.set_utxo(&utxo).unwrap();
-
-        assert_eq!(tree.get_utxo(&outpoint).unwrap(), Some(utxo));
+        crate::database::test::test_utxo(get_tree());
     }
 
     #[test]
     fn test_raw_tx() {
-        let mut tree = get_tree();
-
-        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
-        let tx: Transaction = deserialize(&hex_tx).unwrap();
-
-        tree.set_raw_tx(&tx).unwrap();
-
-        let txid = tx.txid();
-
-        assert_eq!(tree.get_raw_tx(&txid).unwrap(), Some(tx));
+        crate::database::test::test_raw_tx(get_tree());
     }
 
     #[test]
     fn test_tx() {
-        let mut tree = get_tree();
-
-        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
-        let tx: Transaction = deserialize(&hex_tx).unwrap();
-        let txid = tx.txid();
-        let mut tx_details = TransactionDetails {
-            transaction: Some(tx),
-            txid,
-            timestamp: 123456,
-            received: 1337,
-            sent: 420420,
-            height: Some(1000),
-        };
-
-        tree.set_tx(&tx_details).unwrap();
-
-        // get with raw tx too
-        assert_eq!(
-            tree.get_tx(&tx_details.txid, true).unwrap(),
-            Some(tx_details.clone())
-        );
-        // get only raw_tx
-        assert_eq!(
-            tree.get_raw_tx(&tx_details.txid).unwrap(),
-            tx_details.transaction
-        );
-
-        // now get without raw_tx
-        tx_details.transaction = None;
-        assert_eq!(
-            tree.get_tx(&tx_details.txid, false).unwrap(),
-            Some(tx_details)
-        );
+        crate::database::test::test_tx(get_tree());
     }
 
     #[test]
     fn test_last_index() {
-        let mut tree = get_tree();
-
-        tree.set_last_index(ScriptType::External, 1337).unwrap();
-
-        assert_eq!(
-            tree.get_last_index(ScriptType::External).unwrap(),
-            Some(1337)
-        );
-        assert_eq!(tree.get_last_index(ScriptType::Internal).unwrap(), None);
-
-        let res = tree.increment_last_index(ScriptType::External).unwrap();
-        assert_eq!(res, 1338);
-        let res = tree.increment_last_index(ScriptType::Internal).unwrap();
-        assert_eq!(res, 0);
-
-        assert_eq!(
-            tree.get_last_index(ScriptType::External).unwrap(),
-            Some(1338)
-        );
-        assert_eq!(tree.get_last_index(ScriptType::Internal).unwrap(), Some(0));
+        crate::database::test::test_last_index(get_tree());
     }
-
-    // TODO: more tests...
 }
index 8fc0c93a25d5790aa1ff1adf9d34d0c9fa605b2e..9c0b52352d40c77439639627b301b05cab82377b 100644 (file)
@@ -106,3 +106,179 @@ pub trait DatabaseUtils: Database {
 }
 
 impl<T: Database> DatabaseUtils for T {}
+
+#[cfg(test)]
+pub mod test {
+    use std::str::FromStr;
+
+    use bitcoin::consensus::encode::deserialize;
+    use bitcoin::hashes::hex::*;
+    use bitcoin::*;
+
+    use super::*;
+
+    pub fn test_script_pubkey<D: Database>(mut tree: D) {
+        let script = Script::from(
+            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
+        );
+        let path = 42;
+        let script_type = ScriptType::External;
+
+        tree.set_script_pubkey(&script, script_type, path).unwrap();
+
+        assert_eq!(
+            tree.get_script_pubkey_from_path(script_type, path).unwrap(),
+            Some(script.clone())
+        );
+        assert_eq!(
+            tree.get_path_from_script_pubkey(&script).unwrap(),
+            Some((script_type, path.clone()))
+        );
+    }
+
+    pub fn test_batch_script_pubkey<D: BatchDatabase>(mut tree: D) {
+        let mut batch = tree.begin_batch();
+
+        let script = Script::from(
+            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
+        );
+        let path = 42;
+        let script_type = ScriptType::External;
+
+        batch.set_script_pubkey(&script, script_type, path).unwrap();
+
+        assert_eq!(
+            tree.get_script_pubkey_from_path(script_type, path).unwrap(),
+            None
+        );
+        assert_eq!(tree.get_path_from_script_pubkey(&script).unwrap(), None);
+
+        tree.commit_batch(batch).unwrap();
+
+        assert_eq!(
+            tree.get_script_pubkey_from_path(script_type, path).unwrap(),
+            Some(script.clone())
+        );
+        assert_eq!(
+            tree.get_path_from_script_pubkey(&script).unwrap(),
+            Some((script_type, path.clone()))
+        );
+    }
+
+    pub fn test_iter_script_pubkey<D: Database>(mut tree: D) {
+        let script = Script::from(
+            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
+        );
+        let path = 42;
+        let script_type = ScriptType::External;
+
+        tree.set_script_pubkey(&script, script_type, path).unwrap();
+
+        assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
+    }
+
+    pub fn test_del_script_pubkey<D: Database>(mut tree: D) {
+        let script = Script::from(
+            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
+        );
+        let path = 42;
+        let script_type = ScriptType::External;
+
+        tree.set_script_pubkey(&script, script_type, path).unwrap();
+        assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
+
+        tree.del_script_pubkey_from_path(script_type, path).unwrap();
+        assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 0);
+    }
+
+    pub fn test_utxo<D: Database>(mut tree: D) {
+        let outpoint = OutPoint::from_str(
+            "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:0",
+        )
+        .unwrap();
+        let script = Script::from(
+            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
+        );
+        let txout = TxOut {
+            value: 133742,
+            script_pubkey: script,
+        };
+        let utxo = UTXO {
+            txout,
+            outpoint,
+            is_internal: false,
+        };
+
+        tree.set_utxo(&utxo).unwrap();
+
+        assert_eq!(tree.get_utxo(&outpoint).unwrap(), Some(utxo));
+    }
+
+    pub fn test_raw_tx<D: Database>(mut tree: D) {
+        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
+        let tx: Transaction = deserialize(&hex_tx).unwrap();
+
+        tree.set_raw_tx(&tx).unwrap();
+
+        let txid = tx.txid();
+
+        assert_eq!(tree.get_raw_tx(&txid).unwrap(), Some(tx));
+    }
+
+    pub fn test_tx<D: Database>(mut tree: D) {
+        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
+        let tx: Transaction = deserialize(&hex_tx).unwrap();
+        let txid = tx.txid();
+        let mut tx_details = TransactionDetails {
+            transaction: Some(tx),
+            txid,
+            timestamp: 123456,
+            received: 1337,
+            sent: 420420,
+            height: Some(1000),
+        };
+
+        tree.set_tx(&tx_details).unwrap();
+
+        // get with raw tx too
+        assert_eq!(
+            tree.get_tx(&tx_details.txid, true).unwrap(),
+            Some(tx_details.clone())
+        );
+        // get only raw_tx
+        assert_eq!(
+            tree.get_raw_tx(&tx_details.txid).unwrap(),
+            tx_details.transaction
+        );
+
+        // now get without raw_tx
+        tx_details.transaction = None;
+        assert_eq!(
+            tree.get_tx(&tx_details.txid, false).unwrap(),
+            Some(tx_details)
+        );
+    }
+
+    pub fn test_last_index<D: Database>(mut tree: D) {
+        tree.set_last_index(ScriptType::External, 1337).unwrap();
+
+        assert_eq!(
+            tree.get_last_index(ScriptType::External).unwrap(),
+            Some(1337)
+        );
+        assert_eq!(tree.get_last_index(ScriptType::Internal).unwrap(), None);
+
+        let res = tree.increment_last_index(ScriptType::External).unwrap();
+        assert_eq!(res, 1338);
+        let res = tree.increment_last_index(ScriptType::Internal).unwrap();
+        assert_eq!(res, 0);
+
+        assert_eq!(
+            tree.get_last_index(ScriptType::External).unwrap(),
+            Some(1338)
+        );
+        assert_eq!(tree.get_last_index(ScriptType::Internal).unwrap(), Some(0));
+    }
+
+    // TODO: more tests...
+}
index a334231119ef7504c3f8ec4f27811b1684c6d3fe..dff48f9649cacbbe5e6e5973c82cafd4498325c3 100644 (file)
@@ -19,6 +19,10 @@ impl ScriptType {
             ScriptType::Internal => 'i' as u8,
         }
     }
+
+    pub fn is_internal(&self) -> bool {
+        self == &ScriptType::Internal
+    }
 }
 
 impl AsRef<[u8]> for ScriptType {
@@ -34,6 +38,7 @@ impl AsRef<[u8]> for ScriptType {
 pub struct UTXO {
     pub outpoint: OutPoint,
     pub txout: TxOut,
+    pub is_internal: bool,
 }
 
 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
index fc7211e1e54fc2ce78e2c98376e151e4bf8aa8b3..ed7ba9eb0050595ee84bdd692bd27ce20c5b1411 100644 (file)
@@ -109,6 +109,7 @@ mod test {
                     value: 100_000,
                     script_pubkey: Script::new(),
                 },
+                is_internal: false,
             },
             UTXO {
                 outpoint: OutPoint::from_str(
@@ -119,6 +120,7 @@ mod test {
                     value: 200_000,
                     script_pubkey: Script::new(),
                 },
+                is_internal: true,
             },
         ]
     }
index fe0f2b9b869fb988036787b897617372cbbb1a5c..b6cd853217e882c4bd4724803a162504a76c0250 100644 (file)
@@ -217,8 +217,12 @@ where
                 .max_satisfaction_weight(),
         );
 
-        let (available_utxos, use_all_utxos) =
-            self.get_available_utxos(&builder.utxos, &builder.unspendable, builder.send_all)?;
+        let (available_utxos, use_all_utxos) = self.get_available_utxos(
+            builder.change_policy,
+            &builder.utxos,
+            &builder.unspendable,
+            builder.send_all,
+        )?;
         let coin_selection::CoinSelectionResult {
             txin,
             total_amount,
@@ -646,11 +650,11 @@ where
 
     fn get_available_utxos(
         &self,
+        change_policy: tx_builder::ChangeSpendPolicy,
         utxo: &Option<Vec<OutPoint>>,
         unspendable: &Option<Vec<OutPoint>>,
         send_all: bool,
     ) -> Result<(Vec<UTXO>, bool), Error> {
-        // TODO: should we consider unconfirmed received rbf txs as "unspendable" too by default?
         let unspendable_set = match unspendable {
             None => HashSet::new(),
             Some(vec) => vec.into_iter().collect(),
@@ -660,25 +664,28 @@ where
             // with manual coin selection we always want to spend all the selected utxos, no matter
             // what (even if they are marked as unspendable)
             Some(raw_utxos) => {
-                // TODO: unwrap to remove
-                let full_utxos: Vec<_> = raw_utxos
+                let full_utxos = raw_utxos
                     .iter()
-                    .map(|u| self.database.borrow().get_utxo(&u).unwrap())
-                    .collect();
+                    .map(|u| self.database.borrow().get_utxo(&u))
+                    .collect::<Result<Vec<_>, _>>()?;
                 if !full_utxos.iter().all(|u| u.is_some()) {
                     return Err(Error::UnknownUTXO);
                 }
 
                 Ok((full_utxos.into_iter().map(|x| x.unwrap()).collect(), true))
             }
-            // otherwise limit ourselves to the spendable utxos and the `send_all` setting
-            None => Ok((
-                self.list_unspent()?
-                    .into_iter()
-                    .filter(|u| !unspendable_set.contains(&u.outpoint))
-                    .collect(),
-                send_all,
-            )),
+            // otherwise limit ourselves to the spendable utxos for the selected policy, and the `send_all` setting
+            None => {
+                let utxos = self.list_unspent()?.into_iter();
+                let utxos = change_policy.filter_utxos(utxos).into_iter();
+
+                Ok((
+                    utxos
+                        .filter(|u| !unspendable_set.contains(&u.outpoint))
+                        .collect(),
+                    send_all,
+                ))
+            }
         }
     }
 
index 1b43fbdb1a363c2f4059d41ea3080e0e01d61bb6..21fc932e68807310a8fec2a106e380f0c32bc46a 100644 (file)
@@ -5,6 +5,7 @@ use bitcoin::{Address, OutPoint, SigHashType, Transaction};
 
 use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
 use super::utils::FeeRate;
+use crate::types::UTXO;
 
 // TODO: add a flag to ignore change outputs (make them unspendable)
 #[derive(Debug, Default)]
@@ -20,6 +21,7 @@ pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
     pub(crate) locktime: Option<u32>,
     pub(crate) rbf: Option<u32>,
     pub(crate) version: Version,
+    pub(crate) change_policy: ChangeSpendPolicy,
     pub(crate) coin_selection: Cs,
 }
 
@@ -59,11 +61,13 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
         self
     }
 
+    /// These have priority over the "unspendable" utxos
     pub fn utxos(mut self, utxos: Vec<OutPoint>) -> Self {
         self.utxos = Some(utxos);
         self
     }
 
+    /// This has priority over the "unspendable" utxos
     pub fn add_utxo(mut self, utxo: OutPoint) -> Self {
         self.utxos.get_or_insert(vec![]).push(utxo);
         self
@@ -108,6 +112,16 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
         self
     }
 
+    pub fn do_not_spend_change(mut self) -> Self {
+        self.change_policy = ChangeSpendPolicy::ChangeForbidden;
+        self
+    }
+
+    pub fn only_spend_change(mut self) -> Self {
+        self.change_policy = ChangeSpendPolicy::OnlyChange;
+        self
+    }
+
     pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> {
         TxBuilder {
             addressees: self.addressees,
@@ -121,6 +135,7 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
             locktime: self.locktime,
             rbf: self.rbf,
             version: self.version,
+            change_policy: self.change_policy,
             coin_selection,
         }
     }
@@ -176,6 +191,29 @@ impl Default for Version {
     }
 }
 
+#[derive(Debug)]
+pub enum ChangeSpendPolicy {
+    ChangeAllowed,
+    OnlyChange,
+    ChangeForbidden,
+}
+
+impl Default for ChangeSpendPolicy {
+    fn default() -> Self {
+        ChangeSpendPolicy::ChangeAllowed
+    }
+}
+
+impl ChangeSpendPolicy {
+    pub(crate) fn filter_utxos<I: Iterator<Item = UTXO>>(&self, iter: I) -> Vec<UTXO> {
+        match self {
+            ChangeSpendPolicy::ChangeAllowed => iter.collect(),
+            ChangeSpendPolicy::OnlyChange => iter.filter(|utxo| utxo.is_internal).collect(),
+            ChangeSpendPolicy::ChangeForbidden => iter.filter(|utxo| !utxo.is_internal).collect(),
+        }
+    }
+}
+
 #[cfg(test)]
 mod test {
     const ORDERING_TEST_TX: &'static str = "0200000003c26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f54\