[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();
|
||||||
.filter(|u| !unspendable_set.contains(&u.outpoint))
|
|
||||||
.collect(),
|
Ok((
|
||||||
send_all,
|
utxos
|
||||||
)),
|
.filter(|u| !unspendable_set.contains(&u.outpoint))
|
||||||
|
.collect(),
|
||||||
|
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