add create cpfp transaction functionality

This commit is contained in:
Craig Raw 2021-04-23 12:05:30 +02:00
parent 3ff626e2aa
commit cd00535212
4 changed files with 90 additions and 4 deletions

View file

@ -92,6 +92,17 @@ public class EntryCell extends TreeTableCell<Entry, Entry> {
actionBox.getChildren().add(increaseFeeButton);
}
if(blockTransaction.getHeight() <= 0 && containsWalletOutputs(transactionEntry)) {
Button cpfpButton = new Button("");
Glyph cpfpGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.SIGN_OUT_ALT);
cpfpGlyph.setFontSize(12);
cpfpButton.setGraphic(cpfpGlyph);
cpfpButton.setOnAction(event -> {
createCpfp(transactionEntry);
});
actionBox.getChildren().add(cpfpButton);
}
setGraphic(actionBox);
} else if(entry instanceof NodeEntry) {
NodeEntry nodeEntry = (NodeEntry)entry;
@ -245,6 +256,37 @@ public class EntryCell extends TreeTableCell<Entry, Entry> {
return AppServices.getTargetBlockFeeRates().values().iterator().next();
}
private static void createCpfp(TransactionEntry transactionEntry) {
BlockTransaction blockTransaction = transactionEntry.getBlockTransaction();
List<BlockTransactionHashIndex> ourOutputs = transactionEntry.getChildren().stream()
.filter(e -> e instanceof HashIndexEntry)
.map(e -> (HashIndexEntry)e)
.filter(e -> e.getType().equals(HashIndexEntry.Type.OUTPUT))
.map(HashIndexEntry::getHashIndex)
.collect(Collectors.toList());
if(ourOutputs.isEmpty()) {
throw new IllegalStateException("Cannot create CPFP without any wallet outputs to spend");
}
BlockTransactionHashIndex utxo = ourOutputs.get(0);
WalletNode freshNode = transactionEntry.getWallet().getFreshNode(KeyPurpose.RECEIVE);
String label = transactionEntry.getLabel() == null ? "" : transactionEntry.getLabel();
label += (label.isEmpty() ? "" : " ") + "(CPFP)";
Payment payment = new Payment(transactionEntry.getWallet().getAddress(freshNode), label, utxo.getValue(), true);
EventManager.get().post(new SendActionEvent(transactionEntry.getWallet(), List.of(utxo)));
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(transactionEntry.getWallet(), List.of(utxo), List.of(payment), blockTransaction.getFee(), false)));
}
private static boolean containsWalletOutputs(TransactionEntry transactionEntry) {
return transactionEntry.getChildren().stream()
.filter(e -> e instanceof HashIndexEntry)
.map(e -> (HashIndexEntry)e)
.anyMatch(e -> e.getType().equals(HashIndexEntry.Type.OUTPUT));
}
private static void sendSelectedUtxos(TreeTableView<Entry> treeTableView, HashIndexEntry hashIndexEntry) {
List<HashIndexEntry> utxoEntries = treeTableView.getSelectionModel().getSelectedCells().stream()
.map(tp -> tp.getTreeItem().getValue())
@ -286,7 +328,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> {
getItems().add(copyTxid);
if(blockTransaction.getTransaction().isReplaceByFee() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
MenuItem increaseFee = new MenuItem("Increase Fee");
MenuItem increaseFee = new MenuItem("Increase Fee (RBF)");
increaseFee.setOnAction(AE -> {
hide();
increaseFee(transactionEntry);
@ -294,6 +336,16 @@ public class EntryCell extends TreeTableCell<Entry, Entry> {
getItems().add(increaseFee);
}
if(containsWalletOutputs(transactionEntry)) {
MenuItem createCpfp = new MenuItem("Increase Effective Fee (CPFP)");
createCpfp.setOnAction(AE -> {
hide();
createCpfp(transactionEntry);
});
getItems().add(createCpfp);
}
}
}

View file

@ -48,12 +48,14 @@ public class FontAwesome5 extends GlyphFont {
SATELLITE_DISH('\uf7c0'),
SD_CARD('\uf7c2'),
SEARCH('\uf002'),
SIGN_OUT_ALT('\uf2f5'),
SQUARE('\uf0c8'),
TIMES_CIRCLE('\uf057'),
TOGGLE_OFF('\uf204'),
TOGGLE_ON('\uf205'),
TOOLS('\uf7d9'),
UNDO('\uf0e2'),
USER_FRIENDS('\uf500'),
WALLET('\uf555');
private final char ch;

View file

@ -81,6 +81,9 @@ public class SendController extends WalletFormController implements Initializabl
@FXML
private CopyableLabel feeRate;
@FXML
private Label cpfpFeeRate;
@FXML
private Label feeRatePriority;
@ -281,6 +284,8 @@ public class SendController extends WalletFormController implements Initializabl
FeeRatesSelection feeRatesSelection = Config.get().getFeeRatesSelection();
feeRatesSelection = (feeRatesSelection == null ? FeeRatesSelection.MEMPOOL_SIZE : feeRatesSelection);
cpfpFeeRate.managedProperty().bind(cpfpFeeRate.visibleProperty());
cpfpFeeRate.setVisible(false);
setDefaultFeeRate();
updateFeeRateSelection(feeRatesSelection);
feeSelectionToggleGroup.selectToggle(feeRatesSelection == FeeRatesSelection.BLOCK_TARGET ? targetBlocksToggle : mempoolSizeToggle);
@ -346,6 +351,7 @@ public class SendController extends WalletFormController implements Initializabl
}
setFeeRate(feeRate);
setEffectiveFeeRate(walletTransaction);
}
transactionDiagram.update(walletTransaction);
@ -354,7 +360,7 @@ public class SendController extends WalletFormController implements Initializabl
transactionDiagram.sceneProperty().addListener((observable, oldScene, newScene) -> {
if(oldScene == null && newScene != null) {
transactionDiagram.update(null);
transactionDiagram.update(walletTransactionProperty.get());
newScene.getWindow().heightProperty().addListener((observable1, oldValue, newValue) -> {
transactionDiagram.update(walletTransactionProperty.get());
});
@ -636,10 +642,28 @@ public class SendController extends WalletFormController implements Initializabl
}
private void setFeeRate(Double feeRateAmt) {
feeRate.setText(String.format("%.2f", feeRateAmt) + " sats/vByte");
feeRate.setText(String.format("%.2f", feeRateAmt) + " sats/vB");
setFeeRatePriority(feeRateAmt);
}
private void setEffectiveFeeRate(WalletTransaction walletTransaction) {
List<BlockTransaction> unconfirmedUtxoTxs = walletTransaction.getSelectedUtxos().keySet().stream().filter(ref -> ref.getHeight() <= 0)
.map(ref -> getWalletForm().getWallet().getTransactions().get(ref.getHash())).filter(Objects::nonNull).distinct().collect(Collectors.toList());
if(!unconfirmedUtxoTxs.isEmpty()) {
cpfpFeeRate.setVisible(true);
long utxoTxFee = unconfirmedUtxoTxs.stream().mapToLong(BlockTransaction::getFee).sum();
double utxoTxSize = unconfirmedUtxoTxs.stream().mapToDouble(blkTx -> blkTx.getTransaction().getVirtualSize()).sum();
long thisFee = walletTransaction.getFee();
double thisSize = walletTransaction.getTransaction().getVirtualSize();
double effectiveRate = (utxoTxFee + thisFee) / (utxoTxSize + thisSize);
Tooltip tooltip = new Tooltip(String.format("%.2f", effectiveRate) + " sats/vB effective rate");
cpfpFeeRate.setTooltip(tooltip);
cpfpFeeRate.setVisible(true);
} else {
cpfpFeeRate.setVisible(false);
}
}
private void setFeeRatePriority(Double feeRateAmt) {
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
Integer targetBlocks = getTargetBlocks(feeRateAmt);
@ -961,7 +985,7 @@ public class SendController extends WalletFormController implements Initializabl
List<BlockTransactionHashIndex> utxos = event.getUtxos();
utxoSelectorProperty.set(new PresetUtxoSelector(utxos));
utxoFilterProperty.set(null);
updateTransaction(event.getPayments() == null);
updateTransaction(event.getPayments() == null || event.getPayments().stream().anyMatch(Payment::isSendMax));
}
}

View file

@ -90,6 +90,14 @@
</Field>
<Field fx:id="feeRateField" text="Rate:">
<CopyableLabel fx:id="feeRate" />
<Label fx:id="cpfpFeeRate" text="CPFP">
<graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="SIGN_OUT_ALT" />
</graphic>
<padding>
<Insets left="10"/>
</padding>
</Label>
<Region HBox.hgrow="ALWAYS" />
<Label fx:id="feeRatePriority" graphicTextGap="5" contentDisplay="RIGHT">
<graphic>