Skip to content

Commit a9e3692

Browse files
committed
Update InputPair constructor calls to use new constructor
The new constructors will explicitly describe what type of inputs are being created, while the original `new` constructor is made private and used for internal validation
1 parent 910f32e commit a9e3692

3 files changed

Lines changed: 70 additions & 41 deletions

File tree

payjoin-cli/src/app/wallet.rs

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ use std::sync::Arc;
44

55
use anyhow::{anyhow, Context, Result};
66
use bitcoincore_rpc::json::WalletCreateFundedPsbtOptions;
7-
use bitcoincore_rpc::{Auth, Client, RpcApi};
7+
use bitcoincore_rpc::{bitcoincore_rpc_json, Auth, Client, RpcApi};
88
use payjoin::bitcoin::consensus::encode::{deserialize, serialize_hex};
99
use payjoin::bitcoin::consensus::Encodable;
10-
use payjoin::bitcoin::psbt::{Input, Psbt};
10+
use payjoin::bitcoin::psbt::Psbt;
1111
use payjoin::bitcoin::{
12-
Address, Amount, Denomination, FeeRate, Network, OutPoint, Script, Transaction, TxIn, TxOut,
13-
Txid,
12+
Address, AddressType, Amount, Denomination, FeeRate, Network, OutPoint, Script, Transaction,
13+
TxOut, Txid,
1414
};
1515
use payjoin::receive::InputPair;
1616

@@ -145,7 +145,15 @@ impl BitcoindWallet {
145145
.bitcoind
146146
.list_unspent(None, None, None, None, None)
147147
.context("Failed to list unspent")?;
148-
Ok(unspent.into_iter().map(input_pair_from_list_unspent).collect())
148+
149+
unspent
150+
.into_iter()
151+
.map(|utxo| {
152+
let tx = self.bitcoind.get_transaction(&utxo.txid, Some(true))?.transaction()?;
153+
input_pair_from_list_unspent(utxo, tx, self.network()?)
154+
})
155+
.collect::<Result<Vec<InputPair>, _>>()
156+
.context("Failed to convert list unspent entry to InputPair")
149157
}
150158

151159
/// Get the network this wallet is operating on
@@ -158,22 +166,56 @@ impl BitcoindWallet {
158166
}
159167

160168
pub fn input_pair_from_list_unspent(
161-
utxo: bitcoincore_rpc::bitcoincore_rpc_json::ListUnspentResultEntry,
162-
) -> InputPair {
163-
let psbtin = Input {
164-
// NOTE: non_witness_utxo is not necessary because bitcoin-cli always supplies
165-
// witness_utxo, even for non-witness inputs
166-
witness_utxo: Some(TxOut {
167-
value: utxo.amount,
168-
script_pubkey: utxo.script_pub_key.clone(),
169-
}),
170-
redeem_script: utxo.redeem_script.clone(),
171-
witness_script: utxo.witness_script.clone(),
172-
..Default::default()
173-
};
174-
let txin = TxIn {
175-
previous_output: OutPoint { txid: utxo.txid, vout: utxo.vout },
176-
..Default::default()
177-
};
178-
InputPair::new(txin, psbtin).expect("Input pair should be valid")
169+
utxo: bitcoincore_rpc_json::ListUnspentResultEntry,
170+
tx: Transaction,
171+
network: Network,
172+
) -> Result<InputPair> {
173+
match Address::from_script(utxo.script_pub_key.as_script(), network)?.address_type().ok_or(
174+
anyhow!("Address is unknown, non-standard or related to the future witness version"),
175+
)? {
176+
AddressType::P2pkh => Ok(InputPair::new_p2pkh(
177+
Transaction {
178+
version: tx.version,
179+
lock_time: tx.lock_time,
180+
input: tx.input,
181+
output: tx.output,
182+
},
183+
OutPoint { txid: utxo.txid, vout: utxo.vout },
184+
None, // Default sequence
185+
)?),
186+
AddressType::P2sh => {
187+
let redeem_script = utxo.redeem_script.ok_or(anyhow!("Missing redeem script"))?;
188+
Ok(InputPair::new_p2sh(
189+
Transaction {
190+
version: tx.version,
191+
lock_time: tx.lock_time,
192+
input: tx.input,
193+
output: tx.output,
194+
},
195+
OutPoint { txid: utxo.txid, vout: utxo.vout },
196+
redeem_script,
197+
None, // Default sequence
198+
)?)
199+
}
200+
AddressType::P2wpkh => Ok(InputPair::new_p2wpkh(
201+
TxOut { value: utxo.amount, script_pubkey: utxo.script_pub_key },
202+
OutPoint { txid: utxo.txid, vout: utxo.vout },
203+
None,
204+
)?),
205+
AddressType::P2wsh => {
206+
let witness_script = utxo.witness_script.ok_or(anyhow!("Missing witness script"))?;
207+
Ok(InputPair::new_p2wsh(
208+
TxOut { value: utxo.amount, script_pubkey: utxo.script_pub_key },
209+
OutPoint { txid: utxo.txid, vout: utxo.vout },
210+
witness_script,
211+
None, // Default sequence
212+
)?)
213+
}
214+
AddressType::P2tr => Ok(InputPair::new_p2tr(
215+
TxOut { value: utxo.amount, script_pubkey: utxo.script_pub_key },
216+
OutPoint { txid: utxo.txid, vout: utxo.vout },
217+
None, // Default sequence
218+
)?),
219+
_ => Err(anyhow!("Unsupported AddressType")), // AddressType is non-exhaustive
220+
}
179221
}

