2020-08-31 11:26:36 +02:00
|
|
|
// Magical Bitcoin Library
|
|
|
|
// Written in 2020 by
|
|
|
|
// Alekos Filini <alekos.filini@gmail.com>
|
|
|
|
//
|
|
|
|
// Copyright (c) 2020 Magical Bitcoin
|
|
|
|
//
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
|
|
// in the Software without restriction, including without limitation the rights
|
|
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
//
|
|
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
|
|
// copies or substantial portions of the Software.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
// SOFTWARE.
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
//! Transaction builder
|
|
|
|
//!
|
|
|
|
//! ## Example
|
|
|
|
//!
|
|
|
|
//! ```
|
|
|
|
//! # use std::str::FromStr;
|
|
|
|
//! # use bitcoin::*;
|
2020-09-14 14:25:38 +02:00
|
|
|
//! # use bdk::*;
|
2020-09-04 15:45:11 +02:00
|
|
|
//! # 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();
|
2020-10-14 15:21:22 +02:00
|
|
|
//! # let builder: TxBuilder<bdk::database::MemoryDatabase, _> = builder;
|
2020-09-04 15:45:11 +02:00
|
|
|
//! ```
|
|
|
|
|
2020-08-06 13:09:39 +02:00
|
|
|
use std::collections::BTreeMap;
|
2020-08-07 15:35:14 +02:00
|
|
|
use std::default::Default;
|
2020-10-14 15:21:22 +02:00
|
|
|
use std::marker::PhantomData;
|
2020-08-06 13:09:39 +02:00
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
use bitcoin::{OutPoint, Script, SigHashType, Transaction};
|
2020-08-06 13:09:39 +02:00
|
|
|
|
2020-08-06 16:56:41 +02:00
|
|
|
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
|
2020-10-14 15:21:22 +02:00
|
|
|
use crate::database::Database;
|
2020-08-31 10:49:44 +02:00
|
|
|
use crate::types::{FeeRate, UTXO};
|
2020-08-06 16:56:41 +02:00
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// 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;
|
2020-10-14 15:21:22 +02:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct TxBuilder<D: Database, Cs: CoinSelectionAlgorithm<D>> {
|
2020-09-04 15:45:11 +02:00
|
|
|
pub(crate) recipients: Vec<(Script, u64)>,
|
2020-08-06 13:09:39 +02:00
|
|
|
pub(crate) send_all: bool,
|
2020-08-07 11:23:01 +02:00
|
|
|
pub(crate) fee_rate: Option<FeeRate>,
|
2020-08-06 13:09:39 +02:00
|
|
|
pub(crate) policy_path: Option<BTreeMap<String, Vec<usize>>>,
|
|
|
|
pub(crate) utxos: Option<Vec<OutPoint>>,
|
|
|
|
pub(crate) unspendable: Option<Vec<OutPoint>>,
|
2020-08-06 16:56:41 +02:00
|
|
|
pub(crate) sighash: Option<SigHashType>,
|
2020-08-07 15:35:14 +02:00
|
|
|
pub(crate) ordering: TxOrdering,
|
|
|
|
pub(crate) locktime: Option<u32>,
|
2020-08-07 16:30:19 +02:00
|
|
|
pub(crate) rbf: Option<u32>,
|
2020-08-10 17:16:47 +02:00
|
|
|
pub(crate) version: Option<Version>,
|
2020-08-07 19:40:13 +02:00
|
|
|
pub(crate) change_policy: ChangeSpendPolicy,
|
2020-08-08 12:06:40 +02:00
|
|
|
pub(crate) force_non_witness_utxo: bool,
|
2020-08-06 16:56:41 +02:00
|
|
|
pub(crate) coin_selection: Cs,
|
2020-10-14 15:21:22 +02:00
|
|
|
|
|
|
|
phantom: PhantomData<D>,
|
2020-08-06 13:09:39 +02:00
|
|
|
}
|
|
|
|
|
2020-10-14 15:21:22 +02:00
|
|
|
// Unfortunately derive doesn't work with `PhantomData`: https://github.com/rust-lang/rust/issues/26925
|
|
|
|
impl<D: Database, Cs: CoinSelectionAlgorithm<D>> Default for TxBuilder<D, Cs>
|
|
|
|
where
|
|
|
|
Cs: Default,
|
|
|
|
{
|
|
|
|
fn default() -> Self {
|
|
|
|
TxBuilder {
|
|
|
|
recipients: Default::default(),
|
|
|
|
send_all: Default::default(),
|
|
|
|
fee_rate: Default::default(),
|
|
|
|
policy_path: Default::default(),
|
|
|
|
utxos: Default::default(),
|
|
|
|
unspendable: Default::default(),
|
|
|
|
sighash: Default::default(),
|
|
|
|
ordering: Default::default(),
|
|
|
|
locktime: Default::default(),
|
|
|
|
rbf: Default::default(),
|
|
|
|
version: Default::default(),
|
|
|
|
change_policy: Default::default(),
|
|
|
|
force_non_witness_utxo: Default::default(),
|
|
|
|
coin_selection: Default::default(),
|
|
|
|
|
|
|
|
phantom: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<D: Database> TxBuilder<D, DefaultCoinSelectionAlgorithm> {
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Create an empty builder
|
2020-08-06 16:56:41 +02:00
|
|
|
pub fn new() -> Self {
|
|
|
|
Self::default()
|
2020-08-06 13:09:39 +02:00
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Create a builder starting from a list of recipients
|
|
|
|
pub fn with_recipients(recipients: Vec<(Script, u64)>) -> Self {
|
2020-08-31 10:49:44 +02:00
|
|
|
Self::default().set_recipients(recipients)
|
2020-08-06 16:56:41 +02:00
|
|
|
}
|
|
|
|
}
|
2020-08-06 13:09:39 +02:00
|
|
|
|
2020-10-14 15:21:22 +02:00
|
|
|
impl<D: Database, Cs: CoinSelectionAlgorithm<D>> TxBuilder<D, Cs> {
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Replace the recipients already added with a new list
|
|
|
|
pub fn set_recipients(mut self, recipients: Vec<(Script, u64)>) -> Self {
|
2020-08-31 10:49:44 +02:00
|
|
|
self.recipients = recipients;
|
2020-08-06 16:56:41 +02:00
|
|
|
self
|
2020-08-06 13:09:39 +02:00
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// 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));
|
2020-08-06 13:09:39 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// 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.
|
2020-08-08 12:06:40 +02:00
|
|
|
pub fn send_all(mut self) -> Self {
|
|
|
|
self.send_all = true;
|
2020-08-06 13:09:39 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Set a custom fee rate
|
2020-08-07 11:23:01 +02:00
|
|
|
pub fn fee_rate(mut self, fee_rate: FeeRate) -> Self {
|
|
|
|
self.fee_rate = Some(fee_rate);
|
2020-08-06 13:09:39 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// 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)).
|
2020-08-06 16:56:41 +02:00
|
|
|
pub fn policy_path(mut self, policy_path: BTreeMap<String, Vec<usize>>) -> Self {
|
2020-08-06 13:09:39 +02:00
|
|
|
self.policy_path = Some(policy_path);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// 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.
|
2020-08-06 16:56:41 +02:00
|
|
|
pub fn utxos(mut self, utxos: Vec<OutPoint>) -> Self {
|
2020-08-06 13:09:39 +02:00
|
|
|
self.utxos = Some(utxos);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// 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.
|
2020-08-06 16:56:41 +02:00
|
|
|
pub fn add_utxo(mut self, utxo: OutPoint) -> Self {
|
2020-08-06 13:09:39 +02:00
|
|
|
self.utxos.get_or_insert(vec![]).push(utxo);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// 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.
|
2020-08-06 16:56:41 +02:00
|
|
|
pub fn unspendable(mut self, unspendable: Vec<OutPoint>) -> Self {
|
2020-08-06 13:09:39 +02:00
|
|
|
self.unspendable = Some(unspendable);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// 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.
|
2020-08-06 16:56:41 +02:00
|
|
|
pub fn add_unspendable(mut self, unspendable: OutPoint) -> Self {
|
2020-08-06 13:09:39 +02:00
|
|
|
self.unspendable.get_or_insert(vec![]).push(unspendable);
|
|
|
|
self
|
|
|
|
}
|
2020-08-06 16:56:41 +02:00
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Sign with a specific sig hash
|
|
|
|
///
|
|
|
|
/// **Use this option very carefully**
|
2020-08-06 16:56:41 +02:00
|
|
|
pub fn sighash(mut self, sighash: SigHashType) -> Self {
|
|
|
|
self.sighash = Some(sighash);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Choose the ordering for inputs and outputs of the transaction
|
2020-08-07 15:35:14 +02:00
|
|
|
pub fn ordering(mut self, ordering: TxOrdering) -> Self {
|
|
|
|
self.ordering = ordering;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Use a specific nLockTime while creating the transaction
|
|
|
|
///
|
|
|
|
/// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator.
|
2020-08-07 15:35:14 +02:00
|
|
|
pub fn nlocktime(mut self, locktime: u32) -> Self {
|
|
|
|
self.locktime = Some(locktime);
|
2020-08-06 16:56:41 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Enable signaling RBF
|
|
|
|
///
|
|
|
|
/// This will use the default nSequence value of `0xFFFFFFFD`.
|
2020-08-07 16:30:19 +02:00
|
|
|
pub fn enable_rbf(self) -> Self {
|
|
|
|
self.enable_rbf_with_sequence(0xFFFFFFFD)
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// 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.
|
2020-08-07 16:30:19 +02:00
|
|
|
pub fn enable_rbf_with_sequence(mut self, nsequence: u32) -> Self {
|
|
|
|
self.rbf = Some(nsequence);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// 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.
|
2020-10-09 12:03:47 +02:00
|
|
|
pub fn version(mut self, version: i32) -> Self {
|
2020-08-10 17:16:47 +02:00
|
|
|
self.version = Some(Version(version));
|
2020-08-07 16:30:19 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Do not spend change outputs
|
|
|
|
///
|
|
|
|
/// This effectively adds all the change outputs to the "unspendable" list. See
|
|
|
|
/// [`TxBuilder::unspendable`].
|
2020-08-07 19:40:13 +02:00
|
|
|
pub fn do_not_spend_change(mut self) -> Self {
|
|
|
|
self.change_policy = ChangeSpendPolicy::ChangeForbidden;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Only spend change outputs
|
|
|
|
///
|
|
|
|
/// This effectively adds all the non-change outputs to the "unspendable" list. See
|
|
|
|
/// [`TxBuilder::unspendable`].
|
2020-08-07 19:40:13 +02:00
|
|
|
pub fn only_spend_change(mut self) -> Self {
|
|
|
|
self.change_policy = ChangeSpendPolicy::OnlyChange;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Set a specific [`ChangeSpendPolicy`]. See [`TxBuilder::do_not_spend_change`] and
|
|
|
|
/// [`TxBuilder::only_spend_change`] for some shortcuts.
|
2020-08-08 12:06:40 +02:00
|
|
|
pub fn change_policy(mut self, change_policy: ChangeSpendPolicy) -> Self {
|
|
|
|
self.change_policy = change_policy;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// 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.
|
2020-08-08 12:06:40 +02:00
|
|
|
pub fn force_non_witness_utxo(mut self) -> Self {
|
|
|
|
self.force_non_witness_utxo = true;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Choose the coin selection algorithm
|
|
|
|
///
|
|
|
|
/// Overrides the [`DefaultCoinSelectionAlgorithm`](super::coin_selection::DefaultCoinSelectionAlgorithm).
|
2020-10-14 15:21:22 +02:00
|
|
|
pub fn coin_selection<P: CoinSelectionAlgorithm<D>>(
|
|
|
|
self,
|
|
|
|
coin_selection: P,
|
|
|
|
) -> TxBuilder<D, P> {
|
2020-08-06 16:56:41 +02:00
|
|
|
TxBuilder {
|
2020-08-31 10:49:44 +02:00
|
|
|
recipients: self.recipients,
|
2020-08-06 16:56:41 +02:00
|
|
|
send_all: self.send_all,
|
2020-08-07 11:23:01 +02:00
|
|
|
fee_rate: self.fee_rate,
|
2020-08-06 16:56:41 +02:00
|
|
|
policy_path: self.policy_path,
|
|
|
|
utxos: self.utxos,
|
|
|
|
unspendable: self.unspendable,
|
|
|
|
sighash: self.sighash,
|
2020-08-07 15:35:14 +02:00
|
|
|
ordering: self.ordering,
|
|
|
|
locktime: self.locktime,
|
2020-08-07 16:30:19 +02:00
|
|
|
rbf: self.rbf,
|
|
|
|
version: self.version,
|
2020-08-07 19:40:13 +02:00
|
|
|
change_policy: self.change_policy,
|
2020-08-08 12:06:40 +02:00
|
|
|
force_non_witness_utxo: self.force_non_witness_utxo,
|
2020-08-06 16:56:41 +02:00
|
|
|
coin_selection,
|
2020-10-14 15:21:22 +02:00
|
|
|
|
|
|
|
phantom: PhantomData,
|
2020-08-06 16:56:41 +02:00
|
|
|
}
|
|
|
|
}
|
2020-08-06 13:09:39 +02:00
|
|
|
}
|
2020-08-07 15:35:14 +02:00
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Ordering of the transaction's inputs and outputs
|
2020-08-10 17:16:47 +02:00
|
|
|
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
|
2020-08-07 15:35:14 +02:00
|
|
|
pub enum TxOrdering {
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Randomized (default)
|
2020-08-07 15:35:14 +02:00
|
|
|
Shuffle,
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Unchanged
|
2020-08-07 15:35:14 +02:00
|
|
|
Untouched,
|
2020-09-04 15:45:11 +02:00
|
|
|
/// BIP69 / Lexicographic
|
2020-08-07 15:35:14 +02:00
|
|
|
BIP69Lexicographic,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for TxOrdering {
|
|
|
|
fn default() -> Self {
|
|
|
|
TxOrdering::Shuffle
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TxOrdering {
|
2020-08-31 10:49:44 +02:00
|
|
|
pub fn sort_tx(&self, tx: &mut Transaction) {
|
2020-08-07 15:35:14 +02:00
|
|
|
match self {
|
|
|
|
TxOrdering::Untouched => {}
|
|
|
|
TxOrdering::Shuffle => {
|
|
|
|
use rand::seq::SliceRandom;
|
|
|
|
#[cfg(test)]
|
|
|
|
use rand::SeedableRng;
|
|
|
|
|
|
|
|
#[cfg(not(test))]
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
#[cfg(test)]
|
|
|
|
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
|
|
|
|
|
|
|
|
tx.output.shuffle(&mut rng);
|
|
|
|
}
|
|
|
|
TxOrdering::BIP69Lexicographic => {
|
|
|
|
tx.input.sort_unstable_by_key(|txin| {
|
|
|
|
(txin.previous_output.txid, txin.previous_output.vout)
|
|
|
|
});
|
|
|
|
tx.output
|
|
|
|
.sort_unstable_by_key(|txout| (txout.value, txout.script_pubkey.clone()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Transaction version
|
|
|
|
///
|
|
|
|
/// Has a default value of `1`
|
2020-08-10 17:16:47 +02:00
|
|
|
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
|
2020-10-09 12:03:47 +02:00
|
|
|
pub(crate) struct Version(pub(crate) i32);
|
2020-08-07 16:30:19 +02:00
|
|
|
|
|
|
|
impl Default for Version {
|
|
|
|
fn default() -> Self {
|
|
|
|
Version(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Policy regarding the use of change outputs when creating a transaction
|
2020-08-10 17:16:47 +02:00
|
|
|
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
|
2020-08-07 19:40:13 +02:00
|
|
|
pub enum ChangeSpendPolicy {
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Use both change and non-change outputs (default)
|
2020-08-07 19:40:13 +02:00
|
|
|
ChangeAllowed,
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Only use change outputs (see [`TxBuilder::only_spend_change`])
|
2020-08-07 19:40:13 +02:00
|
|
|
OnlyChange,
|
2020-09-04 15:45:11 +02:00
|
|
|
/// Only use non-change outputs (see [`TxBuilder::do_not_spend_change`])
|
2020-08-07 19:40:13 +02:00
|
|
|
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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-07 15:35:14 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
const ORDERING_TEST_TX: &'static str = "0200000003c26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f54\
|
|
|
|
85d1fd600f0100000000ffffffffc26f3eb7932f7acddc5ddd26602b77e75160\
|
|
|
|
79b03090a16e2c2f5485d1fd600f0000000000ffffffff571fb3e02278217852\
|
|
|
|
dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e0500000000ffffffff\
|
|
|
|
03e80300000000000002aaeee80300000000000001aa200300000000000001ff\
|
|
|
|
00000000";
|
|
|
|
macro_rules! ordering_test_tx {
|
|
|
|
() => {
|
|
|
|
deserialize::<bitcoin::Transaction>(&Vec::<u8>::from_hex(ORDERING_TEST_TX).unwrap())
|
|
|
|
.unwrap()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
use bitcoin::consensus::deserialize;
|
|
|
|
use bitcoin::hashes::hex::FromHex;
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
2020-08-10 17:16:47 +02:00
|
|
|
#[test]
|
|
|
|
fn test_output_ordering_default_shuffle() {
|
|
|
|
assert_eq!(TxOrdering::default(), TxOrdering::Shuffle);
|
|
|
|
}
|
|
|
|
|
2020-08-07 15:35:14 +02:00
|
|
|
#[test]
|
|
|
|
fn test_output_ordering_untouched() {
|
|
|
|
let original_tx = ordering_test_tx!();
|
|
|
|
let mut tx = original_tx.clone();
|
|
|
|
|
2020-08-31 10:49:44 +02:00
|
|
|
TxOrdering::Untouched.sort_tx(&mut tx);
|
2020-08-07 15:35:14 +02:00
|
|
|
|
|
|
|
assert_eq!(original_tx, tx);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_output_ordering_shuffle() {
|
|
|
|
let original_tx = ordering_test_tx!();
|
|
|
|
let mut tx = original_tx.clone();
|
|
|
|
|
2020-08-31 10:49:44 +02:00
|
|
|
TxOrdering::Shuffle.sort_tx(&mut tx);
|
2020-08-07 15:35:14 +02:00
|
|
|
|
|
|
|
assert_eq!(original_tx.input, tx.input);
|
|
|
|
assert_ne!(original_tx.output, tx.output);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_output_ordering_bip69() {
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
|
let original_tx = ordering_test_tx!();
|
|
|
|
let mut tx = original_tx.clone();
|
|
|
|
|
2020-08-31 10:49:44 +02:00
|
|
|
TxOrdering::BIP69Lexicographic.sort_tx(&mut tx);
|
2020-08-07 15:35:14 +02:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
tx.input[0].previous_output,
|
|
|
|
bitcoin::OutPoint::from_str(
|
|
|
|
"0e53ec5dfb2cb8a71fec32dc9a634a35b7e24799295ddd5278217822e0b31f57:5"
|
|
|
|
)
|
|
|
|
.unwrap()
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
tx.input[1].previous_output,
|
|
|
|
bitcoin::OutPoint::from_str(
|
|
|
|
"0f60fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2:0"
|
|
|
|
)
|
|
|
|
.unwrap()
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
tx.input[2].previous_output,
|
|
|
|
bitcoin::OutPoint::from_str(
|
|
|
|
"0f60fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2:1"
|
|
|
|
)
|
|
|
|
.unwrap()
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(tx.output[0].value, 800);
|
|
|
|
assert_eq!(tx.output[1].script_pubkey, From::from(vec![0xAA]));
|
|
|
|
assert_eq!(tx.output[2].script_pubkey, From::from(vec![0xAA, 0xEE]));
|
|
|
|
}
|
2020-08-10 17:16:47 +02:00
|
|
|
|
|
|
|
fn get_test_utxos() -> Vec<UTXO> {
|
|
|
|
vec![
|
|
|
|
UTXO {
|
|
|
|
outpoint: OutPoint {
|
|
|
|
txid: Default::default(),
|
|
|
|
vout: 0,
|
|
|
|
},
|
|
|
|
txout: Default::default(),
|
|
|
|
is_internal: false,
|
|
|
|
},
|
|
|
|
UTXO {
|
|
|
|
outpoint: OutPoint {
|
|
|
|
txid: Default::default(),
|
|
|
|
vout: 1,
|
|
|
|
},
|
|
|
|
txout: Default::default(),
|
|
|
|
is_internal: true,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_change_spend_policy_default() {
|
|
|
|
let change_spend_policy = ChangeSpendPolicy::default();
|
|
|
|
let filtered = change_spend_policy.filter_utxos(get_test_utxos().into_iter());
|
|
|
|
|
|
|
|
assert_eq!(filtered.len(), 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_change_spend_policy_no_internal() {
|
|
|
|
let change_spend_policy = ChangeSpendPolicy::ChangeForbidden;
|
|
|
|
let filtered = change_spend_policy.filter_utxos(get_test_utxos().into_iter());
|
|
|
|
|
|
|
|
assert_eq!(filtered.len(), 1);
|
|
|
|
assert_eq!(filtered[0].is_internal, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_change_spend_policy_only_internal() {
|
|
|
|
let change_spend_policy = ChangeSpendPolicy::OnlyChange;
|
|
|
|
let filtered = change_spend_policy.filter_utxos(get_test_utxos().into_iter());
|
|
|
|
|
|
|
|
assert_eq!(filtered.len(), 1);
|
|
|
|
assert_eq!(filtered[0].is_internal, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_default_tx_version_1() {
|
|
|
|
let version = Version::default();
|
|
|
|
assert_eq!(version.0, 1);
|
|
|
|
}
|
2020-08-07 15:35:14 +02:00
|
|
|
}
|