fix high stonewall fee issue, add support for generalized transaction diagram

This commit is contained in:
Craig Raw 2021-10-26 17:35:13 +02:00
parent eb49c97133
commit 99440eda7f
4 changed files with 42 additions and 6 deletions

View file

@ -165,6 +165,14 @@ public class Script {
throw new ProtocolException("Script not a standard form that contains a single hash"); throw new ProtocolException("Script not a standard form that contains a single hash");
} }
public Address getToAddress() {
try {
return getToAddresses()[0];
} catch(Exception e) {
return null;
}
}
/** /**
* Gets the destination address from this script, if it's in the required form. * Gets the destination address from this script, if it's in the required form.
*/ */

View file

@ -7,13 +7,18 @@ public class Payment {
private String label; private String label;
private long amount; private long amount;
private boolean sendMax; private boolean sendMax;
private Type type = Type.DEFAULT; private Type type;
public Payment(Address address, String label, long amount, boolean sendMax) { public Payment(Address address, String label, long amount, boolean sendMax) {
this(address, label, amount, sendMax, Type.DEFAULT);
}
public Payment(Address address, String label, long amount, boolean sendMax, Type type) {
this.address = address; this.address = address;
this.label = label; this.label = label;
this.amount = amount; this.amount = amount;
this.sendMax = sendMax; this.sendMax = sendMax;
this.type = type;
} }
public Address getAddress() { public Address getAddress() {

View file

@ -518,6 +518,10 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
return getWalletTxos().keySet().stream().anyMatch(ref -> ref.getHash().equals(txInput.getOutpoint().getHash()) && ref.getIndex() == txInput.getOutpoint().getIndex()); return getWalletTxos().keySet().stream().anyMatch(ref -> ref.getHash().equals(txInput.getOutpoint().getHash()) && ref.getIndex() == txInput.getOutpoint().getIndex());
} }
public boolean isWalletTxo(TransactionOutput txOutput) {
return getWalletTxos().keySet().stream().anyMatch(ref -> ref.getHash().equals(txOutput.getHash()) && ref.getIndex() == txOutput.getIndex());
}
public boolean isWalletTxo(BlockTransactionHashIndex txo) { public boolean isWalletTxo(BlockTransactionHashIndex txo) {
return getWalletTxos().containsKey(txo); return getWalletTxos().containsKey(txo);
} }
@ -771,7 +775,7 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
List<Long> setChangeAmts = getSetChangeAmounts(selectedUtxoSets, totalPaymentAmount, noChangeFeeRequiredAmt); List<Long> setChangeAmts = getSetChangeAmounts(selectedUtxoSets, totalPaymentAmount, noChangeFeeRequiredAmt);
double noChangeFeeRate = (fee == null ? feeRate : noChangeFeeRequiredAmt / transaction.getVirtualSize()); double noChangeFeeRate = (fee == null ? feeRate : noChangeFeeRequiredAmt / transaction.getVirtualSize());
long costOfChangeAmt = getCostOfChange(noChangeFeeRate, longTermFeeRate); long costOfChangeAmt = getCostOfChange(noChangeFeeRate, longTermFeeRate);
if(setChangeAmts.stream().allMatch(amt -> amt > costOfChangeAmt)) { if(setChangeAmts.stream().allMatch(amt -> amt > costOfChangeAmt) || (numSets > 1 && differenceAmt / transaction.getVirtualSize() > noChangeFeeRate * 2)) {
//Change output is required, determine new fee once change output has been added //Change output is required, determine new fee once change output has been added
WalletNode changeNode = getFreshNode(KeyPurpose.CHANGE); WalletNode changeNode = getFreshNode(KeyPurpose.CHANGE);
while(txExcludedChangeNodes.contains(changeNode)) { while(txExcludedChangeNodes.contains(changeNode)) {
@ -796,7 +800,7 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
if(setChangeAmts.stream().anyMatch(amt -> amt < costOfChangeAmt)) { if(setChangeAmts.stream().anyMatch(amt -> amt < costOfChangeAmt)) {
//The new fee has meant that one of the change outputs is now dust. We pay too high a fee without change, but change is dust when added. //The new fee has meant that one of the change outputs is now dust. We pay too high a fee without change, but change is dust when added.
if(numSets > 1) { if(numSets > 1 && differenceAmt / transaction.getVirtualSize() < noChangeFeeRate * 2) {
//Maximize privacy. Pay a higher fee to keep multiple output sets. //Maximize privacy. Pay a higher fee to keep multiple output sets.
return new WalletTransaction(this, transaction, utxoSelectors, selectedUtxos, txPayments, differenceAmt); return new WalletTransaction(this, transaction, utxoSelectors, selectedUtxos, txPayments, differenceAmt);
} else { } else {

View file

@ -1,6 +1,7 @@
package com.sparrowwallet.drongo.wallet; package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBT;
@ -21,12 +22,17 @@ public class WalletTransaction {
private final List<Payment> payments; private final List<Payment> payments;
private final Map<WalletNode, Long> changeMap; private final Map<WalletNode, Long> changeMap;
private final long fee; private final long fee;
private final Map<Sha256Hash, BlockTransaction> inputTransactions;
public WalletTransaction(Wallet wallet, Transaction transaction, List<UtxoSelector> utxoSelectors, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, List<Payment> payments, long fee) { public WalletTransaction(Wallet wallet, Transaction transaction, List<UtxoSelector> utxoSelectors, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, List<Payment> payments, long fee) {
this(wallet, transaction, utxoSelectors, selectedUtxos, payments, Collections.emptyMap(), fee); this(wallet, transaction, utxoSelectors, selectedUtxos, payments, Collections.emptyMap(), fee);
} }
public WalletTransaction(Wallet wallet, Transaction transaction, List<UtxoSelector> utxoSelectors, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, List<Payment> payments, Map<WalletNode, Long> changeMap, long fee) { public WalletTransaction(Wallet wallet, Transaction transaction, List<UtxoSelector> utxoSelectors, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, List<Payment> payments, Map<WalletNode, Long> changeMap, long fee) {
this(wallet, transaction, utxoSelectors, selectedUtxos, payments, changeMap, fee, Collections.emptyMap());
}
public WalletTransaction(Wallet wallet, Transaction transaction, List<UtxoSelector> utxoSelectors, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, List<Payment> payments, Map<WalletNode, Long> changeMap, long fee, Map<Sha256Hash, BlockTransaction> inputTransactions) {
this.wallet = wallet; this.wallet = wallet;
this.transaction = transaction; this.transaction = transaction;
this.utxoSelectors = utxoSelectors; this.utxoSelectors = utxoSelectors;
@ -34,6 +40,7 @@ public class WalletTransaction {
this.payments = payments; this.payments = payments;
this.changeMap = changeMap; this.changeMap = changeMap;
this.fee = fee; this.fee = fee;
this.inputTransactions = inputTransactions;
} }
public PSBT createPSBT() { public PSBT createPSBT() {
@ -80,12 +87,16 @@ public class WalletTransaction {
return selectedUtxos.keySet().stream().mapToLong(BlockTransactionHashIndex::getValue).sum(); return selectedUtxos.keySet().stream().mapToLong(BlockTransactionHashIndex::getValue).sum();
} }
public Map<Sha256Hash, BlockTransaction> getInputTransactions() {
return inputTransactions;
}
/** /**
* Fee percentage matches the Coldcard implementation of total fee as a percentage of total value out * Fee percentage matches the Coldcard implementation of total fee as a percentage of total value out
* @return the fee percentage * @return the fee percentage
*/ */
public double getFeePercentage() { public double getFeePercentage() {
return (double)getFee() / (getTotal() - getFee()); return getFee() == 0 ? 0 : (double)getFee() / (getTotal() - getFee());
} }
public boolean isCoinControlUsed() { public boolean isCoinControlUsed() {
@ -97,11 +108,19 @@ public class WalletTransaction {
} }
public boolean isPremixSend(Payment payment) { public boolean isPremixSend(Payment payment) {
return isWalletSend(getWallet().getChildWallet(StandardAccount.WHIRLPOOL_PREMIX), payment); return isWalletSend(StandardAccount.WHIRLPOOL_PREMIX, payment);
} }
public boolean isBadbankSend(Payment payment) { public boolean isBadbankSend(Payment payment) {
return isWalletSend(getWallet().getChildWallet(StandardAccount.WHIRLPOOL_BADBANK), payment); return isWalletSend(StandardAccount.WHIRLPOOL_BADBANK, payment);
}
private boolean isWalletSend(StandardAccount childAccount, Payment payment) {
if(getWallet() != null) {
return isWalletSend(getWallet().getChildWallet(childAccount), payment);
}
return false;
} }
public boolean isWalletSend(Wallet wallet, Payment payment) { public boolean isWalletSend(Wallet wallet, Payment payment) {