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-08-06 13:09:39 +02:00
|
|
|
|
2020-08-07 15:35:14 +02:00
|
|
|
use bitcoin::{Address, OutPoint, 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-08-07 11:23:01 +02:00
|
|
|
use super::utils::FeeRate;
|
2020-08-06 16:56:41 +02:00
|
|
|
|
|
|
|
// TODO: add a flag to ignore change outputs (make them unspendable)
|
2020-08-06 13:09:39 +02:00
|
|
|
#[derive(Debug, Default)]
|
2020-08-06 16:56:41 +02:00
|
|
|
pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
|
2020-08-06 13:09:39 +02:00
|
|
|
pub(crate) addressees: Vec<(Address, u64)>,
|
|
|
|
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-06 16:56:41 +02:00
|
|
|
pub(crate) coin_selection: Cs,
|
2020-08-06 13:09:39 +02:00
|
|
|
}
|
|
|
|
|
2020-08-06 16:56:41 +02:00
|
|
|
impl TxBuilder<DefaultCoinSelectionAlgorithm> {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self::default()
|
2020-08-06 13:09:39 +02:00
|
|
|
}
|
|
|
|
|
2020-08-06 16:56:41 +02:00
|
|
|
pub fn from_addressees(addressees: Vec<(Address, u64)>) -> Self {
|
|
|
|
Self::default().set_addressees(addressees)
|
|
|
|
}
|
|
|
|
}
|
2020-08-06 13:09:39 +02:00
|
|
|
|
2020-08-06 16:56:41 +02:00
|
|
|
impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
|
|
|
|
pub fn set_addressees(mut self, addressees: Vec<(Address, u64)>) -> Self {
|
|
|
|
self.addressees = addressees;
|
|
|
|
self
|
2020-08-06 13:09:39 +02:00
|
|
|
}
|
|
|
|
|
2020-08-06 16:56:41 +02:00
|
|
|
pub fn add_addressee(mut self, address: Address, amount: u64) -> Self {
|
2020-08-06 13:09:39 +02:00
|
|
|
self.addressees.push((address, amount));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-08-06 16:56:41 +02:00
|
|
|
pub fn send_all(mut self, send_all: bool) -> Self {
|
|
|
|
self.send_all = send_all;
|
2020-08-06 13:09:39 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
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-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-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-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-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-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
|
|
|
|
|
|
|
pub fn sighash(mut self, sighash: SigHashType) -> Self {
|
|
|
|
self.sighash = Some(sighash);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-08-07 15:35:14 +02:00
|
|
|
pub fn ordering(mut self, ordering: TxOrdering) -> Self {
|
|
|
|
self.ordering = ordering;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn nlocktime(mut self, locktime: u32) -> Self {
|
|
|
|
self.locktime = Some(locktime);
|
2020-08-06 16:56:41 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> {
|
|
|
|
TxBuilder {
|
|
|
|
addressees: self.addressees,
|
|
|
|
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-06 16:56:41 +02:00
|
|
|
coin_selection,
|
|
|
|
}
|
|
|
|
}
|
2020-08-06 13:09:39 +02:00
|
|
|
}
|
2020-08-07 15:35:14 +02:00
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
pub enum TxOrdering {
|
|
|
|
Shuffle,
|
|
|
|
Untouched,
|
|
|
|
BIP69Lexicographic,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for TxOrdering {
|
|
|
|
fn default() -> Self {
|
|
|
|
TxOrdering::Shuffle
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TxOrdering {
|
|
|
|
pub fn modify_tx(&self, tx: &mut Transaction) {
|
|
|
|
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()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_output_ordering_untouched() {
|
|
|
|
let original_tx = ordering_test_tx!();
|
|
|
|
let mut tx = original_tx.clone();
|
|
|
|
|
|
|
|
TxOrdering::Untouched.modify_tx(&mut tx);
|
|
|
|
|
|
|
|
assert_eq!(original_tx, tx);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_output_ordering_shuffle() {
|
|
|
|
let original_tx = ordering_test_tx!();
|
|
|
|
let mut tx = original_tx.clone();
|
|
|
|
|
|
|
|
TxOrdering::Shuffle.modify_tx(&mut tx);
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
TxOrdering::BIP69Lexicographic.modify_tx(&mut tx);
|
|
|
|
|
|
|
|
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]));
|
|
|
|
}
|
|
|
|
}
|