[wallet] Allow limiting the use of internal utxos in TxBuilder
This commit is contained in:
		
							parent
							
								
									85090a28eb
								
							
						
					
					
						commit
						8d9ccf8d0b
					
				| @ -236,6 +236,7 @@ pub trait ElectrumLikeSync { | |||||||
|                 updates.set_utxo(&UTXO { |                 updates.set_utxo(&UTXO { | ||||||
|                     outpoint: OutPoint::new(tx.txid(), i as u32), |                     outpoint: OutPoint::new(tx.txid(), i as u32), | ||||||
|                     txout: output.clone(), |                     txout: output.clone(), | ||||||
|  |                     is_internal: script_type.is_internal(), | ||||||
|                 })?; |                 })?; | ||||||
|                 incoming += output.value; |                 incoming += output.value; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -29,8 +29,11 @@ macro_rules! impl_batch_operations { | |||||||
| 
 | 
 | ||||||
|         fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> { |         fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> { | ||||||
|             let key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key(); |             let key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key(); | ||||||
|             let value = serialize(&utxo.txout); |             let value = json!({ | ||||||
|             self.insert(key, value)$($after_insert)*; |                 "t": utxo.txout, | ||||||
|  |                 "i": utxo.is_internal, | ||||||
|  |             }); | ||||||
|  |             self.insert(key, serde_json::to_vec(&value)?)$($after_insert)*; | ||||||
| 
 | 
 | ||||||
|             Ok(()) |             Ok(()) | ||||||
|         } |         } | ||||||
| @ -101,8 +104,11 @@ macro_rules! impl_batch_operations { | |||||||
|             match res { |             match res { | ||||||
|                 None => Ok(None), |                 None => Ok(None), | ||||||
|                 Some(b) => { |                 Some(b) => { | ||||||
|                     let txout = deserialize(&b)?; |                     let mut val: serde_json::Value = serde_json::from_slice(&b)?; | ||||||
|                     Ok(Some(UTXO { outpoint: outpoint.clone(), txout })) |                     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> { |             .map(|x| -> Result<_, Error> { | ||||||
|                 let (k, v) = x?; |                 let (k, v) = x?; | ||||||
|                 let outpoint = deserialize(&k[1..])?; |                 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() |             .collect() | ||||||
|     } |     } | ||||||
| @ -271,10 +285,14 @@ impl Database for Tree { | |||||||
|         let key = MapKey::UTXO(Some(outpoint)).as_map_key(); |         let key = MapKey::UTXO(Some(outpoint)).as_map_key(); | ||||||
|         self.get(key)? |         self.get(key)? | ||||||
|             .map(|b| -> Result<_, Error> { |             .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 { |                 Ok(UTXO { | ||||||
|                     outpoint: outpoint.clone(), |                     outpoint: outpoint.clone(), | ||||||
|                     txout, |                     txout, | ||||||
|  |                     is_internal, | ||||||
|                 }) |                 }) | ||||||
|             }) |             }) | ||||||
|             .transpose() |             .transpose() | ||||||
| @ -354,18 +372,11 @@ impl BatchDatabase for Tree { | |||||||
| 
 | 
 | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod test { | mod test { | ||||||
|     use std::str::FromStr; |  | ||||||
|     use std::sync::{Arc, Condvar, Mutex, Once}; |     use std::sync::{Arc, Condvar, Mutex, Once}; | ||||||
|     use std::time::{SystemTime, UNIX_EPOCH}; |     use std::time::{SystemTime, UNIX_EPOCH}; | ||||||
| 
 | 
 | ||||||
|     use sled::{Db, Tree}; |     use sled::{Db, Tree}; | ||||||
| 
 | 
 | ||||||
|     use bitcoin::consensus::encode::deserialize; |  | ||||||
|     use bitcoin::hashes::hex::*; |  | ||||||
|     use bitcoin::*; |  | ||||||
| 
 |  | ||||||
|     use crate::database::*; |  | ||||||
| 
 |  | ||||||
|     static mut COUNT: usize = 0; |     static mut COUNT: usize = 0; | ||||||
| 
 | 
 | ||||||
|     lazy_static! { |     lazy_static! { | ||||||
| @ -406,185 +417,41 @@ mod test { | |||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_script_pubkey() { |     fn test_script_pubkey() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_script_pubkey(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())) |  | ||||||
|         ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_batch_script_pubkey() { |     fn test_batch_script_pubkey() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_batch_script_pubkey(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())) |  | ||||||
|         ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_iter_script_pubkey() { |     fn test_iter_script_pubkey() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_iter_script_pubkey(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); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_del_script_pubkey() { |     fn test_del_script_pubkey() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_del_script_pubkey(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] |     #[test] | ||||||
|     fn test_utxo() { |     fn test_utxo() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_utxo(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)); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_raw_tx() { |     fn test_raw_tx() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_raw_tx(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)); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_tx() { |     fn test_tx() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_tx(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) |  | ||||||
|         ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_last_index() { |     fn test_last_index() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_last_index(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)); |  | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     // TODO: more tests...
 |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -113,7 +113,8 @@ impl BatchOperations for MemoryDatabase { | |||||||
| 
 | 
 | ||||||
|     fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> { |     fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> { | ||||||
|         let key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key(); |         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(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| @ -184,10 +185,11 @@ impl BatchOperations for MemoryDatabase { | |||||||
|         match res { |         match res { | ||||||
|             None => Ok(None), |             None => Ok(None), | ||||||
|             Some(b) => { |             Some(b) => { | ||||||
|                 let txout = b.downcast_ref().cloned().unwrap(); |                 let (txout, is_internal) = b.downcast_ref().cloned().unwrap(); | ||||||
|                 Ok(Some(UTXO { |                 Ok(Some(UTXO { | ||||||
|                     outpoint: outpoint.clone(), |                     outpoint: outpoint.clone(), | ||||||
|                     txout, |                     txout, | ||||||
|  |                     is_internal, | ||||||
|                 })) |                 })) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -274,8 +276,12 @@ impl Database for MemoryDatabase { | |||||||
|             .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key)))) |             .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key)))) | ||||||
|             .map(|(k, v)| { |             .map(|(k, v)| { | ||||||
|                 let outpoint = deserialize(&k[1..]).unwrap(); |                 let outpoint = deserialize(&k[1..]).unwrap(); | ||||||
|                 let txout = v.downcast_ref().cloned().unwrap(); |                 let (txout, is_internal) = v.downcast_ref().cloned().unwrap(); | ||||||
|                 Ok(UTXO { outpoint, txout }) |                 Ok(UTXO { | ||||||
|  |                     outpoint, | ||||||
|  |                     txout, | ||||||
|  |                     is_internal, | ||||||
|  |                 }) | ||||||
|             }) |             }) | ||||||
|             .collect() |             .collect() | ||||||
|     } |     } | ||||||
| @ -333,10 +339,11 @@ impl Database for MemoryDatabase { | |||||||
|     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { |     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { | ||||||
|         let key = MapKey::UTXO(Some(outpoint)).as_map_key(); |         let key = MapKey::UTXO(Some(outpoint)).as_map_key(); | ||||||
|         Ok(self.map.get(&key).map(|b| { |         Ok(self.map.get(&key).map(|b| { | ||||||
|             let txout = b.downcast_ref().cloned().unwrap(); |             let (txout, is_internal) = b.downcast_ref().cloned().unwrap(); | ||||||
|             UTXO { |             UTXO { | ||||||
|                 outpoint: outpoint.clone(), |                 outpoint: outpoint.clone(), | ||||||
|                 txout, |                 txout, | ||||||
|  |                 is_internal, | ||||||
|             } |             } | ||||||
|         })) |         })) | ||||||
|     } |     } | ||||||
| @ -399,14 +406,7 @@ impl BatchDatabase for MemoryDatabase { | |||||||
| 
 | 
 | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod test { | mod test { | ||||||
|     use std::str::FromStr; |     use super::MemoryDatabase; | ||||||
| 
 |  | ||||||
|     use bitcoin::consensus::encode::deserialize; |  | ||||||
|     use bitcoin::hashes::hex::*; |  | ||||||
|     use bitcoin::*; |  | ||||||
| 
 |  | ||||||
|     use super::*; |  | ||||||
|     use crate::database::*; |  | ||||||
| 
 | 
 | ||||||
|     fn get_tree() -> MemoryDatabase { |     fn get_tree() -> MemoryDatabase { | ||||||
|         MemoryDatabase::new() |         MemoryDatabase::new() | ||||||
| @ -414,209 +414,41 @@ mod test { | |||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_script_pubkey() { |     fn test_script_pubkey() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_script_pubkey(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())) |  | ||||||
|         ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_batch_script_pubkey() { |     fn test_batch_script_pubkey() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_batch_script_pubkey(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)) |  | ||||||
|         ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_iter_script_pubkey() { |     fn test_iter_script_pubkey() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_iter_script_pubkey(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); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_del_script_pubkey() { |     fn test_del_script_pubkey() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_del_script_pubkey(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); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_utxo() { |     fn test_utxo() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_utxo(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)); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_raw_tx() { |     fn test_raw_tx() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_raw_tx(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)); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_tx() { |     fn test_tx() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_tx(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) |  | ||||||
|         ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_last_index() { |     fn test_last_index() { | ||||||
|         let mut tree = get_tree(); |         crate::database::test::test_last_index(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)); |  | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     // TODO: more tests...
 |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -106,3 +106,179 @@ pub trait DatabaseUtils: Database { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Database> DatabaseUtils for T {} | 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...
 | ||||||
|  | } | ||||||
|  | |||||||
| @ -19,6 +19,10 @@ impl ScriptType { | |||||||
|             ScriptType::Internal => 'i' as u8, |             ScriptType::Internal => 'i' as u8, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     pub fn is_internal(&self) -> bool { | ||||||
|  |         self == &ScriptType::Internal | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl AsRef<[u8]> for ScriptType { | impl AsRef<[u8]> for ScriptType { | ||||||
| @ -34,6 +38,7 @@ impl AsRef<[u8]> for ScriptType { | |||||||
| pub struct UTXO { | pub struct UTXO { | ||||||
|     pub outpoint: OutPoint, |     pub outpoint: OutPoint, | ||||||
|     pub txout: TxOut, |     pub txout: TxOut, | ||||||
|  |     pub is_internal: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)] | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)] | ||||||
|  | |||||||
| @ -109,6 +109,7 @@ mod test { | |||||||
|                     value: 100_000, |                     value: 100_000, | ||||||
|                     script_pubkey: Script::new(), |                     script_pubkey: Script::new(), | ||||||
|                 }, |                 }, | ||||||
|  |                 is_internal: false, | ||||||
|             }, |             }, | ||||||
|             UTXO { |             UTXO { | ||||||
|                 outpoint: OutPoint::from_str( |                 outpoint: OutPoint::from_str( | ||||||
| @ -119,6 +120,7 @@ mod test { | |||||||
|                     value: 200_000, |                     value: 200_000, | ||||||
|                     script_pubkey: Script::new(), |                     script_pubkey: Script::new(), | ||||||
|                 }, |                 }, | ||||||
|  |                 is_internal: true, | ||||||
|             }, |             }, | ||||||
|         ] |         ] | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -217,8 +217,12 @@ where | |||||||
|                 .max_satisfaction_weight(), |                 .max_satisfaction_weight(), | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         let (available_utxos, use_all_utxos) = |         let (available_utxos, use_all_utxos) = self.get_available_utxos( | ||||||
|             self.get_available_utxos(&builder.utxos, &builder.unspendable, builder.send_all)?; |             builder.change_policy, | ||||||
|  |             &builder.utxos, | ||||||
|  |             &builder.unspendable, | ||||||
|  |             builder.send_all, | ||||||
|  |         )?; | ||||||
|         let coin_selection::CoinSelectionResult { |         let coin_selection::CoinSelectionResult { | ||||||
|             txin, |             txin, | ||||||
|             total_amount, |             total_amount, | ||||||
| @ -646,11 +650,11 @@ where | |||||||
| 
 | 
 | ||||||
|     fn get_available_utxos( |     fn get_available_utxos( | ||||||
|         &self, |         &self, | ||||||
|  |         change_policy: tx_builder::ChangeSpendPolicy, | ||||||
|         utxo: &Option<Vec<OutPoint>>, |         utxo: &Option<Vec<OutPoint>>, | ||||||
|         unspendable: &Option<Vec<OutPoint>>, |         unspendable: &Option<Vec<OutPoint>>, | ||||||
|         send_all: bool, |         send_all: bool, | ||||||
|     ) -> Result<(Vec<UTXO>, bool), Error> { |     ) -> Result<(Vec<UTXO>, bool), Error> { | ||||||
|         // TODO: should we consider unconfirmed received rbf txs as "unspendable" too by default?
 |  | ||||||
|         let unspendable_set = match unspendable { |         let unspendable_set = match unspendable { | ||||||
|             None => HashSet::new(), |             None => HashSet::new(), | ||||||
|             Some(vec) => vec.into_iter().collect(), |             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
 |             // with manual coin selection we always want to spend all the selected utxos, no matter
 | ||||||
|             // what (even if they are marked as unspendable)
 |             // what (even if they are marked as unspendable)
 | ||||||
|             Some(raw_utxos) => { |             Some(raw_utxos) => { | ||||||
|                 // TODO: unwrap to remove
 |                 let full_utxos = raw_utxos | ||||||
|                 let full_utxos: Vec<_> = raw_utxos |  | ||||||
|                     .iter() |                     .iter() | ||||||
|                     .map(|u| self.database.borrow().get_utxo(&u).unwrap()) |                     .map(|u| self.database.borrow().get_utxo(&u)) | ||||||
|                     .collect(); |                     .collect::<Result<Vec<_>, _>>()?; | ||||||
|                 if !full_utxos.iter().all(|u| u.is_some()) { |                 if !full_utxos.iter().all(|u| u.is_some()) { | ||||||
|                     return Err(Error::UnknownUTXO); |                     return Err(Error::UnknownUTXO); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 Ok((full_utxos.into_iter().map(|x| x.unwrap()).collect(), true)) |                 Ok((full_utxos.into_iter().map(|x| x.unwrap()).collect(), true)) | ||||||
|             } |             } | ||||||
|             // otherwise limit ourselves to the spendable utxos and the `send_all` setting
 |             // otherwise limit ourselves to the spendable utxos for the selected policy, and the `send_all` setting
 | ||||||
|             None => Ok(( |             None => { | ||||||
|                 self.list_unspent()? |                 let utxos = self.list_unspent()?.into_iter(); | ||||||
|                     .into_iter() |                 let utxos = change_policy.filter_utxos(utxos).into_iter(); | ||||||
|  | 
 | ||||||
|  |                 Ok(( | ||||||
|  |                     utxos | ||||||
|                         .filter(|u| !unspendable_set.contains(&u.outpoint)) |                         .filter(|u| !unspendable_set.contains(&u.outpoint)) | ||||||
|                         .collect(), |                         .collect(), | ||||||
|                     send_all, |                     send_all, | ||||||
|             )), |                 )) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ use bitcoin::{Address, OutPoint, SigHashType, Transaction}; | |||||||
| 
 | 
 | ||||||
| use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; | use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; | ||||||
| use super::utils::FeeRate; | use super::utils::FeeRate; | ||||||
|  | use crate::types::UTXO; | ||||||
| 
 | 
 | ||||||
| // TODO: add a flag to ignore change outputs (make them unspendable)
 | // TODO: add a flag to ignore change outputs (make them unspendable)
 | ||||||
| #[derive(Debug, Default)] | #[derive(Debug, Default)] | ||||||
| @ -20,6 +21,7 @@ pub struct TxBuilder<Cs: CoinSelectionAlgorithm> { | |||||||
|     pub(crate) locktime: Option<u32>, |     pub(crate) locktime: Option<u32>, | ||||||
|     pub(crate) rbf: Option<u32>, |     pub(crate) rbf: Option<u32>, | ||||||
|     pub(crate) version: Version, |     pub(crate) version: Version, | ||||||
|  |     pub(crate) change_policy: ChangeSpendPolicy, | ||||||
|     pub(crate) coin_selection: Cs, |     pub(crate) coin_selection: Cs, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -59,11 +61,13 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /// These have priority over the "unspendable" utxos
 | ||||||
|     pub fn utxos(mut self, utxos: Vec<OutPoint>) -> Self { |     pub fn utxos(mut self, utxos: Vec<OutPoint>) -> Self { | ||||||
|         self.utxos = Some(utxos); |         self.utxos = Some(utxos); | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /// This has priority over the "unspendable" utxos
 | ||||||
|     pub fn add_utxo(mut self, utxo: OutPoint) -> Self { |     pub fn add_utxo(mut self, utxo: OutPoint) -> Self { | ||||||
|         self.utxos.get_or_insert(vec![]).push(utxo); |         self.utxos.get_or_insert(vec![]).push(utxo); | ||||||
|         self |         self | ||||||
| @ -108,6 +112,16 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> { | |||||||
|         self |         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> { |     pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> { | ||||||
|         TxBuilder { |         TxBuilder { | ||||||
|             addressees: self.addressees, |             addressees: self.addressees, | ||||||
| @ -121,6 +135,7 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> { | |||||||
|             locktime: self.locktime, |             locktime: self.locktime, | ||||||
|             rbf: self.rbf, |             rbf: self.rbf, | ||||||
|             version: self.version, |             version: self.version, | ||||||
|  |             change_policy: self.change_policy, | ||||||
|             coin_selection, |             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)] | #[cfg(test)] | ||||||
| mod test { | mod test { | ||||||
|     const ORDERING_TEST_TX: &'static str = "0200000003c26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f54\ |     const ORDERING_TEST_TX: &'static str = "0200000003c26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f54\ | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user