Skip to content

Commit e9a2edc

Browse files
committed
psbt,params: Add method add_planned_input
This can be used to add inputs that come with a plan or PSBT input provided. Remove `SelectedOutpoints` and move the unique UTXO set logic to `PsbtParams`.
1 parent 5b3524f commit e9a2edc

File tree

2 files changed

+55
-73
lines changed

2 files changed

+55
-73
lines changed

src/psbt/params.rs

Lines changed: 50 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ use crate::TxOrdering;
1919
#[derive(Debug)]
2020
pub struct PsbtParams {
2121
// Inputs
22-
pub(crate) utxos: SelectedOutpoints,
22+
pub(crate) set: HashSet<OutPoint>,
23+
pub(crate) utxos: Vec<OutPoint>,
24+
pub(crate) inputs: Vec<Input>,
2325

2426
// Outputs
2527
pub(crate) recipients: Vec<(ScriptBuf, Amount)>,
@@ -44,7 +46,9 @@ pub struct PsbtParams {
4446
impl Default for PsbtParams {
4547
fn default() -> Self {
4648
Self {
49+
set: Default::default(),
4750
utxos: Default::default(),
51+
inputs: Default::default(),
4852
assets: Default::default(),
4953
recipients: Default::default(),
5054
change_descriptor: Default::default(),
@@ -69,18 +73,21 @@ impl PsbtParams {
6973
/// responsible for ensuring that elements of `outpoints` correspond to outputs of previous
7074
/// transactions and are currently unspent.
7175
pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> &mut Self {
72-
self.utxos.extend(outpoints.iter().copied());
76+
self.utxos
77+
.extend(outpoints.iter().copied().filter(|&op| self.set.insert(op)));
7378
self
7479
}
7580

7681
/// Get the currently selected spends.
77-
pub fn utxos(&self) -> &Vec<OutPoint> {
78-
self.utxos.utxos()
82+
pub fn utxos(&self) -> &HashSet<OutPoint> {
83+
&self.set
7984
}
8085

8186
/// Remove a UTXO from the currently selected inputs.
8287
pub fn remove_utxo(&mut self, outpoint: &OutPoint) -> &mut Self {
83-
self.utxos.remove(outpoint);
88+
if self.set.remove(outpoint) {
89+
self.utxos.retain(|op| op != outpoint);
90+
}
8491
self
8592
}
8693

@@ -185,41 +192,18 @@ impl PsbtParams {
185192
self.ordering = ordering;
186193
self
187194
}
188-
}
189-
190-
/// Structure containing the set of outpoints that are manually selected. This
191-
/// ensures that no single outpoint appears more than once in the inputs, while preserving
192-
/// the order in which they are added to the [`PsbtParams`].
193-
#[derive(Debug, Clone, Default)]
194-
pub(crate) struct SelectedOutpoints {
195-
/// Unique set of selected outpoints.
196-
set: HashSet<OutPoint>,
197-
/// UTXOs added.
198-
pub utxos: Vec<OutPoint>,
199-
}
200-
201-
impl SelectedOutpoints {
202-
/// Add an outpoint.
203-
fn extend(&mut self, outpoints: impl IntoIterator<Item = OutPoint>) {
204-
self.utxos
205-
.extend(outpoints.into_iter().filter(|&op| self.set.insert(op)))
206-
}
207195

208-
/// Remove an outpoint.
209-
fn remove(&mut self, outpoint: &OutPoint) {
210-
if self.set.remove(outpoint) {
211-
self.utxos.retain(|op| op != outpoint)
196+
/// Add a planned input.
197+
///
198+
/// This can be used to add inputs that come with a [`Plan`] or [`psbt::Input`] provided.
199+
///
200+
/// [`Plan`]: miniscript::plan::Plan
201+
/// [`psbt::Input`]: bitcoin::psbt::Input
202+
pub fn add_planned_input(&mut self, input: Input) -> &mut Self {
203+
if self.set.insert(input.prev_outpoint()) {
204+
self.inputs.push(input);
212205
}
213-
}
214-
215-
/// Whether the current selection contains the given `outpoint`.
216-
pub fn contains(&self, outpoint: &OutPoint) -> bool {
217-
self.set.contains(outpoint)
218-
}
219-
220-
/// Get the selected spends (UTXOs).
221-
fn utxos(&self) -> &Vec<OutPoint> {
222-
&self.utxos
206+
self
223207
}
224208
}
225209

@@ -262,26 +246,27 @@ pub struct ReplaceParams {
262246
}
263247

264248
impl ReplaceParams {
265-
/// Construct from PSBT `params` and an iterator of `txs` to replace.
266-
pub(crate) fn new(txs: &[Arc<Transaction>], params: PsbtParams) -> Self {
267-
Self {
268-
inner: params,
249+
/// Construct from `inner` params and the `txs` to replace.
250+
pub(crate) fn new(txs: &[Arc<Transaction>], inner: PsbtParams) -> Self {
251+
let mut params = Self {
252+
inner,
269253
..Default::default()
270-
}
271-
.replace(txs)
254+
};
255+
params.replace(txs);
256+
params
272257
}
273258

274259
/// Replace spends of the provided `txs`. This will internally set the internal list
275260
/// of UTXOs to be spent.
276-
pub fn replace(self, txs: &[Arc<Transaction>]) -> Self {
277-
let txs: Vec<Arc<Transaction>> = txs.to_vec();
278-
let mut txids: HashSet<Txid> = txs.iter().map(|tx| tx.compute_txid()).collect();
279-
let mut tx_graph = TxGraph::<BlockId>::default();
280-
let mut utxos = SelectedOutpoints::default();
281-
282-
for tx in txs {
283-
let _ = tx_graph.insert_tx(tx);
284-
}
261+
pub fn replace(&mut self, txs: &[Arc<Transaction>]) {
262+
self.inner.utxos.clear();
263+
let mut utxos = vec![];
264+
265+
let (mut txids_to_replace, txs): (HashSet<Txid>, Vec<Transaction>) = txs
266+
.iter()
267+
.map(|tx| (tx.compute_txid(), tx.as_ref().clone()))
268+
.unzip();
269+
let tx_graph = TxGraph::<BlockId>::new(txs);
285270

286271
// Sanitize the RBF set by removing elements of `txs` which have ancestors
287272
// in the same set. This is to avoid spending outputs of txs that are bound
@@ -291,21 +276,16 @@ impl ReplaceParams {
291276
if tx.is_coinbase()
292277
|| tx_graph
293278
.walk_ancestors(Arc::clone(tx), |_, tx| Some(tx.compute_txid()))
294-
.any(|ancestor_txid| txids.contains(&ancestor_txid))
279+
.any(|ancestor_txid| txids_to_replace.contains(&ancestor_txid))
295280
{
296-
txids.remove(&tx_node.txid);
281+
txids_to_replace.remove(&tx_node.txid);
297282
} else {
298283
utxos.extend(tx.input.iter().map(|txin| txin.previous_output));
299284
}
300285
}
301286

302-
Self {
303-
inner: PsbtParams {
304-
utxos,
305-
..self.inner
306-
},
307-
replace: txids,
308-
}
287+
self.replace = txids_to_replace;
288+
self.inner.add_utxos(&utxos);
309289
}
310290

311291
/// Add recipients.
@@ -325,8 +305,8 @@ impl ReplaceParams {
325305
}
326306

327307
/// Get the currently selected spends.
328-
pub fn utxos(&self) -> &Vec<OutPoint> {
329-
self.inner.utxos()
308+
pub fn utxos(&self) -> &HashSet<OutPoint> {
309+
&self.inner.set
330310
}
331311

332312
/// Remove a UTXO from the currently selected inputs.
@@ -416,7 +396,7 @@ mod test {
416396
let txs: Vec<Arc<Transaction>> =
417397
[tx_a, tx_b, tx_c, tx_d].into_iter().map(Arc::new).collect();
418398
let params = ReplaceParams::new(&txs, PsbtParams::default());
419-
assert_eq!(params.inner.utxos.set, expect_spends);
399+
assert_eq!(params.inner.set, expect_spends);
420400
assert_eq!(params.replace, [txid_a, txid_c].into());
421401
}
422402

@@ -431,20 +411,20 @@ mod test {
431411
}
432412
assert_eq!(
433413
params.utxos(),
434-
&vec![op],
414+
&[op].into(),
435415
"Failed to filter duplicate outpoints"
436416
);
437-
assert!(params.utxos.contains(&op));
417+
assert_eq!(params.set, [op].into());
438418

439-
params.utxos = SelectedOutpoints::default();
419+
params.utxos = vec![];
440420

441421
// Try adding duplicates in the same set.
442422
params.add_utxos(&[op, op, op]);
443423
assert_eq!(
444424
params.utxos(),
445-
&vec![op],
425+
&[op].into(),
446426
"Failed to filter duplicate outpoints"
447427
);
448-
assert!(params.utxos.contains(&op));
428+
assert_eq!(params.set, [op].into());
449429
}
450430
}

src/wallet/mod.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2773,7 +2773,7 @@ impl Wallet {
27732773
let mut canon_params = params.canonical_params.clone();
27742774
canon_params
27752775
.assume_canonical
2776-
.extend(params.utxos().iter().map(|op| op.txid));
2776+
.extend(params.utxos.iter().map(|op| op.txid));
27772777
let txouts = self
27782778
.list_indexed_txouts(canon_params)
27792779
.map(|(_, txo)| (txo.outpoint, txo))
@@ -2882,13 +2882,14 @@ impl Wallet {
28822882
let (assets, change_script, txouts) = self.parse_params(&params);
28832883

28842884
let must_spend: Vec<Input> = params
2885-
.utxos()
2885+
.utxos
28862886
.iter()
28872887
.map(|&op| -> Result<_, CreatePsbtError> {
28882888
let txo = txouts.get(&op).ok_or(CreatePsbtError::UnknownUtxo(op))?;
28892889
self.plan_input(txo, &assets)
28902890
.ok_or(CreatePsbtError::Plan(op))
28912891
})
2892+
.chain(params.inputs.iter().cloned().map(Result::Ok))
28922893
.collect::<Result<_, _>>()?;
28932894

28942895
// Get input candidates
@@ -3068,13 +3069,14 @@ impl Wallet {
30683069
let (assets, change_script, txouts) = self.parse_params(&params);
30693070

30703071
let must_spend: Vec<Input> = params
3071-
.utxos()
3072+
.utxos
30723073
.iter()
30733074
.map(|&op| -> Result<_, CreatePsbtError> {
30743075
let txo = txouts.get(&op).ok_or(CreatePsbtError::UnknownUtxo(op))?;
30753076
self.plan_input(txo, &assets)
30763077
.ok_or(CreatePsbtError::Plan(op))
30773078
})
3079+
.chain(params.inputs.iter().cloned().map(Result::Ok))
30783080
.collect::<Result<_, _>>()?;
30793081

30803082
// Get input candidates

0 commit comments

Comments
 (0)