Write more docs, make TxBuilder::with_recipients take Scripts

This commit is contained in:
Alekos Filini 2020-09-04 15:45:11 +02:00
parent 7065c1fed6
commit eee75219e0
No known key found for this signature in database
GPG Key ID: 5E8AFC3034FDFA4F
13 changed files with 419 additions and 100 deletions

View File

@ -57,7 +57,7 @@ impl AddressValidator for DummyValidator {
}
}
fn main() -> Result<(), magical_bitcoin_wallet::error::Error> {
fn main() -> Result<(), magical_bitcoin_wallet::Error> {
let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
let mut wallet: OfflineWallet<_> =
Wallet::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?;

View File

@ -40,8 +40,7 @@ use miniscript::policy::Concrete;
use miniscript::Descriptor;
use magical_bitcoin_wallet::database::memory::MemoryDatabase;
use magical_bitcoin_wallet::types::ScriptType;
use magical_bitcoin_wallet::{OfflineWallet, Wallet};
use magical_bitcoin_wallet::{OfflineWallet, Wallet, ScriptType};
fn main() {
env_logger::init_from_env(

View File

@ -37,10 +37,10 @@ use log::{debug, error, info, trace, LevelFilter};
use bitcoin::Network;
use magical_bitcoin_wallet::bitcoin;
use magical_bitcoin_wallet::blockchain::ElectrumBlockchain;
use magical_bitcoin_wallet::blockchain::compact_filters::*;
use magical_bitcoin_wallet::cli;
use magical_bitcoin_wallet::sled;
use magical_bitcoin_wallet::{Client, Wallet};
use magical_bitcoin_wallet::Wallet;
fn prepare_home_dir() -> PathBuf {
let mut dir = PathBuf::new();
@ -88,19 +88,17 @@ fn main() {
.unwrap();
debug!("database opened successfully");
let client = Client::new(
matches.value_of("server").unwrap(),
matches.value_of("proxy"),
)
.unwrap();
let wallet = Wallet::new(
descriptor,
change_descriptor,
network,
tree,
ElectrumBlockchain::from(client),
)
.unwrap();
let num_threads = 1;
let mempool = Arc::new(Mempool::default());
let peers = (0..num_threads)
.map(|_| Peer::connect("192.168.1.136:8333", Arc::clone(&mempool), Network::Bitcoin))
.collect::<Result<_, _>>()
.unwrap();
let blockchain =
CompactFiltersBlockchain::new(peers, "./wallet-filters", Some(500_000)).unwrap();
let wallet = Wallet::new(descriptor, change_descriptor, network, tree, blockchain).unwrap();
let wallet = Arc::new(wallet);
if let Some(_sub_matches) = matches.subcommand_matches("repl") {

View File

@ -33,14 +33,14 @@ use log::{debug, error, info, trace, LevelFilter};
use bitcoin::consensus::encode::{deserialize, serialize, serialize_hex};
use bitcoin::hashes::hex::FromHex;
use bitcoin::util::psbt::PartiallySignedTransaction;
use bitcoin::{Address, OutPoint, Txid};
use bitcoin::{Address, OutPoint, Script, Txid};
use crate::blockchain::log_progress;
use crate::error::Error;
use crate::types::ScriptType;
use crate::{FeeRate, TxBuilder, Wallet};
fn parse_recipient(s: &str) -> Result<(Address, u64), String> {
fn parse_recipient(s: &str) -> Result<(Script, u64), String> {
let parts: Vec<_> = s.split(":").collect();
if parts.len() != 2 {
return Err("Invalid format".to_string());
@ -55,7 +55,7 @@ fn parse_recipient(s: &str) -> Result<(Address, u64), String> {
return Err(format!("{:?}", e));
}
Ok((addr.unwrap(), val.unwrap()))
Ok((addr.unwrap().script_pubkey(), val.unwrap()))
}
fn parse_outpoint(s: &str) -> Result<OutPoint, String> {

View File

@ -403,12 +403,16 @@ impl From<bool> for Satisfaction {
/// Descriptor spending policy
#[derive(Debug, Clone, Serialize)]
pub struct Policy {
id: String,
/// Identifier for this policy node
pub id: String,
/// Type of this policy node
#[serde(flatten)]
item: SatisfiableItem,
satisfaction: Satisfaction,
contribution: Satisfaction,
pub item: SatisfiableItem,
/// How a much given PSBT already satisfies this polcy node **(currently unused)**
pub satisfaction: Satisfaction,
/// How the wallet's descriptor can satisfy this policy node
pub contribution: Satisfaction,
}
/// An extra condition that must be satisfied but that is out of control of the user

View File

@ -153,7 +153,10 @@ mod test {
let addr = testutils!(@external descriptors, 10);
wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.create_tx(TxBuilder::with_recipients(vec![(
addr.script_pubkey(),
25_000,
)]))
.unwrap();
}
}

View File

@ -93,7 +93,7 @@
//!
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
//! let (psbt, details) = wallet.create_tx(
//! TxBuilder::with_recipients(vec![(to_address, 50_000)])
//! TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
//! .coin_selection(AlwaysSpendEverything),
//! )?;
//!

View File

@ -22,6 +22,51 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Wallet export
//!
//! This modules implements the wallet export format used by [FullyNoded](https://github.com/Fonta1n3/FullyNoded/blob/10b7808c8b929b171cca537fb50522d015168ac9/Docs/Wallets/Wallet-Export-Spec.md).
//!
//! ## Examples
//!
//! ### Import from JSON
//!
//! ```
//! # use std::str::FromStr;
//! # use bitcoin::*;
//! # use magical_bitcoin_wallet::database::*;
//! # use magical_bitcoin_wallet::wallet::export::*;
//! # use magical_bitcoin_wallet::*;
//! let import = r#"{
//! "descriptor": "wpkh([c258d2e4\/84h\/1h\/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe\/0\/*)",
//! "blockheight":1782088,
//! "label":"testnet"
//! }"#;
//!
//! let import = WalletExport::from_str(import)?;
//! let wallet: OfflineWallet<_> = Wallet::new_offline(&import.descriptor(), import.change_descriptor().as_deref(), Network::Testnet, MemoryDatabase::default())?;
//! # Ok::<_, magical_bitcoin_wallet::Error>(())
//! ```
//!
//! ### Export a `Wallet`
//! ```
//! # use bitcoin::*;
//! # use magical_bitcoin_wallet::database::*;
//! # use magical_bitcoin_wallet::wallet::export::*;
//! # use magical_bitcoin_wallet::*;
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
//! Network::Testnet,
//! MemoryDatabase::default()
//! )?;
//! let export = WalletExport::export_wallet(&wallet, "exported wallet", true)
//! .map_err(ToString::to_string)
//! .map_err(magical_bitcoin_wallet::Error::Generic)?;
//!
//! println!("Exported: {}", export.to_string());
//! # Ok::<_, magical_bitcoin_wallet::Error>(())
//! ```
use std::str::FromStr;
use serde::{Deserialize, Serialize};
@ -32,10 +77,15 @@ use crate::blockchain::Blockchain;
use crate::database::BatchDatabase;
use crate::wallet::Wallet;
/// Structure that contains the export of a wallet
///
/// For a usage example see [this module](crate::wallet::export)'s documentation.
#[derive(Debug, Serialize, Deserialize)]
pub struct WalletExport {
descriptor: String,
/// Earliest block to rescan when looking for the wallet's transactions
pub blockheight: u32,
/// Arbitrary label for the wallet
pub label: String,
}
@ -54,6 +104,17 @@ impl FromStr for WalletExport {
}
impl WalletExport {
/// Export a wallet
///
/// This function returns an error if it determines that the `wallet`'s descriptor(s) are not
/// supported by Bitcoin Core or don't follow the standard derivation paths defined by BIP44
/// and others.
///
/// If `include_blockheight` is `true`, this function will look into the `wallet`'s database
/// for the oldest transaction it knows and use that as the earliest block to rescan.
///
/// If the database is empty or `include_blockheight` is false, the `blockheight` field
/// returned will be `0`.
pub fn export_wallet<B: Blockchain, D: BatchDatabase>(
wallet: &Wallet<B, D>,
label: &str,
@ -118,10 +179,12 @@ impl WalletExport {
}
}
/// Return the external descriptor
pub fn descriptor(&self) -> String {
self.descriptor.clone()
}
/// Return the internal descriptor, if present
pub fn change_descriptor(&self) -> Option<String> {
let replaced = self.descriptor.replace("/0/*", "/1/*");

View File

@ -229,7 +229,7 @@ where
(None, Some(csv)) => csv,
(Some(rbf), Some(csv)) if rbf < csv => return Err(Error::Generic(format!("Cannot enable RBF with nSequence `{}`, since at least `{}` is required to spend with OP_CSV", rbf, csv))),
(None, _) if requirements.timelock.is_some() => 0xFFFFFFFE,
(Some(rbf), _) if rbf >= 0xFFFFFFFE => return Err(Error::Generic("Cannot enable RBF with anumber >= 0xFFFFFFFE".into())),
(Some(rbf), _) if rbf >= 0xFFFFFFFE => return Err(Error::Generic("Cannot enable RBF with a nSequence >= 0xFFFFFFFE".into())),
(Some(rbf), _) => rbf,
(None, _) => 0xFFFFFFFF,
};
@ -254,22 +254,19 @@ where
let calc_fee_bytes = |wu| (wu as f32) * fee_rate.as_sat_vb() / 4.0;
fee_amount += calc_fee_bytes(tx.get_weight());
for (index, (address, satoshi)) in builder.recipients.iter().enumerate() {
for (index, (script_pubkey, satoshi)) in builder.recipients.iter().enumerate() {
let value = match builder.send_all {
true => 0,
false if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)),
false => *satoshi,
};
// TODO: proper checks for testnet/regtest p2sh/p2pkh
if address.network != self.network && self.network != Network::Regtest {
return Err(Error::InvalidAddressNetwork(address.clone()));
} else if self.is_mine(&address.script_pubkey())? {
if self.is_mine(script_pubkey)? {
received += value;
}
let new_out = TxOut {
script_pubkey: address.script_pubkey(),
script_pubkey: script_pubkey.clone(),
value,
};
fee_amount += calc_fee_bytes(serialize(&new_out).len() * 4);
@ -1251,7 +1248,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(0))
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).version(0))
.unwrap();
}
@ -1263,7 +1260,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(1))
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).version(1))
.unwrap();
}
@ -1272,7 +1269,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(42))
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).version(42))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.version, 42);
@ -1283,7 +1280,10 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.create_tx(TxBuilder::with_recipients(vec![(
addr.script_pubkey(),
25_000,
)]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 0);
@ -1294,7 +1294,10 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.create_tx(TxBuilder::with_recipients(vec![(
addr.script_pubkey(),
25_000,
)]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 100_000);
@ -1305,7 +1308,9 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(630_000))
.create_tx(
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).nlocktime(630_000),
)
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
@ -1316,7 +1321,9 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(630_000))
.create_tx(
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).nlocktime(630_000),
)
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
@ -1330,7 +1337,9 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(50000))
.create_tx(
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).nlocktime(50000),
)
.unwrap();
}
@ -1339,7 +1348,10 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.create_tx(TxBuilder::with_recipients(vec![(
addr.script_pubkey(),
25_000,
)]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6);
@ -1350,7 +1362,9 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf())
.create_tx(
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
)
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFD);
@ -1364,7 +1378,10 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf_with_sequence(3))
.create_tx(
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
.enable_rbf_with_sequence(3),
)
.unwrap();
}
@ -1373,20 +1390,23 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.create_tx(TxBuilder::with_recipients(vec![(
addr.script_pubkey(),
25_000,
)]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFE);
}
#[test]
#[should_panic(expected = "Cannot enable RBF with anumber >= 0xFFFFFFFE")]
#[should_panic(expected = "Cannot enable RBF with a nSequence >= 0xFFFFFFFE")]
fn test_create_tx_invalid_rbf_sequence() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr, 25_000)])
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
.enable_rbf_with_sequence(0xFFFFFFFE),
)
.unwrap();
@ -1398,7 +1418,7 @@ mod test {
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr, 25_000)])
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
.enable_rbf_with_sequence(0xDEADBEEF),
)
.unwrap();
@ -1411,7 +1431,10 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.create_tx(TxBuilder::with_recipients(vec![(
addr.script_pubkey(),
25_000,
)]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
@ -1426,7 +1449,8 @@ mod test {
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr.clone(), 25_000)]).do_not_spend_change(),
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
.do_not_spend_change(),
)
.unwrap();
}
@ -1438,7 +1462,11 @@ mod test {
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr.clone(), 25_000), (addr, 10_000)]).send_all(),
TxBuilder::with_recipients(vec![
(addr.script_pubkey(), 25_000),
(addr.script_pubkey(), 10_000),
])
.send_all(),
)
.unwrap();
}
@ -1448,7 +1476,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
@ -1463,7 +1491,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::default(), @add_signature);
@ -1475,7 +1503,7 @@ mod test {
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr.clone(), 0)])
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
.fee_rate(FeeRate::from_sat_per_vb(5.0))
.send_all(),
)
@ -1492,7 +1520,7 @@ mod test {
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr.clone(), 25_000)])
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
.ordering(TxOrdering::Untouched),
)
.unwrap();
@ -1510,7 +1538,10 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 49_800)]))
.create_tx(TxBuilder::with_recipients(vec![(
addr.script_pubkey(),
49_800,
)]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
@ -1525,7 +1556,7 @@ mod test {
// very high fee rate, so that the only output would be below dust
wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr.clone(), 0)])
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
.send_all()
.fee_rate(crate::FeeRate::from_sat_per_vb(453.0)),
)
@ -1538,8 +1569,11 @@ mod test {
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr.clone(), 30_000), (addr.clone(), 10_000)])
.ordering(super::tx_builder::TxOrdering::BIP69Lexicographic),
TxBuilder::with_recipients(vec![
(addr.script_pubkey(), 30_000),
(addr.script_pubkey(), 10_000),
])
.ordering(super::tx_builder::TxOrdering::BIP69Lexicographic),
)
.unwrap();
@ -1557,7 +1591,10 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 30_000)]))
.create_tx(TxBuilder::with_recipients(vec![(
addr.script_pubkey(),
30_000,
)]))
.unwrap();
assert_eq!(psbt.inputs[0].sighash_type, Some(bitcoin::SigHashType::All));
@ -1569,7 +1606,7 @@ mod test {
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr.clone(), 30_000)])
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 30_000)])
.sighash(bitcoin::SigHashType::Single),
)
.unwrap();
@ -1588,7 +1625,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert_eq!(psbt.inputs[0].hd_keypaths.len(), 1);
@ -1612,7 +1649,7 @@ mod test {
let addr = testutils!(@external descriptors, 5);
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert_eq!(psbt.outputs[0].hd_keypaths.len(), 1);
@ -1633,7 +1670,7 @@ mod test {
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert_eq!(
@ -1656,7 +1693,7 @@ mod test {
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert_eq!(psbt.inputs[0].redeem_script, None);
@ -1679,7 +1716,7 @@ mod test {
get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
let script = Script::from(
@ -1699,7 +1736,7 @@ mod test {
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_some());
@ -1712,7 +1749,7 @@ mod test {
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_none());
@ -1726,7 +1763,7 @@ mod test {
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr.clone(), 0)])
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
.force_non_witness_utxo()
.send_all(),
)
@ -1742,7 +1779,10 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.create_tx(TxBuilder::with_recipients(vec![(
addr.script_pubkey(),
25_000,
)]))
.unwrap();
let tx = psbt.extract_tx();
let txid = tx.txid();
@ -1759,7 +1799,10 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.create_tx(TxBuilder::with_recipients(vec![(
addr.script_pubkey(),
25_000,
)]))
.unwrap();
let tx = psbt.extract_tx();
let txid = tx.txid();
@ -1777,7 +1820,9 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf())
.create_tx(
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
)
.unwrap();
let tx = psbt.extract_tx();
let txid = tx.txid();
@ -1798,7 +1843,9 @@ mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 25_000)]).enable_rbf())
.create_tx(
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
)
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
@ -1860,7 +1907,7 @@ mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr.clone(), 0)])
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
.send_all()
.enable_rbf(),
)
@ -1914,7 +1961,7 @@ mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr.clone(), 0)])
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
.utxos(vec![OutPoint {
txid: incoming_txid,
vout: 0,
@ -1961,7 +2008,9 @@ mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf())
.create_tx(
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
)
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
@ -2025,7 +2074,7 @@ mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(
TxBuilder::with_recipients(vec![(addr.clone(), 0)])
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
.send_all()
.add_utxo(OutPoint {
txid: incoming_txid,
@ -2101,7 +2150,9 @@ mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf())
.create_tx(
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
)
.unwrap();
let mut tx = psbt.extract_tx();
assert_eq!(tx.input.len(), 1);
@ -2161,7 +2212,9 @@ mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf())
.create_tx(
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
)
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
@ -2226,7 +2279,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
@ -2242,7 +2295,7 @@ mod test {
get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
@ -2257,7 +2310,7 @@ mod test {
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap();
let (mut psbt, _) = wallet
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
psbt.inputs[0].hd_keypaths.clear();

View File

@ -22,6 +22,72 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Generalized signers
//!
//! This module provides the ability to add customized signers to a [`Wallet`](super::Wallet)
//! through the [`Wallet::add_signer`](super::Wallet::add_signer) function.
//!
//! ```
//! # use std::sync::Arc;
//! # use std::str::FromStr;
//! # use bitcoin::*;
//! # use bitcoin::util::psbt;
//! # use bitcoin::util::bip32::Fingerprint;
//! # use magical_bitcoin_wallet::signer::*;
//! # use magical_bitcoin_wallet::database::*;
//! # use magical_bitcoin_wallet::*;
//! # #[derive(Debug)]
//! # struct CustomHSM;
//! # impl CustomHSM {
//! # fn sign_input(&self, _psbt: &mut psbt::PartiallySignedTransaction, _input: usize) -> Result<(), SignerError> {
//! # Ok(())
//! # }
//! # fn connect() -> Self {
//! # CustomHSM
//! # }
//! # }
//! #[derive(Debug)]
//! struct CustomSigner {
//! device: CustomHSM,
//! }
//!
//! impl CustomSigner {
//! fn connect() -> Self {
//! CustomSigner { device: CustomHSM::connect() }
//! }
//! }
//!
//! impl Signer for CustomSigner {
//! fn sign(
//! &self,
//! psbt: &mut psbt::PartiallySignedTransaction,
//! input_index: Option<usize>,
//! ) -> Result<(), SignerError> {
//! let input_index = input_index.ok_or(SignerError::InputIndexOutOfRange)?;
//! self.device.sign_input(psbt, input_index)?;
//!
//! Ok(())
//! }
//!
//! fn sign_whole_tx(&self) -> bool {
//! false
//! }
//! }
//!
//! let custom_signer = CustomSigner::connect();
//!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
//! let mut wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! wallet.add_signer(
//! ScriptType::External,
//! Fingerprint::from_str("e30f11b8").unwrap().into(),
//! SignerOrdering(200),
//! Arc::new(Box::new(custom_signer))
//! );
//!
//! # Ok::<_, magical_bitcoin_wallet::Error>(())
//! ```
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt;
@ -42,7 +108,7 @@ use miniscript::{Legacy, MiniscriptKey, Segwitv0};
use crate::descriptor::XKeyUtils;
/// Identifier of a signer in the `SignersContainers`. Used as a key to find the right signer among
/// many of them
/// multiple of them
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SignerId<Pk: MiniscriptKey> {
PkHash(<Pk as MiniscriptKey>::Hash),
@ -93,15 +159,30 @@ impl fmt::Display for SignerError {
impl std::error::Error for SignerError {}
/// Trait for signers
///
/// This trait can be implemented to provide customized signers to the wallet. For an example see
/// [`this module`](crate::wallet::signer)'s documentation.
pub trait Signer: fmt::Debug {
/// Sign a PSBT
///
/// The `input_index` argument is only provided if the wallet doesn't declare to sign the whole
/// transaction in one go (see [`Signer::sign_whole_tx`]). Otherwise its value is `None` and
/// can be ignored.
fn sign(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
input_index: Option<usize>,
) -> Result<(), SignerError>;
/// Return whether or not the signer signs the whole transaction in one go instead of every
/// input individually
fn sign_whole_tx(&self) -> bool;
/// Return the secret key for the signer
///
/// This is used internally to reconstruct the original descriptor that may contain secrets.
/// External signers that are meant to keep key isolated should just return `None` here (which
/// is the default for this method, if not overridden).
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
None
}
@ -195,6 +276,11 @@ impl Signer for PrivateKey {
}
}
/// Defines the order in which signers are called
///
/// The default value is `100`. Signers with an ordering above that will be called later,
/// and they will thus see the partial signatures added to the transaction once they get to sign
/// themselves.
#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq)]
pub struct SignerOrdering(pub usize);

View File

@ -22,6 +22,14 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Cross-platform time
//!
//! This module provides a function to get the current timestamp that works on all the platforms
//! supported by the library.
//!
//! It can be useful to compare it with the timestamps found in
//! [`TransactionDetails`](crate::types::TransactionDetails).
use std::time::Duration;
#[cfg(target_arch = "wasm32")]
@ -29,6 +37,7 @@ use js_sys::Date;
#[cfg(not(target_arch = "wasm32"))]
use std::time::{Instant as SystemInstant, SystemTime, UNIX_EPOCH};
/// Return the current timestamp in seconds
#[cfg(not(target_arch = "wasm32"))]
pub fn get_timestamp() -> u64 {
SystemTime::now()
@ -36,6 +45,7 @@ pub fn get_timestamp() -> u64 {
.unwrap()
.as_secs()
}
/// Return the current timestamp in seconds
#[cfg(target_arch = "wasm32")]
pub fn get_timestamp() -> u64 {
let millis = Date::now();

View File

@ -22,17 +22,40 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Transaction builder
//!
//! ## Example
//!
//! ```
//! # use std::str::FromStr;
//! # use bitcoin::*;
//! # use magical_bitcoin_wallet::*;
//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
//! // Create a transaction with one output to `to_address` of 50_000 satoshi, with a custom fee rate
//! // of 5.0 satoshi/vbyte, only spending non-change outputs and with RBF signaling
//! // enabled
//! let builder = TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
//! .fee_rate(FeeRate::from_sat_per_vb(5.0))
//! .do_not_spend_change()
//! .enable_rbf();
//! ```
use std::collections::BTreeMap;
use std::default::Default;
use bitcoin::{Address, OutPoint, SigHashType, Transaction};
use bitcoin::{OutPoint, Script, SigHashType, Transaction};
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
use crate::types::{FeeRate, UTXO};
/// A transaction builder
///
/// This structure contains the configuration that the wallet must follow to build a transaction.
///
/// For an example see [this module](super::tx_builder)'s documentation;
#[derive(Debug, Default)]
pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
pub(crate) recipients: Vec<(Address, u64)>,
pub(crate) recipients: Vec<(Script, u64)>,
pub(crate) send_all: bool,
pub(crate) fee_rate: Option<FeeRate>,
pub(crate) policy_path: Option<BTreeMap<String, Vec<usize>>>,
@ -49,112 +72,182 @@ pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
}
impl TxBuilder<DefaultCoinSelectionAlgorithm> {
/// Create an empty builder
pub fn new() -> Self {
Self::default()
}
pub fn with_recipients(recipients: Vec<(Address, u64)>) -> Self {
/// Create a builder starting from a list of recipients
pub fn with_recipients(recipients: Vec<(Script, u64)>) -> Self {
Self::default().set_recipients(recipients)
}
}
impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
pub fn set_recipients(mut self, recipients: Vec<(Address, u64)>) -> Self {
/// Replace the recipients already added with a new list
pub fn set_recipients(mut self, recipients: Vec<(Script, u64)>) -> Self {
self.recipients = recipients;
self
}
pub fn add_recipient(mut self, address: Address, amount: u64) -> Self {
self.recipients.push((address, amount));
/// Add a recipient to the internal list
pub fn add_recipient(mut self, script_pubkey: Script, amount: u64) -> Self {
self.recipients.push((script_pubkey, amount));
self
}
/// Send all the selected utxos to a single output
///
/// Adding more than one recipients with this option enabled will result in an error.
///
/// The value associated with the only recipient is irrelevant and will be replaced by the wallet.
pub fn send_all(mut self) -> Self {
self.send_all = true;
self
}
/// Set a custom fee rate
pub fn fee_rate(mut self, fee_rate: FeeRate) -> Self {
self.fee_rate = Some(fee_rate);
self
}
/// Set the policy path to use while creating the transaction
///
/// This method accepts a map where the key is the policy node id (see
/// [`Policy::id`](crate::descriptor::Policy::id)) and the value is the list of the indexes of
/// the items that are intended to be satisfied from the policy node (see
/// [`SatisfiableItem::Thresh::items`](crate::descriptor::policy::SatisfiableItem::Thresh::items)).
pub fn policy_path(mut self, policy_path: BTreeMap<String, Vec<usize>>) -> Self {
self.policy_path = Some(policy_path);
self
}
/// These have priority over the "unspendable" utxos
/// Replace the internal list of utxos that **must** be spent with a new list
///
/// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
/// the "utxos" and the "unspendable" list, it will be spent.
pub fn utxos(mut self, utxos: Vec<OutPoint>) -> Self {
self.utxos = Some(utxos);
self
}
/// This has priority over the "unspendable" utxos
/// Add a utxo to the internal list of utxos that **must** be spent
///
/// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
/// the "utxos" and the "unspendable" list, it will be spent.
pub fn add_utxo(mut self, utxo: OutPoint) -> Self {
self.utxos.get_or_insert(vec![]).push(utxo);
self
}
/// Replace the internal list of unspendable utxos with a new list
///
/// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::utxos`] and
/// [`TxBuilder::add_utxo`] have priority over these. See the docs of the two linked methods
/// for more details.
pub fn unspendable(mut self, unspendable: Vec<OutPoint>) -> Self {
self.unspendable = Some(unspendable);
self
}
/// Add a utxo to the internal list of unspendable utxos
///
/// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::utxos`] and
/// [`TxBuilder::add_utxo`] have priority over this. See the docs of the two linked methods
/// for more details.
pub fn add_unspendable(mut self, unspendable: OutPoint) -> Self {
self.unspendable.get_or_insert(vec![]).push(unspendable);
self
}
/// Sign with a specific sig hash
///
/// **Use this option very carefully**
pub fn sighash(mut self, sighash: SigHashType) -> Self {
self.sighash = Some(sighash);
self
}
/// Choose the ordering for inputs and outputs of the transaction
pub fn ordering(mut self, ordering: TxOrdering) -> Self {
self.ordering = ordering;
self
}
/// Use a specific nLockTime while creating the transaction
///
/// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator.
pub fn nlocktime(mut self, locktime: u32) -> Self {
self.locktime = Some(locktime);
self
}
/// Enable signaling RBF
///
/// This will use the default nSequence value of `0xFFFFFFFD`.
pub fn enable_rbf(self) -> Self {
self.enable_rbf_with_sequence(0xFFFFFFFD)
}
/// Enable signaling RBF with a specific nSequence value
///
/// This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator
/// and the given `nsequence` is lower than the CSV value.
///
/// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
/// be a valid nSequence to signal RBF.
pub fn enable_rbf_with_sequence(mut self, nsequence: u32) -> Self {
self.rbf = Some(nsequence);
self
}
/// Build a transaction with a specific version
///
/// The `version` should always be greater than `0` and greater than `1` if the wallet's
/// descriptors contain an "older" (OP_CSV) operator.
pub fn version(mut self, version: u32) -> Self {
self.version = Some(Version(version));
self
}
/// Do not spend change outputs
///
/// This effectively adds all the change outputs to the "unspendable" list. See
/// [`TxBuilder::unspendable`].
pub fn do_not_spend_change(mut self) -> Self {
self.change_policy = ChangeSpendPolicy::ChangeForbidden;
self
}
/// Only spend change outputs
///
/// This effectively adds all the non-change outputs to the "unspendable" list. See
/// [`TxBuilder::unspendable`].
pub fn only_spend_change(mut self) -> Self {
self.change_policy = ChangeSpendPolicy::OnlyChange;
self
}
/// Set a specific [`ChangeSpendPolicy`]. See [`TxBuilder::do_not_spend_change`] and
/// [`TxBuilder::only_spend_change`] for some shortcuts.
pub fn change_policy(mut self, change_policy: ChangeSpendPolicy) -> Self {
self.change_policy = change_policy;
self
}
/// Fill-in the [`psbt::Input::non_witness_utxo`](bitcoin::util::psbt::Input::non_witness_utxo) field even if the wallet only has SegWit
/// descriptors.
///
/// This is useful for signers which always require it, like Trezor hardware wallets.
pub fn force_non_witness_utxo(mut self) -> Self {
self.force_non_witness_utxo = true;
self
}
/// Choose the coin selection algorithm
///
/// Overrides the [`DefaultCoinSelectionAlgorithm`](super::coin_selection::DefaultCoinSelectionAlgorithm).
pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> {
TxBuilder {
recipients: self.recipients,
@ -175,10 +268,14 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
}
}
/// Ordering of the transaction's inputs and outputs
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub enum TxOrdering {
/// Randomized (default)
Shuffle,
/// Unchanged
Untouched,
/// BIP69 / Lexicographic
BIP69Lexicographic,
}
@ -215,7 +312,9 @@ impl TxOrdering {
}
}
// Helper type that wraps u32 and has a default value of 1
/// Transaction version
///
/// Has a default value of `1`
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub(crate) struct Version(pub(crate) u32);
@ -225,10 +324,14 @@ impl Default for Version {
}
}
/// Policy regarding the use of change outputs when creating a transaction
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub enum ChangeSpendPolicy {
/// Use both change and non-change outputs (default)
ChangeAllowed,
/// Only use change outputs (see [`TxBuilder::only_spend_change`])
OnlyChange,
/// Only use non-change outputs (see [`TxBuilder::do_not_spend_change`])
ChangeForbidden,
}

View File

@ -307,7 +307,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr, 25_000)])).unwrap();
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey(), 25_000)])).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx();
@ -334,7 +334,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr, 25_000)])).unwrap();
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey(), 25_000)])).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
@ -373,7 +373,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
let mut total_sent = 0;
for _ in 0..5 {
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 5_000)])).unwrap();
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 5_000)])).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
@ -405,7 +405,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 5_000)]).enable_rbf()).unwrap();
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 5_000)]).enable_rbf()).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
@ -437,7 +437,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 49_000)]).enable_rbf()).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
@ -470,7 +470,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000);
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 49_000)]).enable_rbf()).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
@ -501,7 +501,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000);
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 49_000)]).enable_rbf()).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();