From f534beb62426cf085bd6a1bc4d3f0b98b4a83b06 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Thu, 1 Jun 2023 15:31:15 +0200 Subject: [PATCH] add additional rbf tx inputs if needed as required fee is increased --- drongo | 2 +- .../sparrow/control/EntryCell.java | 4 +- .../sparrow/wallet/SendController.java | 49 ++++++++++++++++--- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/drongo b/drongo index b26c5e52..5b9b3043 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit b26c5e5218b91c760b7f63f47d93b7301223cc2c +Subproject commit 5b9b3043a6fded8e6f1589327a9fa2718b41f90c diff --git a/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java b/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java index 2e0e6842..9aea389c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java @@ -245,8 +245,8 @@ public class EntryCell extends TreeTableCell implements Confirmati double vSize = tx.getVirtualSize(); int inputSize = tx.getInputs().get(0).getLength() + (tx.getInputs().get(0).hasWitness() ? tx.getInputs().get(0).getWitness().getLength() / Transaction.WITNESS_SCALE_FACTOR : 0); List walletUtxos = new ArrayList<>(transactionEntry.getWallet().getWalletUtxos().keySet()); - //Remove any UTXOs created by the transaction that is to be replaced - walletUtxos.removeIf(utxo -> ourOutputs.stream().anyMatch(output -> output.getHash().equals(utxo.getHash()) && output.getIndex() == utxo.getIndex())); + //Remove any UTXOs that are frozen or created by the transaction that is to be replaced + walletUtxos.removeIf(utxo -> utxo.getStatus() == Status.FROZEN || ourOutputs.stream().anyMatch(output -> output.getHash().equals(utxo.getHash()) && output.getIndex() == utxo.getIndex())); Collections.shuffle(walletUtxos); while((double)changeTotal / vSize < getMaxFeeRate() && !walletUtxos.isEmpty() && !cancelTransaction) { //If there is insufficient change output, include another random UTXO so the fee can be increased diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java index b080a06b..c7c45adf 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java @@ -697,6 +697,41 @@ public class SendController extends WalletFormController implements Initializabl protected Task createTask() { return new Task<>() { protected WalletTransaction call() throws InsufficientFundsException { + try { + return getWalletTransaction(); + } catch(InsufficientFundsException e) { + if(e.getTargetValue() != null && includeSpentMempoolOutputs && utxoSelectors.size() == 1 && utxoSelectors.get(0) instanceof PresetUtxoSelector presetUtxoSelector) { + Optional optBlkTx = wallet.getWalletTransactions().values().stream().filter(blkTx -> { + return blkTx.getTransaction().getInputs().stream().anyMatch(txInput -> { + return presetUtxoSelector.getPresetUtxos().stream().anyMatch(ref -> { + return txInput.getOutpoint().getHash().equals(ref.getHash()) && txInput.getOutpoint().getIndex() == ref.getIndex(); + }); + }); + }).findFirst(); + + if(optBlkTx.isPresent()) { + //Creating RBF transaction - include additional UTXOs if available to pay desired fee + Transaction rbfTx = optBlkTx.get().getTransaction(); + List walletUtxos = new ArrayList<>(wallet.getWalletUtxos().keySet()); + //Remove any UTXOs that are frozen or created by the transaction that is to be replaced + walletUtxos.removeIf(utxo -> utxo.getStatus() == Status.FROZEN || rbfTx.getOutputs().stream().anyMatch(output -> output.getHash().equals(utxo.getHash()) && output.getIndex() == utxo.getIndex())); + //Remove any UTXOs that have already been added or previously excluded + walletUtxos.removeAll(presetUtxoSelector.getPresetUtxos()); + walletUtxos.removeAll(presetUtxoSelector.getExcludedUtxos()); + Collections.shuffle(walletUtxos); + while(!walletUtxos.isEmpty() && presetUtxoSelector.getPresetUtxos().stream().mapToLong(BlockTransactionHashIndex::getValue).sum() < e.getTargetValue()) { + presetUtxoSelector.getPresetUtxos().add(walletUtxos.remove(0)); + } + + return getWalletTransaction(); + } + } + + throw e; + } + } + + private WalletTransaction getWalletTransaction() throws InsufficientFundsException { updateMessage("Selecting UTXOs..."); WalletTransaction walletTransaction = wallet.createWalletTransaction(utxoSelectors, utxoFilters, payments, opReturns, excludedChangeNodes, feeRate, longTermFeeRate, fee, currentBlockHeight, groupByAddress, includeMempoolOutputs, includeSpentMempoolOutputs); @@ -1589,22 +1624,22 @@ public class SendController extends WalletFormController implements Initializabl if(utxoSelector instanceof MaxUtxoSelector) { Collection utxos = walletForm.getWallet().getWalletUtxos().keySet(); utxos.remove(event.getUtxo()); - if(utxoFilterProperty.get() instanceof ExcludeUtxoFilter) { - ExcludeUtxoFilter existingUtxoFilter = (ExcludeUtxoFilter)utxoFilterProperty.get(); + if(utxoFilterProperty.get() instanceof ExcludeUtxoFilter existingUtxoFilter) { utxos.removeAll(existingUtxoFilter.getExcludedUtxos()); } PresetUtxoSelector presetUtxoSelector = new PresetUtxoSelector(utxos); + presetUtxoSelector.getExcludedUtxos().add(event.getUtxo()); utxoSelectorProperty.set(presetUtxoSelector); updateTransaction(true); - } else if(utxoSelector instanceof PresetUtxoSelector) { - PresetUtxoSelector presetUtxoSelector = new PresetUtxoSelector(((PresetUtxoSelector)utxoSelector).getPresetUtxos()); + } else if(utxoSelector instanceof PresetUtxoSelector existingUtxoSelector) { + PresetUtxoSelector presetUtxoSelector = new PresetUtxoSelector(existingUtxoSelector.getPresetUtxos(), existingUtxoSelector.getExcludedUtxos()); presetUtxoSelector.getPresetUtxos().remove(event.getUtxo()); + presetUtxoSelector.getExcludedUtxos().add(event.getUtxo()); utxoSelectorProperty.set(presetUtxoSelector); - updateTransaction(true); + updateTransaction(!includeSpentMempoolOutputsProperty.get()); } else { ExcludeUtxoFilter utxoFilter = new ExcludeUtxoFilter(); - if(utxoFilterProperty.get() instanceof ExcludeUtxoFilter) { - ExcludeUtxoFilter existingUtxoFilter = (ExcludeUtxoFilter)utxoFilterProperty.get(); + if(utxoFilterProperty.get() instanceof ExcludeUtxoFilter existingUtxoFilter) { utxoFilter.getExcludedUtxos().addAll(existingUtxoFilter.getExcludedUtxos()); }