refactor rpc elements out

This commit is contained in:
Craig Raw 2020-08-10 16:56:34 +02:00
parent bb635a6c68
commit 79c2e29e34
8 changed files with 296 additions and 110 deletions

2
drongo

@ -1 +1 @@
Subproject commit fff658a3ab33a3f63f5a1cd03c2b7cc1f20bec4a Subproject commit 6a2af38b8a628f36b75c11626b0a8fd608e9d5a0

View file

@ -123,7 +123,8 @@ public class TransactionHexArea extends CodeArea {
cursor = addSegment(segments, cursor, 8, "locktime"); cursor = addSegment(segments, cursor, 8, "locktime");
if(cursor != getLength()) { if(cursor != getLength()) {
throw new IllegalStateException("Cursor position does not match transaction serialisation " + cursor + ": " + getLength()); //While this is normally a good sanity check, the truncation applied means it may fail, so it is left commented out
//throw new IllegalStateException("Cursor position does not match transaction serialisation " + cursor + ": " + getLength());
} }
return segments; return segments;

View file

@ -0,0 +1,205 @@
package com.sparrowwallet.sparrow.net;
import com.github.arteam.simplejsonrpc.client.JsonRpcClient;
import com.github.arteam.simplejsonrpc.client.Transport;
import com.github.arteam.simplejsonrpc.client.builder.BatchRequestBuilder;
import com.github.arteam.simplejsonrpc.client.exception.JsonRpcBatchException;
import com.github.arteam.simplejsonrpc.client.exception.JsonRpcException;
import com.sparrowwallet.drongo.protocol.Sha256Hash;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class BatchedElectrumServerRpc implements ElectrumServerRpc {
private static final Logger log = LoggerFactory.getLogger(BatchedElectrumServerRpc.class);
@Override
public void ping(Transport transport) {
try {
JsonRpcClient client = new JsonRpcClient(transport);
client.createRequest().method("server.ping").id(1).executeNullable();
} catch(JsonRpcException e) {
throw new ElectrumServerRpcException("Error pinging server", e);
}
}
@Override
public List<String> getServerVersion(Transport transport, String clientName, String[] supportedVersions) {
try {
JsonRpcClient client = new JsonRpcClient(transport);
return client.createRequest().returnAsList(String.class).method("server.version").id(1).param("client_name", "Sparrow").param("protocol_version", supportedVersions).execute();
} catch(JsonRpcException e) {
throw new ElectrumServerRpcException("Error getting server version", e);
}
}
@Override
public String getServerBanner(Transport transport) {
try {
JsonRpcClient client = new JsonRpcClient(transport);
return client.createRequest().returnAs(String.class).method("server.banner").id(1).execute();
} catch(JsonRpcException e) {
throw new ElectrumServerRpcException("Error getting server banner", e);
}
}
@Override
public BlockHeaderTip subscribeBlockHeaders(Transport transport) {
try {
JsonRpcClient client = new JsonRpcClient(transport);
return client.createRequest().returnAs(BlockHeaderTip.class).method("blockchain.headers.subscribe").id(1).execute();
} catch(JsonRpcException e) {
throw new ElectrumServerRpcException("Error subscribing to block headers", e);
}
}
@Override
@SuppressWarnings("unchecked")
public Map<String, ScriptHashTx[]> getScriptHashHistory(Transport transport, Map<String, String> pathScriptHashes, boolean failOnError) {
JsonRpcClient client = new JsonRpcClient(transport);
BatchRequestBuilder<String, ScriptHashTx[]> batchRequest = client.createBatchRequest().keysType(String.class).returnType(ScriptHashTx[].class);
for(String path : pathScriptHashes.keySet()) {
batchRequest.add(path, "blockchain.scripthash.get_history", pathScriptHashes.get(path));
}
try {
return batchRequest.execute();
} catch (JsonRpcBatchException e) {
if(failOnError) {
throw new ElectrumServerRpcException("Failed to retrieve references for paths: " + e.getErrors().keySet(), e);
}
Map<String, ScriptHashTx[]> result = (Map<String, ScriptHashTx[]>)e.getSuccesses();
for(Object key : e.getErrors().keySet()) {
result.put((String)key, new ScriptHashTx[] {ScriptHashTx.ERROR_TX});
}
return result;
}
}
@Override
@SuppressWarnings("unchecked")
public Map<String, ScriptHashTx[]> getScriptHashMempool(Transport transport, Map<String, String> pathScriptHashes, boolean failOnError) {
JsonRpcClient client = new JsonRpcClient(transport);
BatchRequestBuilder<String, ScriptHashTx[]> batchRequest = client.createBatchRequest().keysType(String.class).returnType(ScriptHashTx[].class);
for(String path : pathScriptHashes.keySet()) {
batchRequest.add(path, "blockchain.scripthash.get_mempool", pathScriptHashes.get(path));
}
try {
return batchRequest.execute();
} catch (JsonRpcBatchException e) {
if(failOnError) {
throw new ElectrumServerRpcException("Failed to retrieve references for paths: " + e.getErrors().keySet(), e);
}
Map<String, ScriptHashTx[]> result = (Map<String, ScriptHashTx[]>)e.getSuccesses();
for(Object key : e.getErrors().keySet()) {
result.put((String)key, new ScriptHashTx[] {ScriptHashTx.ERROR_TX});
}
return result;
}
}
@Override
public Map<String, String> subscribeScriptHashes(Transport transport, Map<String, String> pathScriptHashes) {
JsonRpcClient client = new JsonRpcClient(transport);
BatchRequestBuilder<String, String> batchRequest = client.createBatchRequest().keysType(String.class).returnType(String.class);
for(String path : pathScriptHashes.keySet()) {
batchRequest.add(path, "blockchain.scripthash.subscribe", pathScriptHashes.get(path));
}
try {
return 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 ElectrumServerRpcException("Failed to subscribe for updates for paths: " + e.getErrors().keySet(), e);
}
}
@Override
@SuppressWarnings("unchecked")
public Map<Integer, String> getBlockHeaders(Transport transport, Set<Integer> blockHeights) {
JsonRpcClient client = new JsonRpcClient(transport);
BatchRequestBuilder<Integer, String> batchRequest = client.createBatchRequest().keysType(Integer.class).returnType(String.class);
for(Integer height : blockHeights) {
batchRequest.add(height, "blockchain.block.header", height);
}
try {
return batchRequest.execute();
} catch (JsonRpcBatchException e) {
return (Map<Integer, String>)e.getSuccesses();
}
}
@Override
@SuppressWarnings("unchecked")
public Map<String, String> getTransactions(Transport transport, Set<String> txids) {
JsonRpcClient client = new JsonRpcClient(transport);
BatchRequestBuilder<String, String> batchRequest = client.createBatchRequest().keysType(String.class).returnType(String.class);
for(String txid : txids) {
batchRequest.add(txid, "blockchain.transaction.get", txid);
}
try {
return batchRequest.execute();
} catch (JsonRpcBatchException e) {
Map<String, String> result = (Map<String, String>)e.getSuccesses();
String strErrorTx = Sha256Hash.ZERO_HASH.toString();
for(Object hash : e.getErrors().keySet()) {
String txhash = (String)hash;
result.put(txhash, strErrorTx);
}
return result;
}
}
@Override
@SuppressWarnings("unchecked")
public Map<String, VerboseTransaction> getVerboseTransactions(Transport transport, Set<String> txids) {
JsonRpcClient client = new JsonRpcClient(transport);
BatchRequestBuilder<String, VerboseTransaction> batchRequest = client.createBatchRequest().keysType(String.class).returnType(VerboseTransaction.class);
for(String txid : txids) {
batchRequest.add(txid, "blockchain.transaction.get", txid, true);
}
try {
return batchRequest.execute();
} catch (JsonRpcBatchException e) {
log.warn("Some errors retrieving transactions: " + e.getErrors());
return (Map<String, VerboseTransaction>)e.getSuccesses();
}
}
@Override
public Map<Integer, Double> getFeeEstimates(Transport transport, List<Integer> targetBlocks) {
JsonRpcClient client = new JsonRpcClient(transport);
BatchRequestBuilder<Integer, Double> batchRequest = client.createBatchRequest().keysType(Integer.class).returnType(Double.class);
for(Integer targetBlock : targetBlocks) {
batchRequest.add(targetBlock, "blockchain.estimatefee", targetBlock);
}
return batchRequest.execute();
}
@Override
public String broadcastTransaction(Transport transport, String txHex) {
try {
JsonRpcClient client = new JsonRpcClient(transport);
return client.createRequest().returnAs(String.class).method("blockchain.transaction.broadcast").id(1).param("raw_tx", txHex).execute();
} catch(JsonRpcException e) {
throw new ElectrumServerRpcException(e.getErrorMessage().getMessage(), e);
}
}
}

View file

@ -1,9 +1,6 @@
package com.sparrowwallet.sparrow.net; package com.sparrowwallet.sparrow.net;
import com.github.arteam.simplejsonrpc.client.*; import com.github.arteam.simplejsonrpc.client.Transport;
import com.github.arteam.simplejsonrpc.client.builder.BatchRequestBuilder;
import com.github.arteam.simplejsonrpc.client.exception.JsonRpcBatchException;
import com.github.arteam.simplejsonrpc.client.exception.JsonRpcException;
import com.google.common.net.HostAndPort; import com.google.common.net.HostAndPort;
import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
@ -12,7 +9,6 @@ import com.sparrowwallet.drongo.wallet.*;
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.io.Config; import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.ServerException;
import com.sparrowwallet.sparrow.wallet.SendController; import com.sparrowwallet.sparrow.wallet.SendController;
import javafx.concurrent.ScheduledService; import javafx.concurrent.ScheduledService;
import javafx.concurrent.Service; import javafx.concurrent.Service;
@ -35,6 +31,8 @@ public class ElectrumServer {
private static final Map<String, String> subscribedScriptHashes = Collections.synchronizedMap(new HashMap<>()); private static final Map<String, String> subscribedScriptHashes = Collections.synchronizedMap(new HashMap<>());
private ElectrumServerRpc electrumServerRpc = new BatchedElectrumServerRpc();
private static synchronized Transport getTransport() throws ServerException { private static synchronized Transport getTransport() throws ServerException {
if(transport == null) { if(transport == null) {
try { try {
@ -85,24 +83,20 @@ public class ElectrumServer {
} }
public void ping() throws ServerException { public void ping() throws ServerException {
JsonRpcClient client = new JsonRpcClient(getTransport()); electrumServerRpc.ping(getTransport());
client.createRequest().method("server.ping").id(1).executeNullable();
} }
public List<String> getServerVersion() throws ServerException { public List<String> getServerVersion() throws ServerException {
JsonRpcClient client = new JsonRpcClient(getTransport()); return electrumServerRpc.getServerVersion(getTransport(), "Sparrow", SUPPORTED_VERSIONS);
//return client.createRequest().returnAsList(String.class).method("server.version").id(1).params("Sparrow", "1.4").execute(); //return client.createRequest().returnAsList(String.class).method("server.version").id(1).params("Sparrow", "1.4").execute();
return client.createRequest().returnAsList(String.class).method("server.version").id(1).param("client_name", "Sparrow").param("protocol_version", SUPPORTED_VERSIONS).execute();
} }
public String getServerBanner() throws ServerException { public String getServerBanner() throws ServerException {
JsonRpcClient client = new JsonRpcClient(getTransport()); return electrumServerRpc.getServerBanner(getTransport());
return client.createRequest().returnAs(String.class).method("server.banner").id(1).execute();
} }
public BlockHeaderTip subscribeBlockHeaders() throws ServerException { public BlockHeaderTip subscribeBlockHeaders() throws ServerException {
JsonRpcClient client = new JsonRpcClient(getTransport()); return electrumServerRpc.subscribeBlockHeaders(getTransport());
return client.createRequest().returnAs(BlockHeaderTip.class).method("blockchain.headers.subscribe").id(1).execute();
} }
public static synchronized boolean isConnected() { public static synchronized boolean isConnected() {
@ -169,22 +163,15 @@ public class ElectrumServer {
public void getReferences(Wallet wallet, String method, Collection<WalletNode> nodes, Map<WalletNode, Set<BlockTransactionHash>> nodeTransactionMap, int startIndex) throws ServerException { public void getReferences(Wallet wallet, String method, Collection<WalletNode> nodes, Map<WalletNode, Set<BlockTransactionHash>> nodeTransactionMap, int startIndex) throws ServerException {
try { try {
JsonRpcClient client = new JsonRpcClient(getTransport()); Map<String, String> pathScriptHashes = new LinkedHashMap<>(nodes.size());
BatchRequestBuilder<String, ScriptHashTx[]> batchRequest = client.createBatchRequest().keysType(String.class).returnType(ScriptHashTx[].class);
for(WalletNode node : nodes) { for(WalletNode node : nodes) {
if(node.getIndex() >= startIndex) { if(node.getIndex() >= startIndex) {
batchRequest.add(node.getDerivationPath(), method, getScriptHash(wallet, node)); pathScriptHashes.put(node.getDerivationPath(), getScriptHash(wallet, node));
} }
} }
Map<String, ScriptHashTx[]> result; //Even if we have some successes, failure to retrieve all references will result in an incomplete wallet history. Don't proceed if that's the case.
try { Map<String, ScriptHashTx[]> result = electrumServerRpc.getScriptHashHistory(getTransport(), pathScriptHashes, true);
result = batchRequest.execute();
} catch (JsonRpcBatchException e) {
//Even if we have some successes, failure to retrieve all references will result in an incomplete wallet history. Don't proceed.
throw new IllegalStateException("Failed to retrieve references for paths: " + e.getErrors().keySet());
}
for(String path : result.keySet()) { for(String path : result.keySet()) {
ScriptHashTx[] txes = result.get(path); ScriptHashTx[] txes = result.get(path);
@ -214,8 +201,8 @@ public class ElectrumServer {
} }
} }
} }
} catch (IllegalStateException e) { } catch (ElectrumServerRpcException e) {
throw new ServerException(e.getCause()); throw new ServerException(e.getMessage(), e.getCause());
} catch (Exception e) { } catch (Exception e) {
throw new ServerException(e); throw new ServerException(e);
} }
@ -223,30 +210,22 @@ public class ElectrumServer {
public void subscribeWalletNodes(Wallet wallet, Collection<WalletNode> nodes, int startIndex) throws ServerException { public void subscribeWalletNodes(Wallet wallet, Collection<WalletNode> nodes, int startIndex) throws ServerException {
try { try {
JsonRpcClient client = new JsonRpcClient(getTransport());
BatchRequestBuilder<String, String> batchRequest = client.createBatchRequest().keysType(String.class).returnType(String.class);
Set<String> scriptHashes = new HashSet<>(); Set<String> scriptHashes = new HashSet<>();
Map<String, String> pathScriptHashes = new LinkedHashMap<>();
for(WalletNode node : nodes) { for(WalletNode node : nodes) {
if(node.getIndex() >= startIndex) { if(node.getIndex() >= startIndex) {
String scriptHash = getScriptHash(wallet, node); String scriptHash = getScriptHash(wallet, node);
if(!subscribedScriptHashes.containsKey(scriptHash) && scriptHashes.add(scriptHash)) { if(!subscribedScriptHashes.containsKey(scriptHash) && scriptHashes.add(scriptHash)) {
batchRequest.add(node.getDerivationPath(), "blockchain.scripthash.subscribe", scriptHash); pathScriptHashes.put(node.getDerivationPath(), scriptHash);
} }
} }
} }
if(scriptHashes.isEmpty()) { if(pathScriptHashes.isEmpty()) {
return; return;
} }
Map<String, String> result; Map<String, String> result = electrumServerRpc.subscribeScriptHashes(getTransport(), pathScriptHashes);
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()) { for(String path : result.keySet()) {
String status = result.get(path); String status = result.get(path);
@ -257,40 +236,29 @@ public class ElectrumServer {
subscribedScriptHashes.put(getScriptHash(wallet, node), status); subscribedScriptHashes.put(getScriptHash(wallet, node), status);
} }
} }
} catch (IllegalStateException e) { } catch (ElectrumServerRpcException e) {
throw new ServerException(e.getCause()); throw new ServerException(e.getMessage(), e.getCause());
} catch (Exception e) { } catch (Exception e) {
throw new ServerException(e); throw new ServerException(e);
} }
} }
@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 {
JsonRpcClient client = new JsonRpcClient(getTransport()); Map<String, String> pathScriptHashes = new LinkedHashMap<>();
BatchRequestBuilder<Integer, ScriptHashTx[]> batchRequest = client.createBatchRequest().keysType(Integer.class).returnType(ScriptHashTx[].class);
for(int i = indexStart; i < transaction.getOutputs().size() && i < indexEnd; i++) { for(int i = indexStart; i < transaction.getOutputs().size() && i < indexEnd; i++) {
TransactionOutput output = transaction.getOutputs().get(i); TransactionOutput output = transaction.getOutputs().get(i);
batchRequest.add(i, "blockchain.scripthash.get_history", getScriptHash(output)); pathScriptHashes.put(Integer.toString(i), getScriptHash(output));
} }
Map<Integer, ScriptHashTx[]> result; Map<String, ScriptHashTx[]> result = electrumServerRpc.getScriptHashHistory(getTransport(), pathScriptHashes, false);
try {
result = batchRequest.execute();
} catch (JsonRpcBatchException e) {
result = (Map<Integer, ScriptHashTx[]>)e.getSuccesses();
for(Object index : e.getErrors().keySet()) {
Integer i = (Integer)index;
result.put(i, new ScriptHashTx[] {ScriptHashTx.ERROR_TX});
}
}
List<Set<BlockTransactionHash>> blockTransactionHashes = new ArrayList<>(transaction.getOutputs().size()); List<Set<BlockTransactionHash>> blockTransactionHashes = new ArrayList<>(transaction.getOutputs().size());
for(int i = 0; i < transaction.getOutputs().size(); i++) { for(int i = 0; i < transaction.getOutputs().size(); i++) {
blockTransactionHashes.add(null); blockTransactionHashes.add(null);
} }
for(Integer index : result.keySet()) { for(String index : result.keySet()) {
ScriptHashTx[] txes = result.get(index); ScriptHashTx[] txes = result.get(index);
int txBlockHeight = 0; int txBlockHeight = 0;
@ -308,7 +276,7 @@ public class ElectrumServer {
.filter(ref -> !ref.getHash().equals(transaction.getTxId()) && ref.getHeight() >= minBlockHeight) .filter(ref -> !ref.getHash().equals(transaction.getTxId()) && ref.getHeight() >= minBlockHeight)
.collect(Collectors.toCollection(TreeSet::new)); .collect(Collectors.toCollection(TreeSet::new));
blockTransactionHashes.set(index, references); blockTransactionHashes.set(Integer.parseInt(index), references);
} }
return blockTransactionHashes; return blockTransactionHashes;
@ -336,7 +304,6 @@ public class ElectrumServer {
} }
} }
@SuppressWarnings("unchecked")
public Map<Integer, BlockHeader> getBlockHeaders(Set<BlockTransactionHash> references) throws ServerException { public Map<Integer, BlockHeader> getBlockHeaders(Set<BlockTransactionHash> references) throws ServerException {
try { try {
Set<Integer> blockHeights = new TreeSet<>(); Set<Integer> blockHeights = new TreeSet<>();
@ -350,18 +317,7 @@ public class ElectrumServer {
return Collections.emptyMap(); return Collections.emptyMap();
} }
JsonRpcClient client = new JsonRpcClient(getTransport()); Map<Integer, String> result = electrumServerRpc.getBlockHeaders(getTransport(), blockHeights);
BatchRequestBuilder<Integer, String> batchRequest = client.createBatchRequest().keysType(Integer.class).returnType(String.class);
for(Integer height : blockHeights) {
batchRequest.add(height, "blockchain.block.header", height);
}
Map<Integer, String> result;
try {
result = batchRequest.execute();
} catch (JsonRpcBatchException e) {
result = (Map<Integer, String>)e.getSuccesses();
}
Map<Integer, BlockHeader> blockHeaderMap = new TreeMap<>(); Map<Integer, BlockHeader> blockHeaderMap = new TreeMap<>();
for(Integer height : result.keySet()) { for(Integer height : result.keySet()) {
@ -383,29 +339,18 @@ public class ElectrumServer {
} }
} }
@SuppressWarnings("unchecked")
public Map<Sha256Hash, BlockTransaction> getTransactions(Set<BlockTransactionHash> references, Map<Integer, BlockHeader> blockHeaderMap) throws ServerException { public Map<Sha256Hash, BlockTransaction> getTransactions(Set<BlockTransactionHash> references, Map<Integer, BlockHeader> blockHeaderMap) throws ServerException {
try { try {
Set<BlockTransactionHash> checkReferences = new TreeSet<>(references); Set<BlockTransactionHash> checkReferences = new TreeSet<>(references);
JsonRpcClient client = new JsonRpcClient(getTransport()); Set<String> txids = new LinkedHashSet<>(references.size());
BatchRequestBuilder<String, String> batchRequest = client.createBatchRequest().keysType(String.class).returnType(String.class);
for(BlockTransactionHash reference : references) { for(BlockTransactionHash reference : references) {
batchRequest.add(reference.getHashAsString(), "blockchain.transaction.get", reference.getHashAsString()); txids.add(reference.getHashAsString());
} }
Map<String, String> result = electrumServerRpc.getTransactions(getTransport(), txids);
String strErrorTx = Sha256Hash.ZERO_HASH.toString(); String strErrorTx = Sha256Hash.ZERO_HASH.toString();
Map<String, String> result;
try {
result = batchRequest.execute();
} catch (JsonRpcBatchException e) {
result = (Map<String, String>)e.getSuccesses();
for(Object hash : e.getErrors().keySet()) {
String txhash = (String)hash;
result.put(txhash, strErrorTx);
}
}
Map<Sha256Hash, BlockTransaction> transactionMap = new HashMap<>(); Map<Sha256Hash, BlockTransaction> transactionMap = new HashMap<>();
for(String txid : result.keySet()) { for(String txid : result.keySet()) {
Sha256Hash hash = Sha256Hash.wrap(txid); Sha256Hash hash = Sha256Hash.wrap(txid);
@ -537,19 +482,12 @@ public class ElectrumServer {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Map<Sha256Hash, BlockTransaction> getReferencedTransactions(Set<Sha256Hash> references) throws ServerException { public Map<Sha256Hash, BlockTransaction> getReferencedTransactions(Set<Sha256Hash> references) throws ServerException {
JsonRpcClient client = new JsonRpcClient(getTransport()); Set<String> txids = new LinkedHashSet<>(references.size());
BatchRequestBuilder<String, VerboseTransaction> batchRequest = client.createBatchRequest().keysType(String.class).returnType(VerboseTransaction.class);
for(Sha256Hash reference : references) { for(Sha256Hash reference : references) {
batchRequest.add(reference.toString(), "blockchain.transaction.get", reference.toString(), true); txids.add(reference.toString());
} }
Map<String, VerboseTransaction> result; Map<String, VerboseTransaction> result = electrumServerRpc.getVerboseTransactions(getTransport(), txids);
try {
result = batchRequest.execute();
} catch (JsonRpcBatchException e) {
log.warn("Some errors retrieving transactions: " + e.getErrors());
result = (Map<String, VerboseTransaction>)e.getSuccesses();
}
Map<Sha256Hash, BlockTransaction> transactionMap = new HashMap<>(); Map<Sha256Hash, BlockTransaction> transactionMap = new HashMap<>();
for(String txid : result.keySet()) { for(String txid : result.keySet()) {
@ -562,13 +500,7 @@ public class ElectrumServer {
} }
public Map<Integer, Double> getFeeEstimates(List<Integer> targetBlocks) throws ServerException { public Map<Integer, Double> getFeeEstimates(List<Integer> targetBlocks) throws ServerException {
JsonRpcClient client = new JsonRpcClient(getTransport()); Map<Integer, Double> targetBlocksFeeRatesBtcKb = electrumServerRpc.getFeeEstimates(getTransport(), targetBlocks);
BatchRequestBuilder<Integer, Double> batchRequest = client.createBatchRequest().keysType(Integer.class).returnType(Double.class);
for(Integer targetBlock : targetBlocks) {
batchRequest.add(targetBlock, "blockchain.estimatefee", targetBlock);
}
Map<Integer, Double> targetBlocksFeeRatesBtcKb = batchRequest.execute();
Map<Integer, Double> targetBlocksFeeRatesSats = new TreeMap<>(); Map<Integer, Double> targetBlocksFeeRatesSats = new TreeMap<>();
for(Integer target : targetBlocksFeeRatesBtcKb.keySet()) { for(Integer target : targetBlocksFeeRatesBtcKb.keySet()) {
@ -582,18 +514,15 @@ public class ElectrumServer {
byte[] rawtxBytes = transaction.bitcoinSerialize(); byte[] rawtxBytes = transaction.bitcoinSerialize();
String rawtxHex = Utils.bytesToHex(rawtxBytes); String rawtxHex = Utils.bytesToHex(rawtxBytes);
JsonRpcClient client = new JsonRpcClient(getTransport());
try { try {
String strTxHash = client.createRequest().returnAs(String.class).method("blockchain.transaction.broadcast").id(1).param("raw_tx", rawtxHex).execute(); String strTxHash = electrumServerRpc.broadcastTransaction(getTransport(), rawtxHex);
Sha256Hash receivedTxid = Sha256Hash.wrap(strTxHash); Sha256Hash receivedTxid = Sha256Hash.wrap(strTxHash);
if(!receivedTxid.equals(transaction.getTxId())) { if(!receivedTxid.equals(transaction.getTxId())) {
throw new ServerException("Received txid was different (" + receivedTxid + ")"); throw new ServerException("Received txid was different (" + receivedTxid + ")");
} }
return receivedTxid; return receivedTxid;
} catch(JsonRpcException e) { } catch(ElectrumServerRpcException | IllegalStateException e) {
throw new ServerException(e.getErrorMessage().getMessage());
} catch(IllegalStateException e) {
throw new ServerException(e.getMessage()); throw new ServerException(e.getMessage());
} }
} }

View file

@ -0,0 +1,33 @@
package com.sparrowwallet.sparrow.net;
import com.github.arteam.simplejsonrpc.client.Transport;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface ElectrumServerRpc {
void ping(Transport transport);
List<String> getServerVersion(Transport transport, String clientName, String[] supportedVersions);
String getServerBanner(Transport transport);
BlockHeaderTip subscribeBlockHeaders(Transport transport);
Map<String, ScriptHashTx[]> getScriptHashHistory(Transport transport, Map<String, String> pathScriptHashes, boolean failOnError);
Map<String, ScriptHashTx[]> getScriptHashMempool(Transport transport, Map<String, String> pathScriptHashes, boolean failOnError);
Map<String, String> subscribeScriptHashes(Transport transport, Map<String, String> pathScriptHashes);
Map<Integer, String> getBlockHeaders(Transport transport, Set<Integer> blockHeights);
Map<String, String> getTransactions(Transport transport, Set<String> txids);
Map<String, VerboseTransaction> getVerboseTransactions(Transport transport, Set<String> txids);
Map<Integer, Double> getFeeEstimates(Transport transport, List<Integer> targetBlocks);
String broadcastTransaction(Transport transport, String txHex);
}

View file

@ -0,0 +1,19 @@
package com.sparrowwallet.sparrow.net;
public class ElectrumServerRpcException extends RuntimeException {
public ElectrumServerRpcException() {
super();
}
public ElectrumServerRpcException(String message) {
super(message);
}
public ElectrumServerRpcException(Throwable cause) {
super(cause);
}
public ElectrumServerRpcException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -1,4 +1,4 @@
package com.sparrowwallet.sparrow.io; package com.sparrowwallet.sparrow.net;
public class ServerException extends Exception { public class ServerException extends Exception {
public ServerException() { public ServerException() {

View file

@ -4,7 +4,6 @@ import com.github.arteam.simplejsonrpc.client.Transport;
import com.github.arteam.simplejsonrpc.server.JsonRpcServer; import com.github.arteam.simplejsonrpc.server.JsonRpcServer;
import com.google.common.net.HostAndPort; import com.google.common.net.HostAndPort;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.ServerException;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.net.SocketFactory; import javax.net.SocketFactory;