diff --git a/drongo b/drongo index 4a4a62f2..da14a9bf 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 4a4a62f239f5de1e25e927ee9996326383ea7f89 +Subproject commit da14a9bf34945713494cfbef86f8f44aedbc727f diff --git a/src/main/java/com/sparrowwallet/sparrow/net/AllHistoryChangedException.java b/src/main/java/com/sparrowwallet/sparrow/net/AllHistoryChangedException.java new file mode 100644 index 00000000..210015d0 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/net/AllHistoryChangedException.java @@ -0,0 +1,18 @@ +package com.sparrowwallet.sparrow.net; + +public class AllHistoryChangedException extends RuntimeException { + public AllHistoryChangedException() { + } + + public AllHistoryChangedException(String message) { + super(message); + } + + public AllHistoryChangedException(String message, Throwable cause) { + super(message, cause); + } + + public AllHistoryChangedException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index b961b98c..577d0da1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -167,8 +167,14 @@ public class ElectrumServer { } public static void addCalculatedScriptHashes(Wallet wallet) { - calculateScriptHashes(wallet, KeyPurpose.RECEIVE).forEach(retrievedScriptHashes::putIfAbsent); - calculateScriptHashes(wallet, KeyPurpose.CHANGE).forEach(retrievedScriptHashes::putIfAbsent); + getCalculatedScriptHashes(wallet).forEach(retrievedScriptHashes::putIfAbsent); + } + + private static Map getCalculatedScriptHashes(Wallet wallet) { + Map storedScriptHashStatuses = new HashMap<>(); + storedScriptHashStatuses.putAll(calculateScriptHashes(wallet, KeyPurpose.RECEIVE)); + storedScriptHashStatuses.putAll(calculateScriptHashes(wallet, KeyPurpose.CHANGE)); + return storedScriptHashStatuses; } private static Map calculateScriptHashes(Wallet wallet, KeyPurpose keyPurpose) { @@ -1188,14 +1194,30 @@ public class ElectrumServer { synchronized(walletSynchronizeLocks.get(wallet)) { if(isConnected()) { ElectrumServer electrumServer = new ElectrumServer(); + Map previousScriptHashes = getCalculatedScriptHashes(wallet); Map> nodeTransactionMap = (nodes == null ? electrumServer.getHistory(wallet) : electrumServer.getHistory(wallet, nodes)); electrumServer.getReferencedTransactions(wallet, nodeTransactionMap); electrumServer.calculateNodeHistory(wallet, nodeTransactionMap); //Add all of the script hashes we have now fetched the history for so we don't need to fetch again until the script hash status changes - for(WalletNode node : (nodes == null ? nodeTransactionMap.keySet() : nodes)) { + Set updatedNodes = new HashSet<>(); + Map> walletNodes = wallet.getWalletNodes(); + for(WalletNode node : (nodes == null ? walletNodes.keySet() : nodes)) { String scriptHash = getScriptHash(wallet, node); - retrievedScriptHashes.put(scriptHash, getSubscribedScriptHashStatus(scriptHash)); + String subscribedStatus = getSubscribedScriptHashStatus(scriptHash); + if(!Objects.equals(subscribedStatus, retrievedScriptHashes.get(scriptHash))) { + updatedNodes.add(node); + } + retrievedScriptHashes.put(scriptHash, subscribedStatus); + } + + //If wallet was not empty, check if all used updated nodes have changed history + if(nodes == null && previousScriptHashes.values().stream().anyMatch(Objects::nonNull)) { + if(!updatedNodes.isEmpty() && updatedNodes.equals(walletNodes.entrySet().stream().filter(entry -> !entry.getValue().isEmpty()).map(Map.Entry::getKey).collect(Collectors.toSet()))) { + //All used nodes on a non-empty wallet have changed history. Abort and trigger a full refresh. + log.info("All used nodes on a non-empty wallet have changed history. Triggering a full wallet refresh."); + throw new AllHistoryChangedException(); + } } //Clear transaction outputs for nodes that have no history - this is useful when a transaction is replaced in the mempool diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java index 6f6550da..5fb6140c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java @@ -10,6 +10,7 @@ import com.sparrowwallet.sparrow.WalletTabData; import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.StorageException; +import com.sparrowwallet.sparrow.net.AllHistoryChangedException; import com.sparrowwallet.sparrow.net.ElectrumServer; import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.net.ServerType; @@ -140,13 +141,25 @@ public class WalletForm { } }); historyService.setOnFailed(workerStateEvent -> { - if(AppServices.isConnected()) { - log.error("Error retrieving wallet history", workerStateEvent.getSource().getException()); - } else { - log.debug("Disconnected while retrieving wallet history", workerStateEvent.getSource().getException()); - } + if(workerStateEvent.getSource().getException() instanceof AllHistoryChangedException) { + try { + storage.backupWallet(); + } catch(IOException e) { + log.error("Error backing up wallet", e); + } - EventManager.get().post(new WalletHistoryFailedEvent(wallet, workerStateEvent.getSource().getException())); + wallet.clearHistory(); + AppServices.clearTransactionHistoryCache(wallet); + EventManager.get().post(new WalletHistoryClearedEvent(wallet, pastWallet, getWalletId())); + } else { + if(AppServices.isConnected()) { + log.error("Error retrieving wallet history", workerStateEvent.getSource().getException()); + } else { + log.debug("Disconnected while retrieving wallet history", workerStateEvent.getSource().getException()); + } + + EventManager.get().post(new WalletHistoryFailedEvent(wallet, workerStateEvent.getSource().getException())); + } }); EventManager.get().post(new WalletHistoryStartedEvent(wallet, nodes));