trigger full wallet refresh when all transaction history has changed on loading

This commit is contained in:
Craig Raw 2021-11-26 14:05:56 +02:00
parent 0302913c3f
commit 3013688447
4 changed files with 64 additions and 11 deletions

2
drongo

@ -1 +1 @@
Subproject commit 4a4a62f239f5de1e25e927ee9996326383ea7f89
Subproject commit da14a9bf34945713494cfbef86f8f44aedbc727f

View file

@ -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);
}
}

View file

@ -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<String, String> getCalculatedScriptHashes(Wallet wallet) {
Map<String, String> storedScriptHashStatuses = new HashMap<>();
storedScriptHashStatuses.putAll(calculateScriptHashes(wallet, KeyPurpose.RECEIVE));
storedScriptHashStatuses.putAll(calculateScriptHashes(wallet, KeyPurpose.CHANGE));
return storedScriptHashStatuses;
}
private static Map<String, String> calculateScriptHashes(Wallet wallet, KeyPurpose keyPurpose) {
@ -1188,14 +1194,30 @@ public class ElectrumServer {
synchronized(walletSynchronizeLocks.get(wallet)) {
if(isConnected()) {
ElectrumServer electrumServer = new ElectrumServer();
Map<String, String> previousScriptHashes = getCalculatedScriptHashes(wallet);
Map<WalletNode, Set<BlockTransactionHash>> 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<WalletNode> updatedNodes = new HashSet<>();
Map<WalletNode, Set<BlockTransactionHashIndex>> 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

View file

@ -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));