diff --git a/src/main/java/com/sparrowwallet/sparrow/control/BalanceChart.java b/src/main/java/com/sparrowwallet/sparrow/control/BalanceChart.java index cff4fc8c..1f10a5b3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/BalanceChart.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/BalanceChart.java @@ -24,6 +24,7 @@ public class BalanceChart extends LineChart { } public void initialize(WalletTransactionsEntry walletTransactionsEntry) { + managedProperty().bind(visibleProperty()); balanceSeries = new XYChart.Series<>(); getData().add(balanceSeries); update(walletTransactionsEntry); @@ -33,6 +34,10 @@ public class BalanceChart extends LineChart { } public void update(WalletTransactionsEntry walletTransactionsEntry) { + if(walletTransactionsEntry.getChildren().isEmpty()) { + setVisible(false); + } + balanceSeries.getData().clear(); List> balanceDataList = walletTransactionsEntry.getChildren().stream() diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumServer.java index f7f397d8..f4942bab 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumServer.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.github.arteam.simplejsonrpc.client.*; import com.github.arteam.simplejsonrpc.client.builder.BatchRequestBuilder; import com.github.arteam.simplejsonrpc.client.exception.JsonRpcBatchException; +import com.github.arteam.simplejsonrpc.client.exception.JsonRpcException; import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod; import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam; import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService; @@ -594,6 +595,26 @@ public class ElectrumServer { return targetBlocksFeeRatesSats; } + public Sha256Hash broadcastTransaction(Transaction transaction) throws ServerException { + byte[] rawtxBytes = transaction.bitcoinSerialize(); + String rawtxHex = Utils.bytesToHex(rawtxBytes); + + JsonRpcClient client = new JsonRpcClient(getTransport()); + try { + String strTxHash = client.createRequest().returnAs(String.class).method("blockchain.transaction.broadcast").id(1).param("raw_tx", rawtxHex).execute(); + Sha256Hash receivedTxid = Sha256Hash.wrap(strTxHash); + if(!receivedTxid.equals(transaction.getTxId())) { + throw new ServerException("Received txid was different (" + receivedTxid + ")"); + } + + return receivedTxid; + } catch(JsonRpcException e) { + throw new ServerException(e.getErrorMessage().getMessage()); + } catch(IllegalStateException e) { + throw new ServerException(e.getMessage()); + } + } + public static String getScriptHash(Wallet wallet, WalletNode node) { byte[] hash = Sha256Hash.hash(wallet.getOutputScript(node).getProgram()); byte[] reversed = Utils.reverseBytes(hash); @@ -1139,6 +1160,24 @@ public class ElectrumServer { } } + public static class BroadcastTransactionService extends Service { + private final Transaction transaction; + + public BroadcastTransactionService(Transaction transaction) { + this.transaction = transaction; + } + + @Override + protected Task createTask() { + return new Task<>() { + protected Sha256Hash call() throws ServerException { + ElectrumServer electrumServer = new ElectrumServer(); + return electrumServer.broadcastTransaction(transaction); + } + }; + } + } + public enum Protocol { TCP { @Override diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index aeb919f3..16b3ba5d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -10,6 +10,7 @@ import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands; +import com.sparrowwallet.sparrow.io.ElectrumServer; import com.sparrowwallet.sparrow.io.Storage; import javafx.collections.FXCollections; import javafx.event.ActionEvent; @@ -154,6 +155,9 @@ public class HeadersController extends TransactionFormController implements Init @FXML private SignaturesProgressBar signaturesProgressBar; + @FXML + private ProgressBar broadcastProgressBar; + @FXML private HBox signButtonBox; @@ -163,6 +167,12 @@ public class HeadersController extends TransactionFormController implements Init @FXML private HBox broadcastButtonBox; + @FXML + private Button viewFinalButton; + + @FXML + private Button broadcastButton; + @Override public void initialize(URL location, ResourceBundle resources) { EventManager.get().register(this); @@ -302,6 +312,10 @@ public class HeadersController extends TransactionFormController implements Init signButtonBox.managedProperty().bind(signButtonBox.visibleProperty()); broadcastButtonBox.managedProperty().bind(broadcastButtonBox.visibleProperty()); + signaturesProgressBar.managedProperty().bind(signaturesProgressBar.visibleProperty()); + broadcastProgressBar.managedProperty().bind(broadcastProgressBar.visibleProperty()); + broadcastProgressBar.visibleProperty().bind(signaturesProgressBar.visibleProperty().not()); + blockchainForm.setVisible(false); signingWalletForm.setVisible(false); sigHashForm.setVisible(false); @@ -312,7 +326,7 @@ public class HeadersController extends TransactionFormController implements Init if(headersForm.getBlockTransaction() != null) { blockchainForm.setVisible(true); - updateBlockchainForm(headersForm.getBlockTransaction()); + updateBlockchainForm(headersForm.getBlockTransaction(), AppController.getCurrentBlockHeight()); } else if(headersForm.getPsbt() != null) { PSBT psbt = headersForm.getPsbt(); @@ -397,10 +411,9 @@ public class HeadersController extends TransactionFormController implements Init feeRate.setText(String.format("%.2f", feeRateAmt) + " sats/vByte"); } - private void updateBlockchainForm(BlockTransaction blockTransaction) { + private void updateBlockchainForm(BlockTransaction blockTransaction, Integer currentHeight) { blockchainForm.setVisible(true); - Integer currentHeight = AppController.getCurrentBlockHeight(); if(currentHeight == null) { blockStatus.setText("Unknown"); } else { @@ -604,7 +617,6 @@ public class HeadersController extends TransactionFormController implements Init } public void extractTransaction(ActionEvent event) { - Button viewFinalButton = (Button)event.getSource(); viewFinalButton.setDisable(true); Transaction finalTx = headersForm.getPsbt().extractTransaction(); @@ -613,9 +625,22 @@ public class HeadersController extends TransactionFormController implements Init } public void broadcastTransaction(ActionEvent event) { + broadcastButton.setDisable(true); extractTransaction(event); + ElectrumServer.BroadcastTransactionService broadcastTransactionService = new ElectrumServer.BroadcastTransactionService(headersForm.getTransaction()); + broadcastTransactionService.setOnSucceeded(workerStateEvent -> { + //Do nothing and wait for WalletNodeHistoryChangedEvent to indicate tx is in mempool + }); + broadcastTransactionService.setOnFailed(workerStateEvent -> { + broadcastProgressBar.setProgress(0); + AppController.showErrorDialog("Error broadcasting transaction", workerStateEvent.getSource().getException().getMessage()); + broadcastButton.setDisable(false); + }); + signaturesProgressBar.setVisible(false); + broadcastProgressBar.setProgress(-1); + broadcastTransactionService.start(); } @Subscribe @@ -635,7 +660,7 @@ public class HeadersController extends TransactionFormController implements Init public void blockTransactionFetched(BlockTransactionFetchedEvent event) { if(event.getTxId().equals(headersForm.getTransaction().getTxId())) { if(event.getBlockTransaction() != null) { - updateBlockchainForm(event.getBlockTransaction()); + updateBlockchainForm(event.getBlockTransaction(), AppController.getCurrentBlockHeight()); } Long feeAmt = calculateFee(event.getInputTransactions()); @@ -766,4 +791,29 @@ public class HeadersController extends TransactionFormController implements Init updateTxId(); } } + + @Subscribe + public void walletNodeHistoryChanged(WalletNodeHistoryChangedEvent event) { + if(headersForm.getSigningWallet() != null && event.getWalletNode(headersForm.getSigningWallet()) != null) { + Sha256Hash txid = headersForm.getTransaction().getTxId(); + ElectrumServer.TransactionReferenceService transactionReferenceService = new ElectrumServer.TransactionReferenceService(Set.of(txid)); + transactionReferenceService.setOnSucceeded(successEvent -> { + Map transactionMap = transactionReferenceService.getValue(); + BlockTransaction blockTransaction = transactionMap.get(txid); + if(blockTransaction != null) { + headersForm.setBlockTransaction(blockTransaction); + signaturesForm.setVisible(false); + updateBlockchainForm(blockTransaction, AppController.getCurrentBlockHeight()); + } + }); + transactionReferenceService.start(); + } + } + + @Subscribe + public void walletBlockHeightChanged(WalletBlockHeightChangedEvent event) { + if(headersForm.getSigningWallet() != null && event.getWallet() == headersForm.getSigningWallet() && headersForm.getBlockTransaction() != null) { + updateBlockchainForm(headersForm.getBlockTransaction(), event.getBlockHeight()); + } + } } \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersForm.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersForm.java index 37455b29..d177f89b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersForm.java @@ -1,6 +1,7 @@ package com.sparrowwallet.sparrow.transaction; import com.sparrowwallet.drongo.protocol.Transaction; +import com.sparrowwallet.drongo.wallet.BlockTransaction; import javafx.fxml.FXMLLoader; import javafx.scene.Node; @@ -15,6 +16,10 @@ public class HeadersForm extends TransactionForm { txdata.setTransaction(finalTransaction); } + void setBlockTransaction(BlockTransaction blockTransaction) { + txdata.setBlockTransaction(blockTransaction); + } + @Override public Node getContents() throws IOException { FXMLLoader loader = new FXMLLoader(getClass().getResource("headers.fxml")); diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css index a36a004a..9c999125 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css @@ -46,7 +46,7 @@ -fx-max-width: Infinity; } -.signatures-progress-bar { +.signatures-progress-bar, #broadcastProgressBar { -fx-padding: 10 0 10 0; } diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml index 1b310962..ca4d827d 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml @@ -22,6 +22,7 @@ + @@ -174,6 +175,7 @@
+ @@ -212,12 +214,12 @@ - -