add silent payment consolidation output and dust threshold change check
This commit is contained in:
parent
3fbad787a4
commit
c87d5cc3c2
@ -16,8 +16,16 @@ public class SilentPayment extends Payment {
|
||||
this(silentPaymentAddress, getDummyAddress(), label, amount, sendMax);
|
||||
}
|
||||
|
||||
public SilentPayment(SilentPaymentAddress silentPaymentAddress, String label, long amount, boolean sendMax, Type type) {
|
||||
this(silentPaymentAddress, getDummyAddress(), label, amount, sendMax, type);
|
||||
}
|
||||
|
||||
public SilentPayment(SilentPaymentAddress silentPaymentAddress, Address address, String label, long amount, boolean sendMax) {
|
||||
super(address == null ? getDummyAddress() : address, label, amount, sendMax, Type.DEFAULT);
|
||||
this(silentPaymentAddress, address, label, amount, sendMax, Type.DEFAULT);
|
||||
}
|
||||
|
||||
public SilentPayment(SilentPaymentAddress silentPaymentAddress, Address address, String label, long amount, boolean sendMax, Type type) {
|
||||
super(address == null ? getDummyAddress() : address, label, amount, sendMax, type);
|
||||
this.silentPaymentAddress = silentPaymentAddress;
|
||||
}
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ public class SilentPaymentAddress {
|
||||
|
||||
public String toAbbreviatedString() {
|
||||
String address = toString();
|
||||
return address.substring(0, 50) + "...";
|
||||
return address.substring(0, 24) + "..." + address.substring(address.length() - 24);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -5,6 +5,10 @@ import com.sparrowwallet.drongo.address.P2AAddress;
|
||||
import com.sparrowwallet.drongo.dns.DnsPayment;
|
||||
import com.sparrowwallet.drongo.dns.DnsPaymentCache;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Payment {
|
||||
private Address address;
|
||||
private String label;
|
||||
@ -66,6 +70,12 @@ public class Payment {
|
||||
|
||||
public enum Type {
|
||||
DEFAULT, WHIRLPOOL_FEE, FAKE_MIX, MIX, ANCHOR;
|
||||
|
||||
public String toDisplayString() {
|
||||
return Arrays.stream(this.toString().toLowerCase(Locale.ROOT).split("_"))
|
||||
.map(w -> Character.toUpperCase(w.charAt(0)) + w.substring(1))
|
||||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
}
|
||||
|
||||
public String getDisplayAddress() {
|
||||
|
||||
@ -1104,15 +1104,15 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
|
||||
|
||||
for(int i = 1; i < numSets; i+=2) {
|
||||
Payment fakeMixPayment;
|
||||
Payment.Type type = Payment.Type.FAKE_MIX;
|
||||
if(policyType == PolicyType.SINGLE_SP) {
|
||||
SilentPaymentAddress spChangeAddress = getSilentPaymentScanAddress().getChangeAddress().getSilentPaymentAddress();
|
||||
fakeMixPayment = new SilentPayment(spChangeAddress, "(Fake Mix)", totalPaymentAmount, false);
|
||||
fakeMixPayment = new SilentPayment(spChangeAddress, "(" + type.toDisplayString() + ")", totalPaymentAmount, false, type);
|
||||
} else {
|
||||
WalletNode mixNode = getFreshNode(getChangeKeyPurpose());
|
||||
txExcludedChangeNodes.add(mixNode);
|
||||
fakeMixPayment = new WalletNodePayment(mixNode, ".." + mixNode + " (Fake Mix)", totalPaymentAmount, false);
|
||||
fakeMixPayment = new WalletNodePayment(mixNode, ".." + mixNode + " (" + type.toDisplayString() + ")", totalPaymentAmount, false, type);
|
||||
}
|
||||
fakeMixPayment.setType(Payment.Type.FAKE_MIX);
|
||||
txPayments.add(fakeMixPayment);
|
||||
}
|
||||
|
||||
@ -1120,7 +1120,11 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
|
||||
for(Payment payment : txPayments) {
|
||||
if(payment instanceof SilentPayment silentPayment) {
|
||||
TransactionOutput output = transaction.addOutput(payment.getAmount(), new Script(new byte[0]));
|
||||
outputs.add(new WalletTransaction.SilentPaymentOutput(output, silentPayment));
|
||||
if(policyType == PolicyType.SINGLE_SP && getSilentPaymentScanAddress().getSilentPaymentAddress().equals(silentPayment.getSilentPaymentAddress())) {
|
||||
outputs.add(new WalletTransaction.SilentPaymentConsolidationOutput(output, silentPayment));
|
||||
} else {
|
||||
outputs.add(new WalletTransaction.SilentPaymentOutput(output, silentPayment));
|
||||
}
|
||||
} else if(payment instanceof WalletNodePayment walletNodePayment) {
|
||||
TransactionOutput output = transaction.addOutput(payment.getAmount(), payment.getAddress());
|
||||
outputs.add(new WalletTransaction.ConsolidationOutput(output, walletNodePayment, payment.getAmount()));
|
||||
@ -1172,13 +1176,15 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Determine if a change output is required by checking if its value is greater than its dust threshold
|
||||
//Determine if a change output is required by checking if its value exceeds both the cost of change and the relay dust threshold
|
||||
List<Long> setChangeAmts = getSetChangeAmounts(selectedUtxoSets, totalPaymentAmount, noChangeFeeRequiredAmt);
|
||||
double noChangeFeeRate = (params.fee() == null ? params.feeRate() : noChangeFeeRequiredAmt / transaction.getVirtualSize());
|
||||
TransactionOutput changeOutput = new TransactionOutput(transaction, setChangeAmts.getFirst(), getNode(KeyPurpose.CHANGE).getOutputScript());
|
||||
long costOfChangeAmt = getCostOfChange(noChangeFeeRate, params.longTermFeeRate());
|
||||
if(setChangeAmts.stream().allMatch(amt -> amt > costOfChangeAmt) || (numSets > 1 && differenceAmt / transaction.getVirtualSize() > noChangeFeeRate * 2)) {
|
||||
long dustThresholdAmt = getDustThreshold(changeOutput, Transaction.DUST_RELAY_TX_FEE);
|
||||
long minChangeAmt = Math.max(costOfChangeAmt, dustThresholdAmt);
|
||||
if(setChangeAmts.stream().allMatch(amt -> amt > minChangeAmt) || (numSets > 1 && differenceAmt / transaction.getVirtualSize() > noChangeFeeRate * 2)) {
|
||||
//Change output is required, determine new fee once change output has been added
|
||||
TransactionOutput changeOutput = new TransactionOutput(transaction, setChangeAmts.getFirst(), getNode(KeyPurpose.CHANGE).getOutputScript());
|
||||
double changeVSize = noChangeVSize + changeOutput.getLength() * numSets;
|
||||
long changeFeeRequiredAmt = params.getRequiredFeeAmount(changeVSize);
|
||||
if(params.isMinRelayRate()) {
|
||||
@ -1211,7 +1217,7 @@ public class Wallet extends Persistable implements Comparable<Wallet> {
|
||||
}
|
||||
}
|
||||
|
||||
if(setChangeAmts.stream().anyMatch(amt -> amt < costOfChangeAmt)) {
|
||||
if(setChangeAmts.stream().anyMatch(amt -> amt < minChangeAmt)) {
|
||||
//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 && differenceAmt / transaction.getVirtualSize() < noChangeFeeRate * 2) {
|
||||
//Maximize privacy. Pay a higher fee to keep multiple output sets.
|
||||
|
||||
@ -3,6 +3,7 @@ package com.sparrowwallet.drongo.wallet;
|
||||
import com.sparrowwallet.drongo.address.Address;
|
||||
import com.sparrowwallet.drongo.dns.DnsPayment;
|
||||
import com.sparrowwallet.drongo.dns.DnsPaymentCache;
|
||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||
import com.sparrowwallet.drongo.protocol.*;
|
||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||
import com.sparrowwallet.drongo.silentpayments.SilentPayment;
|
||||
@ -170,7 +171,7 @@ public class WalletTransaction {
|
||||
}
|
||||
|
||||
if(payment.getType() == Payment.Type.WHIRLPOOL_FEE) {
|
||||
return "Whirlpool fee";
|
||||
return payment.getType().toDisplayString();
|
||||
} else if(isPremixSend(payment)) {
|
||||
int premixIndex = getOutputIndex(payment.getAddress(), payment.getAmount(), Collections.emptySet()) - 2;
|
||||
return "Premix #" + premixIndex;
|
||||
@ -191,9 +192,14 @@ public class WalletTransaction {
|
||||
public Wallet getToWallet(Collection<Wallet> wallets, Payment payment) {
|
||||
for(Wallet openWallet : wallets) {
|
||||
if(openWallet != getWallet() && openWallet.isValid()) {
|
||||
WalletNode addressNode = openWallet.getWalletAddresses().get(payment.getAddress());
|
||||
if(addressNode != null) {
|
||||
return addressNode.getWallet();
|
||||
if(openWallet.getPolicyType() == PolicyType.SINGLE_SP && payment instanceof SilentPayment silentPayment
|
||||
&& silentPayment.getSilentPaymentAddress().equals(openWallet.getSilentPaymentScanAddress().getSilentPaymentAddress())) {
|
||||
return openWallet;
|
||||
} else {
|
||||
WalletNode addressNode = openWallet.getWalletAddresses().get(payment.getAddress());
|
||||
if(addressNode != null) {
|
||||
return addressNode.getWallet();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -206,18 +212,15 @@ public class WalletTransaction {
|
||||
.anyMatch(p -> payment.getAddress() != null && payment.getAddress().equals(p.getAddress()));
|
||||
}
|
||||
|
||||
public List<Payment> getExternalPayments() {
|
||||
return payments.stream().filter(payment -> !(payment instanceof WalletNodePayment)).collect(Collectors.toList());
|
||||
public boolean isConsolidation(Payment payment) {
|
||||
return payment instanceof WalletNodePayment || (wallet != null && wallet.getPolicyType() == PolicyType.SINGLE_SP
|
||||
&& payment instanceof SilentPayment silentPayment && wallet.getSilentPaymentScanAddress().getSilentPaymentAddress().equals(silentPayment.getSilentPaymentAddress()));
|
||||
}
|
||||
|
||||
public List<WalletNodePayment> getWalletNodePayments() {
|
||||
return payments.stream().filter(payment -> payment instanceof WalletNodePayment).map(payment -> (WalletNodePayment)payment).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<SilentPaymentChangeOutput> getSilentPaymentChangeOutputs() {
|
||||
return outputs.stream().filter(o -> o instanceof SilentPaymentChangeOutput).map(o -> (SilentPaymentChangeOutput)o).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static class Output {
|
||||
private final TransactionOutput transactionOutput;
|
||||
|
||||
@ -282,6 +285,12 @@ public class WalletTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
public static class SilentPaymentConsolidationOutput extends SilentPaymentOutput {
|
||||
public SilentPaymentConsolidationOutput(TransactionOutput transactionOutput, SilentPayment silentPayment) {
|
||||
super(transactionOutput, silentPayment);
|
||||
}
|
||||
}
|
||||
|
||||
public static class WalletNodeOutput extends Output {
|
||||
private final WalletNode walletNode;
|
||||
private final Long value;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user