mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-08-10 16:43:14 +00:00
implement silent payments change outputs and other sp related fixes
This commit is contained in:
@@ -250,7 +250,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||
double vSize = tx.getVirtualSize();
|
||||
if(changeTotal == 0) {
|
||||
//Add change output length to vSize if change was not present on the original transaction
|
||||
TransactionOutput changeOutput = new TransactionOutput(new Transaction(), 1L, transactionEntry.getWallet().getFreshNode(KeyPurpose.CHANGE).getOutputScript());
|
||||
TransactionOutput changeOutput = new TransactionOutput(new Transaction(), 1L, transactionEntry.getWallet().getNode(KeyPurpose.CHANGE).getOutputScript());
|
||||
vSize += changeOutput.getLength();
|
||||
}
|
||||
double inputSize = tx.getInputs().get(0).getLength() + (tx.getInputs().get(0).hasWitness() ? (double)tx.getInputs().get(0).getWitness().getLength() / Transaction.WITNESS_SCALE_FACTOR : 0);
|
||||
@@ -335,8 +335,10 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||
|
||||
if(cancelTransaction) {
|
||||
Payment existing = payments.get(0);
|
||||
Address address = transactionEntry.getWallet().getFreshNode(KeyPurpose.CHANGE).getAddress();
|
||||
Payment payment = new Payment(address, existing.getLabel(), existing.getAmount(), true);
|
||||
Payment payment = transactionEntry.getWallet().getPolicyType() == PolicyType.SINGLE_SP ?
|
||||
new SilentPayment(transactionEntry.getWallet().getKeystores().getFirst().getSilentPaymentScanAddress().getChangeAddress().getSilentPaymentAddress(),
|
||||
existing.getLabel(), existing.getAmount(), true) :
|
||||
new Payment(transactionEntry.getWallet().getFreshNode(KeyPurpose.CHANGE).getAddress(), existing.getLabel(), existing.getAmount(), true);
|
||||
payments.clear();
|
||||
payments.add(payment);
|
||||
opReturns.clear();
|
||||
@@ -370,10 +372,10 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||
}
|
||||
|
||||
BlockTransactionHashIndex cpfpUtxo = ourOutputs.get(0);
|
||||
Address freshAddress = transactionEntry.getWallet().getFreshNode(KeyPurpose.RECEIVE).getAddress();
|
||||
TransactionOutput txOutput = new TransactionOutput(new Transaction(), cpfpUtxo.getValue(), freshAddress.getOutputScript());
|
||||
long dustThreshold = freshAddress.getScriptType().getDustThreshold(txOutput, Transaction.DUST_RELAY_TX_FEE);
|
||||
double inputSize = freshAddress.getScriptType().getInputVbytes();
|
||||
Address receiveAddress = transactionEntry.getWallet().getNode(KeyPurpose.RECEIVE).getAddress();
|
||||
TransactionOutput txOutput = new TransactionOutput(new Transaction(), cpfpUtxo.getValue(), receiveAddress.getOutputScript());
|
||||
long dustThreshold = receiveAddress.getScriptType().getDustThreshold(txOutput, Transaction.DUST_RELAY_TX_FEE);
|
||||
double inputSize = receiveAddress.getScriptType().getInputVbytes();
|
||||
double vSize = inputSize + txOutput.getLength();
|
||||
|
||||
List<TxoFilter> txoFilters = List.of(new ExcludeTxoFilter(List.of(cpfpUtxo)), new SpentTxoFilter(), new FrozenTxoFilter(), new CoinbaseTxoFilter(transactionEntry.getWallet()));
|
||||
@@ -397,7 +399,10 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||
|
||||
String label = transactionEntry.getLabel() == null ? "" : transactionEntry.getLabel();
|
||||
label += (label.isEmpty() ? "" : " ") + "(CPFP)";
|
||||
Payment payment = new Payment(freshAddress, label, inputTotal, true);
|
||||
Payment payment = transactionEntry.getWallet().getPolicyType() == PolicyType.SINGLE_SP ?
|
||||
new SilentPayment(transactionEntry.getWallet().getKeystores().getFirst().getSilentPaymentScanAddress().getChangeAddress().getSilentPaymentAddress(),
|
||||
label, inputTotal, true) :
|
||||
new Payment(transactionEntry.getWallet().getFreshNode(KeyPurpose.CHANGE).getAddress(), label, inputTotal, true);
|
||||
|
||||
EventManager.get().post(new SendActionEvent(transactionEntry.getWallet(), utxos));
|
||||
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(transactionEntry.getWallet(), utxos, List.of(payment), null, blockTransaction.getFee(), true, null, true)));
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.sparrowwallet.drongo.address.Address;
|
||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||
import com.sparrowwallet.drongo.dns.DnsPayment;
|
||||
import com.sparrowwallet.drongo.dns.DnsPaymentCache;
|
||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
||||
import com.sparrowwallet.drongo.silentpayments.SilentPayment;
|
||||
@@ -696,7 +697,7 @@ public class TransactionDiagram extends GridPane {
|
||||
List<Long> values = walletTx.getOutputs().stream().filter(output -> !(output instanceof WalletTransaction.NonAddressOutput))
|
||||
.map(output -> output.getTransactionOutput().getValue()).collect(Collectors.toList());
|
||||
values.add(walletTx.getFee());
|
||||
int numOutputs = displayedPayments.size() + walletTx.getChangeMap().size() + 1;
|
||||
int numOutputs = displayedPayments.size() + walletTx.getChangeMap().size() + walletTx.getSilentPaymentChangeOutputs().size() + 1;
|
||||
for(int i = 1; i <= numOutputs; i++) {
|
||||
CubicCurve curve = new CubicCurve();
|
||||
curve.getStyleClass().add("output-line");
|
||||
@@ -789,8 +790,8 @@ public class TransactionDiagram extends GridPane {
|
||||
Set<Integer> seenIndexes = new HashSet<>();
|
||||
for(Map.Entry<WalletNode, Long> changeEntry : walletTx.getChangeMap().entrySet()) {
|
||||
WalletNode changeNode = changeEntry.getKey();
|
||||
WalletNode defaultChangeNode = walletTx.getWallet().getFreshNode(KeyPurpose.CHANGE);
|
||||
boolean overGapLimit = (changeNode.getIndex() - defaultChangeNode.getIndex()) > walletTx.getWallet().getGapLimit();
|
||||
boolean overGapLimit = walletTx.getWallet().getPolicyType() != PolicyType.SINGLE_SP &&
|
||||
(changeNode.getIndex() - walletTx.getWallet().getFreshNode(KeyPurpose.CHANGE).getIndex()) > walletTx.getWallet().getGapLimit();
|
||||
|
||||
HBox actionBox = new HBox();
|
||||
actionBox.setAlignment(Pos.CENTER_LEFT);
|
||||
@@ -845,6 +846,37 @@ public class TransactionDiagram extends GridPane {
|
||||
outputNodes.add(changeIndex, new OutputNode(actionBox, changeAddress, changeEntry.getValue()));
|
||||
}
|
||||
|
||||
for(WalletTransaction.SilentPaymentChangeOutput spChangeOutput : walletTx.getSilentPaymentChangeOutputs()) {
|
||||
HBox actionBox = new HBox();
|
||||
actionBox.setAlignment(Pos.CENTER_LEFT);
|
||||
SilentPayment silentPayment = spChangeOutput.getSilentPayment();
|
||||
SilentPaymentAddress spAddress = silentPayment.getSilentPaymentAddress();
|
||||
String changeDesc = spAddress.toString().substring(0, 8) + "...";
|
||||
Label changeLabel = new Label(changeDesc, getChangeGlyph());
|
||||
changeLabel.getStyleClass().addAll("output-label", "change-label");
|
||||
changeLabel.setSkin(new AddressLabelSkin(changeLabel));
|
||||
Tooltip changeTooltip = new Tooltip("Change of " + getCoinValue(silentPayment.getAmount()) + "\n" + spAddress);
|
||||
changeTooltip.getStyleClass().add("change-label");
|
||||
changeTooltip.setShowDelay(new Duration(TOOLTIP_SHOW_DELAY));
|
||||
changeTooltip.setShowDuration(Duration.INDEFINITE);
|
||||
changeTooltip.setSkin(new AddressTooltipSkin(changeTooltip));
|
||||
changeLabel.setTooltip(changeTooltip);
|
||||
actionBox.getChildren().add(changeLabel);
|
||||
|
||||
if(isExpanded()) {
|
||||
changeLabel.setMinWidth(120);
|
||||
Region region = new Region();
|
||||
region.setMinWidth(20);
|
||||
HBox.setHgrow(region, Priority.ALWAYS);
|
||||
CoinLabel amountLabel = new CoinLabel();
|
||||
amountLabel.setValue(silentPayment.getAmount());
|
||||
amountLabel.setMinWidth(TextUtils.computeTextWidth(amountLabel.getFont(), amountLabel.getText(), 0.0D) + 2);
|
||||
actionBox.getChildren().addAll(region, amountLabel);
|
||||
}
|
||||
|
||||
outputNodes.add(new OutputNode(actionBox, silentPayment.isAddressComputed() ? silentPayment.getAddress() : null, silentPayment.getAmount(), null, spAddress));
|
||||
}
|
||||
|
||||
for(OutputNode outputNode : outputNodes) {
|
||||
outputsBox.getChildren().add(outputNode.outputLabel);
|
||||
outputsBox.getChildren().add(createSpacer());
|
||||
|
||||
@@ -112,6 +112,7 @@ public class TransactionDiagramLabel extends HBox {
|
||||
|
||||
Map<WalletNode, Long> changeMap = walletTx.getChangeMap();
|
||||
outputLabels.addAll(changeMap.entrySet().stream().map(changeEntry -> getOutputLabel(transactionDiagram, changeEntry)).collect(Collectors.toList()));
|
||||
outputLabels.addAll(walletTx.getSilentPaymentChangeOutputs().stream().map(spChange -> getOutputLabel(transactionDiagram, spChange)).collect(Collectors.toList()));
|
||||
|
||||
OutputLabel feeOutputLabel = getFeeOutputLabel(transactionDiagram);
|
||||
if(feeOutputLabel != null) {
|
||||
@@ -220,6 +221,13 @@ public class TransactionDiagramLabel extends HBox {
|
||||
return getOutputLabel(glyph, text);
|
||||
}
|
||||
|
||||
private OutputLabel getOutputLabel(TransactionDiagram transactionDiagram, WalletTransaction.SilentPaymentChangeOutput spChangeOutput) {
|
||||
Glyph glyph = GlyphUtils.getChangeGlyph();
|
||||
String text = "Change of " + transactionDiagram.getCoinValue(spChangeOutput.getSilentPayment().getAmount()) + " to " + spChangeOutput.getSilentPayment();
|
||||
|
||||
return getOutputLabel(glyph, text);
|
||||
}
|
||||
|
||||
private OutputLabel getFeeOutputLabel(TransactionDiagram transactionDiagram) {
|
||||
WalletTransaction walletTx = transactionDiagram.getWalletTransaction();
|
||||
if(walletTx.getFee() < 0) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.net.cormorant;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.sparrowwallet.drongo.address.Address;
|
||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.io.Server;
|
||||
@@ -35,6 +36,10 @@ public class Cormorant {
|
||||
}
|
||||
|
||||
public Server start() throws CormorantBitcoindException {
|
||||
if(useWallets && AppServices.get().getOpenWallets().keySet().stream().anyMatch(wallet -> wallet.getPolicyType() == PolicyType.SINGLE_SP)) {
|
||||
throw new CormorantBitcoindException("Scanning silent payment wallets is not currently supported with Bitcoin Core");
|
||||
}
|
||||
|
||||
bitcoindClient = new BitcoindClient(useWallets);
|
||||
bitcoindClient.initialize();
|
||||
|
||||
|
||||
@@ -126,6 +126,8 @@ public class OutputController extends TransactionFormController implements Initi
|
||||
WalletTransaction.Output output = outputs.get(outputForm.getIndex());
|
||||
if(output instanceof WalletTransaction.NonAddressOutput) {
|
||||
outputFieldset.setText(baseText);
|
||||
} else if(output instanceof WalletTransaction.SilentPaymentChangeOutput) {
|
||||
outputFieldset.setText(baseText + " - Silent Payment Change");
|
||||
} else if(output instanceof WalletTransaction.SilentPaymentOutput) {
|
||||
outputFieldset.setText(baseText + " - Silent Payment");
|
||||
} else if(output instanceof WalletTransaction.ConsolidationOutput) {
|
||||
|
||||
@@ -87,6 +87,8 @@ public class OutputForm extends IndexedTransactionForm {
|
||||
} else {
|
||||
return new Label("Output #" + getIndex(), GlyphUtils.getOpcodeGlyph());
|
||||
}
|
||||
} else if(output instanceof WalletTransaction.SilentPaymentChangeOutput) {
|
||||
return new Label("Change", GlyphUtils.getChangeGlyph());
|
||||
} else if(output instanceof WalletTransaction.PaymentOutput paymentOutput) {
|
||||
Payment payment = paymentOutput.getPayment();
|
||||
return new Label(payment.getLabel() != null && payment.getType() != Payment.Type.FAKE_MIX && payment.getType() != Payment.Type.MIX ? payment.getLabel() : payment.toString(),
|
||||
|
||||
@@ -676,7 +676,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
OptimizationStrategy optimizationStrategy = (OptimizationStrategy)optimizationToggleGroup.getSelectedToggle().getUserData();
|
||||
if(optimizationStrategy == OptimizationStrategy.PRIVACY
|
||||
&& payments.size() == 1
|
||||
&& (payments.get(0).getAddress().getScriptType() == getWalletForm().getWallet().getFreshNode(KeyPurpose.RECEIVE).getAddress().getScriptType())) {
|
||||
&& (payments.get(0).getAddress().getScriptType() == getWalletForm().getWallet().getNode(KeyPurpose.RECEIVE).getAddress().getScriptType())) {
|
||||
selectors.add(new StonewallUtxoSelector(payments.get(0).getAddress().getScriptType(), noInputsFee));
|
||||
}
|
||||
|
||||
@@ -1008,7 +1008,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
|
||||
private boolean isFakeMixPossible(List<Payment> payments) {
|
||||
return utxoSelectorProperty.get() == null && payments.size() == 1
|
||||
&& (payments.get(0).getAddress().getScriptType() == getWalletForm().getWallet().getFreshNode(KeyPurpose.RECEIVE).getAddress().getScriptType())
|
||||
&& (payments.get(0).getAddress().getScriptType() == getWalletForm().getWallet().getNode(KeyPurpose.RECEIVE).getAddress().getScriptType())
|
||||
&& AppServices.getPayjoinURI(payments.get(0).getAddress()) == null;
|
||||
}
|
||||
|
||||
@@ -1652,7 +1652,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
OptimizationStrategy optimizationStrategy = getPreferredOptimizationStrategy();
|
||||
boolean fakeMixPresent = payments.stream().anyMatch(payment -> payment.getType() == Payment.Type.FAKE_MIX);
|
||||
boolean roundPaymentAmounts = userPayments.stream().anyMatch(payment -> payment.getAmount() % 100 == 0);
|
||||
boolean mixedAddressTypes = userPayments.stream().anyMatch(payment -> payment.getAddress().getScriptType() != getWalletForm().getWallet().getFreshNode(KeyPurpose.RECEIVE).getAddress().getScriptType());
|
||||
boolean mixedAddressTypes = userPayments.stream().anyMatch(payment -> payment.getAddress().getScriptType() != getWalletForm().getWallet().getNode(KeyPurpose.RECEIVE).getAddress().getScriptType());
|
||||
boolean addressReuse = walletNodePayments.stream().anyMatch(walletNodePayment -> !walletNodePayment.getWalletNode().getTransactionOutputs().isEmpty());
|
||||
boolean payjoinPresent = userPayments.stream().anyMatch(payment -> AppServices.getPayjoinURI(payment.getAddress()) != null);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user