|
@ -18,6 +18,7 @@ import com.sparrowwallet.sparrow.EventManager; |
|
|
import com.sparrowwallet.sparrow.event.ConnectionEvent; |
|
|
import com.sparrowwallet.sparrow.event.ConnectionEvent; |
|
|
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent; |
|
|
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent; |
|
|
import com.sparrowwallet.sparrow.event.NewBlockEvent; |
|
|
import com.sparrowwallet.sparrow.event.NewBlockEvent; |
|
|
|
|
|
import com.sparrowwallet.sparrow.event.WalletNodeHistoryChangedEvent; |
|
|
import com.sparrowwallet.sparrow.wallet.SendController; |
|
|
import com.sparrowwallet.sparrow.wallet.SendController; |
|
|
import javafx.application.Platform; |
|
|
import javafx.application.Platform; |
|
|
import javafx.concurrent.ScheduledService; |
|
|
import javafx.concurrent.ScheduledService; |
|
@ -37,6 +38,7 @@ import java.security.cert.CertificateException; |
|
|
import java.security.cert.CertificateFactory; |
|
|
import java.security.cert.CertificateFactory; |
|
|
import java.security.cert.X509Certificate; |
|
|
import java.security.cert.X509Certificate; |
|
|
import java.util.*; |
|
|
import java.util.*; |
|
|
|
|
|
import java.util.concurrent.ConcurrentHashMap; |
|
|
import java.util.concurrent.locks.ReentrantLock; |
|
|
import java.util.concurrent.locks.ReentrantLock; |
|
|
import java.util.stream.Collectors; |
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
@ -47,6 +49,8 @@ public class ElectrumServer { |
|
|
|
|
|
|
|
|
private static Transport transport; |
|
|
private static Transport transport; |
|
|
|
|
|
|
|
|
|
|
|
private static final Map<String, String> subscribedScriptHashes = Collections.synchronizedMap(new HashMap<>()); |
|
|
|
|
|
|
|
|
private static synchronized Transport getTransport() throws ServerException { |
|
|
private static synchronized Transport getTransport() throws ServerException { |
|
|
if(transport == null) { |
|
|
if(transport == null) { |
|
|
try { |
|
|
try { |
|
@ -171,6 +175,7 @@ public class ElectrumServer { |
|
|
|
|
|
|
|
|
public void getHistory(Wallet wallet, Collection<WalletNode> nodes, Map<WalletNode, Set<BlockTransactionHash>> nodeTransactionMap, int startIndex) throws ServerException { |
|
|
public void getHistory(Wallet wallet, Collection<WalletNode> nodes, Map<WalletNode, Set<BlockTransactionHash>> nodeTransactionMap, int startIndex) throws ServerException { |
|
|
getReferences(wallet, "blockchain.scripthash.get_history", nodes, nodeTransactionMap, startIndex); |
|
|
getReferences(wallet, "blockchain.scripthash.get_history", nodes, nodeTransactionMap, startIndex); |
|
|
|
|
|
subscribeWalletNodes(wallet, nodes, startIndex); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public void getMempool(Wallet wallet, Collection<WalletNode> nodes, Map<WalletNode, Set<BlockTransactionHash>> nodeTransactionMap, int startIndex) throws ServerException { |
|
|
public void getMempool(Wallet wallet, Collection<WalletNode> nodes, Map<WalletNode, Set<BlockTransactionHash>> nodeTransactionMap, int startIndex) throws ServerException { |
|
@ -231,6 +236,50 @@ public class ElectrumServer { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public void subscribeWalletNodes(Wallet wallet, Collection<WalletNode> nodes, int startIndex) throws ServerException { |
|
|
|
|
|
try { |
|
|
|
|
|
JsonRpcClient client = new JsonRpcClient(getTransport()); |
|
|
|
|
|
BatchRequestBuilder<String, String> batchRequest = client.createBatchRequest().keysType(String.class).returnType(String.class); |
|
|
|
|
|
|
|
|
|
|
|
Set<String> scriptHashes = new HashSet<>(); |
|
|
|
|
|
for(WalletNode node : nodes) { |
|
|
|
|
|
if(node.getIndex() >= startIndex) { |
|
|
|
|
|
String scriptHash = getScriptHash(wallet, node); |
|
|
|
|
|
if(!subscribedScriptHashes.containsKey(scriptHash)) { |
|
|
|
|
|
scriptHashes.add(scriptHash); |
|
|
|
|
|
batchRequest.add(node.getDerivationPath(), "blockchain.scripthash.subscribe", scriptHash); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if(scriptHashes.isEmpty()) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Map<String, String> result; |
|
|
|
|
|
try { |
|
|
|
|
|
result = batchRequest.execute(); |
|
|
|
|
|
} catch(JsonRpcBatchException e) { |
|
|
|
|
|
//Even if we have some successes, failure to subscribe for all script hashes will result in outdated wallet view. Don't proceed.
|
|
|
|
|
|
throw new IllegalStateException("Failed to subscribe for updates for paths: " + e.getErrors().keySet()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for(String path : result.keySet()) { |
|
|
|
|
|
String status = result.get(path); |
|
|
|
|
|
|
|
|
|
|
|
Optional<WalletNode> optionalNode = nodes.stream().filter(n -> n.getDerivationPath().equals(path)).findFirst(); |
|
|
|
|
|
if(optionalNode.isPresent()) { |
|
|
|
|
|
WalletNode node = optionalNode.get(); |
|
|
|
|
|
subscribedScriptHashes.put(getScriptHash(wallet, node), status); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} catch (IllegalStateException e) { |
|
|
|
|
|
throw new ServerException(e.getCause()); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
throw new ServerException(e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
@SuppressWarnings("unchecked") |
|
|
public List<Set<BlockTransactionHash>> getOutputTransactionReferences(Transaction transaction, int indexStart, int indexEnd) throws ServerException { |
|
|
public List<Set<BlockTransactionHash>> getOutputTransactionReferences(Transaction transaction, int indexStart, int indexEnd) throws ServerException { |
|
|
try { |
|
|
try { |
|
@ -545,7 +594,7 @@ public class ElectrumServer { |
|
|
return targetBlocksFeeRatesSats; |
|
|
return targetBlocksFeeRatesSats; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private String getScriptHash(Wallet wallet, WalletNode node) { |
|
|
public static String getScriptHash(Wallet wallet, WalletNode node) { |
|
|
byte[] hash = Sha256Hash.hash(wallet.getOutputScript(node).getProgram()); |
|
|
byte[] hash = Sha256Hash.hash(wallet.getOutputScript(node).getProgram()); |
|
|
byte[] reversed = Utils.reverseBytes(hash); |
|
|
byte[] reversed = Utils.reverseBytes(hash); |
|
|
return Utils.bytesToHex(reversed); |
|
|
return Utils.bytesToHex(reversed); |
|
@ -630,6 +679,16 @@ public class ElectrumServer { |
|
|
public void newBlockHeaderTip(@JsonRpcParam("header") final BlockHeaderTip header) { |
|
|
public void newBlockHeaderTip(@JsonRpcParam("header") final BlockHeaderTip header) { |
|
|
Platform.runLater(() -> EventManager.get().post(new NewBlockEvent(header.height, header.getBlockHeader()))); |
|
|
Platform.runLater(() -> EventManager.get().post(new NewBlockEvent(header.height, header.getBlockHeader()))); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@JsonRpcMethod("blockchain.scripthash.subscribe") |
|
|
|
|
|
public void scriptHashStatusUpdated(@JsonRpcParam("scripthash") final String scriptHash, @JsonRpcParam("status") final String status) { |
|
|
|
|
|
String oldStatus = subscribedScriptHashes.put(scriptHash, status); |
|
|
|
|
|
if(Objects.equals(oldStatus, status)) { |
|
|
|
|
|
System.out.println("Received script hash status update, but status has not changed"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Platform.runLater(() -> EventManager.get().post(new WalletNodeHistoryChangedEvent(scriptHash))); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public static class TcpTransport implements Transport, Closeable { |
|
|
public static class TcpTransport implements Transport, Closeable { |
|
@ -886,6 +945,7 @@ public class ElectrumServer { |
|
|
BlockHeaderTip tip; |
|
|
BlockHeaderTip tip; |
|
|
if(subscribe) { |
|
|
if(subscribe) { |
|
|
tip = electrumServer.subscribeBlockHeaders(); |
|
|
tip = electrumServer.subscribeBlockHeaders(); |
|
|
|
|
|
subscribedScriptHashes.clear(); |
|
|
} else { |
|
|
} else { |
|
|
tip = new BlockHeaderTip(); |
|
|
tip = new BlockHeaderTip(); |
|
|
} |
|
|
} |
|
|