diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index 0e8f2088..27342d43 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -112,6 +112,8 @@ class TransactionBuilder: _inputs: List[UTxO] = field(init=False, default_factory=lambda: []) + _potential_inputs: List[UTxO] = field(init=False, default_factory=lambda: []) + _excluded_inputs: List[UTxO] = field(init=False, default_factory=lambda: []) _input_addresses: List[Union[Address, str]] = field( @@ -331,6 +333,10 @@ def add_output( def inputs(self) -> List[UTxO]: return self._inputs + @property + def potential_inputs(self) -> List[UTxO]: + return self._potential_inputs + @property def excluded_inputs(self) -> List[UTxO]: return self._excluded_inputs @@ -984,14 +990,26 @@ def build( lambda p, n, v: v > 0 ) + # Create a set of all seen utxos in addition to other utxo lists. + # We need this set to avoid adding the same utxo twice. + # The reason of not turning all utxo lists into sets is that we want to keep the order of utxos and make + # utxo selection deterministic. + seen_utxos = set(selected_utxos) + # When there are positive coin or native asset quantity in unfulfilled Value if Value() < unfulfilled_amount: additional_utxo_pool = [] additional_amount = Value() + + for utxo in self.potential_inputs: + additional_amount += utxo.output.amount + seen_utxos.add(utxo) + additional_utxo_pool.append(utxo) + for address in self.input_addresses: for utxo in self.context.utxos(address): if ( - utxo not in selected_utxos + utxo not in seen_utxos and utxo not in self.excluded_inputs and not utxo.output.datum_hash # UTxO with datum should be added by using `add_script_input` and not utxo.output.datum @@ -999,6 +1017,7 @@ def build( ): additional_utxo_pool.append(utxo) additional_amount += utxo.output.amount + seen_utxos.add(utxo) for index, selector in enumerate(self.utxo_selectors): try: diff --git a/test/pycardano/test_txbuilder.py b/test/pycardano/test_txbuilder.py index 34cfdecc..91e8c094 100644 --- a/test/pycardano/test_txbuilder.py +++ b/test/pycardano/test_txbuilder.py @@ -1,3 +1,4 @@ +import copy from dataclasses import replace from test.pycardano.test_key import SK from test.pycardano.util import chain_context @@ -122,6 +123,33 @@ def test_tx_builder_with_certain_input(chain_context): assert expected == tx_body.to_primitive() +def test_tx_builder_with_potential_inputs(chain_context): + tx_builder = TransactionBuilder(chain_context, [RandomImproveMultiAsset([0, 0])]) + sender = "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x" + sender_address = Address.from_primitive(sender) + + utxos = chain_context.utxos(sender) + + tx_builder.potential_inputs.extend(utxos) + + for i in range(20): + utxo = copy.deepcopy(utxos[0]) + utxo.input.index = i + 100 + tx_builder.potential_inputs.append(utxo) + + assert len(tx_builder.potential_inputs) > 1 + + tx_builder.add_output( + TransactionOutput.from_primitive( + [sender, [5000000, {b"1111111111111111111111111111": {b"Token1": 1}}]] + ) + ) + + tx_body = tx_builder.build(change_address=sender_address) + + assert len(tx_body.inputs) < len(tx_builder.potential_inputs) + + def test_tx_builder_multi_asset(chain_context): tx_builder = TransactionBuilder(chain_context) sender = "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x"