Skip to content

Commit

Permalink
Expose TxBuilder.add_foreign_utxo method
Browse files Browse the repository at this point in the history
  • Loading branch information
thunderbiscuit committed Jun 9, 2023
1 parent 7b79f39 commit 7bb22c7
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 16 deletions.
6 changes: 4 additions & 2 deletions bdk-ffi/src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,11 @@ interface TxBuilder {

TxBuilder add_unspendable(OutPoint unspendable);

TxBuilder add_utxos(sequence<OutPoint> outpoints);

TxBuilder add_utxo(OutPoint outpoint);

TxBuilder add_utxos(sequence<OutPoint> outpoints);
TxBuilder add_foreign_utxo(OutPoint outpoint, Input psbt_input, u64 satisfaction_weight);

TxBuilder do_not_spend_change();

Expand Down Expand Up @@ -480,7 +482,7 @@ interface Descriptor {
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);

[Throws=BdkError]
u32 max_satisfaction_weight();
u64 max_satisfaction_weight();

string as_string();

Expand Down
4 changes: 2 additions & 2 deletions bdk-ffi/src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ impl Descriptor {
///
/// # Errors
/// When the descriptor is impossible to satisfy (ex: sh(OP_FALSE)).
pub(crate) fn max_satisfaction_weight(&self) -> Result<u32, BdkError> {
pub(crate) fn max_satisfaction_weight(&self) -> Result<u64, BdkError> {
self.extended_descriptor
.max_satisfaction_weight()
.map(|w| u32::try_from(w).unwrap())
.map(|w| u64::try_from(w).unwrap())
.map_err(BdkError::Miniscript)
}

Expand Down
11 changes: 8 additions & 3 deletions bdk-ffi/src/psbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,8 @@ impl PartiallySignedTransaction {
}
}

/// A key-value map for an input of the corresponding index in the unsigned
/// transaction.
#[derive(Debug)]
/// A key-value map for an input of the corresponding index in the unsigned transaction.
#[derive(Clone, Debug)]
pub(crate) struct Input {
inner: BdkInput,
}
Expand All @@ -106,6 +105,12 @@ impl From<BdkInput> for Input {
}
}

impl From<Input> for BdkInput {
fn from(input: Input) -> Self {
input.inner
}
}

/// A Signature hash type for the corresponding input. As of taproot upgrade, the signature hash
/// type can be either [`EcdsaSighashType`] or [`SchnorrSighashType`] but it is not possible to know
/// directly which signature hash type the user is dealing with. Therefore, the user is responsible
Expand Down
86 changes: 77 additions & 9 deletions bdk-ffi/src/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
use crate::blockchain::Blockchain;
use crate::database::DatabaseConfig;
use crate::descriptor::Descriptor;
use crate::psbt::{Input, PartiallySignedTransaction, PsbtSighashType};
use crate::{
AddressIndex, AddressInfo, Balance, BdkError, LocalUtxo, OutPoint, Progress, ProgressHolder,
RbfValue, Script, ScriptAmount, TransactionDetails, TxBuilderResult,
};
use bdk::bitcoin::blockdata::script::Script as BdkScript;
use bdk::bitcoin::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Sequence, Txid};
use bdk::database::any::AnyDatabase;
Expand All @@ -12,15 +20,6 @@ use std::ops::Deref;
use std::str::FromStr;
use std::sync::{Arc, Mutex, MutexGuard};

use crate::blockchain::Blockchain;
use crate::database::DatabaseConfig;
use crate::descriptor::Descriptor;
use crate::psbt::{Input, PartiallySignedTransaction, PsbtSighashType};
use crate::{
AddressIndex, AddressInfo, Balance, BdkError, LocalUtxo, OutPoint, Progress, ProgressHolder,
RbfValue, Script, ScriptAmount, TransactionDetails, TxBuilderResult,
};

#[derive(Debug)]
pub(crate) struct Wallet {
pub(crate) wallet_mutex: Mutex<BdkWallet<AnyDatabase>>,
Expand Down Expand Up @@ -265,6 +264,7 @@ pub(crate) struct TxBuilder {
pub(crate) drain_to: Option<BdkScript>,
pub(crate) rbf: Option<RbfValue>,
pub(crate) data: Vec<u8>,
pub(crate) foreign_utxos: Vec<(OutPoint, Arc<Input>, u64)>,
}

impl TxBuilder {
Expand All @@ -281,6 +281,7 @@ impl TxBuilder {
drain_to: None,
rbf: None,
data: Vec::new(),
foreign_utxos: Vec::new(),
}
}

Expand Down Expand Up @@ -334,6 +335,53 @@ impl TxBuilder {
})
}

