Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,31 @@ impl Wallet {
.next()
}

/// Inserts a full [`Transaction`] into the wallet's transaction graph.
///
/// This is used for providing a previous transaction when wallet operations require the full
/// transaction data rather than only a [`TxOut`], such as reconstructing a `non_witness_utxo`
/// for foreign inputs.
///
/// Transactions inserted with this method may affect wallet queries such as [`get_tx`],
/// [`list_unspent`], or [`list_output`] if they are relevant to this wallet.
///
/// **WARNINGS:** This should only be used to add transactions that you trust. If the
/// transaction contains outputs owned by this wallet, those outputs will be indexed as wallet
/// outputs.
///
/// You must persist the changes resulting from one or more calls to this method if you need
/// the inserted transaction data to be reloaded after closing the wallet.
/// See [`Wallet::reveal_next_address`].
///
/// [`get_tx`]: Self::get_tx
/// [`list_unspent`]: Self::list_unspent
/// [`list_output`]: Self::list_output
pub fn insert_tx<T: Into<Arc<Transaction>>>(&mut self, tx: T) {
let additions = self.tx_graph.insert_tx(tx);
self.stage.merge(additions.into());
}

/// Inserts a [`TxOut`] at [`OutPoint`] into the wallet's transaction graph.
///
/// This is used for providing a previous output's value so that we can use [`calculate_fee`]
Expand Down
66 changes: 47 additions & 19 deletions src/wallet/tx_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,16 +366,20 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
///
/// This is an **EXPERIMENTAL** feature, API and other major changes are expected.
///
/// In order to use [`Wallet::calculate_fee`] or [`Wallet::calculate_fee_rate`] for a
/// transaction created with foreign UTXO(s) you must manually insert the corresponding
/// TxOut(s) into the tx graph using the [`Wallet::insert_txout`] function.
/// Before calling this method, you must preregister the foreign prev data in the wallet tx
/// graph. Use [`Wallet::insert_txout`] if `psbt_input` only provides a `witness_utxo`, or
/// [`Wallet::insert_tx`] if it provides a `non_witness_utxo`.
///
/// This preregistration is also what allows [`Wallet::calculate_fee`] or
/// [`Wallet::calculate_fee_rate`] to work for transactions created with foreign UTXO(s).
///
/// # Errors
///
/// This method returns errors in the following circumstances:
///
/// 1. The `psbt_input` does not contain a `witness_utxo` or `non_witness_utxo`.
/// 2. The data in `non_witness_utxo` does not match what is in `outpoint`.
/// 3. The wallet tx graph does not already contain the prev data required by `psbt_input`.
///
/// 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`]
Expand Down Expand Up @@ -407,25 +411,32 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
satisfaction_weight: Weight,
sequence: Sequence,
) -> Result<&mut Self, AddForeignUtxoError> {
if psbt_input.witness_utxo.is_none() {
match psbt_input.non_witness_utxo.as_ref() {
Some(tx) => {
if tx.compute_txid() != outpoint.txid {
return Err(AddForeignUtxoError::InvalidTxid {
input_txid: tx.compute_txid(),
foreign_utxo: outpoint,
});
}
if tx.output.len() <= outpoint.vout as usize {
return Err(AddForeignUtxoError::InvalidOutpoint(outpoint));
}
}
None => {
return Err(AddForeignUtxoError::MissingUtxo);
}
if psbt_input.witness_utxo.is_none() && psbt_input.non_witness_utxo.is_none() {
return Err(AddForeignUtxoError::MissingUtxo);
}

if let Some(tx) = psbt_input.non_witness_utxo.as_ref() {
if tx.compute_txid() != outpoint.txid {
return Err(AddForeignUtxoError::InvalidTxid {
input_txid: tx.compute_txid(),
foreign_utxo: outpoint,
});
}
if tx.output.len() <= outpoint.vout as usize {
return Err(AddForeignUtxoError::InvalidOutpoint(outpoint));
}
}

if psbt_input.non_witness_utxo.is_some() {
if self.wallet.tx_graph().get_tx(outpoint.txid).is_none() {
return Err(AddForeignUtxoError::MissingRegisteredTx(outpoint));
}
} else if psbt_input.witness_utxo.is_some()
&& self.wallet.tx_graph().get_txout(outpoint).is_none()
{
return Err(AddForeignUtxoError::MissingRegisteredTxOut(outpoint));
}

let mut existing_index: Option<usize> = None;

for (idx, wutxo) in self.params.utxos.iter().enumerate() {
Expand Down Expand Up @@ -803,6 +814,10 @@ pub enum AddForeignUtxoError {
InvalidOutpoint(OutPoint),
/// Foreign utxo missing witness_utxo or non_witness_utxo
MissingUtxo,
/// Foreign utxo outpoint has not been preregistered in the wallet tx graph
MissingRegisteredTxOut(OutPoint),
/// Foreign utxo parent transaction has not been preregistered in the wallet tx graph
MissingRegisteredTx(OutPoint),
}

impl fmt::Display for AddForeignUtxoError {
Expand All @@ -822,6 +837,16 @@ impl fmt::Display for AddForeignUtxoError {
outpoint.txid, outpoint.vout,
),
Self::MissingUtxo => write!(f, "Foreign utxo missing witness_utxo or non_witness_utxo"),
Self::MissingRegisteredTxOut(outpoint) => write!(
f,
"Foreign UTXO must be inserted with Wallet::insert_txout or Wallet::insert_tx before calling add_foreign_utxo for txid: {} with vout: {}",
outpoint.txid, outpoint.vout,
),
Self::MissingRegisteredTx(outpoint) => write!(
f,
"Foreign UTXO parent transaction must be inserted with Wallet::insert_tx before calling add_foreign_utxo for txid: {} with vout: {}",
outpoint.txid, outpoint.vout,
),
}
}
}
Expand Down Expand Up @@ -1340,6 +1365,9 @@ mod test {
.max_weight_to_satisfy()
.unwrap();

// Preregister the full foreign parent tx for `non_witness_utxo`.
wallet2.insert_tx(tx1.as_ref().clone());

let mut builder = wallet2.build_tx();

// add foreign UTXO with satisfaction weight x
Expand Down
Loading
Loading