Skip to content

Commit d031a58

Browse files
committed
feat(wallet): include resolved inputs in the response when creating transactions
1 parent 7d6ccf6 commit d031a58

File tree

10 files changed

+379
-166
lines changed

10 files changed

+379
-166
lines changed

data_structures/src/transaction_factory.rs

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,22 @@ use crate::{
1616
},
1717
wit::Wit,
1818
};
19+
use itertools::Itertools;
20+
21+
#[derive(Clone, Debug)]
22+
pub struct CollectedOutputs {
23+
pub pointers: Vec<OutputPointer>,
24+
pub resolved: Vec<ValueTransferOutput>,
25+
pub total_value: u64,
26+
}
1927

2028
/// Structure that resumes the information needed to create a Transaction
2129
#[derive(Clone, Debug)]
2230
pub struct TransactionInfo {
23-
pub inputs: Vec<Input>,
24-
pub outputs: Vec<ValueTransferOutput>,
25-
pub input_value: u64,
26-
pub output_value: u64,
2731
pub fee: AbsoluteFee,
32+
pub inputs: CollectedOutputs,
33+
pub output_value: u64,
34+
pub outputs: Vec<ValueTransferOutput>,
2835
}
2936

3037
// Structure that the includes the confirmed and pending balance of a node
@@ -41,10 +48,11 @@ pub struct NodeBalance {
4148
/// can be applied on many heterogeneous data structures that may implement it.
4249
pub trait OutputsCollection {
4350
fn sort_by(&self, strategy: &UtxoSelectionStrategy) -> Vec<OutputPointer>;
44-
fn get_time_lock(&self, outptr: &OutputPointer) -> Option<u64>;
51+
fn get(&self, outptr: &OutputPointer) -> Option<ValueTransferOutput>;
52+
fn get_usage_timeout(&self, outptr: &OutputPointer) -> Option<u64>;
4553
fn get_value(&self, outptr: &OutputPointer) -> Option<u64>;
4654
fn get_included_block_number(&self, outptr: &OutputPointer) -> Option<Epoch>;
47-
fn set_used_output_pointer(&mut self, outptrs: &[Input], ts: u64);
55+
fn set_used_output_pointer(&mut self, outptrs: impl Iterator<Item = Input>, ts: u64);
4856

4957
/// Select enough UTXOs to sum up to `amount`.
5058
///
@@ -57,27 +65,28 @@ pub trait OutputsCollection {
5765
// The block number must be lower than this limit
5866
block_number_limit: Option<u32>,
5967
utxo_strategy: &UtxoSelectionStrategy,
60-
) -> Result<(Vec<OutputPointer>, u64), TransactionError> {
68+
) -> Result<CollectedOutputs, TransactionError> {
6169
// FIXME: this is a very naive utxo selection algorithm
6270
if amount == 0 {
6371
// Transactions with no inputs make no sense
6472
return Err(TransactionError::ZeroAmount);
6573
}
6674

67-
let mut acc = 0;
75+
let mut total_value = 0;
6876
let mut total: u64 = 0;
69-
let mut list = vec![];
77+
let mut outputs = vec![];
78+
let mut pointers = vec![];
7079

7180
let utxo_iter = self.sort_by(utxo_strategy);
7281

73-
for op in utxo_iter.iter() {
74-
let value = self.get_value(op).unwrap();
82+
for pointer in utxo_iter.iter() {
83+
let output: ValueTransferOutput = self.get(pointer).unwrap();
7584
total = total
76-
.checked_add(value)
85+
.checked_add(output.value)
7786
.ok_or(TransactionError::OutputValueOverflow)?;
7887

79-
if let Some(time_lock) = self.get_time_lock(op) {
80-
if time_lock > timestamp {
88+
if let Some(usage_timeout) = self.get_usage_timeout(pointer) {
89+
if usage_timeout > timestamp {
8190
continue;
8291
}
8392
}
@@ -86,7 +95,7 @@ pub trait OutputsCollection {
8695
// Ignore all outputs created after `block_number_limit`.
8796
// Outputs from the genesis block will never be ignored because `block_number_limit`
8897
// can't go lower than `0`.
89-
if let Some(limit) = self.get_included_block_number(op) {
98+
if let Some(limit) = self.get_included_block_number(pointer) {
9099
if limit > block_number_limit {
91100
continue;
92101
}
@@ -95,20 +104,25 @@ pub trait OutputsCollection {
95104
}
96105
}
97106

98-
acc += value;
99-
list.push(*op);
107+
total_value += output.value;
108+
pointers.push(*pointer);
109+
outputs.push(output);
100110

101-
if acc >= amount {
111+
if total_value >= amount {
102112
break;
103113
}
104114
}
105115

106-
if acc >= amount {
107-
Ok((list, acc))
116+
if total_value >= amount {
117+
Ok(CollectedOutputs {
118+
resolved: outputs,
119+
pointers,
120+
total_value,
121+
})
108122
} else {
109123
Err(TransactionError::NoMoney {
110124
total_balance: total,
111-
available_balance: acc,
125+
available_balance: total_value,
112126
transaction_value: amount,
113127
})
114128
}
@@ -148,16 +162,14 @@ pub trait OutputsCollection {
148162
.checked_add(absolute_fee.as_nanowits())
149163
.ok_or(TransactionError::FeeOverflow)?;
150164

151-
let (output_pointers, input_value) =
165+
let inputs =
152166
self.take_enough_utxos(amount, timestamp, block_number_limit, utxo_strategy)?;
153-
let inputs: Vec<Input> = output_pointers.into_iter().map(Input::new).collect();
154167

155168
Ok(TransactionInfo {
169+
fee: absolute_fee,
156170
inputs,
157-
outputs,
158-
input_value,
159171
output_value,
160-
fee: absolute_fee,
172+
outputs,
161173
})
162174
}
163175
Fee::Relative(priority) => {
@@ -168,21 +180,23 @@ pub trait OutputsCollection {
168180
.checked_add(absolute_fee.as_nanowits())
169181
.ok_or(TransactionError::FeeOverflow)?;
170182

171-
let (output_pointers, input_value) = self.take_enough_utxos(
183+
let inputs = self.take_enough_utxos(
172184
amount,
173185
timestamp,
174186
block_number_limit,
175187
utxo_strategy,
176188
)?;
177-
let inputs: Vec<Input> = output_pointers.into_iter().map(Input::new).collect();
178189

179-
let new_weight =
180-
calculate_weight(inputs.len(), outputs.len() + 1, dr_output, max_weight)?;
190+
let new_weight = calculate_weight(
191+
inputs.pointers.len(),
192+
outputs.len() + 1,
193+
dr_output,
194+
max_weight,
195+
)?;
181196
if new_weight == current_weight {
182197
return Ok(TransactionInfo {
183198
inputs,
184199
outputs,
185-
input_value,
186200
output_value,
187201
fee: absolute_fee,
188202
});
@@ -316,21 +330,23 @@ pub fn build_vtt(
316330
max_weight,
317331
)?;
318332

333+
let used_pointers = tx_info.inputs.pointers.iter().cloned().map(Input::new);
334+
319335
// Mark UTXOs as used so we don't double spend
320336
// Save the timestamp after which the transaction will be considered timed out
321337
// and the output will become available for spending it again
322338
if !dry_run {
323-
utxos.set_used_output_pointer(&tx_info.inputs, timestamp + tx_pending_timeout);
339+
utxos.set_used_output_pointer(used_pointers.clone(), timestamp + tx_pending_timeout);
324340
}
325341

326342
let mut outputs = tx_info.outputs;
327343
insert_change_output(
328344
&mut outputs,
329345
own_pkh,
330-
tx_info.input_value - tx_info.output_value - tx_info.fee.as_nanowits(),
346+
tx_info.inputs.total_value - tx_info.output_value - tx_info.fee.as_nanowits(),
331347
);
332348

333-
Ok(VTTransactionBody::new(tx_info.inputs, outputs))
349+
Ok(VTTransactionBody::new(used_pointers.collect_vec(), outputs))
334350
}
335351

336352
/// Build data request transaction with the given outputs and fee.
@@ -362,21 +378,27 @@ pub fn build_drt(
362378
max_weight,
363379
)?;
364380

381+
let used_pointers = tx_info.inputs.pointers.iter().cloned().map(Input::new);
382+
365383
// Mark UTXOs as used so we don't double spend
366384
// Save the timestamp after which the transaction will be considered timed out
367385
// and the output will become available for spending it again
368386
if !dry_run {
369-
utxos.set_used_output_pointer(&tx_info.inputs, timestamp + tx_pending_timeout);
387+
utxos.set_used_output_pointer(used_pointers.clone(), timestamp + tx_pending_timeout);
370388
}
371389

372390
let mut outputs = tx_info.outputs;
373391
insert_change_output(
374392
&mut outputs,
375393
own_pkh,
376-
tx_info.input_value - tx_info.output_value - tx_info.fee.as_nanowits(),
394+
tx_info.inputs.total_value - tx_info.output_value - tx_info.fee.as_nanowits(),
377395
);
378396

379-
Ok(DRTransactionBody::new(tx_info.inputs, outputs, dr_output))
397+
Ok(DRTransactionBody::new(
398+
used_pointers.collect_vec(),
399+
outputs,
400+
dr_output,
401+
))
380402
}
381403

382404
/// Check if there are enough collateral for a CommitTransaction
@@ -438,19 +460,21 @@ pub fn build_commit_collateral(
438460
u32::MAX,
439461
)?;
440462

463+
let used_pointers = tx_info.inputs.pointers.iter().cloned().map(Input::new);
464+
441465
// Mark UTXOs as used so we don't double spend
442466
// Save the timestamp after which the transaction will be considered timed out
443467
// and the output will become available for spending it again
444-
utxos.set_used_output_pointer(&tx_info.inputs, timestamp + tx_pending_timeout);
468+
utxos.set_used_output_pointer(used_pointers.clone(), timestamp + tx_pending_timeout);
445469

446470
let mut outputs = tx_info.outputs;
447471
insert_change_output(
448472
&mut outputs,
449473
own_pkh,
450-
tx_info.input_value - tx_info.output_value - tx_info.fee.as_nanowits(),
474+
tx_info.inputs.total_value - tx_info.output_value - tx_info.fee.as_nanowits(),
451475
);
452476

453-
Ok((tx_info.inputs, outputs))
477+
Ok((used_pointers.collect_vec(), outputs))
454478
}
455479

456480
/// Calculate the sum of the values of the outputs pointed by the

data_structures/src/utxo_pool.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -422,8 +422,12 @@ impl<'a> OutputsCollection for NodeUtxosRef<'a> {
422422
}
423423
}
424424

425-
fn get_time_lock(&self, outptr: &OutputPointer) -> Option<u64> {
426-
let time_lock = self.all_utxos.get(outptr).map(|vto| vto.time_lock);
425+
fn get(&self, outptr: &OutputPointer) -> Option<ValueTransferOutput> {
426+
self.all_utxos.get(outptr)
427+
}
428+
429+
fn get_usage_timeout(&self, outptr: &OutputPointer) -> Option<u64> {
430+
let time_lock = self.get(outptr).map(|vto| vto.time_lock);
427431
let time_lock_by_used = self.own_utxos.get(outptr).copied();
428432

429433
// The most restrictive time_lock will be used to avoid UTXOs during a transaction creation
@@ -436,14 +440,14 @@ impl<'a> OutputsCollection for NodeUtxosRef<'a> {
436440
}
437441

438442
fn get_value(&self, outptr: &OutputPointer) -> Option<u64> {
439-
self.all_utxos.get(outptr).map(|vto| vto.value)
443+
self.get(outptr).map(|vto| vto.value)
440444
}
441445

442446
fn get_included_block_number(&self, outptr: &OutputPointer) -> Option<u32> {
443447
self.all_utxos.included_in_block_number(outptr)
444448
}
445449

446-
fn set_used_output_pointer(&mut self, _inputs: &[Input], _ts: u64) {
450+
fn set_used_output_pointer(&mut self, _inputs: impl Iterator<Item = Input>, _ts: u64) {
447451
log::warn!("Mutable operations not supported in `NodeUtxosRef`, use `NodeUtxos` instead");
448452
}
449453
}
@@ -474,8 +478,12 @@ impl<'a> OutputsCollection for NodeUtxos<'a> {
474478
self.as_ref().sort_by(strategy)
475479
}
476480

477-
fn get_time_lock(&self, outptr: &OutputPointer) -> Option<u64> {
478-
self.as_ref().get_time_lock(outptr)
481+
fn get(&self, outptr: &OutputPointer) -> Option<ValueTransferOutput> {
482+
self.as_ref().get(outptr)
483+
}
484+
485+
fn get_usage_timeout(&self, outptr: &OutputPointer) -> Option<u64> {
486+
self.as_ref().get_usage_timeout(outptr)
479487
}
480488

481489
fn get_value(&self, outptr: &OutputPointer) -> Option<u64> {
@@ -486,7 +494,7 @@ impl<'a> OutputsCollection for NodeUtxos<'a> {
486494
self.as_ref().get_included_block_number(outptr)
487495
}
488496

489-
fn set_used_output_pointer(&mut self, inputs: &[Input], ts: u64) {
497+
fn set_used_output_pointer(&mut self, inputs: impl Iterator<Item = Input>, ts: u64) {
490498
for input in inputs {
491499
let current_ts = self.own_utxos.get_mut(input.output_pointer()).unwrap();
492500
*current_ts = ts;
@@ -559,7 +567,7 @@ pub struct UtxoInfo {
559567
#[allow(clippy::cast_sign_loss)]
560568
fn create_utxo_metadata(
561569
vto: &ValueTransferOutput,
562-
o: &OutputPointer,
570+
output_pointer: OutputPointer,
563571
all_utxos: &UnspentOutputsPool,
564572
block_number_limit: u32,
565573
) -> UtxoMetadata {
@@ -569,10 +577,11 @@ fn create_utxo_metadata(
569577
} else {
570578
0
571579
};
572-
let utxo_mature: bool = all_utxos.included_in_block_number(o).unwrap() <= block_number_limit;
580+
let utxo_mature: bool =
581+
all_utxos.included_in_block_number(&output_pointer).unwrap() <= block_number_limit;
573582

574583
UtxoMetadata {
575-
output_pointer: *o,
584+
output_pointer,
576585
value: vto.value,
577586
timelock,
578587
utxo_mature,
@@ -597,7 +606,7 @@ pub fn get_utxo_info(
597606
// We do not need to do anything here because this method returns all utxos.
598607
},
599608
|(o, (vto, _block_number))| {
600-
v.push(create_utxo_metadata(vto, o, all_utxos, block_number_limit));
609+
v.push(create_utxo_metadata(vto, *o, all_utxos, block_number_limit));
601610
},
602611
);
603612
v
@@ -609,7 +618,7 @@ pub fn get_utxo_info(
609618
all_utxos
610619
.get(o)
611620
.as_ref()
612-
.map(|vto| create_utxo_metadata(vto, o, all_utxos, block_number_limit))
621+
.map(|vto| create_utxo_metadata(vto, *o, all_utxos, block_number_limit))
613622
})
614623
.collect()
615624
};

0 commit comments

Comments
 (0)