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 String coreAuth;
private boolean useLegacyCoreWallet;
private boolean legacyServer;
private Server electrumServer;
private List<Server> 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;
}

View file

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

View file

@ -76,7 +76,7 @@ public class ElectrumServer {
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<>();
@ -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<String> unsubscribeScriptHashes = new HashSet<>(subscribedRecent);
private void subscribeRecent(ElectrumServer electrumServer, int currentHeight) {
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);
if(!unsubscribeScriptHashes.isEmpty() && serverCapability.supportsUnsubscribe()) {
electrumServerRpc.unsubscribeScriptHashes(transport, unsubscribeScriptHashes);
}
subscribedRecent.removeAll(unsubscribeScriptHashes);
subscribedRecent.keySet().removeAll(unsubscribeScriptHashes);
broadcastRecent.clear();
Map<String, String> 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);
}

View file

@ -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<String> 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<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) {
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
public String getServerBanner(Transport transport) {
try {