@@ -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