mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 07:07:43 +01:00
Add Bitcoin Core version & health check to RpcService
Make a 'getnetworkinfo' RPC call to bitcoind immediately upon startup, to check that the node is up (and throw a ConnectException to ensure that the user is presented with an appropriate warning popup otherwise). Log a warning if the node version is outside the 0.18.0 - 0.20.1 range. Additionally, call 'getbestblockhash' to check that the chain tip is not stale (> 6 hours old). As part of this, make sure the 'getblock' RPC call works correctly with verbosity < 2, by fixing JSON deserialisation of the response when the block or txs are in summary (hex string) form. (These version & health checks are almost identical to the ones done by the original btcd-cli4j library during RPC client startup.)
This commit is contained in:
parent
d595cac477
commit
4b0711bfcc
4 changed files with 83 additions and 4 deletions
|
@ -26,6 +26,8 @@ import bisq.core.dao.state.model.blockchain.ScriptType;
|
|||
import bisq.core.dao.state.model.blockchain.TxInput;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.network.http.HttpException;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
|
@ -38,17 +40,24 @@ import com.google.inject.Inject;
|
|||
import javax.inject.Named;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.primitives.Chars;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -69,6 +78,7 @@ public class RpcService {
|
|||
private static final Set<String> BSQ_TXS_DISALLOWING_SEGWIT_PUB_KEYS = Set.of(
|
||||
"d1f45e55be6101b1b75e6bf9fc5e5341c6ab420647be7555863bbbddd84e92f3" // in mainnet block 660384, 2020-12-07
|
||||
);
|
||||
private static final Range<Integer> SUPPORTED_NODE_VERSION_RANGE = Range.closedOpen(180000, 210000);
|
||||
|
||||
private final String rpcUser;
|
||||
private final String rpcPassword;
|
||||
|
@ -143,12 +153,13 @@ public class RpcService {
|
|||
.rpcUser(rpcUser)
|
||||
.rpcPassword(rpcPassword)
|
||||
.build();
|
||||
checkNodeVersionAndHealth();
|
||||
|
||||
daemon = new BitcoindDaemon(rpcBlockHost, rpcBlockPort, throwable -> {
|
||||
log.error(throwable.toString());
|
||||
throwable.printStackTrace();
|
||||
UserThread.execute(() -> errorHandler.accept(new RpcException(throwable)));
|
||||
});
|
||||
// TODO: Client should ping or request network info from bitcoind to make sure it is up.
|
||||
|
||||
log.info("Setup took {} ms", System.currentTimeMillis() - startTs);
|
||||
} catch (Throwable e) {
|
||||
|
@ -170,6 +181,35 @@ public class RpcService {
|
|||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private String decodeNodeVersion(Integer encodedVersion) {
|
||||
var paddedEncodedVersion = Strings.padStart(encodedVersion.toString(), 8, '0');
|
||||
|
||||
return Lists.partition(Chars.asList(paddedEncodedVersion.toCharArray()), 2).stream()
|
||||
.map(chars -> new String(Chars.toArray(chars)).replaceAll("^0", ""))
|
||||
.collect(Collectors.joining("."))
|
||||
.replaceAll("\\.0$", "");
|
||||
}
|
||||
|
||||
private void checkNodeVersionAndHealth() throws IOException, HttpException {
|
||||
var networkInfo = client.getNetworkInfo();
|
||||
var nodeVersion = decodeNodeVersion(networkInfo.getVersion());
|
||||
|
||||
if (SUPPORTED_NODE_VERSION_RANGE.contains(networkInfo.getVersion())) {
|
||||
log.info("Got Bitcoin Core version: {}", nodeVersion);
|
||||
} else {
|
||||
log.warn("Server version mismatch - client optimized for '[{} .. {})', node responded with '{}'",
|
||||
decodeNodeVersion(SUPPORTED_NODE_VERSION_RANGE.lowerEndpoint()),
|
||||
decodeNodeVersion(SUPPORTED_NODE_VERSION_RANGE.upperEndpoint()), nodeVersion);
|
||||
}
|
||||
|
||||
var bestRawBlock = client.getBlock(client.getBestBlockHash(), 1);
|
||||
long currentTime = System.currentTimeMillis() / 1000;
|
||||
if ((currentTime - bestRawBlock.getTime()) > TimeUnit.HOURS.toSeconds(6)) {
|
||||
log.warn("Last available block was mined >{} hours ago; please check your network connection",
|
||||
((currentTime - bestRawBlock.getTime()) / 3600));
|
||||
}
|
||||
}
|
||||
|
||||
void addNewDtoBlockHandler(Consumer<RawBlock> dtoBlockHandler,
|
||||
Consumer<Throwable> errorHandler) {
|
||||
daemon.setBlockListener(blockHash -> {
|
||||
|
|
|
@ -17,14 +17,18 @@
|
|||
|
||||
package bisq.core.dao.node.full.rpc.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
|
@ -63,4 +67,18 @@ public class RawBlock {
|
|||
private String previousBlockHash;
|
||||
@JsonProperty("nextblockhash")
|
||||
private String nextBlockHash;
|
||||
|
||||
@JsonCreator
|
||||
public static Summarized summarized(String hex) {
|
||||
var result = new Summarized();
|
||||
result.setHex(hex);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class Summarized extends RawBlock {
|
||||
@Getter(onMethod_ = @JsonValue)
|
||||
private String hex;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,12 @@
|
|||
|
||||
package bisq.core.dao.node.full.rpc.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -56,4 +58,19 @@ public class RawTransaction {
|
|||
private Long blockTime;
|
||||
private Long time;
|
||||
private String hex;
|
||||
|
||||
@JsonCreator
|
||||
public static Summarized summarized(String hex) {
|
||||
var result = new Summarized();
|
||||
result.setHex(hex);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class Summarized extends RawTransaction {
|
||||
@Override
|
||||
@JsonValue
|
||||
public String getHex() {
|
||||
return super.getHex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
package bisq.core.dao.node.full.rpc;
|
||||
|
||||
import bisq.core.dao.node.full.rpc.dto.RawBlock;
|
||||
import bisq.core.dao.node.full.rpc.dto.RawTransaction;
|
||||
|
||||
import bisq.network.http.HttpException;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
@ -36,7 +39,6 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.IOException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
@ -149,13 +151,11 @@ public class BitcoindClientTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore // TODO: Allow serialization/deserialization between RawBlock (with special field) and Json string
|
||||
public void testGetBlock_verbosity_0() throws Exception {
|
||||
doTestGetBlock(0, "\"" + TEST_BLOCK_VERBOSITY_0 + "\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // TODO: Allow serialization/deserialization between RawTransaction (with special field) and Json string
|
||||
public void testGetBlock_verbosity_1() throws Exception {
|
||||
doTestGetBlock(1, TEST_BLOCK_VERBOSITY_1);
|
||||
}
|
||||
|
@ -173,6 +173,10 @@ public class BitcoindClientTest {
|
|||
var block = client.getBlock(TEST_BLOCK_HASH, verbosity);
|
||||
var blockJsonRoundTripped = new ObjectMapper().writeValueAsString(block);
|
||||
|
||||
assertEquals(verbosity == 0, block instanceof RawBlock.Summarized);
|
||||
assertEquals(verbosity == 1, block.getTx() != null &&
|
||||
block.getTx().stream().allMatch(tx -> tx instanceof RawTransaction.Summarized));
|
||||
|
||||
assertEquals(blockJson, blockJsonRoundTripped);
|
||||
assertEquals(expectedRequest, mockOutputStream.toString(UTF_8));
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue