From e8d8fa61268ec8ac4dd5c14e6715d4a4bde2fe49 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Sat, 26 Sep 2020 12:29:25 +0200 Subject: [PATCH] add utxo filters --- .../drongo/wallet/ExcludeUtxoFilter.java | 31 +++++++++++++++++++ .../drongo/wallet/UtxoFilter.java | 5 +++ .../sparrowwallet/drongo/wallet/Wallet.java | 21 ++++++++----- 3 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/drongo/wallet/ExcludeUtxoFilter.java create mode 100644 src/main/java/com/sparrowwallet/drongo/wallet/UtxoFilter.java diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/ExcludeUtxoFilter.java b/src/main/java/com/sparrowwallet/drongo/wallet/ExcludeUtxoFilter.java new file mode 100644 index 0000000..f87ae7d --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/wallet/ExcludeUtxoFilter.java @@ -0,0 +1,31 @@ +package com.sparrowwallet.drongo.wallet; + +import java.util.ArrayList; +import java.util.Collection; + +public class ExcludeUtxoFilter implements UtxoFilter { + private final Collection excludedUtxos; + + public ExcludeUtxoFilter() { + this.excludedUtxos = new ArrayList<>(); + } + + public ExcludeUtxoFilter(Collection excludedUtxos) { + this.excludedUtxos = new ArrayList<>(excludedUtxos); + } + + @Override + public boolean isEligible(BlockTransactionHashIndex candidate) { + for(BlockTransactionHashIndex excludedUtxo : excludedUtxos) { + if(candidate.getHash().equals(excludedUtxo.getHash()) && candidate.getIndex() == excludedUtxo.getIndex()) { + return false; + } + } + + return true; + } + + public Collection getExcludedUtxos() { + return excludedUtxos; + } +} diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/UtxoFilter.java b/src/main/java/com/sparrowwallet/drongo/wallet/UtxoFilter.java new file mode 100644 index 0000000..5b2a6f8 --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/wallet/UtxoFilter.java @@ -0,0 +1,5 @@ +package com.sparrowwallet.drongo.wallet; + +public interface UtxoFilter { + boolean isEligible(BlockTransactionHashIndex candidate); +} diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java index 4962896..9a62622 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java @@ -424,11 +424,11 @@ public class Wallet { return getFee(changeOutput, feeRate, longTermFeeRate); } - public WalletTransaction createWalletTransaction(List utxoSelectors, Address recipientAddress, long recipientAmount, double feeRate, double longTermFeeRate, Long fee, Integer currentBlockHeight, boolean sendAll, boolean groupByAddress, boolean includeMempoolChange) throws InsufficientFundsException { + public WalletTransaction createWalletTransaction(List utxoSelectors, List utxoFilters, Address recipientAddress, long recipientAmount, double feeRate, double longTermFeeRate, Long fee, Integer currentBlockHeight, boolean sendAll, boolean groupByAddress, boolean includeMempoolChange) throws InsufficientFundsException { long valueRequiredAmt = recipientAmount; while(true) { - Map selectedUtxos = selectInputs(utxoSelectors, valueRequiredAmt, feeRate, longTermFeeRate, groupByAddress, includeMempoolChange); + Map selectedUtxos = selectInputs(utxoSelectors, utxoFilters, valueRequiredAmt, feeRate, longTermFeeRate, groupByAddress, includeMempoolChange); long totalSelectedAmt = selectedUtxos.keySet().stream().mapToLong(BlockTransactionHashIndex::getValue).sum(); Transaction transaction = new Transaction(); @@ -513,8 +513,8 @@ public class Wallet { } } - private Map selectInputs(List utxoSelectors, Long targetValue, double feeRate, double longTermFeeRate, boolean groupByAddress, boolean includeMempoolChange) throws InsufficientFundsException { - List utxoPool = getGroupedUtxos(feeRate, longTermFeeRate, groupByAddress); + private Map selectInputs(List utxoSelectors, List utxoFilters, Long targetValue, double feeRate, double longTermFeeRate, boolean groupByAddress, boolean includeMempoolChange) throws InsufficientFundsException { + List utxoPool = getGroupedUtxos(utxoFilters, feeRate, longTermFeeRate, groupByAddress); List filters = new ArrayList<>(); filters.add(new OutputGroup.Filter(1, 6)); @@ -540,17 +540,22 @@ public class Wallet { throw new InsufficientFundsException("Not enough combined value in UTXOs for output value " + targetValue); } - private List getGroupedUtxos(double feeRate, double longTermFeeRate, boolean groupByAddress) { + private List getGroupedUtxos(List utxoFilters, double feeRate, double longTermFeeRate, boolean groupByAddress) { List outputGroups = new ArrayList<>(); - getGroupedUtxos(outputGroups, getNode(KeyPurpose.RECEIVE), feeRate, longTermFeeRate, groupByAddress); - getGroupedUtxos(outputGroups, getNode(KeyPurpose.CHANGE), feeRate, longTermFeeRate, groupByAddress); + getGroupedUtxos(outputGroups, getNode(KeyPurpose.RECEIVE), utxoFilters, feeRate, longTermFeeRate, groupByAddress); + getGroupedUtxos(outputGroups, getNode(KeyPurpose.CHANGE), utxoFilters, feeRate, longTermFeeRate, groupByAddress); return outputGroups; } - private void getGroupedUtxos(List outputGroups, WalletNode purposeNode, double feeRate, double longTermFeeRate, boolean groupByAddress) { + private void getGroupedUtxos(List outputGroups, WalletNode purposeNode, List utxoFilters, double feeRate, double longTermFeeRate, boolean groupByAddress) { for(WalletNode addressNode : purposeNode.getChildren()) { OutputGroup outputGroup = null; for(BlockTransactionHashIndex utxo : addressNode.getUnspentTransactionOutputs()) { + Optional matchedFilter = utxoFilters.stream().filter(utxoFilter -> !utxoFilter.isEligible(utxo)).findAny(); + if(matchedFilter.isPresent()) { + continue; + } + if(outputGroup == null || !groupByAddress) { outputGroup = new OutputGroup(getStoredBlockHeight(), getInputWeightUnits(), feeRate, longTermFeeRate); outputGroups.add(outputGroup);