handle inclusion of rbf mempool inputs in new wallet txes

This commit is contained in:
Craig Raw 2020-11-24 20:34:13 +02:00
parent 49799fc0c8
commit 6b20c6558a
2 changed files with 23 additions and 15 deletions

View file

@ -313,15 +313,19 @@ public class Wallet {
} }
public Map<BlockTransactionHashIndex, WalletNode> getWalletUtxos() { public Map<BlockTransactionHashIndex, WalletNode> getWalletUtxos() {
return getWalletUtxos(false);
}
public Map<BlockTransactionHashIndex, WalletNode> getWalletUtxos(boolean includeMempoolInputs) {
Map<BlockTransactionHashIndex, WalletNode> walletUtxos = new TreeMap<>(); Map<BlockTransactionHashIndex, WalletNode> walletUtxos = new TreeMap<>();
getWalletUtxos(walletUtxos, getNode(KeyPurpose.RECEIVE)); getWalletUtxos(walletUtxos, getNode(KeyPurpose.RECEIVE), includeMempoolInputs);
getWalletUtxos(walletUtxos, getNode(KeyPurpose.CHANGE)); getWalletUtxos(walletUtxos, getNode(KeyPurpose.CHANGE), includeMempoolInputs);
return walletUtxos; return walletUtxos;
} }
private void getWalletUtxos(Map<BlockTransactionHashIndex, WalletNode> walletUtxos, WalletNode purposeNode) { private void getWalletUtxos(Map<BlockTransactionHashIndex, WalletNode> walletUtxos, WalletNode purposeNode, boolean includeMempoolInputs) {
for(WalletNode addressNode : purposeNode.getChildren()) { for(WalletNode addressNode : purposeNode.getChildren()) {
for(BlockTransactionHashIndex utxo : addressNode.getUnspentTransactionOutputs()) { for(BlockTransactionHashIndex utxo : addressNode.getUnspentTransactionOutputs(includeMempoolInputs)) {
walletUtxos.put(utxo, addressNode); walletUtxos.put(utxo, addressNode);
} }
} }
@ -436,12 +440,12 @@ public class Wallet {
return getFee(changeOutput, feeRate, longTermFeeRate); return getFee(changeOutput, feeRate, longTermFeeRate);
} }
public WalletTransaction createWalletTransaction(List<UtxoSelector> utxoSelectors, List<UtxoFilter> utxoFilters, List<Payment> payments, double feeRate, double longTermFeeRate, Long fee, Integer currentBlockHeight, boolean groupByAddress, boolean includeMempoolChange) throws InsufficientFundsException { public WalletTransaction createWalletTransaction(List<UtxoSelector> utxoSelectors, List<UtxoFilter> utxoFilters, List<Payment> payments, double feeRate, double longTermFeeRate, Long fee, Integer currentBlockHeight, boolean groupByAddress, boolean includeMempoolChange, boolean includeMempoolInputs) throws InsufficientFundsException {
long totalPaymentAmount = payments.stream().map(Payment::getAmount).mapToLong(v -> v).sum(); long totalPaymentAmount = payments.stream().map(Payment::getAmount).mapToLong(v -> v).sum();
long valueRequiredAmt = totalPaymentAmount; long valueRequiredAmt = totalPaymentAmount;
while(true) { while(true) {
Map<BlockTransactionHashIndex, WalletNode> selectedUtxos = selectInputs(utxoSelectors, utxoFilters, valueRequiredAmt, feeRate, longTermFeeRate, groupByAddress, includeMempoolChange); Map<BlockTransactionHashIndex, WalletNode> selectedUtxos = selectInputs(utxoSelectors, utxoFilters, valueRequiredAmt, feeRate, longTermFeeRate, groupByAddress, includeMempoolChange, includeMempoolInputs);
long totalSelectedAmt = selectedUtxos.keySet().stream().mapToLong(BlockTransactionHashIndex::getValue).sum(); long totalSelectedAmt = selectedUtxos.keySet().stream().mapToLong(BlockTransactionHashIndex::getValue).sum();
Transaction transaction = new Transaction(); Transaction transaction = new Transaction();
@ -539,8 +543,8 @@ public class Wallet {
} }
} }
private Map<BlockTransactionHashIndex, WalletNode> selectInputs(List<UtxoSelector> utxoSelectors, List<UtxoFilter> utxoFilters, Long targetValue, double feeRate, double longTermFeeRate, boolean groupByAddress, boolean includeMempoolChange) throws InsufficientFundsException { private Map<BlockTransactionHashIndex, WalletNode> selectInputs(List<UtxoSelector> utxoSelectors, List<UtxoFilter> utxoFilters, Long targetValue, double feeRate, double longTermFeeRate, boolean groupByAddress, boolean includeMempoolChange, boolean includeMempoolInputs) throws InsufficientFundsException {
List<OutputGroup> utxoPool = getGroupedUtxos(utxoFilters, feeRate, longTermFeeRate, groupByAddress); List<OutputGroup> utxoPool = getGroupedUtxos(utxoFilters, feeRate, longTermFeeRate, groupByAddress, includeMempoolInputs);
List<OutputGroup.Filter> filters = new ArrayList<>(); List<OutputGroup.Filter> filters = new ArrayList<>();
filters.add(new OutputGroup.Filter(1, 6)); filters.add(new OutputGroup.Filter(1, 6));
@ -556,7 +560,7 @@ public class Wallet {
Collection<BlockTransactionHashIndex> selectedInputs = utxoSelector.select(targetValue, filteredPool); Collection<BlockTransactionHashIndex> selectedInputs = utxoSelector.select(targetValue, filteredPool);
long total = selectedInputs.stream().mapToLong(BlockTransactionHashIndex::getValue).sum(); long total = selectedInputs.stream().mapToLong(BlockTransactionHashIndex::getValue).sum();
if(total > targetValue) { if(total > targetValue) {
Map<BlockTransactionHashIndex, WalletNode> utxos = getWalletUtxos(); Map<BlockTransactionHashIndex, WalletNode> utxos = getWalletUtxos(includeMempoolInputs);
utxos.keySet().retainAll(selectedInputs); utxos.keySet().retainAll(selectedInputs);
return utxos; return utxos;
} }
@ -566,17 +570,17 @@ public class Wallet {
throw new InsufficientFundsException("Not enough combined value in UTXOs for output value " + targetValue); throw new InsufficientFundsException("Not enough combined value in UTXOs for output value " + targetValue);
} }
private List<OutputGroup> getGroupedUtxos(List<UtxoFilter> utxoFilters, double feeRate, double longTermFeeRate, boolean groupByAddress) { private List<OutputGroup> getGroupedUtxos(List<UtxoFilter> utxoFilters, double feeRate, double longTermFeeRate, boolean groupByAddress, boolean includeMempoolInputs) {
List<OutputGroup> outputGroups = new ArrayList<>(); List<OutputGroup> outputGroups = new ArrayList<>();
getGroupedUtxos(outputGroups, getNode(KeyPurpose.RECEIVE), utxoFilters, feeRate, longTermFeeRate, groupByAddress); getGroupedUtxos(outputGroups, getNode(KeyPurpose.RECEIVE), utxoFilters, feeRate, longTermFeeRate, groupByAddress, includeMempoolInputs);
getGroupedUtxos(outputGroups, getNode(KeyPurpose.CHANGE), utxoFilters, feeRate, longTermFeeRate, groupByAddress); getGroupedUtxos(outputGroups, getNode(KeyPurpose.CHANGE), utxoFilters, feeRate, longTermFeeRate, groupByAddress, includeMempoolInputs);
return outputGroups; return outputGroups;
} }
private void getGroupedUtxos(List<OutputGroup> outputGroups, WalletNode purposeNode, List<UtxoFilter> utxoFilters, double feeRate, double longTermFeeRate, boolean groupByAddress) { private void getGroupedUtxos(List<OutputGroup> outputGroups, WalletNode purposeNode, List<UtxoFilter> utxoFilters, double feeRate, double longTermFeeRate, boolean groupByAddress, boolean includeMempoolInputs) {
for(WalletNode addressNode : purposeNode.getChildren()) { for(WalletNode addressNode : purposeNode.getChildren()) {
OutputGroup outputGroup = null; OutputGroup outputGroup = null;
for(BlockTransactionHashIndex utxo : addressNode.getUnspentTransactionOutputs()) { for(BlockTransactionHashIndex utxo : addressNode.getUnspentTransactionOutputs(includeMempoolInputs)) {
Optional<UtxoFilter> matchedFilter = utxoFilters.stream().filter(utxoFilter -> !utxoFilter.isEligible(utxo)).findAny(); Optional<UtxoFilter> matchedFilter = utxoFilters.stream().filter(utxoFilter -> !utxoFilter.isEligible(utxo)).findAny();
if(matchedFilter.isPresent()) { if(matchedFilter.isPresent()) {
continue; continue;

View file

@ -113,8 +113,12 @@ public class WalletNode implements Comparable<WalletNode> {
} }
public Set<BlockTransactionHashIndex> getUnspentTransactionOutputs() { public Set<BlockTransactionHashIndex> getUnspentTransactionOutputs() {
return getUnspentTransactionOutputs(false);
}
public Set<BlockTransactionHashIndex> getUnspentTransactionOutputs(boolean includeMempoolInputs) {
Set<BlockTransactionHashIndex> unspentTXOs = new TreeSet<>(transactionOutputs); Set<BlockTransactionHashIndex> unspentTXOs = new TreeSet<>(transactionOutputs);
return unspentTXOs.stream().filter(txo -> !txo.isSpent()).collect(Collectors.toCollection(HashSet::new)); return unspentTXOs.stream().filter(txo -> !txo.isSpent() || (includeMempoolInputs && txo.getSpentBy().getHeight() <= 0)).collect(Collectors.toCollection(HashSet::new));
} }
public long getUnspentValue() { public long getUnspentValue() {