Skip to content

Commit ba45b8c

Browse files
committed
#490 Cache collateral utxos
1 parent 95515e4 commit ba45b8c

File tree

4 files changed

+159
-17
lines changed

4 files changed

+159
-17
lines changed

function/src/main/java/com/bloxbean/cardano/client/function/TxBuilderContext.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class TxBuilderContext {
4747
//Stores utxos used in the transaction.
4848
//This list is cleared after each build() call.
4949
private Set<Utxo> utxos = new HashSet<>();
50+
private Set<Utxo> collateralUtxos = new HashSet<>();
5051

5152
@Setter(AccessLevel.NONE)
5253
private Map<String, Tuple<PlutusScript, byte[]>> refScripts = new HashMap<>();
@@ -205,6 +206,24 @@ public void clearUtxos() {
205206
utxos.clear();
206207
}
207208

209+
public void addCollateralUtxo(Utxo utxo) {
210+
collateralUtxos.add(utxo);
211+
}
212+
213+
public Set<Utxo> getCollateralUtxos() {
214+
return collateralUtxos;
215+
}
216+
217+
public void clearCollateralUtxos() {
218+
collateralUtxos.clear();
219+
}
220+
221+
public Set<Utxo> getAllUtxos() {
222+
Set<Utxo> allUtxos = new HashSet<>(utxos);
223+
allUtxos.addAll(collateralUtxos);
224+
return allUtxos;
225+
}
226+
208227
@SneakyThrows
209228
public void addRefScripts(PlutusScript plutusScript) {
210229
refScripts.put(HexUtil.encodeHexString(plutusScript.getScriptHash()), new Tuple<>(plutusScript, plutusScript.scriptRefBytes()));
@@ -307,6 +326,7 @@ private Transaction buildTransaction(TxBuilder txBuilder) {
307326
private void clearTempStates() {
308327
clearMintMultiAssets();
309328
clearUtxos();
329+
clearCollateralUtxos();
310330
clearRefScripts();
311331
}
312332
}

function/src/main/java/com/bloxbean/cardano/client/function/helper/ScriptBalanceTxProviders.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.bloxbean.cardano.client.function.helper;
22

3+
import com.bloxbean.cardano.client.api.AddressIterator;
34
import com.bloxbean.cardano.client.api.TransactionEvaluator;
5+
import com.bloxbean.cardano.client.api.common.AddressIterators;
46
import com.bloxbean.cardano.client.function.TxBuilder;
57
import com.bloxbean.cardano.client.function.exception.TxBuildException;
68
import com.bloxbean.cardano.client.transaction.spec.Value;
@@ -15,20 +17,38 @@
1517
public class ScriptBalanceTxProviders {
1618

1719
//TODO -- Unit tests pending
20+
1821
/**
1922
* Function to balance an unbalanced transaction using Automatic Utxo Discovery with Additional Signers.
2023
* This function invokes {@link BalanceTxBuilders#balanceTxWithAdditionalSigners(String, int)} to balance the transaction.
2124
* If any new inputs are added to the transaction during balancing, the script cost and fee will be recomputed.
2225
*
2326
* @param feePayer Fee payer address
27+
* @param additionalSigners o of Additional signers. This is required for accurate fee calculation.
28+
* @param containsScript If the transaction contains script
29+
* @return
30+
*/
31+
public static TxBuilder balanceTx(String feePayer, int additionalSigners, boolean containsScript) {
32+
return balanceTx(AddressIterators.of(feePayer), additionalSigners, containsScript);
33+
}
34+
35+
/**
36+
* Function to balance an unbalanced transaction using Automatic Utxo Discovery with Additional Signers.
37+
* This function invokes {@link BalanceTxBuilders#balanceTxWithAdditionalSigners(String, int)} to balance the transaction.
38+
* If any new inputs are added to the transaction during balancing, the script cost and fee will be recomputed.
39+
*
40+
* @param feePayerAddrIter Fee payer address iterator
2441
* @param additionalSigners No of Additional signers. This is required for accurate fee calculation.
2542
* @param containsScript If the transaction contains script
2643
* @return TxBuilder
2744
*/
28-
public static TxBuilder balanceTx(String feePayer, int additionalSigners, boolean containsScript) {
45+
public static TxBuilder balanceTx(AddressIterator feePayerAddrIter, int additionalSigners, boolean containsScript) {
2946
return (ctx, transaction) -> {
47+
48+
String feePayerAddr = feePayerAddrIter.getFirst().getAddress();
49+
3050
int inputSize = transaction.getBody().getInputs().size();
31-
BalanceTxBuilders.balanceTxWithAdditionalSigners(feePayer, additionalSigners).apply(ctx, transaction);
51+
BalanceTxBuilders.balanceTxWithAdditionalSigners(feePayerAddrIter.clone(), additionalSigners).apply(ctx, transaction);
3252
int newInputSize = transaction.getBody().getInputs().size();
3353

3454
if (!containsScript || (newInputSize == inputSize)) { //TODO -- check for contains script
@@ -45,7 +65,7 @@ public static TxBuilder balanceTx(String feePayer, int additionalSigners, boolea
4565
BigInteger fee = transaction.getBody().getFee();
4666
transaction.getBody().setFee(BigInteger.valueOf(170000));
4767
transaction.getBody().getOutputs().stream()
48-
.filter(output -> feePayer.equals(output.getAddress()))
68+
.filter(output -> feePayerAddr.equals(output.getAddress()))
4969
.max((to1, to2) -> to1.getValue().getCoin().compareTo(to2.getValue().getCoin()))
5070
.ifPresent(transactionOutput -> transactionOutput.setValue(transactionOutput.getValue().add(Value.builder().coin(fee).build())));
5171

@@ -56,7 +76,7 @@ public static TxBuilder balanceTx(String feePayer, int additionalSigners, boolea
5676

5777
//Recompute script cost
5878
ScriptCostEvaluators.evaluateScriptCost().apply(ctx, transaction);
59-
BalanceTxBuilders.balanceTxWithAdditionalSigners(feePayer, additionalSigners).apply(ctx, transaction);
79+
BalanceTxBuilders.balanceTxWithAdditionalSigners(feePayerAddrIter, additionalSigners).apply(ctx, transaction);
6080
};
6181
}
6282
}

quicktx/src/main/java/com/bloxbean/cardano/client/quicktx/QuickTxBuilder.java

Lines changed: 107 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import com.bloxbean.cardano.client.transaction.spec.TransactionInput;
2424
import com.bloxbean.cardano.client.util.JsonUtil;
2525
import com.bloxbean.cardano.client.util.Tuple;
26+
import com.bloxbean.cardano.hdwallet.Wallet;
27+
import com.bloxbean.cardano.hdwallet.util.HDWalletAddressIterator;
2628
import lombok.NonNull;
2729
import lombok.extern.slf4j.Slf4j;
2830

@@ -147,7 +149,9 @@ public TxContext compose(AbstractTx... txs) {
147149
public class TxContext {
148150
private AbstractTx[] txList;
149151
private String feePayer;
152+
private Wallet feePayerWallet;
150153
private String collateralPayer;
154+
private Wallet collateralPayerWallet;
151155
private Set<byte[]> requiredSigners;
152156
private Set<TransactionInput> collateralInputs;
153157

@@ -183,18 +187,70 @@ public class TxContext {
183187
* When there are more than one txs, fee payer address is mandatory.
184188
*
185189
* @param address fee payer address
186-
* @return TxContext
190+
* @return TxContext - the updated transaction context with the fee payer set
191+
* @throws TxBuildException if the fee payer has already been set
187192
*/
188193
public TxContext feePayer(String address) {
194+
if (feePayerWallet != null || feePayer != null)
195+
throw new TxBuildException("The fee payer has already been set. It can only be set once.");
196+
189197
this.feePayer = address;
190198
return this;
191199
}
192200

201+
/**
202+
* Sets the fee payer wallet for the transaction. When there is only one tx, sender's address is used as fee payer address.
203+
* When there are more than one txs, fee payer address/wallet is mandatory.
204+
*
205+
* @param feePayerWallet the wallet that will act as the fee payer for the transaction
206+
* @return TxContext - the updated transaction context with the fee payer set
207+
* @throws TxBuildException if the fee payer has already been set
208+
*/
209+
public TxContext feePayer(Wallet feePayerWallet) {
210+
if (feePayerWallet != null || feePayer != null)
211+
throw new TxBuildException("The fee payer has already been set. It can only be set once.");
212+
213+
this.feePayerWallet = feePayerWallet;
214+
// TODO feePayer is not used in this scenarios, but it must be set to avoid breaking other things.
215+
this.feePayer = this.feePayerWallet.getBaseAddress(0).getAddress();
216+
217+
return this;
218+
}
219+
220+
/**
221+
* Sets the provided collateral payer address. This method ensures that the collateral payer can only be set once.
222+
*
223+
* @param address the address of the collateral payer to be set
224+
* @return TxContext
225+
* @throws TxBuildException if the collateral payer has already been set
226+
*/
193227
public TxContext collateralPayer(String address) {
228+
if (collateralPayerWallet != null || collateralPayer != null)
229+
throw new TxBuildException("The collateral payer has already been set. It can only be set once.");
230+
194231
this.collateralPayer = address;
195232
return this;
196233
}
197234

235+
/**
236+
* Sets the collateral payer using the provided wallet. This method ensures that the collateral payer
237+
* is set only once.
238+
*
239+
* @param wallet the wallet from which the collateral payer address will be derived
240+
* @return TxContext
241+
* @throws TxBuildException if the collateral payer has already been set
242+
*/
243+
public TxContext collateralPayer(Wallet wallet) {
244+
if (collateralPayerWallet != null || collateralPayer != null)
245+
throw new TxBuildException("The collateral payer has already been set. It can only be set once.");
246+
247+
this.collateralPayerWallet = wallet;
248+
// TODO collateralPayer is not used in this scenarios, but it must be set to avoid breaking other things.
249+
this.collateralPayer = this.collateralPayerWallet.getBaseAddress(0).getAddress();
250+
251+
return this;
252+
}
253+
198254
/**
199255
* Set a TxBuilder function to transform the transaction before balance calculation.
200256
* This is useful when additional transformation logic is required before balance calculation.
@@ -281,7 +337,10 @@ private Tuple<TxBuilderContext, TxBuilder> _build() {
281337
((ScriptTx) tx).withChangeAddress(feePayer);
282338
}
283339
if (tx.getFromAddress() == null && tx instanceof ScriptTx) {
284-
((ScriptTx) tx).from(feePayer);
340+
if (feePayerWallet != null)
341+
((ScriptTx) tx).from(feePayerWallet);
342+
else
343+
((ScriptTx) tx).from(feePayer);
285344
}
286345

287346
txBuilder = txBuilder.andThen(tx.complete());
@@ -338,7 +397,7 @@ private Tuple<TxBuilderContext, TxBuilder> _build() {
338397
if (preBalanceTrasformer != null)
339398
txBuilder = txBuilder.andThen(preBalanceTrasformer);
340399

341-
if (feePayer == null) {
400+
if (feePayer == null && feePayerWallet == null) {
342401
if (txList.length == 1) {
343402
feePayer = txList[0].getFeePayer();
344403
if (feePayer == null)
@@ -352,9 +411,18 @@ private Tuple<TxBuilderContext, TxBuilder> _build() {
352411
txBuilder = buildValidityIntervalTxBuilder(txBuilder);
353412

354413
if (containsScriptTx) {
355-
if (collateralPayer == null)
356-
collateralPayer = feePayer;
357-
txBuilder = txBuilder.andThen(buildCollateralOutput(collateralPayer));
414+
if (collateralPayer == null && collateralPayerWallet == null) {
415+
if (feePayerWallet != null)
416+
collateralPayerWallet = feePayerWallet;
417+
else
418+
collateralPayer = feePayer;
419+
}
420+
421+
if (collateralPayerWallet != null) {
422+
txBuilder = txBuilder.andThen(buildCollateralOutput(collateralPayerWallet));
423+
} else {
424+
txBuilder = txBuilder.andThen(buildCollateralOutput(collateralPayer));
425+
}
358426
}
359427

360428
if (containsScriptTx) {
@@ -393,7 +461,11 @@ private Tuple<TxBuilderContext, TxBuilder> _build() {
393461
}
394462

395463
//Balance outputs
396-
txBuilder = txBuilder.andThen(ScriptBalanceTxProviders.balanceTx(feePayer, totalSigners, containsScriptTx));
464+
if (feePayerWallet != null) {
465+
var walletAddrIterator = new HDWalletAddressIterator(feePayerWallet, utxoSupplier);
466+
txBuilder = txBuilder.andThen(ScriptBalanceTxProviders.balanceTx(walletAddrIterator, totalSigners, containsScriptTx));
467+
} else
468+
txBuilder = txBuilder.andThen(ScriptBalanceTxProviders.balanceTx(feePayer, totalSigners, containsScriptTx));
397469

398470
if ((containsScriptTx || hasMultiAssetMint) && removeDuplicateScriptWitnesses) {
399471
txBuilder = txBuilder.andThen(DuplicateScriptWitnessChecker.removeDuplicateScriptWitnesses());
@@ -420,22 +492,45 @@ private int getTotalSigners() {
420492
return totalSigners;
421493
}
422494

423-
private TxBuilder buildCollateralOutput(String feePayer) {
495+
private TxBuilder buildCollateralOutput(String payingAddress) {
496+
if (collateralInputs != null && !collateralInputs.isEmpty()) {
497+
List<Utxo> collateralUtxos = collateralInputs.stream()
498+
.map(input -> utxoSupplier.getTxOutput(input.getTransactionId(), input.getIndex()))
499+
.map(optionalUtxo -> optionalUtxo.get())
500+
.collect(Collectors.toList());
501+
return CollateralBuilders.collateralOutputs(payingAddress, List.copyOf(collateralUtxos));
502+
} else {
503+
UtxoSelectionStrategy utxoSelectionStrategy = new DefaultUtxoSelectionStrategyImpl(utxoSupplier);
504+
Set<Utxo> collateralUtxos = utxoSelectionStrategy.select(payingAddress, DEFAULT_COLLATERAL_AMT, null);
505+
if (collateralUtxos.size() > MAX_COLLATERAL_INPUTS) {
506+
utxoSelectionStrategy = new LargestFirstUtxoSelectionStrategy(utxoSupplier);
507+
collateralUtxos = utxoSelectionStrategy.select(payingAddress, DEFAULT_COLLATERAL_AMT, null);
508+
}
509+
510+
return CollateralBuilders.collateralOutputs(payingAddress, List.copyOf(collateralUtxos));
511+
}
512+
}
513+
514+
private TxBuilder buildCollateralOutput(Wallet payingWallet) {
515+
String collateralPayerAddress = payingWallet.getBaseAddressString(0); //TODO: first addr as collateral output addr
516+
424517
if (collateralInputs != null && !collateralInputs.isEmpty()) {
425518
List<Utxo> collateralUtxos = collateralInputs.stream()
426519
.map(input -> utxoSupplier.getTxOutput(input.getTransactionId(), input.getIndex()))
427520
.map(optionalUtxo -> optionalUtxo.get())
428521
.collect(Collectors.toList());
429-
return CollateralBuilders.collateralOutputs(feePayer, List.copyOf(collateralUtxos));
522+
return CollateralBuilders.collateralOutputs(collateralPayerAddress, List.copyOf(collateralUtxos));
430523
} else {
431524
UtxoSelectionStrategy utxoSelectionStrategy = new DefaultUtxoSelectionStrategyImpl(utxoSupplier);
432-
Set<Utxo> collateralUtxos = utxoSelectionStrategy.select(feePayer, DEFAULT_COLLATERAL_AMT, null);
525+
var hdWalletAddressIterator = new HDWalletAddressIterator(payingWallet, utxoSupplier);
526+
527+
List<Utxo> collateralUtxos = utxoSelectionStrategy.selectUtxos(hdWalletAddressIterator, List.of(DEFAULT_COLLATERAL_AMT), null);
433528
if (collateralUtxos.size() > MAX_COLLATERAL_INPUTS) {
434529
utxoSelectionStrategy = new LargestFirstUtxoSelectionStrategy(utxoSupplier);
435-
collateralUtxos = utxoSelectionStrategy.select(feePayer, DEFAULT_COLLATERAL_AMT, null);
530+
collateralUtxos = utxoSelectionStrategy.selectUtxos(hdWalletAddressIterator, List.of(DEFAULT_COLLATERAL_AMT), null);
436531
}
437532

438-
return CollateralBuilders.collateralOutputs(feePayer, List.copyOf(collateralUtxos));
533+
return CollateralBuilders.collateralOutputs(collateralPayerAddress, List.copyOf(collateralUtxos));
439534
}
440535
}
441536

quicktx/src/main/java/com/bloxbean/cardano/client/quicktx/ScriptTx.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public class ScriptTx extends AbstractTx<ScriptTx> {
4646
protected List<TransactionInput> referenceInputs;
4747

4848
protected String fromAddress;
49+
protected Wallet fromWallet;
4950
private StakeTx stakeTx;
5051
private GovTx govTx;
5152

@@ -641,13 +642,19 @@ protected String getFromAddress() {
641642

642643
@Override
643644
protected Wallet getFromWallet() {
644-
return null;
645+
return fromWallet;
645646
}
646647

647648
void from(String address) {
648649
this.fromAddress = address;
649650
}
650651

652+
void from(Wallet wallet) {
653+
this.fromWallet = wallet;
654+
// TODO fromAddress is not used in this scenarios, but it must be set to avoid breaking other things.
655+
this.fromAddress = this.fromWallet.getBaseAddressString(0);
656+
}
657+
651658
@Override
652659
protected void postBalanceTx(Transaction transaction) {
653660
if (spendingContexts != null && !spendingContexts.isEmpty()) {

0 commit comments

Comments
 (0)