Skip to content

Commit

Permalink
calculate and cache script hashes statuses on wallet load to avoid un…
Browse files Browse the repository at this point in the history
…necessary initial history fetching
  • Loading branch information
craigraw committed May 20, 2021
1 parent 1677c47 commit 42bfe57
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 4 deletions.
50 changes: 47 additions & 3 deletions src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
Expand Down Expand Up @@ -89,7 +88,7 @@ private static synchronized Transport getTransport() throws ServerException {
}

//If changing server, don't rely on previous transaction history
if(!electrumServer.equals(previousServerAddress)) {
if(previousServerAddress != null && !electrumServer.equals(previousServerAddress)) {
retrievedScriptHashes.clear();
}
previousServerAddress = electrumServer;
Expand Down Expand Up @@ -160,6 +159,51 @@ public static synchronized void closeActiveConnection() throws ServerException {
}
}

public static void addCalculatedScriptHashes(Wallet wallet) {
calculateScriptHashes(wallet, KeyPurpose.RECEIVE).forEach(retrievedScriptHashes::putIfAbsent);
calculateScriptHashes(wallet, KeyPurpose.CHANGE).forEach(retrievedScriptHashes::putIfAbsent);
}

private static Map<String, String> calculateScriptHashes(Wallet wallet, KeyPurpose keyPurpose) {
Map<String, String> calculatedScriptHashes = new LinkedHashMap<>();
for(WalletNode walletNode : wallet.getNode(keyPurpose).getChildren()) {
String scriptHash = getScriptHash(wallet, walletNode);

List<BlockTransactionHashIndex> txos = new ArrayList<>(walletNode.getTransactionOutputs());
txos.addAll(walletNode.getTransactionOutputs().stream().filter(BlockTransactionHashIndex::isSpent).map(BlockTransactionHashIndex::getSpentBy).collect(Collectors.toList()));
Set<Sha256Hash> unique = new HashSet<>(txos.size());
txos.removeIf(ref -> !unique.add(ref.getHash()));
txos.sort((txo1, txo2) -> {
if(txo1.getHeight() != txo2.getHeight()) {
return txo1.getComparisonHeight() - txo2.getComparisonHeight();
}

if(txo1.isSpent() && txo1.getSpentBy().equals(txo2)) {
return -1;
}

if(txo2.isSpent() && txo2.getSpentBy().equals(txo1)) {
return 1;
}

//We cannot further sort by order within a block, so sometimes multiple txos to an address will mean an incorrect status
return 0;
});
if(!txos.isEmpty()) {
StringBuilder scriptHashStatus = new StringBuilder();
for(BlockTransactionHashIndex txo : txos) {
scriptHashStatus.append(txo.getHash().toString()).append(":").append(txo.getHeight()).append(":");
}

calculatedScriptHashes.put(scriptHash, Utils.bytesToHex(Sha256Hash.hash(scriptHashStatus.toString().getBytes(StandardCharsets.UTF_8))));
} else {
calculatedScriptHashes.put(scriptHash, null);
}
}

return calculatedScriptHashes;
}

public static void clearRetrievedScriptHashes(Wallet wallet) {
wallet.getNode(KeyPurpose.RECEIVE).getChildren().stream().map(node -> getScriptHash(wallet, node)).forEach(scriptHash -> retrievedScriptHashes.remove(scriptHash));
wallet.getNode(KeyPurpose.CHANGE).getChildren().stream().map(node -> getScriptHash(wallet, node)).forEach(scriptHash -> retrievedScriptHashes.remove(scriptHash));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public WalletForm(Storage storage, Wallet currentWallet, Wallet backupWallet, bo
//Unencrypted wallets load before isConnected is true, waiting for the ConnectionEvent to refresh history - save the backup for this event
savedPastWallet = backupWallet;

if(refreshHistory) {
if(refreshHistory && wallet.isValid()) {
ElectrumServer.addCalculatedScriptHashes(wallet);
refreshHistory(AppServices.getCurrentBlockHeight(), backupWallet);
}
}
Expand Down

0 comments on commit 42bfe57

Please sign in to comment.