updates.set_utxo(&UTXO {
outpoint: OutPoint::new(tx.txid(), i as u32),
txout: output.clone(),
+ is_internal: script_type.is_internal(),
})?;
incoming += output.value;
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(())
}
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 }))
}
}
}
.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()
}
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()
#[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! {
#[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...
}
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(())
}
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,
}))
}
}
.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()
}
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,
}
}))
}
#[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()
#[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...
}
}
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...
+}
ScriptType::Internal => 'i' as u8,
}
}
+
+ pub fn is_internal(&self) -> bool {
+ self == &ScriptType::Internal
+ }
}
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)]
value: 100_000,
script_pubkey: Script::new(),
},
+ is_internal: false,
},
UTXO {
outpoint: OutPoint::from_str(
value: 200_000,
script_pubkey: Script::new(),
},
+ is_internal: true,
},
]
}
.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,
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(),
// 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,
+ ))
+ }
}
}
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)]
pub(crate) locktime: Option<u32>,
pub(crate) rbf: Option<u32>,
pub(crate) version: Version,
+ pub(crate) change_policy: ChangeSpendPolicy,
pub(crate) coin_selection: 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
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,
locktime: self.locktime,
rbf: self.rbf,
version: self.version,
+ change_policy: self.change_policy,
coin_selection,
}
}
}
}
+#[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\