Skip to content

Commit 36df24b

Browse files
authored
Merge branch 'master' into ruslan/int-to-i32
2 parents e37c8be + 0a451c4 commit 36df24b

File tree

1 file changed

+73
-9
lines changed

1 file changed

+73
-9
lines changed

rust/src/tx_builder.rs

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,26 @@ impl TransactionBuilder {
192192
}
193193
},
194194
CoinSelectionStrategyCIP2::RandomImprove => {
195+
fn add_random_input(
196+
s: &mut TransactionBuilder,
197+
rng: &mut rand::rngs::ThreadRng,
198+
available_inputs: &mut Vec<TransactionUnspentOutput>,
199+
input_total: &Value,
200+
output_total: &Value,
201+
added: &Value,
202+
needed: &Value
203+
) -> Result<(Value, Value, Value, Value, TransactionUnspentOutput), JsError> {
204+
let random_index = rng.gen_range(0..available_inputs.len());
205+
let input = available_inputs.swap_remove(random_index);
206+
// differing from CIP2, we include the needed fees in the targets instead of just output values
207+
let input_fee = s.fee_for_input(&input.output.address, &input.input, &input.output.amount)?;
208+
s.add_input(&input.output.address, &input.input, &input.output.amount);
209+
let new_input_total = input_total.checked_add(&input.output.amount)?;
210+
let new_output_total = output_total.checked_add(&Value::new(&input_fee))?;
211+
let new_needed = needed.checked_add(&Value::new(&input_fee))?;
212+
let new_added = added.checked_add(&input.output.amount)?;
213+
Ok((new_input_total, new_output_total, new_added, new_needed, input))
214+
}
195215
use rand::Rng;
196216
if self.outputs.0.iter().any(|output| output.amount.multiasset.is_some()) {
197217
return Err(JsError::from_str("Multiasset values not supported by RandomImprove. Please use LargestFirst"));
@@ -208,15 +228,11 @@ impl TransactionBuilder {
208228
if available_inputs.is_empty() {
209229
return Err(JsError::from_str("UTxO Balance Insufficient"));
210230
}
211-
let random_index = rng.gen_range(0..available_inputs.len());
212-
let input = available_inputs.swap_remove(random_index);
213-
// differing from CIP2, we include the needed fees in the targets instead of just output values
214-
let input_fee = self.fee_for_input(&input.output.address, &input.input, &input.output.amount)?;
215-
self.add_input(&input.output.address, &input.input, &input.output.amount);
216-
input_total = input_total.checked_add(&input.output.amount)?;
217-
output_total = output_total.checked_add(&Value::new(&input_fee))?;
218-
needed = needed.checked_add(&Value::new(&input_fee))?;
219-
added = added.checked_add(&input.output.amount)?;
231+
let (new_input_total, new_output_total, new_added, new_needed, input) = add_random_input(self, &mut rng, &mut available_inputs, &input_total, &output_total, &added, &needed)?;
232+
input_total = new_input_total;
233+
output_total = new_output_total;
234+
added = new_added;
235+
needed = new_needed;
220236
associated_inputs.entry(output.clone()).or_default().push(input);
221237
}
222238
}
@@ -240,6 +256,23 @@ impl TransactionBuilder {
240256
}
241257
}
242258
}
259+
// Phase 3: add extra inputs needed for fees
260+
// We do this at the end because this new inputs won't be associated with
261+
// a specific output, so the improvement algorithm we do above does not apply here.
262+
if input_total < output_total {
263+
let mut added = Value::new(&Coin::zero());
264+
let mut remaining_amount = output_total.checked_sub(&input_total)?;
265+
while added < remaining_amount {
266+
if available_inputs.is_empty() {
267+
return Err(JsError::from_str("UTxO Balance Insufficient"));
268+
}
269+
let (new_input_total, new_output_total, new_added, new_remaining_amount, _) = add_random_input(self, &mut rng, &mut available_inputs, &input_total, &output_total, &added, &remaining_amount)?;
270+
input_total = new_input_total;
271+
output_total = new_output_total;
272+
added = new_added;
273+
remaining_amount = new_remaining_amount;
274+
}
275+
}
243276
},
244277
}
245278

@@ -2143,6 +2176,37 @@ mod tests {
21432176
assert!(add_inputs_res.is_ok(), "{:?}", add_inputs_res.err());
21442177
}
21452178

2179+
#[test]
2180+
fn tx_builder_cip2_random_improve_adds_enough_for_fees() {
2181+
// we have a = 1 to test increasing fees when more inputs are added
2182+
let linear_fee = LinearFee::new(&to_bignum(1), &to_bignum(0));
2183+
let mut tx_builder = TransactionBuilder::new(
2184+
&linear_fee,
2185+
&Coin::zero(),
2186+
&to_bignum(0),
2187+
9999,
2188+
9999,
2189+
&to_bignum(0),
2190+
);
2191+
const COST: u64 = 100;
2192+
tx_builder.add_output(&TransactionOutput::new(
2193+
&Address::from_bech32("addr1vyy6nhfyks7wdu3dudslys37v252w2nwhv0fw2nfawemmnqs6l44z").unwrap(),
2194+
&Value::new(&to_bignum(COST))
2195+
)).unwrap();
2196+
assert_eq!(tx_builder.min_fee().unwrap(), to_bignum(53));
2197+
let mut available_inputs = TransactionUnspentOutputs::new();
2198+
available_inputs.add(&make_input(1u8, Value::new(&to_bignum(150))));
2199+
available_inputs.add(&make_input(2u8, Value::new(&to_bignum(150))));
2200+
available_inputs.add(&make_input(3u8, Value::new(&to_bignum(150))));
2201+
let add_inputs_res =
2202+
tx_builder.add_inputs_from(&available_inputs, CoinSelectionStrategyCIP2::RandomImprove);
2203+
assert!(add_inputs_res.is_ok(), "{:?}", add_inputs_res.err());
2204+
assert_eq!(tx_builder.min_fee().unwrap(), to_bignum(264));
2205+
let change_addr = ByronAddress::from_base58("Ae2tdPwUPEZGUEsuMAhvDcy94LKsZxDjCbgaiBBMgYpR8sKf96xJmit7Eho").unwrap().to_address();
2206+
let add_change_res = tx_builder.add_change_if_needed(&change_addr);
2207+
assert!(add_change_res.is_ok(), "{:?}", add_change_res.err());
2208+
}
2209+
21462210
fn build_tx_pay_to_multisig() {
21472211
let linear_fee = LinearFee::new(&to_bignum(10), &to_bignum(2));
21482212
let mut tx_builder =

0 commit comments

Comments
 (0)