From 4d93381124a2602cae2021b6fac5196395bbf171 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Wed, 4 Jun 2025 14:52:33 +0200 Subject: [PATCH] improve electrum server script hash unsubscribe support --- .../com/sparrowwallet/sparrow/io/Config.java | 10 ++++++++ .../sparrow/io/ElectrumPersonalServer.java | 3 ++- .../sparrow/net/ElectrumServer.java | 23 +++++++++++-------- .../sparrow/net/SimpleElectrumServerRpc.java | 21 +++++++++++++++-- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Config.java b/src/main/java/com/sparrowwallet/sparrow/io/Config.java index f93ad0f4..0a04de2a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Config.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Config.java @@ -69,6 +69,7 @@ public class Config { private File coreDataDir; private String coreAuth; private boolean useLegacyCoreWallet; + private boolean legacyServer; private Server electrumServer; private List recentElectrumServers; private File electrumServerCert; @@ -549,6 +550,15 @@ public class Config { flush(); } + public boolean isLegacyServer() { + return legacyServer; + } + + public void setLegacyServer(boolean legacyServer) { + this.legacyServer = legacyServer; + flush(); + } + public Server getElectrumServer() { return electrumServer; } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java index 6f1e4d42..ddcb4cf2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java @@ -37,7 +37,8 @@ public class ElectrumPersonalServer implements WalletExport { try { BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); writer.write("# Electrum Personal Server configuration file fragments\n"); - writer.write("# Copy the lines below into the relevant sections in your EPS config.ini file\n\n"); + writer.write("# First close Sparrow and edit your config file in Sparrow home to set \"legacyServer\": true\n"); + writer.write("# Then copy the lines below into the relevant sections in your EPS config.ini file\n\n"); writer.write("# Copy into [master-public-keys] section\n"); Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); writeWalletXpub(masterWallet, writer); diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index 75baf3a3..29114e26 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -76,7 +76,7 @@ public class ElectrumServer { private static final Set sameHeightTxioScriptHashes = ConcurrentHashMap.newKeySet(); - private final static Set subscribedRecent = ConcurrentHashMap.newKeySet(); + private final static Map subscribedRecent = new ConcurrentHashMap<>(); private final static Map broadcastRecent = new ConcurrentHashMap<>(); @@ -1255,7 +1255,7 @@ public class ElectrumServer { if(!serverVersion.isEmpty()) { String server = serverVersion.getFirst().toLowerCase(Locale.ROOT); if(server.contains("electrumx")) { - return new ServerCapability(true, false); + return new ServerCapability(true, true); } if(server.startsWith("cormorant")) { @@ -1312,6 +1312,10 @@ public class ElectrumServer { //ignore } } + + if(server.startsWith("electrumpersonalserver")) { + return new ServerCapability(false, false); + } } return new ServerCapability(false, true); @@ -1626,7 +1630,7 @@ public class ElectrumServer { try { electrumServerRpc.subscribeScriptHashes(transport, null, subscribeScriptHashes); - subscribedRecent.addAll(subscribeScriptHashes.values()); + subscribeScriptHashes.values().forEach(scriptHash -> subscribedRecent.put(scriptHash, AppServices.getCurrentBlockHeight())); } catch(ElectrumServerRpcException e) { log.debug("Error subscribing to recent mempool transaction outputs", e); } @@ -2032,7 +2036,7 @@ public class ElectrumServer { Config config = Config.get(); if(!isBlockstorm(totalBlocks) && !AppServices.isUsingProxy() && config.getServer().getProtocol().equals(Protocol.SSL) && (config.getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER || config.getServerType() == ServerType.ELECTRUM_SERVER)) { - subscribeRecent(electrumServer); + subscribeRecent(electrumServer, AppServices.getCurrentBlockHeight() == null ? endHeight : AppServices.getCurrentBlockHeight()); } Double nextBlockMedianFeeRate = null; @@ -2048,13 +2052,14 @@ public class ElectrumServer { return Network.get() != Network.MAINNET && totalBlocks > 2; } - private void subscribeRecent(ElectrumServer electrumServer) { - Set unsubscribeScriptHashes = new HashSet<>(subscribedRecent); + private void subscribeRecent(ElectrumServer electrumServer, int currentHeight) { + Set unsubscribeScriptHashes = subscribedRecent.entrySet().stream().filter(entry -> entry.getValue() == null || entry.getValue() <= currentHeight - 3) + .map(Map.Entry::getKey).collect(Collectors.toSet()); unsubscribeScriptHashes.removeIf(subscribedScriptHashes::containsKey); if(!unsubscribeScriptHashes.isEmpty() && serverCapability.supportsUnsubscribe()) { electrumServerRpc.unsubscribeScriptHashes(transport, unsubscribeScriptHashes); } - subscribedRecent.removeAll(unsubscribeScriptHashes); + subscribedRecent.keySet().removeAll(unsubscribeScriptHashes); broadcastRecent.clear(); Map subscribeScriptHashes = new HashMap<>(); @@ -2074,7 +2079,7 @@ public class ElectrumServer { if(!subscribeScriptHashes.isEmpty()) { Random random = new Random(); - int additionalRandomScriptHashes = random.nextInt(4) + 4; + int additionalRandomScriptHashes = random.nextInt(8); for(int i = 0; i < additionalRandomScriptHashes; i++) { byte[] randomScriptHashBytes = new byte[32]; random.nextBytes(randomScriptHashBytes); @@ -2086,7 +2091,7 @@ public class ElectrumServer { try { electrumServerRpc.subscribeScriptHashes(transport, null, subscribeScriptHashes); - subscribedRecent.addAll(subscribeScriptHashes.values()); + subscribeScriptHashes.values().forEach(scriptHash -> subscribedRecent.put(scriptHash, currentHeight)); } catch(ElectrumServerRpcException e) { log.debug("Error subscribing to recent mempool transactions", e); } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java b/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java index 7a16e03b..d660009c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java @@ -10,6 +10,7 @@ import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent; +import com.sparrowwallet.sparrow.io.Config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,16 +39,32 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc { @Override public List getServerVersion(Transport transport, String clientName, String[] supportedVersions) { + if(Config.get().isLegacyServer()) { + return getLegacyServerVersion(transport, clientName); + } + try { JsonRpcClient client = new JsonRpcClient(transport); - //Using 1.4 as the version number as EPS tries to parse this number to a float :( return new RetryLogic>(MAX_RETRIES, RETRY_DELAY, IllegalStateException.class).getResult(() -> - client.createRequest().returnAsList(String.class).method("server.version").id(idCounter.incrementAndGet()).params(clientName, "1.4").execute()); + client.createRequest().returnAsList(String.class).method("server.version").id(idCounter.incrementAndGet()).params(clientName, supportedVersions).execute()); + } catch(JsonRpcException e) { + return getLegacyServerVersion(transport, clientName); } catch(Exception e) { throw new ElectrumServerRpcException("Error getting server version", e); } } + private List getLegacyServerVersion(Transport transport, String clientName) { + try { + //Fallback to using 1.4 as the version number as EPS tries to parse this number to a float :( + JsonRpcClient client = new JsonRpcClient(transport); + return new RetryLogic>(MAX_RETRIES, RETRY_DELAY, IllegalStateException.class).getResult(() -> + client.createRequest().returnAsList(String.class).method("server.version").id(idCounter.incrementAndGet()).params(clientName, "1.4").execute()); + } catch(Exception ex) { + throw new ElectrumServerRpcException("Error getting legacy server version", ex); + } + } + @Override public String getServerBanner(Transport transport) { try {