diff --git a/src/main/java/com/sparrowwallet/sparrow/control/CoinTreeTable.java b/src/main/java/com/sparrowwallet/sparrow/control/CoinTreeTable.java index 7afb2570..0d5da3a9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/CoinTreeTable.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/CoinTreeTable.java @@ -2,8 +2,11 @@ package com.sparrowwallet.sparrow.control; import com.sparrowwallet.drongo.BitcoinUnit; import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent; import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.wallet.Entry; +import javafx.application.Platform; +import javafx.scene.control.Label; import javafx.scene.control.TreeTableView; public class CoinTreeTable extends TreeTableView { @@ -33,4 +36,20 @@ public class CoinTreeTable extends TreeTableView { refresh(); } } + + public void updateHistoryStatus(WalletHistoryStatusEvent event) { + Platform.runLater(() -> { + if(event.getErrorMessage() != null) { + setPlaceholder(new Label("Error loading transactions: " + event.getErrorMessage())); + } else if(event.isLoading()) { + if(event.getStatusMessage() != null) { + setPlaceholder(new Label(event.getStatusMessage() + "...")); + } else { + setPlaceholder(new Label("Loading transactions...")); + } + } else { + setPlaceholder(new Label("No transactions")); + } + }); + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WalletHistoryStatusEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WalletHistoryStatusEvent.java new file mode 100644 index 00000000..5a19b63c --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/WalletHistoryStatusEvent.java @@ -0,0 +1,41 @@ +package com.sparrowwallet.sparrow.event; + +public class WalletHistoryStatusEvent { + private final boolean loaded; + private final String statusMessage; + private final String errorMessage; + + public WalletHistoryStatusEvent(boolean loaded) { + this.loaded = loaded; + this.statusMessage = null; + this.errorMessage = null; + } + + public WalletHistoryStatusEvent(boolean loaded, String statusMessage) { + this.loaded = false; + this.statusMessage = statusMessage; + this.errorMessage = null; + } + + public WalletHistoryStatusEvent(String errorMessage) { + this.loaded = false; + this.statusMessage = null; + this.errorMessage = errorMessage; + } + + public boolean isLoading() { + return !loaded; + } + + public boolean isLoaded() { + return loaded; + } + + public String getStatusMessage() { + return statusMessage; + } + + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java b/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java index dd6f9a23..b415db64 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java @@ -6,6 +6,8 @@ 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.sparrowwallet.drongo.protocol.Sha256Hash; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,6 +63,7 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc { public Map getScriptHashHistory(Transport transport, Map pathScriptHashes, boolean failOnError) { JsonRpcClient client = new JsonRpcClient(transport); BatchRequestBuilder batchRequest = client.createBatchRequest().keysType(String.class).returnType(ScriptHashTx[].class); + EventManager.get().post(new WalletHistoryStatusEvent(false, "Loading transactions")); for(String path : pathScriptHashes.keySet()) { batchRequest.add(path, "blockchain.scripthash.get_history", pathScriptHashes.get(path)); @@ -112,6 +115,7 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc { public Map subscribeScriptHashes(Transport transport, Map pathScriptHashes) { JsonRpcClient client = new JsonRpcClient(transport); BatchRequestBuilder batchRequest = client.createBatchRequest().keysType(String.class).returnType(String.class); + EventManager.get().post(new WalletHistoryStatusEvent(false, "Finding transactions")); for(String path : pathScriptHashes.keySet()) { batchRequest.add(path, "blockchain.scripthash.subscribe", pathScriptHashes.get(path)); @@ -130,6 +134,8 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc { public Map getBlockHeaders(Transport transport, Set blockHeights) { JsonRpcClient client = new JsonRpcClient(transport); BatchRequestBuilder batchRequest = client.createBatchRequest().keysType(Integer.class).returnType(String.class); + EventManager.get().post(new WalletHistoryStatusEvent(false, "Retrieving blocks")); + for(Integer height : blockHeights) { batchRequest.add(height, "blockchain.block.header", height); } @@ -146,6 +152,8 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc { public Map getTransactions(Transport transport, Set txids) { JsonRpcClient client = new JsonRpcClient(transport); BatchRequestBuilder batchRequest = client.createBatchRequest().keysType(String.class).returnType(String.class); + EventManager.get().post(new WalletHistoryStatusEvent(false, "Retrieving transactions")); + for(String txid : txids) { batchRequest.add(txid, "blockchain.transaction.get", txid); } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index 8201dfd4..8e1e4038 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -180,6 +180,10 @@ public class ElectrumServer { pathScriptHashes.put(node.getDerivationPath(), getScriptHash(wallet, node)); } + if(pathScriptHashes.isEmpty()) { + return; + } + //Even if we have some successes, failure to retrieve all references will result in an incomplete wallet history. Don't proceed if that's the case. Map result = electrumServerRpc.getScriptHashHistory(getTransport(), pathScriptHashes, true); diff --git a/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java b/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java index 4061d118..2d6a26ef 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java @@ -7,13 +7,13 @@ import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.protocol.Sha256Hash; import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.sparrow.AppController; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; -import static com.sparrowwallet.drongo.protocol.Transaction.DUST_RELAY_TX_FEE; - public class SimpleElectrumServerRpc implements ElectrumServerRpc { private static final Logger log = LoggerFactory.getLogger(SimpleElectrumServerRpc.class); private static final int MAX_TARGET_BLOCKS = 25; @@ -65,6 +65,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc { Map result = new LinkedHashMap<>(); for(String path : pathScriptHashes.keySet()) { + EventManager.get().post(new WalletHistoryStatusEvent(false, "Loading transactions for " + path)); try { ScriptHashTx[] scriptHashTxes = client.createRequest().returnAs(ScriptHashTx[].class).method("blockchain.scripthash.get_history").id(path).params(pathScriptHashes.get(path)).execute(); result.put(path, scriptHashTxes); @@ -107,6 +108,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc { Map result = new LinkedHashMap<>(); for(String path : pathScriptHashes.keySet()) { + EventManager.get().post(new WalletHistoryStatusEvent(false, "Finding transactions for " + path)); try { String scriptHash = client.createRequest().returnAs(String.class).method("blockchain.scripthash.subscribe").id(path).params(pathScriptHashes.get(path)).executeNullable(); result.put(path, scriptHash); @@ -125,6 +127,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc { Map result = new LinkedHashMap<>(); for(Integer blockHeight : blockHeights) { + EventManager.get().post(new WalletHistoryStatusEvent(false, "Retrieving block at height " + blockHeight)); try { String blockHeader = client.createRequest().returnAs(String.class).method("blockchain.block.header").id(blockHeight).params(blockHeight).execute(); result.put(blockHeight, blockHeader); @@ -144,6 +147,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc { Map result = new LinkedHashMap<>(); for(String txid : txids) { + EventManager.get().post(new WalletHistoryStatusEvent(false, "Retrieving transaction [" + txid.substring(0, 6) + "]")); try { String rawTxHex = client.createRequest().returnAs(String.class).method("blockchain.transaction.get").id(txid).params(txid).execute(); result.put(txid, rawTxHex); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/AddressesController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/AddressesController.java index 35e9271d..e0e03a9b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/AddressesController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/AddressesController.java @@ -5,10 +5,7 @@ import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.wallet.WalletNode; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.control.AddressTreeTable; -import com.sparrowwallet.sparrow.event.BitcoinUnitChangedEvent; -import com.sparrowwallet.sparrow.event.WalletEntryLabelChangedEvent; -import com.sparrowwallet.sparrow.event.WalletHistoryChangedEvent; -import com.sparrowwallet.sparrow.event.WalletNodesChangedEvent; +import com.sparrowwallet.sparrow.event.*; import javafx.fxml.FXML; import javafx.fxml.Initializable; diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionsController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionsController.java index 07663ecd..778e370d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionsController.java @@ -122,4 +122,9 @@ public class TransactionsController extends WalletFormController implements Init public void exchangeRatesUpdated(ExchangeRatesUpdatedEvent event) { setFiatBalance(event.getCurrencyRate(), getWalletForm().getWalletTransactionsEntry().getBalance()); } + + @Subscribe + public void walletHistoryStatus(WalletHistoryStatusEvent event) { + transactionsTable.updateHistoryStatus(event); + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java index dbac9c45..200eb967 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java @@ -125,4 +125,9 @@ public class UtxosController extends WalletFormController implements Initializab utxosChart.setBitcoinUnit(getWalletForm().getWallet(), event.getBitcoinUnit()); updateSendSelected(event.getBitcoinUnit()); } + + @Subscribe + public void walletHistoryStatus(WalletHistoryStatusEvent event) { + utxosTable.updateHistoryStatus(event); + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java index 50a3daac..7082d1a1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java @@ -74,11 +74,14 @@ public class WalletForm { log.debug(node == null ? "Refreshing full wallet history" : "Requesting node wallet history for " + node.getDerivationPath()); ElectrumServer.TransactionHistoryService historyService = new ElectrumServer.TransactionHistoryService(wallet, node); historyService.setOnSucceeded(workerStateEvent -> { + EventManager.get().post(new WalletHistoryStatusEvent(true)); updateWallet(previousWallet, blockHeight); }); historyService.setOnFailed(workerStateEvent -> { log.error("Error retrieving wallet history", workerStateEvent.getSource().getException()); + EventManager.get().post(new WalletHistoryStatusEvent(workerStateEvent.getSource().getException().getMessage())); }); + EventManager.get().post(new WalletHistoryStatusEvent(false)); historyService.start(); } }