/// Add a foreign UTXO i.e. a UTXO not owned by this wallet.
/// At a minimum to add a foreign UTXO we need:
/// outpoint: To add it to the raw transaction.
/// psbt_input: To know the value.
/// satisfaction_weight: To know how much weight/vbytes the input will add to the transaction for fee calculation.
///
/// There are several security concerns about adding foreign UTXOs that application developers should consider.
/// First, how do you know the value of the input is correct? If a non_witness_utxo is provided in the
/// psbt_input then this method implicitly verifies the value by checking it against the transaction.
/// If only a witness_utxo is provided then this method does not verify the value but just takes it as a
/// given – it is up to you to check that whoever sent you the input_psbt was not lying!
///
/// Secondly, you must somehow provide satisfaction_weight of the input. Depending on your application
/// it may be important that this be known precisely. If not, a malicious counterparty may fool you into putting in
/// a value that is too low, giving the transaction a lower than expected feerate. They could also fool you
/// into putting a value that is too high causing you to pay a fee that is too high. The party who is broadcasting
/// the transaction can of course check the real input weight matches the expected weight prior to broadcasting.
///
/// To guarantee the satisfaction_weight is correct, you can require the party providing the psbt_input provide
/// a miniscript descriptor for the input so you can check it against the script_pubkey and then ask it for the
/// max_satisfaction_weight.
///
/// Errors
/// This method returns errors in the following circumstances:
/// The psbt_input does not contain a witness_utxo or non_witness_utxo.
/// The data in non_witness_utxo does not match what is in outpoint.
///
/// Note unless you set only_witness_utxo any non-taproot psbt_input you pass to this method must
/// have non_witness_utxo set otherwise you will get an error when finish is called.
pub(crate) fn add_foreign_utxo(
&self,
outpoint: OutPoint,
psbt_input: Arc<Input>,
satisfaction_weight: u64,
) -> Arc<Self> {
// TODO: Why doesn't the OutPoint parameter here need an Arc?

let mut current_foreign_utxos: Vec<(OutPoint, Arc<Input>, u64)> =
self.foreign_utxos.clone();
let new_foreign_utxo = (outpoint, psbt_input, satisfaction_weight);
current_foreign_utxos.push(new_foreign_utxo);
Arc::new(TxBuilder {
foreign_utxos: current_foreign_utxos,
..self.clone()
})
}

/// Do not spend change outputs. This effectively adds all the change outputs to the "unspendable" list. See TxBuilder.unspendable.
pub(crate) fn do_not_spend_change(&self) -> Arc<Self> {
Arc::new(TxBuilder {
Expand Down Expand Up @@ -446,6 +494,26 @@ impl TxBuilder {
let utxos: &[BdkOutPoint] = &bdk_utxos;
tx_builder.add_utxos(utxos)?;
}
if !self.foreign_utxos.is_empty() {
// TODO: Not sure why the double dereference ** is needed here... it just works?
// I really just need to grab the Input inside the Arc but not sure how else to do it.
for (outpoint, input, value) in self.foreign_utxos.iter() {
let input_new: Input = (**input).clone();
tx_builder.add_foreign_utxo(outpoint.into(), input_new.into(), *value as usize)?;
}

// let bdk_foreign_utxos: Vec<(OutPoint, Arc<Input>, u64)> = self
// .foreign_utxos.iter().map(|(outpoint, input, value)| {
// (outpoint, input.clone(), *value)
// }
// ).collect();
// let foreign_utxos: Vec<(OutPoint, Arc<Input>, u64)> = bdk_foreign_utxos;
// for (outpoint, input, value) in foreign_utxos.iter() {

// foreign_utxos.forEach(|(outpoint, input, value)| {
// tx_builder.add_foreign_utxo(outpoint, input, value)?;
// });
}
if !self.unspendable.is_empty() {
let bdk_unspendable: Vec<BdkOutPoint> =
self.unspendable.iter().map(BdkOutPoint::from).collect();
Expand Down

0 comments on commit 7bb22c7

Please sign in to comment.