improve electrum server script hash unsubscribe support

This commit is contained in:
Craig Raw 2025-06-04 14:52:33 +02:00
parent 364909cfa3
commit 4d93381124
4 changed files with 45 additions and 12 deletions

View file

@ -69,6 +69,7 @@ public class Config {
private File coreDataDir; private File coreDataDir;
private String coreAuth; private String coreAuth;
private boolean useLegacyCoreWallet; private boolean useLegacyCoreWallet;
private boolean legacyServer;
private Server electrumServer; private Server electrumServer;
private List<Server> recentElectrumServers; private List<Server> recentElectrumServers;
private File electrumServerCert; private File electrumServerCert;
@ -549,6 +550,15 @@ public class Config {
flush(); flush();
} }
public boolean isLegacyServer() {
return legacyServer;
}
public void setLegacyServer(boolean legacyServer) {
this.legacyServer = legacyServer;
flush();
}
public Server getElectrumServer() { public Server getElectrumServer() {
return electrumServer; return electrumServer;
} }

View file

@ -37,7 +37,8 @@ public class ElectrumPersonalServer implements WalletExport {
try { try {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
writer.write("# Electrum Personal Server configuration file fragments\n"); 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"); writer.write("# Copy into [master-public-keys] section\n");
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
writeWalletXpub(masterWallet, writer); writeWalletXpub(masterWallet, writer);

View file

@ -76,7 +76,7 @@ public class ElectrumServer {
private static final Set<String> sameHeightTxioScriptHashes = ConcurrentHashMap.newKeySet(); private static final Set<String> sameHeightTxioScriptHashes = ConcurrentHashMap.newKeySet();
private final static Set<String> subscribedRecent = ConcurrentHashMap.newKeySet(); private final static Map<String, Integer> subscribedRecent = new ConcurrentHashMap<>();
private final static Map<String, String> broadcastRecent = new ConcurrentHashMap<>(); private final static Map<String, String> broadcastRecent = new ConcurrentHashMap<>();
@ -1255,7 +1255,7 @@ public class ElectrumServer {
if(!serverVersion.isEmpty()) { if(!serverVersion.isEmpty()) {
String server = serverVersion.getFirst().toLowerCase(Locale.ROOT); String server = serverVersion.getFirst().toLowerCase(Locale.ROOT);
if(server.contains("electrumx")) { if(server.contains("electrumx")) {
return new ServerCapability(true, false); return new ServerCapability(true, true);
} }
if(server.startsWith("cormorant")) { if(server.startsWith("cormorant")) {
@ -1312,6 +1312,10 @@ public class ElectrumServer {
//ignore //ignore
} }
} }
if(server.startsWith("electrumpersonalserver")) {
return new ServerCapability(false, false);
}
} }
return new ServerCapability(false, true); return new ServerCapability(false, true);
@ -1626,7 +1630,7 @@ public class ElectrumServer {
try { try {
electrumServerRpc.subscribeScriptHashes(transport, null, subscribeScriptHashes); electrumServerRpc.subscribeScriptHashes(transport, null, subscribeScriptHashes);
subscribedRecent.addAll(subscribeScriptHashes.values()); subscribeScriptHashes.values().forEach(scriptHash -> subscribedRecent.put(scriptHash, AppServices.getCurrentBlockHeight()));
} catch(ElectrumServerRpcException e) { } catch(ElectrumServerRpcException e) {
log.debug("Error subscribing to recent mempool transaction outputs", e); log.debug("Error subscribing to recent mempool transaction outputs", e);
} }
@ -2032,7 +2036,7 @@ public class ElectrumServer {
Config config = Config.get(); Config config = Config.get();
if(!isBlockstorm(totalBlocks) && !AppServices.isUsingProxy() && config.getServer().getProtocol().equals(Protocol.SSL) if(!isBlockstorm(totalBlocks) && !AppServices.isUsingProxy() && config.getServer().getProtocol().equals(Protocol.SSL)
&& (config.getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER || config.getServerType() == ServerType.ELECTRUM_SERVER)) { && (config.getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER || config.getServerType() == ServerType.ELECTRUM_SERVER)) {
subscribeRecent(electrumServer); subscribeRecent(electrumServer, AppServices.getCurrentBlockHeight() == null ? endHeight : AppServices.getCurrentBlockHeight());
} }
Double nextBlockMedianFeeRate = null; Double nextBlockMedianFeeRate = null;
@ -2048,13 +2052,14 @@ public class ElectrumServer {
return Network.get() != Network.MAINNET && totalBlocks > 2; return Network.get() != Network.MAINNET && totalBlocks > 2;
} }
private void subscribeRecent(ElectrumServer electrumServer) { private void subscribeRecent(ElectrumServer electrumServer, int currentHeight) {
Set<String> unsubscribeScriptHashes = new HashSet<>(subscribedRecent); Set<String> unsubscribeScriptHashes = subscribedRecent.entrySet().stream().filter(entry -> entry.getValue() == null || entry.getValue() <= currentHeight - 3)
.map(Map.Entry::getKey).collect(Collectors.toSet());
unsubscribeScriptHashes.removeIf(subscribedScriptHashes::containsKey); unsubscribeScriptHashes.removeIf(subscribedScriptHashes::containsKey);
if(!unsubscribeScriptHashes.isEmpty() && serverCapability.supportsUnsubscribe()) { if(!unsubscribeScriptHashes.isEmpty() && serverCapability.supportsUnsubscribe()) {
electrumServerRpc.unsubscribeScriptHashes(transport, unsubscribeScriptHashes); electrumServerRpc.unsubscribeScriptHashes(transport, unsubscribeScriptHashes);
} }
subscribedRecent.removeAll(unsubscribeScriptHashes); subscribedRecent.keySet().removeAll(unsubscribeScriptHashes);
broadcastRecent.clear(); broadcastRecent.clear();
Map<String, String> subscribeScriptHashes = new HashMap<>(); Map<String, String> subscribeScriptHashes = new HashMap<>();
@ -2074,7 +2079,7 @@ public class ElectrumServer {
if(!subscribeScriptHashes.isEmpty()) { if(!subscribeScriptHashes.isEmpty()) {
Random random = new Random(); Random random = new Random();
int additionalRandomScriptHashes = random.nextInt(4) + 4; int additionalRandomScriptHashes = random.nextInt(8);
for(int i = 0; i < additionalRandomScriptHashes; i++) { for(int i = 0; i < additionalRandomScriptHashes; i++) {
byte[] randomScriptHashBytes = new byte[32]; byte[] randomScriptHashBytes = new byte[32];
random.nextBytes(randomScriptHashBytes); random.nextBytes(randomScriptHashBytes);
@ -2086,7 +2091,7 @@ public class ElectrumServer {
try { try {
electrumServerRpc.subscribeScriptHashes(transport, null, subscribeScriptHashes); electrumServerRpc.subscribeScriptHashes(transport, null, subscribeScriptHashes);
subscribedRecent.addAll(subscribeScriptHashes.values()); subscribeScriptHashes.values().forEach(scriptHash -> subscribedRecent.put(scriptHash, currentHeight));
} catch(ElectrumServerRpcException e) { } catch(ElectrumServerRpcException e) {
log.debug("Error subscribing to recent mempool transactions", e); log.debug("Error subscribing to recent mempool transactions", e);
} }

View file

@ -10,6 +10,7 @@ import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent; import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent;
import com.sparrowwallet.sparrow.io.Config;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -38,16 +39,32 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
@Override @Override
public List<String> getServerVersion(Transport transport, String clientName, String[] supportedVersions) { public List<String> getServerVersion(Transport transport, String clientName, String[] supportedVersions) {
if(Config.get().isLegacyServer()) {
return getLegacyServerVersion(transport, clientName);
}
try { try {
JsonRpcClient client = new JsonRpcClient(transport); 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<List<String>>(MAX_RETRIES, RETRY_DELAY, IllegalStateException.class).getResult(() -> return new RetryLogic<List<String>>(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) { } catch(Exception e) {
throw new ElectrumServerRpcException("Error getting server version", e); throw new ElectrumServerRpcException("Error getting server version", e);
} }
} }
private List<String> 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<List<String>>(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 @Override
public String getServerBanner(Transport transport) { public String getServerBanner(Transport transport) {
try { try {