mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2025-11-05 11:56:37 +00:00
cormorant: add block stats rpc call, and prefer for block summaries
This commit is contained in:
parent
94b27ba7e8
commit
b1ab157ee3
10 changed files with 119 additions and 8 deletions
|
|
@ -4,7 +4,6 @@ import com.github.arteam.simplejsonrpc.client.JsonRpcClient;
|
|||
import com.github.arteam.simplejsonrpc.client.Transport;
|
||||
import com.github.arteam.simplejsonrpc.client.exception.JsonRpcBatchException;
|
||||
import com.github.arteam.simplejsonrpc.client.exception.JsonRpcException;
|
||||
import com.github.arteam.simplejsonrpc.core.domain.ErrorMessage;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
|
|
@ -191,6 +190,24 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<Integer, BlockStats> getBlockStats(Transport transport, Set<Integer> blockHeights) {
|
||||
PagedBatchRequestBuilder<Integer, BlockStats> batchRequest = PagedBatchRequestBuilder.create(transport, idCounter).keysType(Integer.class).returnType(BlockStats.class);
|
||||
|
||||
for(Integer height : blockHeights) {
|
||||
batchRequest.add(height, "blockchain.block.stats", height);
|
||||
}
|
||||
|
||||
try {
|
||||
return batchRequest.execute();
|
||||
} catch(JsonRpcBatchException e) {
|
||||
return (Map<Integer, BlockStats>)e.getSuccesses();
|
||||
} catch(Exception e) {
|
||||
throw new ElectrumServerRpcException("Failed to retrieve block stats for block heights: " + blockHeights, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, String> getTransactions(Transport transport, Wallet wallet, Set<String> txids) {
|
||||
|
|
|
|||
14
src/main/java/com/sparrowwallet/sparrow/net/BlockStats.java
Normal file
14
src/main/java/com/sparrowwallet/sparrow/net/BlockStats.java
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package com.sparrowwallet.sparrow.net;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.sparrowwallet.sparrow.BlockSummary;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record BlockStats(int height, String blockhash, double[] feerate_percentiles, int total_weight, int txs, long time) {
|
||||
public BlockSummary toBlockSummary() {
|
||||
Double medianFee = feerate_percentiles != null && feerate_percentiles.length > 0 ? feerate_percentiles[feerate_percentiles.length / 2] : null;
|
||||
return new BlockSummary(height, new Date(time * 1000), medianFee, txs, total_weight);
|
||||
}
|
||||
}
|
||||
|
|
@ -82,6 +82,8 @@ public class ElectrumServer {
|
|||
|
||||
private static Server coreElectrumServer;
|
||||
|
||||
private static ServerCapability serverCapability;
|
||||
|
||||
private static final Pattern RPC_WALLET_LOADING_PATTERN = Pattern.compile(".*\"(Wallet loading failed[:.][^\"]*)\".*");
|
||||
|
||||
private static synchronized CloseableTransport getTransport() throws ServerException {
|
||||
|
|
@ -981,6 +983,21 @@ public class ElectrumServer {
|
|||
}
|
||||
|
||||
public Map<Integer, BlockSummary> getBlockSummaryMap(Integer height, BlockHeader blockHeader) throws ServerException {
|
||||
if(serverCapability.supportsBlockStats()) {
|
||||
if(height == null) {
|
||||
Integer current = AppServices.getCurrentBlockHeight();
|
||||
if(current == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Set<Integer> heights = IntStream.range(current - 1, current + 1).boxed().collect(Collectors.toSet());
|
||||
Map<Integer, BlockStats> blockStats = electrumServerRpc.getBlockStats(getTransport(), heights);
|
||||
return blockStats.keySet().stream().collect(Collectors.toMap(java.util.function.Function.identity(), v -> blockStats.get(v).toBlockSummary()));
|
||||
} else {
|
||||
Map<Integer, BlockStats> blockStats = electrumServerRpc.getBlockStats(getTransport(), Set.of(height));
|
||||
return blockStats.keySet().stream().collect(Collectors.toMap(java.util.function.Function.identity(), v -> blockStats.get(v).toBlockSummary()));
|
||||
}
|
||||
}
|
||||
|
||||
FeeRatesSource feeRatesSource = Config.get().getFeeRatesSource();
|
||||
feeRatesSource = (feeRatesSource == null ? FeeRatesSource.MEMPOOL_SPACE : feeRatesSource);
|
||||
|
||||
|
|
@ -1010,7 +1027,7 @@ public class ElectrumServer {
|
|||
if(current == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Set<BlockTransactionHash> references = IntStream.range(current - 4, current + 1)
|
||||
Set<BlockTransactionHash> references = IntStream.range(current - 1, current + 1)
|
||||
.mapToObj(i -> new BlockTransaction(null, i, null, null, null)).collect(Collectors.toSet());
|
||||
Map<Integer, BlockHeader> blockHeaders = getBlockHeaders(null, references);
|
||||
return blockHeaders.keySet().stream()
|
||||
|
|
@ -1219,7 +1236,7 @@ public class ElectrumServer {
|
|||
}
|
||||
|
||||
if(server.startsWith("cormorant")) {
|
||||
return new ServerCapability(true);
|
||||
return new ServerCapability(true, false, true);
|
||||
}
|
||||
|
||||
if(server.startsWith("electrs/")) {
|
||||
|
|
@ -1405,7 +1422,7 @@ public class ElectrumServer {
|
|||
firstCall = false;
|
||||
|
||||
//If electrumx is detected, we can upgrade to batched RPC. Electrs/EPS do not support batching.
|
||||
ServerCapability serverCapability = getServerCapability(serverVersion);
|
||||
serverCapability = getServerCapability(serverVersion);
|
||||
if(serverCapability.supportsBatching()) {
|
||||
log.debug("Upgrading to batched JSON-RPC");
|
||||
electrumServerRpc = new BatchedElectrumServerRpc(electrumServerRpc.getIdCounterValue(), serverCapability.getMaxTargetBlocks());
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ public interface ElectrumServerRpc {
|
|||
|
||||
Map<Integer, String> getBlockHeaders(Transport transport, Wallet wallet, Set<Integer> blockHeights);
|
||||
|
||||
Map<Integer, BlockStats> getBlockStats(Transport transport, Set<Integer> blockHeights);
|
||||
|
||||
Map<String, String> getTransactions(Transport transport, Wallet wallet, Set<String> txids);
|
||||
|
||||
Map<String, VerboseTransaction> getVerboseTransactions(Transport transport, Set<String> txids, String scriptHash);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
|||
import com.sparrowwallet.drongo.wallet.BlockTransaction;
|
||||
import com.sparrowwallet.drongo.wallet.BlockTransactionHash;
|
||||
|
||||
class ScriptHashTx {
|
||||
public class ScriptHashTx {
|
||||
public static final ScriptHashTx ERROR_TX = new ScriptHashTx() {
|
||||
@Override
|
||||
public BlockTransactionHash getBlockchainTransactionHash() {
|
||||
|
|
|
|||
|
|
@ -5,15 +5,29 @@ import com.sparrowwallet.sparrow.AppServices;
|
|||
public class ServerCapability {
|
||||
private final boolean supportsBatching;
|
||||
private final int maxTargetBlocks;
|
||||
private final boolean supportsRecentMempool;
|
||||
private final boolean supportsBlockStats;
|
||||
|
||||
public ServerCapability(boolean supportsBatching) {
|
||||
this.supportsBatching = supportsBatching;
|
||||
this.maxTargetBlocks = AppServices.TARGET_BLOCKS_RANGE.getLast();
|
||||
this(supportsBatching, AppServices.TARGET_BLOCKS_RANGE.getLast());
|
||||
}
|
||||
|
||||
public ServerCapability(boolean supportsBatching, int maxTargetBlocks) {
|
||||
this.supportsBatching = supportsBatching;
|
||||
this.maxTargetBlocks = maxTargetBlocks;
|
||||
this.supportsRecentMempool = false;
|
||||
this.supportsBlockStats = false;
|
||||
}
|
||||
|
||||
public ServerCapability(boolean supportsBatching, boolean supportsRecentMempool, boolean supportsBlockStats) {
|
||||
this(supportsBatching, AppServices.TARGET_BLOCKS_RANGE.getLast(), supportsRecentMempool, supportsBlockStats);
|
||||
}
|
||||
|
||||
public ServerCapability(boolean supportsBatching, int maxTargetBlocks, boolean supportsRecentMempool, boolean supportsBlockStats) {
|
||||
this.supportsBatching = supportsBatching;
|
||||
this.maxTargetBlocks = maxTargetBlocks;
|
||||
this.supportsRecentMempool = supportsRecentMempool;
|
||||
this.supportsBlockStats = supportsBlockStats;
|
||||
}
|
||||
|
||||
public boolean supportsBatching() {
|
||||
|
|
@ -23,4 +37,12 @@ public class ServerCapability {
|
|||
public int getMaxTargetBlocks() {
|
||||
return maxTargetBlocks;
|
||||
}
|
||||
|
||||
public boolean supportsRecentMempool() {
|
||||
return supportsRecentMempool;
|
||||
}
|
||||
|
||||
public boolean supportsBlockStats() {
|
||||
return supportsBlockStats;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,6 +177,29 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, BlockStats> getBlockStats(Transport transport, Set<Integer> blockHeights) {
|
||||
JsonRpcClient client = new JsonRpcClient(transport);
|
||||
|
||||
Map<Integer, BlockStats> result = new LinkedHashMap<>();
|
||||
for(Integer blockHeight : blockHeights) {
|
||||
try {
|
||||
BlockStats blockStats = new RetryLogic<BlockStats>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() ->
|
||||
client.createRequest().returnAs(BlockStats.class).method("blockchain.block.stats").id(idCounter.incrementAndGet()).params(blockHeight).execute());
|
||||
result.put(blockHeight, blockStats);
|
||||
} catch(ServerException e) {
|
||||
//If there is an error with the server connection, don't keep trying - this may take too long given many blocks
|
||||
throw new ElectrumServerRpcException("Failed to retrieve block stats for block height: " + blockHeight, e);
|
||||
} catch(JsonRpcException e) {
|
||||
log.warn("Failed to retrieve block stats for block height: " + blockHeight + (e.getErrorMessage() != null ? " (" + e.getErrorMessage().getMessage() + ")" : ""));
|
||||
} catch(Exception e) {
|
||||
log.warn("Failed to retrieve block stats for block height: " + blockHeight + " (" + e.getMessage() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getTransactions(Transport transport, Wallet wallet, Set<String> txids) {
|
||||
JsonRpcClient client = new JsonRpcClient(transport);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import com.sparrowwallet.sparrow.AppServices;
|
|||
import java.util.Date;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class VerboseTransaction {
|
||||
public class VerboseTransaction {
|
||||
public String blockhash;
|
||||
public long blocktime;
|
||||
public int confirmations;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcOptional;
|
|||
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam;
|
||||
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import com.sparrowwallet.sparrow.net.BlockStats;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -48,6 +49,9 @@ public interface BitcoindClientService {
|
|||
@JsonRpcMethod("getblockheader")
|
||||
VerboseBlockHeader getBlockHeader(@JsonRpcParam("blockhash") String blockhash);
|
||||
|
||||
@JsonRpcMethod("getblockstats")
|
||||
BlockStats getBlockStats(@JsonRpcParam("blockhash") int hash_or_height);
|
||||
|
||||
@JsonRpcMethod("getrawtransaction")
|
||||
Object getRawTransaction(@JsonRpcParam("txid") String txid, @JsonRpcParam("verbose") boolean verbose);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.sparrowwallet.sparrow.EventManager;
|
|||
import com.sparrowwallet.sparrow.SparrowWallet;
|
||||
import com.sparrowwallet.sparrow.event.MempoolEntriesInitializedEvent;
|
||||
import com.sparrowwallet.drongo.Version;
|
||||
import com.sparrowwallet.sparrow.net.BlockStats;
|
||||
import com.sparrowwallet.sparrow.net.cormorant.Cormorant;
|
||||
import com.sparrowwallet.sparrow.net.cormorant.bitcoind.*;
|
||||
import com.sparrowwallet.sparrow.net.cormorant.index.TxEntry;
|
||||
|
|
@ -157,6 +158,17 @@ public class ElectrumServerService {
|
|||
}
|
||||
}
|
||||
|
||||
@JsonRpcMethod("blockchain.block.stats")
|
||||
public BlockStats getBlockStats(@JsonRpcParam("height") int height) throws BitcoindIOException, BlockNotFoundException {
|
||||
try {
|
||||
return bitcoindClient.getBitcoindService().getBlockStats(height);
|
||||
} catch(JsonRpcException e) {
|
||||
throw new BlockNotFoundException(e.getErrorMessage());
|
||||
} catch(IllegalStateException e) {
|
||||
throw new BitcoindIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@JsonRpcMethod("blockchain.transaction.get")
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object getTransaction(@JsonRpcParam("tx_hash") String tx_hash, @JsonRpcParam("verbose") @JsonRpcOptional boolean verbose) throws BitcoindIOException, TransactionNotFoundException {
|
||||
|
|
|
|||
Loading…
Reference in a new issue