payjoin/src/psbt/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,6 @@ pub(crate) enum InternalPsbtInputError {
243243
/// TxOut provided in `segwit_utxo` doesn't match the one in `non_segwit_utxo`
244244
SegWitTxOutMismatch,
245245
AddressType(AddressTypeError),
246-
NoRedeemScript,
247246
InvalidScriptPubKey(AddressType),
248247
}
249248

@@ -254,7 +253,6 @@ impl fmt::Display for InternalPsbtInputError {
254253
Self::UnequalTxid => write!(f, "transaction ID of previous transaction doesn't match one specified in input spending it"),
255254
Self::SegWitTxOutMismatch => write!(f, "transaction output provided in SegWit UTXO field doesn't match the one in non-SegWit UTXO field"),
256255
Self::AddressType(_) => write!(f, "invalid address type"),
257-
Self::NoRedeemScript => write!(f, "provided p2sh PSBT input is missing a redeem_script"),
258256
Self::InvalidScriptPubKey(e) => write!(f, "provided script was not a valid type of {e}")
259257
}
260258
}
@@ -267,7 +265,6 @@ impl std::error::Error for InternalPsbtInputError {
267265
Self::UnequalTxid => None,
268266
Self::SegWitTxOutMismatch => None,
269267
Self::AddressType(error) => Some(error),
270-
Self::NoRedeemScript => None,
271268
Self::InvalidScriptPubKey(_) => None,
272269
}
273270
}

payjoin/src/receive/mod.rs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,12 @@ pub struct InputPair {
4747
}
4848

4949
impl InputPair {
50-
pub fn new(txin: TxIn, psbtin: psbt::Input) -> Result<Self, PsbtInputError> {
50+
/// Helper function for internally validating inputs and constructing input pairs
51+
fn new(txin: TxIn, psbtin: psbt::Input) -> Result<Self, PsbtInputError> {
5152
let input_pair = Self { txin, psbtin };
5253
let raw = InternalInputPair::from(&input_pair);
5354
raw.validate_utxo()?;
54-
let address_type = raw.address_type().map_err(InternalPsbtInputError::AddressType)?;
55-
if address_type == AddressType::P2sh && input_pair.psbtin.redeem_script.is_none() {
56-
return Err(InternalPsbtInputError::NoRedeemScript.into());
57-
}
55+
5856
Ok(input_pair)
5957
}
6058

@@ -78,11 +76,7 @@ impl InputPair {
7876
..psbt::Input::default()
7977
};
8078

81-
let input_pair = Self { txin, psbtin };
82-
let raw = InternalInputPair::from(&input_pair);
83-
raw.validate_utxo()?;
84-
85-
Ok(input_pair)
79+
Self::new(txin, psbtin)
8680
}
8781

8882
fn get_txout_for_outpoint(
@@ -151,11 +145,7 @@ impl InputPair {
151145
witness_script,
152146
..psbt::Input::default()
153147
};
154-
let input_pair = Self { txin, psbtin };
155-
let raw = InternalInputPair::from(&input_pair);
156-
raw.validate_utxo()?;
157-
158-
Ok(input_pair)
148+
Self::new(txin, psbtin)
159149
}
160150

161151
/// Constructs a new ['InputPair'] for spending a native SegWit P2WPKH output

0 commit comments

Comments
 (0)