diff --git a/apitest/src/main/resources/logback.xml b/apitest/src/main/resources/logback.xml
index 9c5d0974bff..28279faa118 100644
--- a/apitest/src/main/resources/logback.xml
+++ b/apitest/src/main/resources/logback.xml
@@ -16,6 +16,5 @@
-
diff --git a/build.gradle b/build.gradle
index 257e48852b9..0b9143631cd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,7 +29,6 @@ configure(subprojects) {
ext { // in alphabetical order
bcVersion = '1.63'
bitcoinjVersion = '2a80db4'
- btcdCli4jVersion = '27b94333'
codecVersion = '1.13'
easybindVersion = '1.0.3'
easyVersion = '4.0.1'
@@ -46,7 +45,7 @@ configure(subprojects) {
httpclientVersion = '4.5.12'
httpcoreVersion = '4.4.13'
ioVersion = '2.6'
- jacksonVersion = '2.8.10'
+ jacksonVersion = '2.12.1'
javafxVersion = '11.0.2'
javaxAnnotationVersion = '1.2'
jcsvVersion = '1.4.0'
@@ -54,6 +53,7 @@ configure(subprojects) {
jfoenixVersion = '9.0.6'
joptVersion = '5.0.4'
jsonsimpleVersion = '1.1.1'
+ jsonrpc4jVersion = '1.6.0.bisq.1'
junitVersion = '4.12'
jupiterVersion = '5.7.0'
kotlinVersion = '1.3.41'
@@ -314,23 +314,9 @@ configure(project(':core')) {
exclude(module: 'commons-codec')
}
compile "com.google.guava:guava:$guavaVersion"
- compile("network.bisq.btcd-cli4j:btcd-cli4j-core:$btcdCli4jVersion") {
- exclude(module: 'guava')
- exclude(module: 'slf4j-api')
- exclude(module: 'httpclient')
- exclude(module: 'commons-lang3')
- exclude(module: 'jackson-core')
- exclude(module: 'jackson-annotations')
- exclude(module: 'jackson-databind')
- }
- compile("network.bisq.btcd-cli4j:btcd-cli4j-daemon:$btcdCli4jVersion") {
- exclude(module: 'guava')
- exclude(module: 'slf4j-api')
- exclude(module: 'httpclient')
- exclude(module: 'commons-lang3')
- exclude(module: 'jackson-core')
- exclude(module: 'jackson-annotations')
- exclude(module: 'jackson-databind')
+ compile("com.github.bisq-network:jsonrpc4j:$jsonrpc4jVersion") {
+ exclude(module: 'base64')
+ exclude(module: 'httpcore-nio')
}
compile "com.fasterxml.jackson.core:jackson-core:$jacksonVersion"
compile "com.fasterxml.jackson.core:jackson-annotations:$jacksonVersion"
@@ -678,7 +664,7 @@ configure(project(':apitest')) {
"${result.skippedTestCount} skipped] html report contains skipped test info")
// Show report link if all tests passed in case you want to see more detail, stdout, skipped, etc.
- if(result.resultType == TestResult.ResultType.SUCCESS) {
+ if (result.resultType == TestResult.ResultType.SUCCESS) {
DirectoryReport htmlReport = getReports().getHtml()
String reportUrl = new org.gradle.internal.logging.ConsoleRenderer()
.asClickableFileUrl(htmlReport.getEntryPoint())
diff --git a/common/src/main/java/bisq/common/util/Utilities.java b/common/src/main/java/bisq/common/util/Utilities.java
index 826f1823d94..3d530b918d2 100644
--- a/common/src/main/java/bisq/common/util/Utilities.java
+++ b/common/src/main/java/bisq/common/util/Utilities.java
@@ -25,6 +25,7 @@
import com.google.gson.GsonBuilder;
import com.google.common.base.Splitter;
+import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
@@ -60,6 +61,7 @@
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -67,12 +69,15 @@
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
+
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
@@ -109,22 +114,38 @@ public static ListeningExecutorService getListeningExecutorService(String name,
return MoreExecutors.listeningDecorator(getThreadPoolExecutor(name, corePoolSize, maximumPoolSize, keepAliveTimeInSec));
}
+ public static ListeningExecutorService getListeningExecutorService(String name,
+ int corePoolSize,
+ int maximumPoolSize,
+ long keepAliveTimeInSec,
+ BlockingQueue workQueue) {
+ return MoreExecutors.listeningDecorator(getThreadPoolExecutor(name, corePoolSize, maximumPoolSize, keepAliveTimeInSec, workQueue));
+ }
+
public static ThreadPoolExecutor getThreadPoolExecutor(String name,
int corePoolSize,
int maximumPoolSize,
long keepAliveTimeInSec) {
+ return getThreadPoolExecutor(name, corePoolSize, maximumPoolSize, keepAliveTimeInSec,
+ new ArrayBlockingQueue<>(maximumPoolSize));
+ }
+
+ private static ThreadPoolExecutor getThreadPoolExecutor(String name,
+ int corePoolSize,
+ int maximumPoolSize,
+ long keepAliveTimeInSec,
+ BlockingQueue workQueue) {
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat(name)
.setDaemon(true)
.build();
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTimeInSec,
- TimeUnit.SECONDS, new ArrayBlockingQueue<>(maximumPoolSize), threadFactory);
+ TimeUnit.SECONDS, workQueue, threadFactory);
executor.allowCoreThreadTimeOut(true);
executor.setRejectedExecutionHandler((r, e) -> log.debug("RejectedExecutionHandler called"));
return executor;
}
-
@SuppressWarnings("SameParameterValue")
public static ScheduledThreadPoolExecutor getScheduledThreadPoolExecutor(String name,
int corePoolSize,
@@ -144,6 +165,31 @@ public static ScheduledThreadPoolExecutor getScheduledThreadPoolExecutor(String
return executor;
}
+ // TODO: Can some/all of the uses of this be replaced by guava MoreExecutors.shutdownAndAwaitTermination(..)?
+ public static void shutdownAndAwaitTermination(ExecutorService executor, long timeout, TimeUnit unit) {
+ executor.shutdown();
+ try {
+ if (!executor.awaitTermination(timeout, unit)) {
+ executor.shutdownNow();
+ }
+ } catch (InterruptedException e) {
+ executor.shutdownNow();
+ }
+ }
+
+ public static FutureCallback failureCallback(Consumer errorHandler) {
+ return new FutureCallback<>() {
+ @Override
+ public void onSuccess(V result) {
+ }
+
+ @Override
+ public void onFailure(@NotNull Throwable t) {
+ errorHandler.accept(t);
+ }
+ };
+ }
+
/**
* @return true if defaults read -g AppleInterfaceStyle
has an exit status of 0
(i.e. _not_ returning "key not found").
*/
diff --git a/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java b/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java
index 1c9c605f04d..2d56e784dc0 100644
--- a/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java
+++ b/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java
@@ -37,7 +37,6 @@
import javax.inject.Named;
-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;
@@ -56,8 +55,6 @@
import lombok.extern.slf4j.Slf4j;
-import org.jetbrains.annotations.NotNull;
-
@Slf4j
public class ExportJsonFilesService implements DaoSetupService {
private final DaoStateService daoStateService;
@@ -160,15 +157,10 @@ public void maybeExportToJson() {
return null;
});
- Futures.addCallback(future, new FutureCallback<>() {
- public void onSuccess(Void ignore) {
- }
-
- public void onFailure(@NotNull Throwable throwable) {
- log.error(throwable.toString());
- throwable.printStackTrace();
- }
- }, MoreExecutors.directExecutor());
+ Futures.addCallback(future, Utilities.failureCallback(throwable -> {
+ log.error(throwable.toString());
+ throwable.printStackTrace();
+ }), MoreExecutors.directExecutor());
}
}
diff --git a/core/src/main/java/bisq/core/dao/node/full/FullNode.java b/core/src/main/java/bisq/core/dao/node/full/FullNode.java
index 2bf8ac5f44d..1bdd6b1ffba 100644
--- a/core/src/main/java/bisq/core/dao/node/full/FullNode.java
+++ b/core/src/main/java/bisq/core/dao/node/full/FullNode.java
@@ -20,6 +20,7 @@
import bisq.core.dao.node.BsqNode;
import bisq.core.dao.node.explorer.ExportJsonFilesService;
import bisq.core.dao.node.full.network.FullNodeNetworkService;
+import bisq.core.dao.node.full.rpc.NotificationHandlerException;
import bisq.core.dao.node.parser.BlockParser;
import bisq.core.dao.node.parser.exceptions.BlockHashNotConnectingException;
import bisq.core.dao.node.parser.exceptions.BlockHeightNotConnectingException;
@@ -34,11 +35,10 @@
import bisq.common.UserThread;
import bisq.common.handlers.ResultHandler;
-import com.neemre.btcdcli4j.core.http.HttpLayerException;
-import com.neemre.btcdcli4j.daemon.NotificationHandlerException;
-
import javax.inject.Inject;
+import java.net.ConnectException;
+
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
@@ -64,15 +64,14 @@ public class FullNode extends BsqNode {
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
- @SuppressWarnings("WeakerAccess")
@Inject
- public FullNode(BlockParser blockParser,
- DaoStateService daoStateService,
- DaoStateSnapshotService daoStateSnapshotService,
- P2PService p2PService,
- RpcService rpcService,
- ExportJsonFilesService exportJsonFilesService,
- FullNodeNetworkService fullNodeNetworkService) {
+ private FullNode(BlockParser blockParser,
+ DaoStateService daoStateService,
+ DaoStateSnapshotService daoStateSnapshotService,
+ P2PService p2PService,
+ RpcService rpcService,
+ ExportJsonFilesService exportJsonFilesService,
+ FullNodeNetworkService fullNodeNetworkService) {
super(blockParser, daoStateService, daoStateSnapshotService, p2PService, exportJsonFilesService);
this.rpcService = rpcService;
@@ -150,7 +149,7 @@ protected void onParseBlockChainComplete() {
private void addBlockHandler() {
if (!addBlockHandlerAdded) {
addBlockHandlerAdded = true;
- rpcService.addNewBtcBlockHandler(rawBlock -> {
+ rpcService.addNewDtoBlockHandler(rawBlock -> {
try {
// We need to call that before parsing to have set the chain tip correctly for clients
// which might listen for new blocks on daoStateService. DaoStateListener.onNewBlockHeight
@@ -238,7 +237,7 @@ private void parseBlockRecursively(int blockHeight,
Consumer newBlockHandler,
ResultHandler resultHandler,
Consumer errorHandler) {
- rpcService.requestBtcBlock(blockHeight,
+ rpcService.requestDtoBlock(blockHeight,
rawBlock -> {
try {
doParseBlock(rawBlock).ifPresent(newBlockHandler);
@@ -270,20 +269,18 @@ private void handleError(Throwable throwable) {
if (throwable instanceof RpcException) {
Throwable cause = throwable.getCause();
if (cause != null) {
- if (cause instanceof HttpLayerException) {
- if (((HttpLayerException) cause).getCode() == 1004004) {
- if (warnMessageHandler != null)
- warnMessageHandler.accept("You have configured Bisq to run as DAO full node but there is no " +
- "localhost Bitcoin Core node detected. You need to have Bitcoin Core started and synced before " +
- "starting Bisq. Please restart Bisq with proper DAO full node setup or switch to lite node mode.");
- return;
- }
+ if (cause instanceof ConnectException) {
+ if (warnMessageHandler != null)
+ warnMessageHandler.accept("You have configured Bisq to run as DAO full node but there is no " +
+ "localhost Bitcoin Core node detected. You need to have Bitcoin Core started and synced before " +
+ "starting Bisq. Please restart Bisq with proper DAO full node setup or switch to lite node mode.");
+ return;
} else if (cause instanceof NotificationHandlerException) {
- // Maybe we need to react specifically to errors as in NotificationHandlerException.getError()
- // So far only IO_UNKNOWN was observed
- log.error("Error type of NotificationHandlerException: " + ((NotificationHandlerException) cause).getError().toString());
+ log.error("Error from within block notification daemon: {}", cause.getCause().toString());
startReOrgFromLastSnapshot();
return;
+ } else if (cause instanceof Error) {
+ throw (Error) cause;
}
}
}
diff --git a/core/src/main/java/bisq/core/dao/node/full/RawTx.java b/core/src/main/java/bisq/core/dao/node/full/RawTx.java
index 301f56eb0e3..bd291a0cd49 100644
--- a/core/src/main/java/bisq/core/dao/node/full/RawTx.java
+++ b/core/src/main/java/bisq/core/dao/node/full/RawTx.java
@@ -128,4 +128,11 @@ public static RawTx fromProto(protobuf.BaseTx protoBaseTx) {
txInputs,
outputs);
}
+
+ @Override
+ public String toString() {
+ return "RawTx{" +
+ "\n rawTxOutputs=" + rawTxOutputs +
+ "\n} " + super.toString();
+ }
}
diff --git a/core/src/main/java/bisq/core/dao/node/full/RpcService.java b/core/src/main/java/bisq/core/dao/node/full/RpcService.java
index 6ff0f41f38b..394f420de6f 100644
--- a/core/src/main/java/bisq/core/dao/node/full/RpcService.java
+++ b/core/src/main/java/bisq/core/dao/node/full/RpcService.java
@@ -17,7 +17,14 @@
package bisq.core.dao.node.full;
+import bisq.core.dao.node.full.rpc.BitcoindClient;
+import bisq.core.dao.node.full.rpc.BitcoindDaemon;
+import bisq.core.dao.node.full.rpc.dto.DtoPubKeyScript;
+import bisq.core.dao.node.full.rpc.dto.RawDtoBlock;
+import bisq.core.dao.node.full.rpc.dto.RawDtoInput;
+import bisq.core.dao.node.full.rpc.dto.RawDtoTransaction;
import bisq.core.dao.state.model.blockchain.PubKeyScript;
+import bisq.core.dao.state.model.blockchain.ScriptType;
import bisq.core.dao.state.model.blockchain.TxInput;
import bisq.core.user.Preferences;
@@ -28,34 +35,28 @@
import org.bitcoinj.core.Utils;
-import com.neemre.btcdcli4j.core.BitcoindException;
-import com.neemre.btcdcli4j.core.BtcdCli4jVersion;
-import com.neemre.btcdcli4j.core.CommunicationException;
-import com.neemre.btcdcli4j.core.client.BtcdClient;
-import com.neemre.btcdcli4j.core.client.BtcdClientImpl;
-import com.neemre.btcdcli4j.core.domain.RawTransaction;
-import com.neemre.btcdcli4j.core.domain.enums.ScriptTypes;
-import com.neemre.btcdcli4j.daemon.BtcdDaemon;
-import com.neemre.btcdcli4j.daemon.BtcdDaemonImpl;
-import com.neemre.btcdcli4j.daemon.event.BlockListener;
-
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
-
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.Properties;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -70,6 +71,11 @@
*/
@Slf4j
public class RpcService {
+ private static final int ACTIVATE_HARD_FORK_2_HEIGHT_MAINNET = 680300;
+ private static final int ACTIVATE_HARD_FORK_2_HEIGHT_TESTNET = 1943000;
+ private static final int ACTIVATE_HARD_FORK_2_HEIGHT_REGTEST = 1;
+ private static final Range SUPPORTED_NODE_VERSION_RANGE = Range.closedOpen(180000, 210100);
+
private final String rpcUser;
private final String rpcPassword;
private final String rpcHost;
@@ -77,8 +83,8 @@ public class RpcService {
private final int rpcBlockPort;
private final String rpcBlockHost;
- private BtcdClient client;
- private BtcdDaemon daemon;
+ private BitcoindClient client;
+ private BitcoindDaemon daemon;
// We could use multiple threads but then we need to support ordering of results in a queue
// Keep that for optimization after measuring performance differences
@@ -89,13 +95,12 @@ public class RpcService {
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
- @SuppressWarnings("WeakerAccess")
@Inject
- public RpcService(Preferences preferences,
- @Named(Config.RPC_HOST) String rpcHost,
- @Named(Config.RPC_PORT) int rpcPort,
- @Named(Config.RPC_BLOCK_NOTIFICATION_PORT) int rpcBlockPort,
- @Named(Config.RPC_BLOCK_NOTIFICATION_HOST) String rpcBlockHost) {
+ private RpcService(Preferences preferences,
+ @Named(Config.RPC_HOST) String rpcHost,
+ @Named(Config.RPC_PORT) int rpcPort,
+ @Named(Config.RPC_BLOCK_NOTIFICATION_PORT) int rpcBlockPort,
+ @Named(Config.RPC_BLOCK_NOTIFICATION_HOST) String rpcBlockHost) {
this.rpcUser = preferences.getRpcUser();
this.rpcPassword = preferences.getRpcPw();
@@ -127,53 +132,32 @@ public void shutDown() {
log.info("daemon shut down");
}
- if (client != null) {
- client.close();
- log.info("client closed");
- }
-
executor.shutdown();
}
void setup(ResultHandler resultHandler, Consumer errorHandler) {
ListenableFuture future = executor.submit(() -> {
try {
- log.info("Starting RPCService with btcd-cli4j version {} on {}:{} with user {}, " +
- "listening for blocknotify on port {} from {}",
- BtcdCli4jVersion.VERSION, this.rpcHost, this.rpcPort, this.rpcUser, this.rpcBlockPort,
- this.rpcBlockHost);
+ log.info("Starting RpcService on {}:{} with user {}, listening for blocknotify on port {} from {}",
+ this.rpcHost, this.rpcPort, this.rpcUser, this.rpcBlockPort, this.rpcBlockHost);
long startTs = System.currentTimeMillis();
- PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
- CloseableHttpClient httpProvider = HttpClients.custom().setConnectionManager(cm).build();
- Properties nodeConfig = new Properties();
- nodeConfig.setProperty("node.bitcoind.rpc.protocol", "http");
- nodeConfig.setProperty("node.bitcoind.rpc.host", rpcHost);
- nodeConfig.setProperty("node.bitcoind.rpc.auth_scheme", "Basic");
- nodeConfig.setProperty("node.bitcoind.rpc.user", rpcUser);
- nodeConfig.setProperty("node.bitcoind.rpc.password", rpcPassword);
- nodeConfig.setProperty("node.bitcoind.rpc.port", Integer.toString(rpcPort));
- nodeConfig.setProperty("node.bitcoind.notification.block.port", Integer.toString(rpcBlockPort));
- nodeConfig.setProperty("node.bitcoind.notification.block.host", rpcBlockHost);
- nodeConfig.setProperty("node.bitcoind.notification.alert.port", Integer.toString(bisq.network.utils.Utils.findFreeSystemPort()));
- nodeConfig.setProperty("node.bitcoind.notification.wallet.port", Integer.toString(bisq.network.utils.Utils.findFreeSystemPort()));
-
- nodeConfig.setProperty("node.bitcoind.http.auth_scheme", "Basic");
- BtcdClientImpl client = new BtcdClientImpl(httpProvider, nodeConfig);
- daemon = new BtcdDaemonImpl(client, throwable -> {
+
+ client = BitcoindClient.builder()
+ .rpcHost(rpcHost)
+ .rpcPort(rpcPort)
+ .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)));
});
+
log.info("Setup took {} ms", System.currentTimeMillis() - startTs);
- this.client = client;
- } catch (BitcoindException | CommunicationException e) {
- if (e instanceof CommunicationException)
- log.error("Probably Bitcoin Core is not running or the rpc port is not set correctly. rpcPort=" + rpcPort);
- log.error(e.toString());
- e.printStackTrace();
- log.error(e.getCause() != null ? e.getCause().toString() : "e.getCause()=null");
- throw new RpcException(e.getMessage(), e);
} catch (Throwable e) {
log.error(e.toString());
e.printStackTrace();
@@ -193,31 +177,46 @@ public void onFailure(@NotNull Throwable throwable) {
}, MoreExecutors.directExecutor());
}
- void addNewBtcBlockHandler(Consumer btcBlockHandler,
+ 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 {
+ 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 dtoBlockHandler,
Consumer errorHandler) {
- daemon.addBlockListener(new BlockListener() {
- @Override
- public void blockDetected(com.neemre.btcdcli4j.core.domain.RawBlock rawBtcBlock) {
- if (rawBtcBlock.getHeight() == null || rawBtcBlock.getHeight() == 0) {
- log.warn("We received a RawBlock with no data. blockHash={}", rawBtcBlock.getHash());
- return;
- }
-
- try {
- log.info("New block received: height={}, id={}", rawBtcBlock.getHeight(), rawBtcBlock.getHash());
- List txList = rawBtcBlock.getTx().stream()
- .map(e -> getTxFromRawTransaction(e, rawBtcBlock))
- .collect(Collectors.toList());
- UserThread.execute(() -> {
- btcBlockHandler.accept(new RawBlock(rawBtcBlock.getHeight(),
- rawBtcBlock.getTime() * 1000, // rawBtcBlock.getTime() is in sec but we want ms
- rawBtcBlock.getHash(),
- rawBtcBlock.getPreviousBlockHash(),
- ImmutableList.copyOf(txList)));
- });
- } catch (Throwable t) {
- errorHandler.accept(t);
- }
+ daemon.setBlockListener(blockHash -> {
+ try {
+ var rawDtoBlock = client.getBlock(blockHash, 2);
+ log.info("New block received: height={}, id={}", rawDtoBlock.getHeight(), rawDtoBlock.getHash());
+
+ var block = getBlockFromRawDtoBlock(rawDtoBlock);
+ UserThread.execute(() -> dtoBlockHandler.accept(block));
+ } catch (Throwable t) {
+ errorHandler.accept(t);
}
});
}
@@ -235,23 +234,17 @@ public void onFailure(@NotNull Throwable throwable) {
}, MoreExecutors.directExecutor());
}
- void requestBtcBlock(int blockHeight,
+ void requestDtoBlock(int blockHeight,
Consumer resultHandler,
Consumer errorHandler) {
ListenableFuture future = executor.submit(() -> {
long startTs = System.currentTimeMillis();
String blockHash = client.getBlockHash(blockHeight);
- com.neemre.btcdcli4j.core.domain.RawBlock rawBtcBlock = client.getBlock(blockHash, 2);
- List txList = rawBtcBlock.getTx().stream()
- .map(e -> getTxFromRawTransaction(e, rawBtcBlock))
- .collect(Collectors.toList());
- log.info("requestBtcBlock from bitcoind at blockHeight {} with {} txs took {} ms",
- blockHeight, txList.size(), System.currentTimeMillis() - startTs);
- return new RawBlock(rawBtcBlock.getHeight(),
- rawBtcBlock.getTime() * 1000, // rawBtcBlock.getTime() is in sec but we want ms
- rawBtcBlock.getHash(),
- rawBtcBlock.getPreviousBlockHash(),
- ImmutableList.copyOf(txList));
+ var rawDtoBlock = client.getBlock(blockHash, 2);
+ var block = getBlockFromRawDtoBlock(rawDtoBlock);
+ log.info("requestDtoBlock from bitcoind at blockHeight {} with {} txs took {} ms",
+ blockHeight, block.getRawTxs().size(), System.currentTimeMillis() - startTs);
+ return block;
});
Futures.addCallback(future, new FutureCallback<>() {
@@ -262,7 +255,7 @@ public void onSuccess(RawBlock block) {
@Override
public void onFailure(@NotNull Throwable throwable) {
- log.error("Error at requestBtcBlock: blockHeight={}", blockHeight);
+ log.error("Error at requestDtoBlock: blockHeight={}", blockHeight);
UserThread.execute(() -> errorHandler.accept(throwable));
}
}, MoreExecutors.directExecutor());
@@ -273,41 +266,50 @@ public void onFailure(@NotNull Throwable throwable) {
// Private
///////////////////////////////////////////////////////////////////////////////////////////
- private RawTx getTxFromRawTransaction(RawTransaction rawBtcTx,
- com.neemre.btcdcli4j.core.domain.RawBlock rawBtcBlock) {
- String txId = rawBtcTx.getTxId();
- long blockTime = rawBtcBlock.getTime() * 1000; // We convert block time from sec to ms
- int blockHeight = rawBtcBlock.getHeight();
- String blockHash = rawBtcBlock.getHash();
- final List txInputs = rawBtcTx.getVIn()
+ private static RawBlock getBlockFromRawDtoBlock(RawDtoBlock rawDtoBlock) {
+ List txList = rawDtoBlock.getTx().stream()
+ .map(e -> getTxFromRawTransaction(e, rawDtoBlock))
+ .collect(Collectors.toList());
+ return new RawBlock(rawDtoBlock.getHeight(),
+ rawDtoBlock.getTime() * 1000, // rawDtoBlock.getTime() is in sec but we want ms
+ rawDtoBlock.getHash(),
+ rawDtoBlock.getPreviousBlockHash(),
+ ImmutableList.copyOf(txList));
+ }
+
+ private static RawTx getTxFromRawTransaction(RawDtoTransaction rawDtoTx,
+ RawDtoBlock rawDtoBlock) {
+ String txId = rawDtoTx.getTxId();
+ long blockTime = rawDtoBlock.getTime() * 1000; // We convert block time from sec to ms
+ int blockHeight = rawDtoBlock.getHeight();
+ String blockHash = rawDtoBlock.getHash();
+
+ // Extracting pubKeys for segwit (P2WPKH) inputs, instead of just P2PKH inputs as
+ // originally, changes the DAO state and thus represents a hard fork. We disallow
+ // it until the fork activates, which is determined by block height.
+ boolean allowSegwit = blockHeight >= getActivateHardFork2Height();
+
+ final List txInputs = rawDtoTx.getVIn()
.stream()
.filter(rawInput -> rawInput != null && rawInput.getVOut() != null && rawInput.getTxId() != null)
.map(rawInput -> {
- // We don't support segWit inputs yet as well as no pay to pubkey txs...
- String[] split = rawInput.getScriptSig().getAsm().split("\\[ALL] ");
- String pubKeyAsHex;
- if (split.length == 2) {
- pubKeyAsHex = rawInput.getScriptSig().getAsm().split("\\[ALL] ")[1];
- } else {
- // If we receive a pay to pubkey tx the pubKey is not included as
- // it is in the output already.
- // Bitcoin Core creates payToPubKey tx when spending mined coins (regtest)...
- pubKeyAsHex = null;
- log.debug("pubKeyAsHex is not set as we received a not supported sigScript " +
- "(segWit or payToPubKey tx). txId={}, asm={}",
- rawBtcTx.getTxId(), rawInput.getScriptSig().getAsm());
+ String pubKeyAsHex = extractPubKeyAsHex(rawInput, allowSegwit);
+ if (pubKeyAsHex == null) {
+ log.debug("pubKeyAsHex is not set as we received a not supported sigScript. " +
+ "txId={}, asm={}, txInWitness={}",
+ rawDtoTx.getTxId(), rawInput.getScriptSig().getAsm(), rawInput.getTxInWitness());
}
return new TxInput(rawInput.getTxId(), rawInput.getVOut(), pubKeyAsHex);
})
.collect(Collectors.toList());
- final List txOutputs = rawBtcTx.getVOut()
+ final List txOutputs = rawDtoTx.getVOut()
.stream()
.filter(e -> e != null && e.getN() != null && e.getValue() != null && e.getScriptPubKey() != null)
- .map(rawBtcTxOutput -> {
+ .map(rawDtoTxOutput -> {
byte[] opReturnData = null;
- com.neemre.btcdcli4j.core.domain.PubKeyScript scriptPubKey = rawBtcTxOutput.getScriptPubKey();
- if (ScriptTypes.NULL_DATA.equals(scriptPubKey.getType()) && scriptPubKey.getAsm() != null) {
+ DtoPubKeyScript scriptPubKey = rawDtoTxOutput.getScriptPubKey();
+ if (ScriptType.NULL_DATA.equals(scriptPubKey.getType()) && scriptPubKey.getAsm() != null) {
String[] chunks = scriptPubKey.getAsm().split(" ");
// We get on testnet a lot of "OP_RETURN 0" data, so we filter those away
if (chunks.length == 2 && "OP_RETURN".equals(chunks[0]) && !"0".equals(chunks[1])) {
@@ -327,9 +329,9 @@ private RawTx getTxFromRawTransaction(RawTransaction rawBtcTx,
String address = scriptPubKey.getAddresses() != null &&
scriptPubKey.getAddresses().size() == 1 ? scriptPubKey.getAddresses().get(0) : null;
PubKeyScript pubKeyScript = new PubKeyScript(scriptPubKey);
- return new RawTxOutput(rawBtcTxOutput.getN(),
- rawBtcTxOutput.getValue().movePointRight(8).longValue(),
- rawBtcTx.getTxId(),
+ return new RawTxOutput(rawDtoTxOutput.getN(),
+ BigDecimal.valueOf(rawDtoTxOutput.getValue()).movePointRight(8).longValueExact(),
+ rawDtoTx.getTxId(),
pubKeyScript,
address,
opReturnData,
@@ -345,4 +347,33 @@ private RawTx getTxFromRawTransaction(RawTransaction rawBtcTx,
ImmutableList.copyOf(txInputs),
ImmutableList.copyOf(txOutputs));
}
+
+ private static int getActivateHardFork2Height() {
+ return Config.baseCurrencyNetwork().isMainnet() ? ACTIVATE_HARD_FORK_2_HEIGHT_MAINNET :
+ Config.baseCurrencyNetwork().isTestnet() ? ACTIVATE_HARD_FORK_2_HEIGHT_TESTNET :
+ ACTIVATE_HARD_FORK_2_HEIGHT_REGTEST;
+ }
+
+ @VisibleForTesting
+ static String extractPubKeyAsHex(RawDtoInput rawInput, boolean allowSegwit) {
+ // We only allow inputs with a single SIGHASH_ALL signature. That is, multisig or
+ // signing of only some of the tx inputs/outputs is intentionally disallowed...
+ if (rawInput.getScriptSig() == null) {
+ // coinbase input - no pubKey to extract
+ return null;
+ }
+ String[] split = rawInput.getScriptSig().getAsm().split(" ");
+ if (split.length == 2 && split[0].endsWith("[ALL]")) {
+ // P2PKH input
+ return split[1];
+ }
+ List txInWitness = rawInput.getTxInWitness() != null ? rawInput.getTxInWitness() : List.of();
+ if (allowSegwit && split.length < 2 && txInWitness.size() == 2 && txInWitness.get(0).endsWith("01")) {
+ // P2WPKH or P2SH-P2WPKH input
+ return txInWitness.get(1);
+ }
+ // If we receive a pay to pubkey tx, the pubKey is not included as it is in the
+ // output already.
+ return null;
+ }
}
diff --git a/core/src/main/java/bisq/core/dao/node/full/rpc/BitcoindClient.java b/core/src/main/java/bisq/core/dao/node/full/rpc/BitcoindClient.java
new file mode 100644
index 00000000000..dd9e97f9fc1
--- /dev/null
+++ b/core/src/main/java/bisq/core/dao/node/full/rpc/BitcoindClient.java
@@ -0,0 +1,122 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.dao.node.full.rpc;
+
+import bisq.core.dao.node.full.rpc.dto.DtoNetworkInfo;
+import bisq.core.dao.node.full.rpc.dto.RawDtoBlock;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLStreamHandler;
+
+import java.nio.charset.StandardCharsets;
+
+import java.io.IOException;
+
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+
+
+import com.googlecode.jsonrpc4j.JsonRpcHttpClient;
+import com.googlecode.jsonrpc4j.JsonRpcMethod;
+import com.googlecode.jsonrpc4j.ProxyUtil;
+import com.googlecode.jsonrpc4j.RequestIDGenerator;
+
+public interface BitcoindClient {
+ @JsonRpcMethod("getblock")
+ RawDtoBlock getBlock(String headerHash, int verbosity) throws IOException;
+
+ @JsonRpcMethod("getblockcount")
+ Integer getBlockCount() throws IOException;
+
+ @JsonRpcMethod("getblockhash")
+ String getBlockHash(Integer blockHeight) throws IOException;
+
+ @JsonRpcMethod("getbestblockhash")
+ String getBestBlockHash() throws IOException;
+
+ @JsonRpcMethod("getnetworkinfo")
+ DtoNetworkInfo getNetworkInfo() throws IOException;
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+ class Builder {
+ private String rpcHost;
+ private int rpcPort = -1;
+ private String rpcUser;
+ private String rpcPassword;
+ private URLStreamHandler urlStreamHandler;
+ private RequestIDGenerator requestIDGenerator;
+
+ public Builder rpcHost(String rpcHost) {
+ this.rpcHost = rpcHost;
+ return this;
+ }
+
+ public Builder rpcPort(int rpcPort) {
+ this.rpcPort = rpcPort;
+ return this;
+ }
+
+ public Builder rpcUser(String rpcUser) {
+ this.rpcUser = rpcUser;
+ return this;
+ }
+
+ public Builder rpcPassword(String rpcPassword) {
+ this.rpcPassword = rpcPassword;
+ return this;
+ }
+
+ public Builder urlStreamHandler(URLStreamHandler urlStreamHandler) {
+ this.urlStreamHandler = urlStreamHandler;
+ return this;
+ }
+
+ public Builder requestIDGenerator(RequestIDGenerator requestIDGenerator) {
+ this.requestIDGenerator = requestIDGenerator;
+ return this;
+ }
+
+ public BitcoindClient build() throws MalformedURLException {
+ var userPass = checkNotNull(rpcUser, "rpcUser not set") +
+ ":" + checkNotNull(rpcPassword, "rpcPassword not set");
+
+ var headers = Collections.singletonMap("Authorization", "Basic " +
+ Base64.getEncoder().encodeToString(userPass.getBytes(StandardCharsets.US_ASCII)));
+
+ var httpClient = new JsonRpcHttpClient(
+ new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, true),
+ new URL("http", rpcHost, rpcPort, "", urlStreamHandler),
+ headers);
+ Optional.ofNullable(requestIDGenerator).ifPresent(httpClient::setRequestIDGenerator);
+ return ProxyUtil.createClientProxy(getClass().getClassLoader(), BitcoindClient.class, httpClient);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/dao/node/full/rpc/BitcoindDaemon.java b/core/src/main/java/bisq/core/dao/node/full/rpc/BitcoindDaemon.java
new file mode 100644
index 00000000000..b607ac4e2af
--- /dev/null
+++ b/core/src/main/java/bisq/core/dao/node/full/rpc/BitcoindDaemon.java
@@ -0,0 +1,125 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.dao.node.full.rpc;
+
+import bisq.common.util.Utilities;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.apache.commons.io.IOUtils;
+
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.SocketException;
+
+import java.nio.charset.StandardCharsets;
+
+import java.io.IOException;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class BitcoindDaemon {
+ private final ListeningExecutorService executor = Utilities.getSingleThreadListeningExecutor("block-notification-server");
+ private final ListeningExecutorService workerPool = Utilities.getListeningExecutorService("block-notification-worker-%d",
+ 1, 10, 60, new ArrayBlockingQueue<>(100));
+ private final ServerSocket serverSocket;
+ private final Consumer errorHandler;
+ private volatile boolean active;
+ private volatile BlockListener blockListener = blockHash -> {
+ };
+
+ public BitcoindDaemon(String host, int port, Consumer errorHandler) throws NotificationHandlerException {
+ this(newServerSocket(host, port), errorHandler);
+ }
+
+ @VisibleForTesting
+ BitcoindDaemon(ServerSocket serverSocket, Consumer errorHandler) {
+ this.serverSocket = serverSocket;
+ this.errorHandler = errorHandler;
+ initialize();
+ }
+
+ private static ServerSocket newServerSocket(String host, int port) throws NotificationHandlerException {
+ try {
+ return new ServerSocket(port, 5, InetAddress.getByName(host));
+ } catch (Exception e) {
+ throw new NotificationHandlerException(e);
+ }
+ }
+
+ private void initialize() {
+ active = true;
+ var serverFuture = executor.submit((Callable) () -> {
+ try {
+ while (active) {
+ try (var socket = serverSocket.accept(); var is = socket.getInputStream()) {
+ var blockHash = IOUtils.toString(is, StandardCharsets.UTF_8).trim();
+ var future = workerPool.submit((Callable) () -> {
+ try {
+ blockListener.blockDetected(blockHash);
+ return null;
+ } catch (RuntimeException e) {
+ throw new NotificationHandlerException(e);
+ }
+ });
+ Futures.addCallback(future, Utilities.failureCallback(errorHandler), MoreExecutors.directExecutor());
+ }
+ }
+ } catch (SocketException e) {
+ if (active) {
+ throw new NotificationHandlerException(e);
+ }
+ } catch (Exception e) {
+ throw new NotificationHandlerException(e);
+ } finally {
+ log.info("Shutting down block notification server");
+ }
+ return null;
+ });
+ Futures.addCallback(serverFuture, Utilities.failureCallback(errorHandler), MoreExecutors.directExecutor());
+ }
+
+ public void shutdown() {
+ active = false;
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ log.error("Error closing block notification server socket", e);
+ } finally {
+ Utilities.shutdownAndAwaitTermination(executor, 1, TimeUnit.SECONDS);
+ Utilities.shutdownAndAwaitTermination(workerPool, 5, TimeUnit.SECONDS);
+ }
+ }
+
+ public void setBlockListener(BlockListener blockListener) {
+ this.blockListener = blockListener;
+ }
+
+ public interface BlockListener {
+ void blockDetected(String blockHash);
+ }
+}
diff --git a/core/src/main/java/bisq/core/dao/node/full/rpc/NotificationHandlerException.java b/core/src/main/java/bisq/core/dao/node/full/rpc/NotificationHandlerException.java
new file mode 100644
index 00000000000..3f0278eee9a
--- /dev/null
+++ b/core/src/main/java/bisq/core/dao/node/full/rpc/NotificationHandlerException.java
@@ -0,0 +1,24 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.dao.node.full.rpc;
+
+public class NotificationHandlerException extends Exception {
+ public NotificationHandlerException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/src/main/java/bisq/core/dao/node/full/rpc/dto/DtoNetworkInfo.java b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/DtoNetworkInfo.java
new file mode 100644
index 00000000000..2b64073b93a
--- /dev/null
+++ b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/DtoNetworkInfo.java
@@ -0,0 +1,114 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.dao.node.full.rpc.dto;
+
+import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
+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.Getter;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonPropertyOrder({"version", "subversion", "protocolversion", "localservices", "localservicesnames", "localrelay",
+ "timeoffset", "networkactive", "connections", "connections_in", "connections_out", "networks", "relayfee",
+ "incrementalfee", "localaddresses", "warnings"})
+public class DtoNetworkInfo {
+ private Integer version;
+ @JsonProperty("subversion")
+ private String subVersion;
+ @JsonProperty("protocolversion")
+ private Integer protocolVersion;
+ @JsonProperty("localservices")
+ private String localServices;
+ @JsonProperty("localservicesnames")
+ private List localServicesNames;
+ @JsonProperty("localrelay")
+ private Boolean localRelay;
+ @JsonProperty("timeoffset")
+ private Integer timeOffset;
+ @JsonProperty("networkactive")
+ private Boolean networkActive;
+ private Integer connections;
+ @JsonProperty("connections_in")
+ private Integer connectionsIn;
+ @JsonProperty("connections_out")
+ private Integer connectionsOut;
+ private List networks;
+ @JsonProperty("relayfee")
+ private Double relayFee;
+ @JsonProperty("incrementalfee")
+ private Double incrementalFee;
+ @JsonProperty("localaddresses")
+ private List localAddresses;
+ private String warnings;
+
+ @Data
+ @NoArgsConstructor
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ @JsonPropertyOrder({"name", "limited", "reachable", "proxy", "proxy_randomize_credentials"})
+ public static class Network {
+ private NetworkType name;
+ private Boolean limited;
+ private Boolean reachable;
+ private String proxy;
+ @JsonProperty("proxy_randomize_credentials")
+ private Boolean proxyRandomizeCredentials;
+ }
+
+ @Data
+ @NoArgsConstructor
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ @JsonPropertyOrder({"address", "port", "score"})
+ public static class LocalAddress {
+ private String address;
+ private Integer port;
+ private Integer score;
+ }
+
+ @RequiredArgsConstructor
+ public enum NetworkType {
+ IPV4("ipv4"), IPV6("ipv6"), ONION("onion");
+
+ @Getter(onMethod_ = @JsonValue)
+ private final String name;
+ }
+
+ @RequiredArgsConstructor
+ public enum ServiceFlag {
+ @JsonEnumDefaultValue
+ UNKNOWN(0),
+ // Taken from https://github.com/bitcoin/bitcoin/blob/master/src/protocol.h:
+ NETWORK(1),
+ BLOOM(1 << 2),
+ WITNESS(1 << 3),
+ COMPACT_FILTERS(1 << 6),
+ NETWORK_LIMITED(1 << 10);
+
+ @Getter
+ private final int value;
+ }
+}
diff --git a/core/src/main/java/bisq/core/dao/node/full/rpc/dto/DtoPubKeyScript.java b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/DtoPubKeyScript.java
new file mode 100644
index 00000000000..a9fd9331c3b
--- /dev/null
+++ b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/DtoPubKeyScript.java
@@ -0,0 +1,42 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.dao.node.full.rpc.dto;
+
+import bisq.core.dao.state.model.blockchain.ScriptType;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+import java.util.List;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonPropertyOrder({"asm", "hex", "reqSigs", "type", "addresses"})
+public class DtoPubKeyScript {
+ private String asm;
+ private String hex;
+ private Integer reqSigs;
+ private ScriptType type;
+ private List addresses;
+}
diff --git a/core/src/main/java/bisq/core/dao/node/full/rpc/dto/DtoSignatureScript.java b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/DtoSignatureScript.java
new file mode 100644
index 00000000000..77edaaa6e2b
--- /dev/null
+++ b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/DtoSignatureScript.java
@@ -0,0 +1,35 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.dao.node.full.rpc.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonPropertyOrder({"asm", "hex"})
+public class DtoSignatureScript {
+ private String asm;
+ private String hex;
+}
diff --git a/core/src/main/java/bisq/core/dao/node/full/rpc/dto/RawDtoBlock.java b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/RawDtoBlock.java
new file mode 100644
index 00000000000..91edb701b49
--- /dev/null
+++ b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/RawDtoBlock.java
@@ -0,0 +1,84 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+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
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true, value = "ntx")
+@JsonPropertyOrder({"hash", "confirmations", "strippedsize", "size", "weight", "height", "version", "versionHex",
+ "merkleroot", "tx", "time", "mediantime", "nonce", "bits", "difficulty", "chainwork", "nTx",
+ "previousblockhash", "nextblockhash"})
+public class RawDtoBlock {
+ private String hash;
+ private Integer confirmations;
+ @JsonProperty("strippedsize")
+ private Integer strippedSize;
+ private Integer size;
+ private Integer weight;
+ private Integer height;
+ private Integer version;
+ private String versionHex;
+ @JsonProperty("merkleroot")
+ private String merkleRoot;
+ private List tx;
+ private Long time;
+ @JsonProperty("mediantime")
+ private Long medianTime;
+ private Long nonce;
+ private String bits;
+ private Double difficulty;
+ @JsonProperty("chainwork")
+ private String chainWork;
+ // There seems to be a bug in Jackson where it misses and/or duplicates this field without
+ // an explicit @JsonProperty annotation plus the @JsonIgnoreProperties 'ntx' term above:
+ @JsonProperty("nTx")
+ private Integer nTx;
+ @JsonProperty("previousblockhash")
+ 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 RawDtoBlock {
+ @Getter(onMethod_ = @JsonValue)
+ private String hex;
+ }
+}
diff --git a/core/src/main/java/bisq/core/dao/node/full/rpc/dto/RawDtoInput.java b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/RawDtoInput.java
new file mode 100644
index 00000000000..a33c2b25e9b
--- /dev/null
+++ b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/RawDtoInput.java
@@ -0,0 +1,45 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.dao.node.full.rpc.dto;
+
+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 java.util.List;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonPropertyOrder({"txid", "vout", "coinbase", "scriptSig", "txinwitness", "sequence"})
+public class RawDtoInput {
+ @JsonProperty("txid")
+ private String txId;
+ @JsonProperty("vout")
+ private Integer vOut;
+ private String coinbase;
+ private DtoSignatureScript scriptSig;
+ @JsonProperty("txinwitness")
+ private List txInWitness;
+ private Long sequence;
+}
diff --git a/core/src/main/java/bisq/core/dao/node/full/rpc/dto/RawDtoOutput.java b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/RawDtoOutput.java
new file mode 100644
index 00000000000..4bd4d60569a
--- /dev/null
+++ b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/RawDtoOutput.java
@@ -0,0 +1,36 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.dao.node.full.rpc.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonPropertyOrder({"value", "n", "scriptPubKey"})
+public class RawDtoOutput {
+ private Double value;
+ private Integer n;
+ private DtoPubKeyScript scriptPubKey;
+}
diff --git a/core/src/main/java/bisq/core/dao/node/full/rpc/dto/RawDtoTransaction.java b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/RawDtoTransaction.java
new file mode 100644
index 00000000000..e8e2c752060
--- /dev/null
+++ b/core/src/main/java/bisq/core/dao/node/full/rpc/dto/RawDtoTransaction.java
@@ -0,0 +1,76 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+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.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonPropertyOrder({"txid", "hash", "version", "size", "vsize", "weight", "locktime", "vin", "vout", "hex"})
+public class RawDtoTransaction {
+ @JsonProperty("in_active_chain")
+ private Boolean inActiveChain;
+ @JsonProperty("txid")
+ private String txId;
+ private String hash;
+ private Integer version;
+ private Integer size;
+ @JsonProperty("vsize")
+ private Integer vSize;
+ private Integer weight;
+ @JsonProperty("locktime")
+ private Long lockTime;
+ @JsonProperty("vin")
+ private List vIn;
+ @JsonProperty("vout")
+ private List vOut;
+ @JsonProperty("blockhash")
+ private String blockHash;
+ private Integer confirmations;
+ @JsonProperty("blocktime")
+ 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 RawDtoTransaction {
+ @Override
+ @JsonValue
+ public String getHex() {
+ return super.getHex();
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/dao/state/model/blockchain/PubKeyScript.java b/core/src/main/java/bisq/core/dao/state/model/blockchain/PubKeyScript.java
index a3b6750ff1f..adde8ae8d93 100644
--- a/core/src/main/java/bisq/core/dao/state/model/blockchain/PubKeyScript.java
+++ b/core/src/main/java/bisq/core/dao/state/model/blockchain/PubKeyScript.java
@@ -17,6 +17,7 @@
package bisq.core.dao.state.model.blockchain;
+import bisq.core.dao.node.full.rpc.dto.DtoPubKeyScript;
import bisq.core.dao.state.model.ImmutableDaoStateModel;
import bisq.common.proto.persistable.PersistablePayload;
@@ -43,9 +44,9 @@ public class PubKeyScript implements PersistablePayload, ImmutableDaoStateModel
private final String asm;
private final String hex;
- public PubKeyScript(com.neemre.btcdcli4j.core.domain.PubKeyScript scriptPubKey) {
+ public PubKeyScript(DtoPubKeyScript scriptPubKey) {
this(scriptPubKey.getReqSigs() != null ? scriptPubKey.getReqSigs() : 0,
- ScriptType.forName(scriptPubKey.getType().getName()),
+ scriptPubKey.getType(),
scriptPubKey.getAddresses() != null ? ImmutableList.copyOf(scriptPubKey.getAddresses()) : null,
scriptPubKey.getAsm(),
scriptPubKey.getHex());
diff --git a/core/src/main/java/bisq/core/dao/state/model/blockchain/ScriptType.java b/core/src/main/java/bisq/core/dao/state/model/blockchain/ScriptType.java
index 173f78ce11a..6c560f1b885 100644
--- a/core/src/main/java/bisq/core/dao/state/model/blockchain/ScriptType.java
+++ b/core/src/main/java/bisq/core/dao/state/model/blockchain/ScriptType.java
@@ -39,6 +39,7 @@
public enum ScriptType implements ImmutableDaoStateModel {
UNDEFINED("undefined"),
// https://github.com/bitcoin/bitcoin/blob/master/src/script/standard.cpp
+ NONSTANDARD("nonstandard"),
PUB_KEY("pubkey"),
PUB_KEY_HASH("pubkeyhash"),
SCRIPT_HASH("scripthash"),
@@ -46,12 +47,11 @@ public enum ScriptType implements ImmutableDaoStateModel {
NULL_DATA("nulldata"),
WITNESS_V0_KEYHASH("witness_v0_keyhash"),
WITNESS_V0_SCRIPTHASH("witness_v0_scripthash"),
- WITNESS_UNKNOWN("witness_unknown"),
- NONSTANDARD("nonstandard");
+ WITNESS_V1_TAPROOT("witness_v1_taproot"),
+ WITNESS_UNKNOWN("witness_unknown");
private final String name;
-
@JsonValue
private String getName() {
return name;
diff --git a/core/src/main/java/bisq/core/provider/price/PriceRequest.java b/core/src/main/java/bisq/core/provider/price/PriceRequest.java
index 8d5025d3c7c..fa9134099c4 100644
--- a/core/src/main/java/bisq/core/provider/price/PriceRequest.java
+++ b/core/src/main/java/bisq/core/provider/price/PriceRequest.java
@@ -78,14 +78,6 @@ public void shutDown() {
if (provider != null) {
provider.shutDown();
}
-
- executorService.shutdown();
- try {
- if (!executorService.awaitTermination(1, TimeUnit.SECONDS)) {
- executorService.shutdownNow();
- }
- } catch (InterruptedException e) {
- executorService.shutdownNow();
- }
+ Utilities.shutdownAndAwaitTermination(executorService, 1, TimeUnit.SECONDS);
}
}
diff --git a/core/src/test/java/bisq/core/dao/node/full/RpcServiceTest.java b/core/src/test/java/bisq/core/dao/node/full/RpcServiceTest.java
new file mode 100644
index 00000000000..4b10a835efc
--- /dev/null
+++ b/core/src/test/java/bisq/core/dao/node/full/RpcServiceTest.java
@@ -0,0 +1,140 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.dao.node.full;
+
+import bisq.core.dao.node.full.rpc.dto.RawDtoInput;
+import bisq.core.dao.node.full.rpc.dto.DtoSignatureScript;
+
+import java.util.List;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class RpcServiceTest {
+ private static final String SIGNATURE = "3045" +
+ "022100b6c2fa10587d6fed3a0eecfd098b160f69a850beca139fe03ef65bec4cba1c5b" +
+ "02204a833a16c22bbd32722243ea3270e672f646ee9406e8797e11093951e92efbd5";
+ private static final String SIGNATURE_1 = "3044" +
+ "02201f00d9a4aab1a3a239f1ad95a910092c0c55423480d609eaad4599cf7ecb7f48" +
+ "0220668b1a9cf5624b1c4ece6da3f64bc6021e509f588ae1006601acd8a9f83b3576";
+ private static final String SIGNATURE_2 = "3045" +
+ "022100982eca77a72a2bdba51b9231afd4521400bee1bb7830634eb26db2b0c621bc46" +
+ "022073d7325916e2b5ceb1d2e510a5161fd9115105a8dafa94068864624bb10d190e";
+ private static final String PUB_KEY =
+ "03dcca91c2ec7229f1b4f4c4f664c92d3303dddef8d38736f6a7f28de16f3ce416";
+ private static final String PUB_KEY_1 =
+ "0229713ad5c604c585128b3a5da6de20d78fc33bd3b595e9991f4c0e1fee99f845";
+ private static final String PUB_KEY_2 =
+ "0398ad45a74bf5a5c5a8ec31de6815d2e805a23e68c0f8001770e74bc4c17c5b31";
+ private static final String MULTISIG_REDEEM_SCRIPT_HEX =
+ "5221" + PUB_KEY_1 + "21" + PUB_KEY_2 + "52ae"; // OP_2 pub1 pub2 OP_2 OP_CHECKMULTISIG
+ private static final String P2WPKH_REDEEM_SCRIPT_HEX =
+ "0014" + "9bc809698674ec7c01d35d438e9d0de1aa87b6c8"; // 0 hash160
+ private static final String P2WSH_REDEEM_SCRIPT_HEX =
+ "0020" + "223d978073802f79e6ecdc7591e5dc1f0ea7030d6466f73c6b90391bc72e886f"; // 0 hash256
+
+ @Test
+ public void testExtractPubKeyAsHex_coinbase() {
+ checkExtractPubKeyAsHexReturnsNull(new RawDtoInput());
+ }
+
+ @Test
+ public void testExtractPubKeyAsHex_P2PK() {
+ var input = rawInput(SIGNATURE + "[ALL]");
+ checkExtractPubKeyAsHexReturnsNull(input);
+ }
+
+ @Test
+ public void testExtractPubKeyAsHex_P2PKH() {
+ var input = rawInput(SIGNATURE + "[ALL] " + PUB_KEY);
+ assertEquals(PUB_KEY, RpcService.extractPubKeyAsHex(input, true));
+ assertEquals(PUB_KEY, RpcService.extractPubKeyAsHex(input, false));
+ }
+
+ @Test
+ public void testExtractPubKeyAsHex_P2WPKH() {
+ var input = rawInput("", SIGNATURE + "01", PUB_KEY);
+ assertEquals(PUB_KEY, RpcService.extractPubKeyAsHex(input, true));
+ assertNull(RpcService.extractPubKeyAsHex(input, false));
+ }
+
+ @Test
+ public void testExtractPubKeyAsHex_P2SH_P2WPKH() {
+ var input = rawInput(P2WPKH_REDEEM_SCRIPT_HEX, SIGNATURE + "01", PUB_KEY);
+ assertEquals(PUB_KEY, RpcService.extractPubKeyAsHex(input, true));
+ assertNull(RpcService.extractPubKeyAsHex(input, false));
+ }
+
+ @Test
+ public void testExtractPubKeyAsHex_P2PKH_nonDefaultSighash() {
+ var input = rawInput(SIGNATURE + "[SINGLE|ANYONECANPAY] " + PUB_KEY);
+ checkExtractPubKeyAsHexReturnsNull(input);
+ }
+
+ @Test
+ public void testExtractPubKeyAsHex_P2WPKH_nonDefaultSighash() {
+ var input = rawInput("", SIGNATURE + "82", PUB_KEY);
+ checkExtractPubKeyAsHexReturnsNull(input);
+ }
+
+ @Test
+ public void testExtractPubKeyAsHex_P2SH_P2WPKH_nonDefaultSighash() {
+ var input = rawInput(P2WPKH_REDEEM_SCRIPT_HEX, SIGNATURE + "82", PUB_KEY);
+ checkExtractPubKeyAsHexReturnsNull(input);
+ }
+
+ @Test
+ public void testExtractPubKeyAsHex_P2SH_multisig() {
+ var input = rawInput("0 " + SIGNATURE_1 + "[ALL] " + SIGNATURE_2 + "[ALL] " + MULTISIG_REDEEM_SCRIPT_HEX);
+ checkExtractPubKeyAsHexReturnsNull(input);
+ }
+
+ @Test
+ public void testExtractPubKeyAsHex_P2SH_multisig_nonDefaultSighash() {
+ var input = rawInput("0 " + SIGNATURE_1 + "[ALL] " + SIGNATURE_2 + "[NONE] " + MULTISIG_REDEEM_SCRIPT_HEX);
+ checkExtractPubKeyAsHexReturnsNull(input);
+ }
+
+ @Test
+ public void testExtractPubKeyAsHex_P2WSH_multisig() {
+ var input = rawInput("", "", SIGNATURE_1 + "01", SIGNATURE_2 + "01", MULTISIG_REDEEM_SCRIPT_HEX);
+ checkExtractPubKeyAsHexReturnsNull(input);
+ }
+
+ @Test
+ public void testExtractPubKeyAsHex_P2SH_P2WSH_multisig() {
+ var input = rawInput(P2WSH_REDEEM_SCRIPT_HEX, "", SIGNATURE_1 + "01", SIGNATURE_2 + "01", MULTISIG_REDEEM_SCRIPT_HEX);
+ checkExtractPubKeyAsHexReturnsNull(input);
+ }
+
+ private static void checkExtractPubKeyAsHexReturnsNull(RawDtoInput input) {
+ assertNull(RpcService.extractPubKeyAsHex(input, true));
+ assertNull(RpcService.extractPubKeyAsHex(input, false));
+ }
+
+ private static RawDtoInput rawInput(String asm, String... txInWitness) {
+ var input = new RawDtoInput();
+ var scriptSig = new DtoSignatureScript();
+ scriptSig.setAsm(asm);
+ input.setScriptSig(scriptSig);
+ input.setTxInWitness(txInWitness.length > 0 ? List.of(txInWitness) : null);
+ return input;
+ }
+}
diff --git a/core/src/test/java/bisq/core/dao/node/full/rpc/BitcoindClientTest.java b/core/src/test/java/bisq/core/dao/node/full/rpc/BitcoindClientTest.java
new file mode 100644
index 00000000000..ab3548cb983
--- /dev/null
+++ b/core/src/test/java/bisq/core/dao/node/full/rpc/BitcoindClientTest.java
@@ -0,0 +1,234 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.dao.node.full.rpc;
+
+import bisq.core.dao.node.full.rpc.dto.RawDtoBlock;
+import bisq.core.dao.node.full.rpc.dto.RawDtoTransaction;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.net.ConnectException;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+
+
+import com.googlecode.jsonrpc4j.HttpException;
+import com.googlecode.jsonrpc4j.JsonRpcClientException;
+import com.googlecode.jsonrpc4j.RequestIDGenerator;
+import kotlin.text.Charsets;
+
+public class BitcoindClientTest {
+ private static final String TEST_BLOCK_HASH = "015f37a20d517645a11a6cdd316049f41bc77b4a4057b2dd092114b78147f42c";
+ private static final String TEST_BLOCK_VERBOSITY_0 = readFromResourcesUnPrettified("getblock-result-verbosity-0.txt");
+ private static final String TEST_BLOCK_VERBOSITY_1 = readFromResourcesUnPrettified("getblock-result-verbosity-1.json");
+ private static final String TEST_BLOCK_VERBOSITY_2 = readFromResourcesUnPrettified("getblock-result-verbosity-2.json");
+ private static final String TEST_NETWORK_INFO = readFromResourcesUnPrettified("getnetworkinfo-result.json");
+
+ private BitcoindClient client;
+ private int mockResponseCode = 200;
+ private boolean canConnect = true;
+ private ByteArrayInputStream mockResponse;
+ private ByteArrayInputStream mockErrorResponse;
+ private ByteArrayOutputStream mockOutputStream = new ByteArrayOutputStream();
+
+ @Before
+ public void setUp() throws Exception {
+ var mockURLStreamHandler = mock(MyURLStreamHandler.class);
+ var mockRequestIDGenerator = mock(RequestIDGenerator.class);
+
+ client = BitcoindClient.builder()
+ .rpcHost("127.0.0.1")
+ .rpcPort(18443)
+ .rpcUser("bisqdao")
+ .rpcPassword("bsq")
+ .urlStreamHandler(mockURLStreamHandler)
+ .requestIDGenerator(mockRequestIDGenerator)
+ .build();
+
+ when(mockURLStreamHandler.openConnection(any(), any())).then(inv -> {
+ var connection = mock(HttpURLConnection.class);
+ if (canConnect) {
+ when(connection.getOutputStream()).thenReturn(mockOutputStream);
+ if (mockResponseCode < 400) {
+ when(connection.getInputStream()).thenReturn(mockResponse);
+ } else {
+ when(connection.getInputStream()).thenThrow(IOException.class);
+ when(connection.getErrorStream()).thenReturn(mockErrorResponse);
+ }
+ } else {
+ doThrow(ConnectException.class).when(connection).connect();
+ }
+ return connection;
+ });
+ when(mockRequestIDGenerator.generateID()).thenReturn("987654321");
+ }
+
+ @Test
+ public void testGetBlockCount() throws Exception {
+ var expectedRequest = toJson("{'id':'987654321','jsonrpc':'2.0','method':'getblockcount','params':[]}");
+ mockResponse = toJsonIS("{'result':'150','error':null,'id':'123456789'}");
+
+ assertEquals((Integer) 150, client.getBlockCount());
+ assertEquals(expectedRequest, mockOutputStream.toString(UTF_8));
+ }
+
+ @Test(expected = ConnectException.class)
+ public void testGetBlockCount_noConnection() throws Exception {
+ canConnect = false;
+
+ client.getBlockCount();
+ }
+
+ @Test(expected = HttpException.class)
+ public void testGetBlockCount_wrongCredentials() throws Exception {
+ mockResponseCode = 401;
+// mockResponseCustomHeaders.put("WWW-Authenticate", "[Basic realm=\"jsonrpc\"]");
+
+ client.getBlockCount();
+ }
+
+ @Test
+ public void testGetBlockHash() throws Exception {
+ var expectedRequest = toJson("{'id':'987654321','jsonrpc':'2.0','method':'getblockhash','params':[139]}");
+ mockResponse = toJsonIS("{'result':'" + TEST_BLOCK_HASH + "','error':null,'id':'123456789'}");
+
+ assertEquals(TEST_BLOCK_HASH, client.getBlockHash(139));
+ assertEquals(expectedRequest, mockOutputStream.toString(UTF_8));
+ }
+
+ @Test
+ public void testGetBestBlockHash() throws Exception {
+ var expectedRequest = toJson("{'id':'987654321','jsonrpc':'2.0','method':'getbestblockhash','params':[]}");
+ mockResponse = toJsonIS("{'result':'" + TEST_BLOCK_HASH + "','error':null,'id':'123456789'}");
+
+ assertEquals(TEST_BLOCK_HASH, client.getBestBlockHash());
+ assertEquals(expectedRequest, mockOutputStream.toString(UTF_8));
+ }
+
+ @Test(expected = JsonRpcClientException.class)
+ public void testGetBlockHash_heightOutOfRange() throws Exception {
+ mockResponseCode = 500;
+ mockErrorResponse = toJsonIS("{'result':null,'error':{'code':-8,'message':'Block height out of range'},'id':'123456789'}");
+
+ client.getBlockHash(151);
+ }
+
+ @Test
+ public void testGetBlock_verbosity_0() throws Exception {
+ doTestGetBlock(0, "\"" + TEST_BLOCK_VERBOSITY_0 + "\"");
+ }
+
+ @Test
+ public void testGetBlock_verbosity_1() throws Exception {
+ doTestGetBlock(1, TEST_BLOCK_VERBOSITY_1);
+ }
+
+ @Test
+ public void testGetBlock_verbosity_2() throws Exception {
+ doTestGetBlock(2, TEST_BLOCK_VERBOSITY_2);
+ }
+
+ private void doTestGetBlock(int verbosity, String blockJson) throws Exception {
+ var expectedRequest = toJson("{'id':'987654321','jsonrpc':'2.0','method':'getblock','params':['"
+ + TEST_BLOCK_HASH + "'," + verbosity + "]}");
+ mockResponse = toJsonIS("{'result':" + blockJson + ",'error':null,'id':'123456789'}");
+
+ var block = client.getBlock(TEST_BLOCK_HASH, verbosity);
+ var blockJsonRoundTripped = new ObjectMapper().writeValueAsString(block);
+
+ assertEquals(verbosity == 0, block instanceof RawDtoBlock.Summarized);
+ assertEquals(verbosity == 1, block.getTx() != null &&
+ block.getTx().stream().allMatch(tx -> tx instanceof RawDtoTransaction.Summarized));
+
+ assertEquals(blockJson, blockJsonRoundTripped);
+ assertEquals(expectedRequest, mockOutputStream.toString(UTF_8));
+ }
+
+ @Test(expected = JsonRpcClientException.class)
+ public void testGetBlock_blockNotFound() throws Exception {
+ mockResponseCode = 500;
+ mockErrorResponse = toJsonIS("{'result':null,'error':{'code':-5,'message':'Block not found'},'id':'123456789'}");
+
+ client.getBlock(TEST_BLOCK_HASH.replace('f', 'e'), 2);
+ }
+
+ @Test(expected = JsonRpcClientException.class)
+ public void testGetBlock_malformedHash() throws Exception {
+ mockResponseCode = 500;
+ mockErrorResponse = toJsonIS("{'result':null,'error':{'code':-8,'message':'blockhash must be of length 64 " +
+ "(not 3, for \\'foo\\')'},'id':'123456789'}");
+
+ client.getBlock("foo", 2);
+ }
+
+ @Test
+ public void testGetNetworkInfo() throws Exception {
+ var expectedRequest = toJson("{'id':'987654321','jsonrpc':'2.0','method':'getnetworkinfo','params':[]}");
+ mockResponse = toJsonIS("{'result':" + TEST_NETWORK_INFO + ",'error':null,'id':'123456789'}");
+
+ var networkInfo = client.getNetworkInfo();
+ var networkInfoRoundTripped = new ObjectMapper().writeValueAsString(networkInfo);
+ var expectedNetworkInfoStr = TEST_NETWORK_INFO.replace("MY_CUSTOM_SERVICE", "UNKNOWN");
+
+ assertEquals(expectedNetworkInfoStr, networkInfoRoundTripped);
+ assertEquals(expectedRequest, mockOutputStream.toString(UTF_8));
+ }
+
+ private static String toJson(String json) {
+ return json.replace("'", "\"").replace("\\\"", "'");
+ }
+
+ private static ByteArrayInputStream toJsonIS(String json) {
+ return new ByteArrayInputStream(toJson(json).getBytes(UTF_8));
+ }
+
+ private static String readFromResourcesUnPrettified(String resourceName) {
+ try {
+ var path = Paths.get(BitcoindClientTest.class.getResource(resourceName).toURI());
+ return new String(Files.readAllBytes(path), Charsets.UTF_8).replaceAll("(\\s+\\B|\\B\\s+|\\v)", "");
+ } catch (Exception e) {
+ return "";
+ }
+ }
+
+ private static abstract class MyURLStreamHandler extends URLStreamHandler {
+ @Override
+ public abstract URLConnection openConnection(URL u, Proxy p);
+ }
+}
diff --git a/core/src/test/java/bisq/core/dao/node/full/rpc/BitcoindDaemonTest.java b/core/src/test/java/bisq/core/dao/node/full/rpc/BitcoindDaemonTest.java
new file mode 100644
index 00000000000..0cc9dc20652
--- /dev/null
+++ b/core/src/test/java/bisq/core/dao/node/full/rpc/BitcoindDaemonTest.java
@@ -0,0 +1,183 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.dao.node.full.rpc;
+
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.mockito.Mockito.*;
+
+public class BitcoindDaemonTest {
+ private BitcoindDaemon daemon;
+ private int acceptAnotherCount;
+ private CountDownLatch errorHandlerLatch = new CountDownLatch(1);
+ private Consumer errorHandler = mock(ThrowableConsumer.class);
+ private BitcoindDaemon.BlockListener blockListener = mock(BitcoindDaemon.BlockListener.class);
+ private Socket socket = mock(Socket.class);
+ private volatile boolean socketClosed;
+
+ @Before
+ public void setUp() throws Exception {
+ var serverSocket = mock(ServerSocket.class);
+
+ when(serverSocket.accept()).then(invocation -> waitToAccept(() -> {
+ if (socketClosed) {
+ throw new SocketException();
+ }
+ return socket;
+ }));
+ doAnswer((VoidAnswer) invocation -> {
+ socketClosed = true;
+ acceptAnother(1);
+ }).when(serverSocket).close();
+
+ doAnswer((VoidAnswer) invocation -> errorHandlerLatch.countDown()).when(errorHandler).accept(any());
+
+ daemon = new BitcoindDaemon(serverSocket, errorHandler);
+ daemon.setBlockListener(blockListener);
+ }
+
+ @After
+ public void tearDown() {
+ daemon.shutdown();
+ }
+
+ @Test
+ public void testNoBlocksMissedDuringFloodOfIncomingBlocks() throws Exception {
+ var latch = new CountDownLatch(1); // to block all the daemon worker threads until shutdown, as if stuck
+
+ doAnswer((VoidAnswer) invocation -> latch.await()).when(blockListener).blockDetected(any());
+ when(socket.getInputStream()).then(invocation -> new ByteArrayInputStream("foo".getBytes()));
+
+ acceptAnother(50);
+ waitUntilAllAccepted();
+
+ // Unblock all the daemon worker threads and shut down.
+ latch.countDown();
+ daemon.shutdown();
+
+ verify(blockListener, times(50)).blockDetected("foo");
+ }
+
+ @Test
+ public void testBlockHashIsTrimmed() throws Exception {
+ when(socket.getInputStream()).then(invocation -> new ByteArrayInputStream("\r\nbar \n".getBytes()));
+
+ acceptAnother(1);
+ waitUntilAllAccepted();
+ daemon.shutdown();
+
+ verify(blockListener).blockDetected("bar");
+ }
+
+ @Test
+ public void testBrokenSocketRead() throws Exception {
+ when(socket.getInputStream()).thenThrow(IOException.class);
+
+ acceptAnother(1);
+ errorHandlerLatch.await(5, TimeUnit.SECONDS);
+
+ verify(errorHandler).accept(argThat(t -> t instanceof NotificationHandlerException &&
+ t.getCause() instanceof IOException));
+ }
+
+ @Test
+ public void testRuntimeExceptionInBlockListener() throws Exception {
+ daemon.setBlockListener(blockHash -> {
+ throw new IndexOutOfBoundsException();
+ });
+ when(socket.getInputStream()).then(invocation -> new ByteArrayInputStream("foo".getBytes()));
+
+ acceptAnother(1);
+ errorHandlerLatch.await(5, TimeUnit.SECONDS);
+
+ verify(errorHandler).accept(argThat(t -> t instanceof NotificationHandlerException &&
+ t.getCause() instanceof IndexOutOfBoundsException));
+ }
+
+
+ @Test
+ public void testErrorInBlockListener() throws Exception {
+ synchronized (this) {
+ daemon.setBlockListener(blockHash -> {
+ throw new Error();
+ });
+ when(socket.getInputStream()).then(invocation -> new ByteArrayInputStream("foo".getBytes()));
+ acceptAnother(1);
+ }
+ errorHandlerLatch.await(5, TimeUnit.SECONDS);
+
+ verify(errorHandler).accept(any(Error.class));
+ }
+
+ @Test(expected = NotificationHandlerException.class)
+ public void testUnknownHost() throws Exception {
+ new BitcoindDaemon("[", -1, errorHandler).shutdown();
+ }
+
+ private synchronized void acceptAnother(int n) {
+ acceptAnotherCount += n;
+ notifyAll();
+ }
+
+ private synchronized V waitToAccept(Callable onAccept) throws Exception {
+ while (acceptAnotherCount == 0) {
+ wait();
+ }
+ var result = onAccept.call();
+ acceptAnotherCount--;
+ notifyAll();
+ return result;
+ }
+
+ private synchronized void waitUntilAllAccepted() throws InterruptedException {
+ while (acceptAnotherCount > 0) {
+ wait();
+ }
+ notifyAll();
+ }
+
+ private interface ThrowableConsumer extends Consumer {
+ }
+
+ private interface VoidAnswer extends Answer {
+ void voidAnswer(InvocationOnMock invocation) throws Throwable;
+
+ @Override
+ default Void answer(InvocationOnMock invocation) throws Throwable {
+ voidAnswer(invocation);
+ return null;
+ }
+ }
+}
diff --git a/core/src/test/resources/bisq/core/dao/node/full/rpc/getblock-result-verbosity-0.txt b/core/src/test/resources/bisq/core/dao/node/full/rpc/getblock-result-verbosity-0.txt
new file mode 100644
index 00000000000..e363535db5f
--- /dev/null
+++ b/core/src/test/resources/bisq/core/dao/node/full/rpc/getblock-result-verbosity-0.txt
@@ -0,0 +1,53 @@
+00000020a33d8cf2a1567a148dad1a4099599bafa631135262413a4bdd1182be5673471abe69039afc7c93936e3e2860da8cab522281fe4139a453d1
+1a3d5cfe75e93761bf25b25fffff7f20010000000b020000000001010000000000000000000000000000000000000000000000000000000000000000
+ffffffff05028b000101ffffffff02495b062a0100000017a914f2d479a78b981e4a2b05be5f89ef7c468ac48e78870000000000000000266a24aa21
+a9ed9f5a62816cebb2044be9db74a26762020defe9e8b251b73af2abefad7d4b355c0120000000000000000000000000000000000000000000000000
+0000000000000000000000000100000001274c7174e814eb731712b80187660f7a6ab2949a3271ef80037685cdde2d78c3020000006b483045022100
+b6c2fa10587d6fed3a0eecfd098b160f69a850beca139fe03ef65bec4cba1c5b02204a833a16c22bbd32722243ea3270e672f646ee9406e8797e1109
+3951e92efbd5012103dcca91c2ec7229f1b4f4c4f664c92d3303dddef8d38736f6a7f28de16f3ce416ffffffff03881300000000000017a9144c0e48
+93237f85479f489b32c8ff0faf3ee2e1c987c247090000000000160014007128282856f8e8f3c75909f9f1474b55cb1f1605f8902500000000160014
+9bc809698674ec7c01d35d438e9d0de1aa87b6c800000000010000000114facc8cf47a984cedf9ba84db10ad767e18c6fb6edbac39ce8f138a1e5b43
+9100000000da0047304402201f00d9a4aab1a3a239f1ad95a910092c0c55423480d609eaad4599cf7ecb7f480220668b1a9cf5624b1c4ece6da3f64b
+c6021e509f588ae1006601acd8a9f83b357601483045022100982eca77a72a2bdba51b9231afd4521400bee1bb7830634eb26db2b0c621bc46022073
+d7325916e2b5ceb1d2e510a5161fd9115105a8dafa94068864624bb10d190e014752210229713ad5c604c585128b3a5da6de20d78fc33bd3b595e999
+1f4c0e1fee99f845210398ad45a74bf5a5c5a8ec31de6815d2e805a23e68c0f8001770e74bc4c17c5b3152aefeffffff01b06a21000000000017a914
+4c0e4893237f85479f489b32c8ff0faf3ee2e1c9878a0000000100000000010133ba0d88494567a02bffc406b31bd5eb29f0b536a96326baca349b3a
+96f241e90200000000ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987067e180000000000160014f6da24
+d081a1b63dcacf4a5762a8ed91fd472c685b74b900000000001600144755561caa18d651bf59912545764811d0ab96f60247304402200d4f21475675
+3861adf835f5d175835d3cd50a19edbf9881572ab7f831a030de0220181b94bbb7d7b0a7a6eee06881a3265cb28405f2d14055e1ff23ac6500471930
+012102772467db1e5909e7e9f2b169daccf556e7e2981b145c756db649f3972d913c320000000001000000000101e06ec4548803dadb10ef6c66e4f3
+1f319161dc9ec31631e967773b8e042836180200000000ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987
+f4390900000000001600145230c895305a232ef2a4feb0a91e7d99e22fd515d20bd20000000000160014b6fbbd9053e47891fae7f3db7dd3966062b2
+513c0247304402203eeb1713b582be1d74bf6a9f95c573dd41baeedf1efd1bc9a1ad1cccad97c4f70220799a399f53f9325f6cf9681b0138f80bd80f
+3dc900a4d0ab5cc3c97d5be85f1801210255f56a7be9f88ccf5885ac2f8cd67320424d533d71083270a2891b2488ffb22b0000000001000000000101
+4c701c32d3b0ce9408d8ec96d80934dbc6cb42df616e6e751dae82afdb46214e0200000000ffffffff03881300000000000017a9144c0e4893237f85
+479f489b32c8ff0faf3ee2e1c987c02709000000000016001489c79bc0628d2d8b1cd91c2ed0e75db13e6f3f3a8a07a00600000000160014062d20a6
+692350b7a39397c50857a7f725788da002483045022100ebb8e0ddab46b762e3a9555442cc7ee35c4353d9152e856c97251913902a5056022010d3a0
+bb51d931a18174dc8ed0ffa37c5ff29f8e924b71d86850de31f3ea4c6e012102e0284cdeae6a8c971e2ea5004ebf9196ee9b3037d6f1ed039c4b5672
+a69cddc60000000001000000000101c3f609b5166e32bd0c29168767621bfc56411a3ff9e84932fc2c612407566a370200000000ffffffff03881300
+000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987006a1800000000001600141437b91493d1929b1b42a80e83229c347c28f937
+eaaf90060000000016001491ad2cce99e8e4455de5f44559816b98213f3503024830450221008f8eee212f209ba2a179197bd4ba35a35ad7a3045990
+25ddbd192a6e7e64c1920220242c297726948ad408ce54c9a0e0287b283c53dc68323537f24a7e3ecd8c526b012103f870bcd3a46e80e4b1236302e6
+2318b412cc97ef096fc976a89deb569dc11ef1000000000100000001ba4f0ae59d7cb81f0c7edd63796387fde6825a3536953c2824d7d945c192bd20
+020000006b483045022100ba97f6336b3bb3e07cf584010c7b8ab52957e34e462e71252c63f498d51f45b70220708dd78d2d9943f8c176055963ab70
+6870274068fe7b1e5d87592a837336a5340121039978f14b2463d7d4790cdf2a37c2a3d872dd517ca91db7f6f7a858a7ac661c60ffffffff03881300
+000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987c02709000000000016001413afc3a26c010dddaf2410a9d97b5054a4e8d309
+04e05938000000001600145228ee46a95383b314396dda75e707b8bed830340000000001000000015d71dc04e56bc08b61180a2b9531a0747f56615f
+f27ec51cbc72ac7a800cc27a020000006a473044022073a8a8ee9cc490093e6de5708b3727cef35f41038713fab9a5c235b4b400b73102206f97f4fc
+8faefb534e85c0f3773f2d67781c9871f681211276619054fc54015201210374e07f24beca2270cf305100652149a64c80d76611f775ec276658aeae
+4ef0b5ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987006a1800000000001600141c397ba7ea8410dbd5
+56c51f8184a14ab015114324b6a10600000000160014c38c3d890c415f2c13f6925c1ad1d4a7cb4f7dfe0000000001000000000102142a200a7460ad
+f754232337f48bf7c38ba411eed0cd2f98900ffd9f2adc1ed20100000000ffffffff99d67c2a69b04831bd229ea6022c91c68999c38d0ba92211b56f
+15c5f8222bf50100000000ffffffff03e803000000000000220020223d978073802f79e6ecdc7591e5dc1f0ea7030d6466f73c6b90391bc72e886f00
+00000000000000226a20758e9207848c631c6839b1382bb22a52b6ef0645d733389d7be2efb1e8b71454db972100000000001600145e41d2fb8de1c2
+50410416d8dd153c685d3f9c7b024730440220073a37eb4371dc3d0cf218d6e9b8e6044275acd07402ccebdf24f65b60a3c1f70220647e71c173f992
+fc0c5ec6c2b0b1653a95118098269e318c41d1bc33da3ff14f012103c3e858472f39d31c6defdf38b4778660501f0ccfa524b3dd8ba61117b7646635
+0247304402202b8ef5de1c56328d3797265272540a054fc04c158b23ee6385b69b14486422c10220749f591fcf4ef995df8f1e7d9aa3cf0c045f1616
+386b86b419197b360c871fca012102c73e60f00bc72b56568a9f371b9122b3ee29d41730670e98ff8da58e7bbfab280000000001000000000102913a
+5817e0b3bddb0bc6869e12200b5115be11522cf76125229d47e184da48460100000000ffffffff684d66517d21106d1bcbba96964e31a532fec33c09
+8cd621e2500c702340b6780100000000ffffffff03e803000000000000220020e3e81046fd9659b5725736efa404bc1c8f9b2ff6f0af7cb7ddacdcc6
+1e1c72310000000000000000226a20c63aacf2e8be20752b6f689c0308967cbc335641f2948a4a7962fdde6c464730f2962100000000001600145e41
+d2fb8de1c250410416d8dd153c685d3f9c7b02483045022100c9665b9abe7fcab10f775eeafc2391c1fee84c50b50df6d697b1db9a7eea5dd3022070
+cb7aa57b8f5bf9f2eff11263cf2ea871ab7b9ddfc8e47671cee50ada547243012102016f9a6cc454bd1e74c28df36a079231a215812c60581d1e1745
+e650f82bd1230248304502210094cdec8e08f32919b3f25c8672041305c848b4206256ad64f7090dc97dfd1bf002205c397a310cebc690fac04d1394
+5e012d0031246d4574e92f97e3701ca729b6140121029b739486d7cf402b3db3913187fad7897e8a5ec3cd8607e9b5fc54a71958b03100000000
diff --git a/core/src/test/resources/bisq/core/dao/node/full/rpc/getblock-result-verbosity-1.json b/core/src/test/resources/bisq/core/dao/node/full/rpc/getblock-result-verbosity-1.json
new file mode 100644
index 00000000000..7016fad55ba
--- /dev/null
+++ b/core/src/test/resources/bisq/core/dao/node/full/rpc/getblock-result-verbosity-1.json
@@ -0,0 +1,33 @@
+{
+ "hash": "015f37a20d517645a11a6cdd316049f41bc77b4a4057b2dd092114b78147f42c",
+ "confirmations": 12,
+ "strippedsize": 2270,
+ "size": 3178,
+ "weight": 9988,
+ "height": 139,
+ "version": 536870912,
+ "versionHex": "20000000",
+ "merkleroot": "6137e975fe5c3d1ad153a43941fe812252ab8cda60283e6e93937cfc9a0369be",
+ "tx": [
+ "09bbfd286d0399b57ac7c85956fccbe7e080cedd7a01723d8d68038f0cf57159",
+ "e2fc769668f7306ca09865c10dd744ed5510f1cec35b8f854c9ed346229a303b",
+ "b36a2c90dab09a0b99d30a3b132af37b79d8266a1510decc34683a2784228337",
+ "f52b22f8c5156fb51122a90b8dc39989c6912c02a69e22bd3148b0692a7cd699",
+ "4648da84e1479d222561f72c5211be15510b20129e86c60bdbbdb3e017583a91",
+ "d21edc2a9ffd0f90982fcdd0ee11a48bc3f78bf437232354f7ad60740a202a14",
+ "78b64023700c50e221d68c093cc3fe32a5314e9696bacb1b6d10217d51664d68",
+ "dd4243c2743a2d2351814a14628e1976e6fb208e63bd2bbd441180c441205027",
+ "aa33deb87512c2c417a0a9a17c58a36dcf2686fca8b50cc608ad952178a350c2",
+ "719606705c6832f7180ab9db5e1ce6a51bad80a5cbf3b57b806dd10f3d7d5124",
+ "16cd3283068d2965f26045e15a285e39a762af32ec388ae8c28fb6cb2c468768"
+ ],
+ "time": 1605510591,
+ "mediantime": 1589548514,
+ "nonce": 1,
+ "bits": "207fffff",
+ "difficulty": 4.656542373906925E-10,
+ "chainwork": "0000000000000000000000000000000000000000000000000000000000000118",
+ "nTx": 11,
+ "previousblockhash": "1a477356be8211dd4b3a4162521331a6af9b5999401aad8d147a56a1f28c3da3",
+ "nextblockhash": "7d8267341a57f1f626b450eb22b2dbf208f13ec176e1cc015aa4d2b2ea55016d"
+}
diff --git a/core/src/test/resources/bisq/core/dao/node/full/rpc/getblock-result-verbosity-2.json b/core/src/test/resources/bisq/core/dao/node/full/rpc/getblock-result-verbosity-2.json
new file mode 100644
index 00000000000..5947c1c9029
--- /dev/null
+++ b/core/src/test/resources/bisq/core/dao/node/full/rpc/getblock-result-verbosity-2.json
@@ -0,0 +1,698 @@
+{
+ "hash": "015f37a20d517645a11a6cdd316049f41bc77b4a4057b2dd092114b78147f42c",
+ "confirmations": 12,
+ "strippedsize": 2270,
+ "size": 3178,
+ "weight": 9988,
+ "height": 139,
+ "version": 536870912,
+ "versionHex": "20000000",
+ "merkleroot": "6137e975fe5c3d1ad153a43941fe812252ab8cda60283e6e93937cfc9a0369be",
+ "tx": [
+ {
+ "txid": "09bbfd286d0399b57ac7c85956fccbe7e080cedd7a01723d8d68038f0cf57159",
+ "hash": "ed1620cdd028c99d57ee0709f963976bb75cac93f5171f5a2f5dd9976e5c56bc",
+ "version": 2,
+ "size": 171,
+ "vsize": 144,
+ "weight": 576,
+ "locktime": 0,
+ "vin": [
+ {
+ "coinbase": "028b000101",
+ "sequence": 4294967295
+ }
+ ],
+ "vout": [
+ {
+ "value": 50.00026953,
+ "n": 0,
+ "scriptPubKey": {
+ "asm": "OP_HASH160 f2d479a78b981e4a2b05be5f89ef7c468ac48e78 OP_EQUAL",
+ "hex": "a914f2d479a78b981e4a2b05be5f89ef7c468ac48e7887",
+ "reqSigs": 1,
+ "type": "scripthash",
+ "addresses": [
+ "2NFPC3k5RjZ4GAQgLqJdiVdJ6gqWqEBryXq"
+ ]
+ }
+ },
+ {
+ "value": 0.0,
+ "n": 1,
+ "scriptPubKey": {
+ "asm": "OP_RETURN aa21a9ed9f5a62816cebb2044be9db74a26762020defe9e8b251b73af2abefad7d4b355c",
+ "hex": "6a24aa21a9ed9f5a62816cebb2044be9db74a26762020defe9e8b251b73af2abefad7d4b355c",
+ "type": "nulldata"
+ }
+ }
+ ],
+ "hex": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff05028b000101ffffffff02495b062a0100000017a914f2d479a78b981e4a2b05be5f89ef7c468ac48e78870000000000000000266a24aa21a9ed9f5a62816cebb2044be9db74a26762020defe9e8b251b73af2abefad7d4b355c0120000000000000000000000000000000000000000000000000000000000000000000000000"
+ },
+ {
+ "txid": "e2fc769668f7306ca09865c10dd744ed5510f1cec35b8f854c9ed346229a303b",
+ "hash": "e2fc769668f7306ca09865c10dd744ed5510f1cec35b8f854c9ed346229a303b",
+ "version": 1,
+ "size": 252,
+ "vsize": 252,
+ "weight": 1008,
+ "locktime": 0,
+ "vin": [
+ {
+ "txid": "c3782ddecd85760380ef71329a94b26a7a0f668701b8121773eb14e874714c27",
+ "vout": 2,
+ "scriptSig": {
+ "asm": "3045022100b6c2fa10587d6fed3a0eecfd098b160f69a850beca139fe03ef65bec4cba1c5b02204a833a16c22bbd32722243ea3270e672f646ee9406e8797e11093951e92efbd5[ALL] 03dcca91c2ec7229f1b4f4c4f664c92d3303dddef8d38736f6a7f28de16f3ce416",
+ "hex": "483045022100b6c2fa10587d6fed3a0eecfd098b160f69a850beca139fe03ef65bec4cba1c5b02204a833a16c22bbd32722243ea3270e672f646ee9406e8797e11093951e92efbd5012103dcca91c2ec7229f1b4f4c4f664c92d3303dddef8d38736f6a7f28de16f3ce416"
+ },
+ "sequence": 4294967295
+ }
+ ],
+ "vout": [
+ {
+ "value": 5.0E-5,
+ "n": 0,
+ "scriptPubKey": {
+ "asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
+ "hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
+ "reqSigs": 1,
+ "type": "scripthash",
+ "addresses": [
+ "2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
+ ]
+ }
+ },
+ {
+ "value": 0.00608194,
+ "n": 1,
+ "scriptPubKey": {
+ "asm": "0 007128282856f8e8f3c75909f9f1474b55cb1f16",
+ "hex": "0014007128282856f8e8f3c75909f9f1474b55cb1f16",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1qqpcjs2pg2muw3u78tyylnu28fd2uk8ckr30ezc"
+ ]
+ }
+ },
+ {
+ "value": 6.30257669,
+ "n": 2,
+ "scriptPubKey": {
+ "asm": "0 9bc809698674ec7c01d35d438e9d0de1aa87b6c8",
+ "hex": "00149bc809698674ec7c01d35d438e9d0de1aa87b6c8",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1qn0yqj6vxwnk8cqwnt4pca8gdux4g0dkghc9rur"
+ ]
+ }
+ }
+ ],
+ "hex": "0100000001274c7174e814eb731712b80187660f7a6ab2949a3271ef80037685cdde2d78c3020000006b483045022100b6c2fa10587d6fed3a0eecfd098b160f69a850beca139fe03ef65bec4cba1c5b02204a833a16c22bbd32722243ea3270e672f646ee9406e8797e11093951e92efbd5012103dcca91c2ec7229f1b4f4c4f664c92d3303dddef8d38736f6a7f28de16f3ce416ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987c247090000000000160014007128282856f8e8f3c75909f9f1474b55cb1f1605f89025000000001600149bc809698674ec7c01d35d438e9d0de1aa87b6c800000000"
+ },
+ {
+ "txid": "b36a2c90dab09a0b99d30a3b132af37b79d8266a1510decc34683a2784228337",
+ "hash": "b36a2c90dab09a0b99d30a3b132af37b79d8266a1510decc34683a2784228337",
+ "version": 1,
+ "size": 301,
+ "vsize": 301,
+ "weight": 1204,
+ "locktime": 138,
+ "vin": [
+ {
+ "txid": "91435b1e8a138fce39acdb6efbc6187e76ad10db84baf9ed4c987af48cccfa14",
+ "vout": 0,
+ "scriptSig": {
+ "asm": "0 304402201f00d9a4aab1a3a239f1ad95a910092c0c55423480d609eaad4599cf7ecb7f480220668b1a9cf5624b1c4ece6da3f64bc6021e509f588ae1006601acd8a9f83b3576[ALL] 3045022100982eca77a72a2bdba51b9231afd4521400bee1bb7830634eb26db2b0c621bc46022073d7325916e2b5ceb1d2e510a5161fd9115105a8dafa94068864624bb10d190e[ALL] 52210229713ad5c604c585128b3a5da6de20d78fc33bd3b595e9991f4c0e1fee99f845210398ad45a74bf5a5c5a8ec31de6815d2e805a23e68c0f8001770e74bc4c17c5b3152ae",
+ "hex": "0047304402201f00d9a4aab1a3a239f1ad95a910092c0c55423480d609eaad4599cf7ecb7f480220668b1a9cf5624b1c4ece6da3f64bc6021e509f588ae1006601acd8a9f83b357601483045022100982eca77a72a2bdba51b9231afd4521400bee1bb7830634eb26db2b0c621bc46022073d7325916e2b5ceb1d2e510a5161fd9115105a8dafa94068864624bb10d190e014752210229713ad5c604c585128b3a5da6de20d78fc33bd3b595e9991f4c0e1fee99f845210398ad45a74bf5a5c5a8ec31de6815d2e805a23e68c0f8001770e74bc4c17c5b3152ae"
+ },
+ "sequence": 4294967294
+ }
+ ],
+ "vout": [
+ {
+ "value": 0.0219,
+ "n": 0,
+ "scriptPubKey": {
+ "asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
+ "hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
+ "reqSigs": 1,
+ "type": "scripthash",
+ "addresses": [
+ "2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
+ ]
+ }
+ }
+ ],
+ "hex": "010000000114facc8cf47a984cedf9ba84db10ad767e18c6fb6edbac39ce8f138a1e5b439100000000da0047304402201f00d9a4aab1a3a239f1ad95a910092c0c55423480d609eaad4599cf7ecb7f480220668b1a9cf5624b1c4ece6da3f64bc6021e509f588ae1006601acd8a9f83b357601483045022100982eca77a72a2bdba51b9231afd4521400bee1bb7830634eb26db2b0c621bc46022073d7325916e2b5ceb1d2e510a5161fd9115105a8dafa94068864624bb10d190e014752210229713ad5c604c585128b3a5da6de20d78fc33bd3b595e9991f4c0e1fee99f845210398ad45a74bf5a5c5a8ec31de6815d2e805a23e68c0f8001770e74bc4c17c5b3152aefeffffff01b06a21000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c9878a000000"
+ },
+ {
+ "txid": "f52b22f8c5156fb51122a90b8dc39989c6912c02a69e22bd3148b0692a7cd699",
+ "hash": "a1cb920222f6470dae2b90af42ec4da2e541bc4de9cec11441ca2dd0032bca2c",
+ "version": 1,
+ "size": 254,
+ "vsize": 173,
+ "weight": 689,
+ "locktime": 0,
+ "vin": [
+ {
+ "txid": "e941f2963a9b34caba2663a936b5f029ebd51bb306c4ff2ba0674549880dba33",
+ "vout": 2,
+ "scriptSig": {
+ "asm": "",
+ "hex": ""
+ },
+ "txinwitness": [
+ "304402200d4f214756753861adf835f5d175835d3cd50a19edbf9881572ab7f831a030de0220181b94bbb7d7b0a7a6eee06881a3265cb28405f2d14055e1ff23ac650047193001",
+ "02772467db1e5909e7e9f2b169daccf556e7e2981b145c756db649f3972d913c32"
+ ],
+ "sequence": 4294967295
+ }
+ ],
+ "vout": [
+ {
+ "value": 5.0E-5,
+ "n": 0,
+ "scriptPubKey": {
+ "asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
+ "hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
+ "reqSigs": 1,
+ "type": "scripthash",
+ "addresses": [
+ "2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
+ ]
+ }
+ },
+ {
+ "value": 0.01605126,
+ "n": 1,
+ "scriptPubKey": {
+ "asm": "0 f6da24d081a1b63dcacf4a5762a8ed91fd472c68",
+ "hex": "0014f6da24d081a1b63dcacf4a5762a8ed91fd472c68",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1q7mdzf5yp5xmrmjk0fftk928dj875wtrgkwqr5x"
+ ]
+ }
+ },
+ {
+ "value": 0.12153947,
+ "n": 2,
+ "scriptPubKey": {
+ "asm": "0 4755561caa18d651bf59912545764811d0ab96f6",
+ "hex": "00144755561caa18d651bf59912545764811d0ab96f6",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1qga24v892rrt9r06ejyj52ajgz8g2h9hkwquzg4"
+ ]
+ }
+ }
+ ],
+ "hex": "0100000000010133ba0d88494567a02bffc406b31bd5eb29f0b536a96326baca349b3a96f241e90200000000ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987067e180000000000160014f6da24d081a1b63dcacf4a5762a8ed91fd472c685b74b900000000001600144755561caa18d651bf59912545764811d0ab96f60247304402200d4f214756753861adf835f5d175835d3cd50a19edbf9881572ab7f831a030de0220181b94bbb7d7b0a7a6eee06881a3265cb28405f2d14055e1ff23ac6500471930012102772467db1e5909e7e9f2b169daccf556e7e2981b145c756db649f3972d913c3200000000"
+ },
+ {
+ "txid": "4648da84e1479d222561f72c5211be15510b20129e86c60bdbbdb3e017583a91",
+ "hash": "16e1ae45d2c6ddaaefeed97a822a95a2933096bf377db147a63d9b730cc30d20",
+ "version": 1,
+ "size": 254,
+ "vsize": 173,
+ "weight": 689,
+ "locktime": 0,
+ "vin": [
+ {
+ "txid": "183628048e3b7767e93116c39edc6191311ff3e4666cef10dbda038854c46ee0",
+ "vout": 2,
+ "scriptSig": {
+ "asm": "",
+ "hex": ""
+ },
+ "txinwitness": [
+ "304402203eeb1713b582be1d74bf6a9f95c573dd41baeedf1efd1bc9a1ad1cccad97c4f70220799a399f53f9325f6cf9681b0138f80bd80f3dc900a4d0ab5cc3c97d5be85f1801",
+ "0255f56a7be9f88ccf5885ac2f8cd67320424d533d71083270a2891b2488ffb22b"
+ ],
+ "sequence": 4294967295
+ }
+ ],
+ "vout": [
+ {
+ "value": 5.0E-5,
+ "n": 0,
+ "scriptPubKey": {
+ "asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
+ "hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
+ "reqSigs": 1,
+ "type": "scripthash",
+ "addresses": [
+ "2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
+ ]
+ }
+ },
+ {
+ "value": 0.0060466,
+ "n": 1,
+ "scriptPubKey": {
+ "asm": "0 5230c895305a232ef2a4feb0a91e7d99e22fd515",
+ "hex": "00145230c895305a232ef2a4feb0a91e7d99e22fd515",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1q2gcv39fstg3jau4yl6c2j8nan83zl4g4w5ch6j"
+ ]
+ }
+ },
+ {
+ "value": 0.13765586,
+ "n": 2,
+ "scriptPubKey": {
+ "asm": "0 b6fbbd9053e47891fae7f3db7dd3966062b2513c",
+ "hex": "0014b6fbbd9053e47891fae7f3db7dd3966062b2513c",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1qkmammyznu3ufr7h870dhm5ukvp3ty5fudkl63e"
+ ]
+ }
+ }
+ ],
+ "hex": "01000000000101e06ec4548803dadb10ef6c66e4f31f319161dc9ec31631e967773b8e042836180200000000ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987f4390900000000001600145230c895305a232ef2a4feb0a91e7d99e22fd515d20bd20000000000160014b6fbbd9053e47891fae7f3db7dd3966062b2513c0247304402203eeb1713b582be1d74bf6a9f95c573dd41baeedf1efd1bc9a1ad1cccad97c4f70220799a399f53f9325f6cf9681b0138f80bd80f3dc900a4d0ab5cc3c97d5be85f1801210255f56a7be9f88ccf5885ac2f8cd67320424d533d71083270a2891b2488ffb22b00000000"
+ },
+ {
+ "txid": "d21edc2a9ffd0f90982fcdd0ee11a48bc3f78bf437232354f7ad60740a202a14",
+ "hash": "bae1180680abb989f5b89b5f49729d3eedcf757a12fd0b7649c944a4ba37eaee",
+ "version": 1,
+ "size": 255,
+ "vsize": 173,
+ "weight": 690,
+ "locktime": 0,
+ "vin": [
+ {
+ "txid": "4e2146dbaf82ae1d756e6e61df42cbc6db3409d896ecd80894ceb0d3321c704c",
+ "vout": 2,
+ "scriptSig": {
+ "asm": "",
+ "hex": ""
+ },
+ "txinwitness": [
+ "3045022100ebb8e0ddab46b762e3a9555442cc7ee35c4353d9152e856c97251913902a5056022010d3a0bb51d931a18174dc8ed0ffa37c5ff29f8e924b71d86850de31f3ea4c6e01",
+ "02e0284cdeae6a8c971e2ea5004ebf9196ee9b3037d6f1ed039c4b5672a69cddc6"
+ ],
+ "sequence": 4294967295
+ }
+ ],
+ "vout": [
+ {
+ "value": 5.0E-5,
+ "n": 0,
+ "scriptPubKey": {
+ "asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
+ "hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
+ "reqSigs": 1,
+ "type": "scripthash",
+ "addresses": [
+ "2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
+ ]
+ }
+ },
+ {
+ "value": 0.006,
+ "n": 1,
+ "scriptPubKey": {
+ "asm": "0 89c79bc0628d2d8b1cd91c2ed0e75db13e6f3f3a",
+ "hex": "001489c79bc0628d2d8b1cd91c2ed0e75db13e6f3f3a",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1q38rehsrz35kck8xershdpe6akylx70e63azh0p"
+ ]
+ }
+ },
+ {
+ "value": 1.11150986,
+ "n": 2,
+ "scriptPubKey": {
+ "asm": "0 062d20a6692350b7a39397c50857a7f725788da0",
+ "hex": "0014062d20a6692350b7a39397c50857a7f725788da0",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1qqckjpfnfydgt0gunjlzss4a87ujh3rdq7u4yrr"
+ ]
+ }
+ }
+ ],
+ "hex": "010000000001014c701c32d3b0ce9408d8ec96d80934dbc6cb42df616e6e751dae82afdb46214e0200000000ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987c02709000000000016001489c79bc0628d2d8b1cd91c2ed0e75db13e6f3f3a8a07a00600000000160014062d20a6692350b7a39397c50857a7f725788da002483045022100ebb8e0ddab46b762e3a9555442cc7ee35c4353d9152e856c97251913902a5056022010d3a0bb51d931a18174dc8ed0ffa37c5ff29f8e924b71d86850de31f3ea4c6e012102e0284cdeae6a8c971e2ea5004ebf9196ee9b3037d6f1ed039c4b5672a69cddc600000000"
+ },
+ {
+ "txid": "78b64023700c50e221d68c093cc3fe32a5314e9696bacb1b6d10217d51664d68",
+ "hash": "f072bb4fec0ae128ed678d24475cf719c0dd3ba0b44bf14f7300df60408ee365",
+ "version": 1,
+ "size": 255,
+ "vsize": 173,
+ "weight": 690,
+ "locktime": 0,
+ "vin": [
+ {
+ "txid": "376a560724612cfc3249e8f93f1a4156fc1b62678716290cbd326e16b509f6c3",
+ "vout": 2,
+ "scriptSig": {
+ "asm": "",
+ "hex": ""
+ },
+ "txinwitness": [
+ "30450221008f8eee212f209ba2a179197bd4ba35a35ad7a304599025ddbd192a6e7e64c1920220242c297726948ad408ce54c9a0e0287b283c53dc68323537f24a7e3ecd8c526b01",
+ "03f870bcd3a46e80e4b1236302e62318b412cc97ef096fc976a89deb569dc11ef1"
+ ],
+ "sequence": 4294967295
+ }
+ ],
+ "vout": [
+ {
+ "value": 5.0E-5,
+ "n": 0,
+ "scriptPubKey": {
+ "asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
+ "hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
+ "reqSigs": 1,
+ "type": "scripthash",
+ "addresses": [
+ "2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
+ ]
+ }
+ },
+ {
+ "value": 0.016,
+ "n": 1,
+ "scriptPubKey": {
+ "asm": "0 1437b91493d1929b1b42a80e83229c347c28f937",
+ "hex": "00141437b91493d1929b1b42a80e83229c347c28f937",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1qzsmmj9yn6xffkx6z4q8gxg5ux37z37fhr598r9"
+ ]
+ }
+ },
+ {
+ "value": 1.10145514,
+ "n": 2,
+ "scriptPubKey": {
+ "asm": "0 91ad2cce99e8e4455de5f44559816b98213f3503",
+ "hex": "001491ad2cce99e8e4455de5f44559816b98213f3503",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1qjxkjen5earjy2h0973z4nqttnqsn7dgrnsgy27"
+ ]
+ }
+ }
+ ],
+ "hex": "01000000000101c3f609b5166e32bd0c29168767621bfc56411a3ff9e84932fc2c612407566a370200000000ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987006a1800000000001600141437b91493d1929b1b42a80e83229c347c28f937eaaf90060000000016001491ad2cce99e8e4455de5f44559816b98213f3503024830450221008f8eee212f209ba2a179197bd4ba35a35ad7a304599025ddbd192a6e7e64c1920220242c297726948ad408ce54c9a0e0287b283c53dc68323537f24a7e3ecd8c526b012103f870bcd3a46e80e4b1236302e62318b412cc97ef096fc976a89deb569dc11ef100000000"
+ },
+ {
+ "txid": "dd4243c2743a2d2351814a14628e1976e6fb208e63bd2bbd441180c441205027",
+ "hash": "dd4243c2743a2d2351814a14628e1976e6fb208e63bd2bbd441180c441205027",
+ "version": 1,
+ "size": 252,
+ "vsize": 252,
+ "weight": 1008,
+ "locktime": 0,
+ "vin": [
+ {
+ "txid": "20bd92c145d9d724283c9536355a82e6fd87637963dd7e0c1fb87c9de50a4fba",
+ "vout": 2,
+ "scriptSig": {
+ "asm": "3045022100ba97f6336b3bb3e07cf584010c7b8ab52957e34e462e71252c63f498d51f45b70220708dd78d2d9943f8c176055963ab706870274068fe7b1e5d87592a837336a534[ALL] 039978f14b2463d7d4790cdf2a37c2a3d872dd517ca91db7f6f7a858a7ac661c60",
+ "hex": "483045022100ba97f6336b3bb3e07cf584010c7b8ab52957e34e462e71252c63f498d51f45b70220708dd78d2d9943f8c176055963ab706870274068fe7b1e5d87592a837336a5340121039978f14b2463d7d4790cdf2a37c2a3d872dd517ca91db7f6f7a858a7ac661c60"
+ },
+ "sequence": 4294967295
+ }
+ ],
+ "vout": [
+ {
+ "value": 5.0E-5,
+ "n": 0,
+ "scriptPubKey": {
+ "asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
+ "hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
+ "reqSigs": 1,
+ "type": "scripthash",
+ "addresses": [
+ "2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
+ ]
+ }
+ },
+ {
+ "value": 0.006,
+ "n": 1,
+ "scriptPubKey": {
+ "asm": "0 13afc3a26c010dddaf2410a9d97b5054a4e8d309",
+ "hex": "001413afc3a26c010dddaf2410a9d97b5054a4e8d309",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1qzwhu8gnvqyxamteyzz5aj76s2jjw35cfctjvy8"
+ ]
+ }
+ },
+ {
+ "value": 9.45414148,
+ "n": 2,
+ "scriptPubKey": {
+ "asm": "0 5228ee46a95383b314396dda75e707b8bed83034",
+ "hex": "00145228ee46a95383b314396dda75e707b8bed83034",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1q2g5wu34f2wpmx9pedhd8tec8hzldsvp56hljn8"
+ ]
+ }
+ }
+ ],
+ "hex": "0100000001ba4f0ae59d7cb81f0c7edd63796387fde6825a3536953c2824d7d945c192bd20020000006b483045022100ba97f6336b3bb3e07cf584010c7b8ab52957e34e462e71252c63f498d51f45b70220708dd78d2d9943f8c176055963ab706870274068fe7b1e5d87592a837336a5340121039978f14b2463d7d4790cdf2a37c2a3d872dd517ca91db7f6f7a858a7ac661c60ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987c02709000000000016001413afc3a26c010dddaf2410a9d97b5054a4e8d30904e05938000000001600145228ee46a95383b314396dda75e707b8bed8303400000000"
+ },
+ {
+ "txid": "aa33deb87512c2c417a0a9a17c58a36dcf2686fca8b50cc608ad952178a350c2",
+ "hash": "aa33deb87512c2c417a0a9a17c58a36dcf2686fca8b50cc608ad952178a350c2",
+ "version": 1,
+ "size": 251,
+ "vsize": 251,
+ "weight": 1004,
+ "locktime": 0,
+ "vin": [
+ {
+ "txid": "7ac20c807aac72bc1cc57ef25f61567f74a031952b0a18618bc06be504dc715d",
+ "vout": 2,
+ "scriptSig": {
+ "asm": "3044022073a8a8ee9cc490093e6de5708b3727cef35f41038713fab9a5c235b4b400b73102206f97f4fc8faefb534e85c0f3773f2d67781c9871f681211276619054fc540152[ALL] 0374e07f24beca2270cf305100652149a64c80d76611f775ec276658aeae4ef0b5",
+ "hex": "473044022073a8a8ee9cc490093e6de5708b3727cef35f41038713fab9a5c235b4b400b73102206f97f4fc8faefb534e85c0f3773f2d67781c9871f681211276619054fc54015201210374e07f24beca2270cf305100652149a64c80d76611f775ec276658aeae4ef0b5"
+ },
+ "sequence": 4294967295
+ }
+ ],
+ "vout": [
+ {
+ "value": 5.0E-5,
+ "n": 0,
+ "scriptPubKey": {
+ "asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
+ "hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
+ "reqSigs": 1,
+ "type": "scripthash",
+ "addresses": [
+ "2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
+ ]
+ }
+ },
+ {
+ "value": 0.016,
+ "n": 1,
+ "scriptPubKey": {
+ "asm": "0 1c397ba7ea8410dbd556c51f8184a14ab0151143",
+ "hex": "00141c397ba7ea8410dbd556c51f8184a14ab0151143",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1qrsuhhfl2ssgdh42kc50crp9pf2cp2y2ruplr8l"
+ ]
+ }
+ },
+ {
+ "value": 1.1126122,
+ "n": 2,
+ "scriptPubKey": {
+ "asm": "0 c38c3d890c415f2c13f6925c1ad1d4a7cb4f7dfe",
+ "hex": "0014c38c3d890c415f2c13f6925c1ad1d4a7cb4f7dfe",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1qcwxrmzgvg90jcylkjfwp45w55l957l07mtsydu"
+ ]
+ }
+ }
+ ],
+ "hex": "01000000015d71dc04e56bc08b61180a2b9531a0747f56615ff27ec51cbc72ac7a800cc27a020000006a473044022073a8a8ee9cc490093e6de5708b3727cef35f41038713fab9a5c235b4b400b73102206f97f4fc8faefb534e85c0f3773f2d67781c9871f681211276619054fc54015201210374e07f24beca2270cf305100652149a64c80d76611f775ec276658aeae4ef0b5ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987006a1800000000001600141c397ba7ea8410dbd556c51f8184a14ab015114324b6a10600000000160014c38c3d890c415f2c13f6925c1ad1d4a7cb4f7dfe00000000"
+ },
+ {
+ "txid": "719606705c6832f7180ab9db5e1ce6a51bad80a5cbf3b57b806dd10f3d7d5124",
+ "hash": "c670f473daee2ad2fabc2b8cc9e99d499c19ef9f2d3331345674a0d3b1b96639",
+ "version": 1,
+ "size": 425,
+ "vsize": 263,
+ "weight": 1052,
+ "locktime": 0,
+ "vin": [
+ {
+ "txid": "d21edc2a9ffd0f90982fcdd0ee11a48bc3f78bf437232354f7ad60740a202a14",
+ "vout": 1,
+ "scriptSig": {
+ "asm": "",
+ "hex": ""
+ },
+ "txinwitness": [
+ "30440220073a37eb4371dc3d0cf218d6e9b8e6044275acd07402ccebdf24f65b60a3c1f70220647e71c173f992fc0c5ec6c2b0b1653a95118098269e318c41d1bc33da3ff14f01",
+ "03c3e858472f39d31c6defdf38b4778660501f0ccfa524b3dd8ba61117b7646635"
+ ],
+ "sequence": 4294967295
+ },
+ {
+ "txid": "f52b22f8c5156fb51122a90b8dc39989c6912c02a69e22bd3148b0692a7cd699",
+ "vout": 1,
+ "scriptSig": {
+ "asm": "",
+ "hex": ""
+ },
+ "txinwitness": [
+ "304402202b8ef5de1c56328d3797265272540a054fc04c158b23ee6385b69b14486422c10220749f591fcf4ef995df8f1e7d9aa3cf0c045f1616386b86b419197b360c871fca01",
+ "02c73e60f00bc72b56568a9f371b9122b3ee29d41730670e98ff8da58e7bbfab28"
+ ],
+ "sequence": 4294967295
+ }
+ ],
+ "vout": [
+ {
+ "value": 1.0E-5,
+ "n": 0,
+ "scriptPubKey": {
+ "asm": "0 223d978073802f79e6ecdc7591e5dc1f0ea7030d6466f73c6b90391bc72e886f",
+ "hex": "0020223d978073802f79e6ecdc7591e5dc1f0ea7030d6466f73c6b90391bc72e886f",
+ "reqSigs": 1,
+ "type": "witness_v0_scripthash",
+ "addresses": [
+ "bcrt1qyg7e0qrnsqhhnehvm36erewuru82wqcdv3n0w0rtjqu3h3ew3phs27rfy6"
+ ]
+ }
+ },
+ {
+ "value": 0.0,
+ "n": 1,
+ "scriptPubKey": {
+ "asm": "OP_RETURN 758e9207848c631c6839b1382bb22a52b6ef0645d733389d7be2efb1e8b71454",
+ "hex": "6a20758e9207848c631c6839b1382bb22a52b6ef0645d733389d7be2efb1e8b71454",
+ "type": "nulldata"
+ }
+ },
+ {
+ "value": 0.02201563,
+ "n": 2,
+ "scriptPubKey": {
+ "asm": "0 5e41d2fb8de1c250410416d8dd153c685d3f9c7b",
+ "hex": "00145e41d2fb8de1c250410416d8dd153c685d3f9c7b",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1qteqa97udu8p9qsgyzmvd69fudpwnl8rmt8putj"
+ ]
+ }
+ }
+ ],
+ "hex": "01000000000102142a200a7460adf754232337f48bf7c38ba411eed0cd2f98900ffd9f2adc1ed20100000000ffffffff99d67c2a69b04831bd229ea6022c91c68999c38d0ba92211b56f15c5f8222bf50100000000ffffffff03e803000000000000220020223d978073802f79e6ecdc7591e5dc1f0ea7030d6466f73c6b90391bc72e886f0000000000000000226a20758e9207848c631c6839b1382bb22a52b6ef0645d733389d7be2efb1e8b71454db972100000000001600145e41d2fb8de1c250410416d8dd153c685d3f9c7b024730440220073a37eb4371dc3d0cf218d6e9b8e6044275acd07402ccebdf24f65b60a3c1f70220647e71c173f992fc0c5ec6c2b0b1653a95118098269e318c41d1bc33da3ff14f012103c3e858472f39d31c6defdf38b4778660501f0ccfa524b3dd8ba61117b76466350247304402202b8ef5de1c56328d3797265272540a054fc04c158b23ee6385b69b14486422c10220749f591fcf4ef995df8f1e7d9aa3cf0c045f1616386b86b419197b360c871fca012102c73e60f00bc72b56568a9f371b9122b3ee29d41730670e98ff8da58e7bbfab2800000000"
+ },
+ {
+ "txid": "16cd3283068d2965f26045e15a285e39a762af32ec388ae8c28fb6cb2c468768",
+ "hash": "a854736ce90b603599bf21be28bea909c3ffafaed57d5eacea1f1730b68db0d8",
+ "version": 1,
+ "size": 427,
+ "vsize": 264,
+ "weight": 1054,
+ "locktime": 0,
+ "vin": [
+ {
+ "txid": "4648da84e1479d222561f72c5211be15510b20129e86c60bdbbdb3e017583a91",
+ "vout": 1,
+ "scriptSig": {
+ "asm": "",
+ "hex": ""
+ },
+ "txinwitness": [
+ "3045022100c9665b9abe7fcab10f775eeafc2391c1fee84c50b50df6d697b1db9a7eea5dd3022070cb7aa57b8f5bf9f2eff11263cf2ea871ab7b9ddfc8e47671cee50ada54724301",
+ "02016f9a6cc454bd1e74c28df36a079231a215812c60581d1e1745e650f82bd123"
+ ],
+ "sequence": 4294967295
+ },
+ {
+ "txid": "78b64023700c50e221d68c093cc3fe32a5314e9696bacb1b6d10217d51664d68",
+ "vout": 1,
+ "scriptSig": {
+ "asm": "",
+ "hex": ""
+ },
+ "txinwitness": [
+ "304502210094cdec8e08f32919b3f25c8672041305c848b4206256ad64f7090dc97dfd1bf002205c397a310cebc690fac04d13945e012d0031246d4574e92f97e3701ca729b61401",
+ "029b739486d7cf402b3db3913187fad7897e8a5ec3cd8607e9b5fc54a71958b031"
+ ],
+ "sequence": 4294967295
+ }
+ ],
+ "vout": [
+ {
+ "value": 1.0E-5,
+ "n": 0,
+ "scriptPubKey": {
+ "asm": "0 e3e81046fd9659b5725736efa404bc1c8f9b2ff6f0af7cb7ddacdcc61e1c7231",
+ "hex": "0020e3e81046fd9659b5725736efa404bc1c8f9b2ff6f0af7cb7ddacdcc61e1c7231",
+ "reqSigs": 1,
+ "type": "witness_v0_scripthash",
+ "addresses": [
+ "bcrt1qu05pq3hajevm2ujhxmh6gp9urj8ektlk7zhhed7a4nwvv8suwgcstjh7k7"
+ ]
+ }
+ },
+ {
+ "value": 0.0,
+ "n": 1,
+ "scriptPubKey": {
+ "asm": "OP_RETURN c63aacf2e8be20752b6f689c0308967cbc335641f2948a4a7962fdde6c464730",
+ "hex": "6a20c63aacf2e8be20752b6f689c0308967cbc335641f2948a4a7962fdde6c464730",
+ "type": "nulldata"
+ }
+ },
+ {
+ "value": 0.0220133,
+ "n": 2,
+ "scriptPubKey": {
+ "asm": "0 5e41d2fb8de1c250410416d8dd153c685d3f9c7b",
+ "hex": "00145e41d2fb8de1c250410416d8dd153c685d3f9c7b",
+ "reqSigs": 1,
+ "type": "witness_v0_keyhash",
+ "addresses": [
+ "bcrt1qteqa97udu8p9qsgyzmvd69fudpwnl8rmt8putj"
+ ]
+ }
+ }
+ ],
+ "hex": "01000000000102913a5817e0b3bddb0bc6869e12200b5115be11522cf76125229d47e184da48460100000000ffffffff684d66517d21106d1bcbba96964e31a532fec33c098cd621e2500c702340b6780100000000ffffffff03e803000000000000220020e3e81046fd9659b5725736efa404bc1c8f9b2ff6f0af7cb7ddacdcc61e1c72310000000000000000226a20c63aacf2e8be20752b6f689c0308967cbc335641f2948a4a7962fdde6c464730f2962100000000001600145e41d2fb8de1c250410416d8dd153c685d3f9c7b02483045022100c9665b9abe7fcab10f775eeafc2391c1fee84c50b50df6d697b1db9a7eea5dd3022070cb7aa57b8f5bf9f2eff11263cf2ea871ab7b9ddfc8e47671cee50ada547243012102016f9a6cc454bd1e74c28df36a079231a215812c60581d1e1745e650f82bd1230248304502210094cdec8e08f32919b3f25c8672041305c848b4206256ad64f7090dc97dfd1bf002205c397a310cebc690fac04d13945e012d0031246d4574e92f97e3701ca729b6140121029b739486d7cf402b3db3913187fad7897e8a5ec3cd8607e9b5fc54a71958b03100000000"
+ }
+ ],
+ "time": 1605510591,
+ "mediantime": 1589548514,
+ "nonce": 1,
+ "bits": "207fffff",
+ "difficulty": 4.656542373906925E-10,
+ "chainwork": "0000000000000000000000000000000000000000000000000000000000000118",
+ "nTx": 11,
+ "previousblockhash": "1a477356be8211dd4b3a4162521331a6af9b5999401aad8d147a56a1f28c3da3",
+ "nextblockhash": "7d8267341a57f1f626b450eb22b2dbf208f13ec176e1cc015aa4d2b2ea55016d"
+}
diff --git a/core/src/test/resources/bisq/core/dao/node/full/rpc/getnetworkinfo-result.json b/core/src/test/resources/bisq/core/dao/node/full/rpc/getnetworkinfo-result.json
new file mode 100644
index 00000000000..08e347328f3
--- /dev/null
+++ b/core/src/test/resources/bisq/core/dao/node/full/rpc/getnetworkinfo-result.json
@@ -0,0 +1,52 @@
+{
+ "version": 210000,
+ "subversion": "/Satoshi:0.21.0/",
+ "protocolversion": 70016,
+ "localservices": "000000000100040d",
+ "localservicesnames": [
+ "NETWORK",
+ "BLOOM",
+ "WITNESS",
+ "NETWORK_LIMITED",
+ "MY_CUSTOM_SERVICE"
+ ],
+ "localrelay": true,
+ "timeoffset": -2,
+ "networkactive": true,
+ "connections": 9,
+ "connections_in": 0,
+ "connections_out": 9,
+ "networks": [
+ {
+ "name": "ipv4",
+ "limited": false,
+ "reachable": true,
+ "proxy": "",
+ "proxy_randomize_credentials": false
+ },
+ {
+ "name": "ipv6",
+ "limited": false,
+ "reachable": true,
+ "proxy": "",
+ "proxy_randomize_credentials": false
+ },
+ {
+ "name": "onion",
+ "limited": true,
+ "reachable": false,
+ "proxy": "",
+ "proxy_randomize_credentials": false
+ }
+ ],
+ "relayfee": 1.0E-5,
+ "incrementalfee": 1.0E-5,
+ "localaddresses": [
+ {
+ "address": "2001:0:2851:782c:2c65:3676:fde6:18f8",
+ "port": 8333,
+ "score": 26
+ }
+ ],
+ "warnings": ""
+}
diff --git a/daemon/src/main/resources/logback.xml b/daemon/src/main/resources/logback.xml
index ac5e6444ea0..bc8edf02211 100644
--- a/daemon/src/main/resources/logback.xml
+++ b/daemon/src/main/resources/logback.xml
@@ -10,7 +10,6 @@
-
diff --git a/desktop/src/main/resources/logback.xml b/desktop/src/main/resources/logback.xml
index 6b05588a4fe..c21adbf3255 100644
--- a/desktop/src/main/resources/logback.xml
+++ b/desktop/src/main/resources/logback.xml
@@ -10,7 +10,4 @@
-
-
-
diff --git a/gradle/witness/gradle-witness.gradle b/gradle/witness/gradle-witness.gradle
index 61ffd6bdbe4..661c16a8d62 100644
--- a/gradle/witness/gradle-witness.gradle
+++ b/gradle/witness/gradle-witness.gradle
@@ -16,9 +16,9 @@ dependencyVerification {
'aopalliance:aopalliance:0addec670fedcd3f113c5c8091d783280d23f75e3acb841b61a9cdb079376a08',
'ch.qos.logback:logback-classic:86a0268c3c96888d4e49d8a754b5b2173286aee100559e803efcbb0df676c66e',
'ch.qos.logback:logback-core:58738067842476feeae5768e832cd36a0e40ce41576ba5739c3632d376bd8c86',
- 'com.fasterxml.jackson.core:jackson-annotations:2566b3a6662afa3c6af4f5b25006cb46be2efc68f1b5116291d6998a8cdf7ed3',
- 'com.fasterxml.jackson.core:jackson-core:39a74610521d7fb9eb3f437bb8739bbf47f6435be12d17bf954c731a0c6352bb',
- 'com.fasterxml.jackson.core:jackson-databind:fcf3c2b0c332f5f54604f7e27fa7ee502378a2cc5df6a944bbfae391872c32ff',
+ 'com.fasterxml.jackson.core:jackson-annotations:203cefdfa6c81e6aa84e11f292f29ca97344a3c3bc0293abea065cd837592873',
+ 'com.fasterxml.jackson.core:jackson-core:cc899cb6eae0c80b87d590eea86528797369cc4feb7b79463207d6bb18f0c257',
+ 'com.fasterxml.jackson.core:jackson-databind:f2ca3c28ebded59c98447d51afe945323df961540af66a063c015597af936aa0',
'com.github.JesusMcCloud:jtorctl:389d61b1b5a85eb2f23c582c3913ede49f80c9f2b553e4762382c836270e57e5',
'com.github.bisq-network.netlayer:tor.external:c396f9e6caa034c0fde9634705640b90df8b0dec977c8b7a9c8198f95e0f776a',
'com.github.bisq-network.netlayer:tor.native:744bb5751245053f20a75447d9502be124dc1447c3422303bcc842e2ff10d8e4',
@@ -29,6 +29,7 @@ dependencyVerification {
'com.github.bisq-network.tor-binary:tor-binary-macos:cfa7b726ef5777472374df56bf80b9a0e7bf6721e2f79d9b851b9601f159a5ed',
'com.github.bisq-network.tor-binary:tor-binary-windows:d6240b4859551ed4f7e359771851d7d692d4fc3431563592dfe54502a406e1a0',
'com.github.bisq-network:bitcoinj:65ed08fa5777ea4a08599bdd575e7dc1f4ba2d4d5835472551439d6f6252e68a',
+ 'com.github.bisq-network:jsonrpc4j:842b4a660440ef53cd436da2e21c3e1fed939b620a3fc7542307deb3e77fdeb6',
'com.github.ravn:jsocks:3c71600af027b2b6d4244e4ad14d98ff2352a379410daebefff5d8cd48d742a4',
'com.github.sarxos:webcam-capture:d960b7ea8ec3ddf2df0725ef214c3fccc9699ea7772df37f544e1f8e4fd665f6',
'com.google.android:annotations:ba734e1e84c09d615af6a09d33034b4f0442f8772dec120efb376d86a565ae15',
@@ -68,8 +69,6 @@ dependencyVerification {
'net.glxn:qrgen:c85d9d8512d91e8ad11fe56259a7825bd50ce0245447e236cf168d1b17591882',
'net.jcip:jcip-annotations:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.sf.jopt-simple:jopt-simple:df26cc58f235f477db07f753ba5a3ab243ebe5789d9f89ecf68dd62ea9a66c28',
- 'network.bisq.btcd-cli4j:btcd-cli4j-core:4634b39de93764c4609e295e254e8c3b1427ba24febf43352f4f315029c5b1b3',
- 'network.bisq.btcd-cli4j:btcd-cli4j-daemon:fa3580d2f309e220b9c4f67d0437461fa10cfec75f4468a038b58bdbc36caaee',
'org.apache.commons:commons-compress:5f2df1e467825e4cac5996d44890c4201c000b43c0b23cffc0782d28a0beb9b0',
'org.apache.commons:commons-lang3:9375aad1000cdd5bd3068e832de9802094fac1f145655251e141d5d0072fab9a',
'org.apache.httpcomponents:httpclient:bc5f065aba5dd815ee559dd24d9bcb797fb102ff9cfa036f5091ebc529bd3b93',
diff --git a/inventory/src/main/resources/logback.xml b/inventory/src/main/resources/logback.xml
index 6b05588a4fe..c21adbf3255 100644
--- a/inventory/src/main/resources/logback.xml
+++ b/inventory/src/main/resources/logback.xml
@@ -10,7 +10,4 @@
-
-
-
diff --git a/monitor/src/main/java/bisq/monitor/Metric.java b/monitor/src/main/java/bisq/monitor/Metric.java
index a54a99e9620..08598e1e9cf 100644
--- a/monitor/src/main/java/bisq/monitor/Metric.java
+++ b/monitor/src/main/java/bisq/monitor/Metric.java
@@ -18,6 +18,7 @@
package bisq.monitor;
import bisq.common.app.Version;
+import bisq.common.util.Utilities;
import java.util.Properties;
import java.util.Random;
@@ -141,13 +142,6 @@ public void run() {
* shut down or after one minute.
*/
public static void haltAllMetrics() {
- executor.shutdown();
-
- try {
- if (!Metric.executor.awaitTermination(2, TimeUnit.MINUTES))
- Metric.executor.shutdownNow();
- } catch (InterruptedException e) {
- Metric.executor.shutdownNow();
- }
+ Utilities.shutdownAndAwaitTermination(executor, 2, TimeUnit.MINUTES);
}
}
diff --git a/p2p/src/main/java/bisq/network/http/HttpException.java b/p2p/src/main/java/bisq/network/http/HttpException.java
index c078a274422..303849fbd50 100644
--- a/p2p/src/main/java/bisq/network/http/HttpException.java
+++ b/p2p/src/main/java/bisq/network/http/HttpException.java
@@ -21,11 +21,7 @@
public class HttpException extends Exception {
@Getter
- private int responseCode;
-
- public HttpException(String message) {
- super(message);
- }
+ private final int responseCode;
public HttpException(String message, int responseCode) {
super(message);
diff --git a/p2p/src/main/java/bisq/network/p2p/network/TorNetworkNode.java b/p2p/src/main/java/bisq/network/p2p/network/TorNetworkNode.java
index 9777b0882d6..a3b2aec44ef 100644
--- a/p2p/src/main/java/bisq/network/p2p/network/TorNetworkNode.java
+++ b/p2p/src/main/java/bisq/network/p2p/network/TorNetworkNode.java
@@ -23,6 +23,7 @@
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.proto.network.NetworkProtoResolver;
+import bisq.common.util.Utilities;
import org.berndpruenster.netlayer.tor.HiddenServiceSocket;
import org.berndpruenster.netlayer.tor.Tor;
@@ -31,7 +32,6 @@
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
-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.MoreExecutors;
@@ -55,7 +55,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
@@ -310,13 +309,8 @@ public void run() {
return null;
});
- Futures.addCallback(torStartupFuture, new FutureCallback() {
- public void onSuccess(Void ignore) {
- }
-
- public void onFailure(@NotNull Throwable throwable) {
- UserThread.execute(() -> log.error("Hidden service creation failed: " + throwable));
- }
- }, MoreExecutors.directExecutor());
+ Futures.addCallback(torStartupFuture, Utilities.failureCallback(throwable ->
+ UserThread.execute(() -> log.error("Hidden service creation failed: " + throwable))
+ ), MoreExecutors.directExecutor());
}
}
diff --git a/p2p/src/main/java/bisq/network/p2p/peers/keepalive/KeepAliveManager.java b/p2p/src/main/java/bisq/network/p2p/peers/keepalive/KeepAliveManager.java
index 655bde151c6..c42b67d0d29 100644
--- a/p2p/src/main/java/bisq/network/p2p/peers/keepalive/KeepAliveManager.java
+++ b/p2p/src/main/java/bisq/network/p2p/peers/keepalive/KeepAliveManager.java
@@ -30,6 +30,7 @@
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.proto.network.NetworkEnvelope;
+import bisq.common.util.Utilities;
import javax.inject.Inject;
@@ -110,24 +111,17 @@ public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
Pong pong = new Pong(ping.getNonce());
SettableFuture future = networkNode.sendMessage(connection, pong);
- Futures.addCallback(future, new FutureCallback() {
- @Override
- public void onSuccess(Connection connection) {
+ Futures.addCallback(future, Utilities.failureCallback(throwable -> {
+ if (!stopped) {
+ String errorMessage = "Sending pong to " + connection +
+ " failed. That is expected if the peer is offline. " +
+ "Exception: " + throwable.getMessage();
+ log.info(errorMessage);
+ peerManager.handleConnectionFault(connection);
+ } else {
+ log.warn("We have stopped already. We ignore that networkNode.sendMessage.onFailure call.");
}
-
- @Override
- public void onFailure(@NotNull Throwable throwable) {
- if (!stopped) {
- String errorMessage = "Sending pong to " + connection +
- " failed. That is expected if the peer is offline. " +
- "Exception: " + throwable.getMessage();
- log.info(errorMessage);
- peerManager.handleConnectionFault(connection);
- } else {
- log.warn("We have stopped already. We ignore that networkNode.sendMessage.onFailure call.");
- }
- }
- }, MoreExecutors.directExecutor());
+ }), MoreExecutors.directExecutor());
} else {
log.warn("We have stopped already. We ignore that onMessage call.");
}
diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto
index da52a00af02..dfef1f67d1e 100644
--- a/proto/src/main/proto/pb.proto
+++ b/proto/src/main/proto/pb.proto
@@ -1832,6 +1832,8 @@ enum ScriptType {
WITNESS_V0_KEYHASH = 6;
WITNESS_V0_SCRIPTHASH = 7;
NONSTANDARD = 8;
+ WITNESS_UNKNOWN = 9;
+ WITNESS_V1_TAPROOT = 10;
}
message PubKeyScript {
diff --git a/seednode/src/main/resources/logback.xml b/seednode/src/main/resources/logback.xml
index 47fdc20cfa1..914b9d3d4ba 100644
--- a/seednode/src/main/resources/logback.xml
+++ b/seednode/src/main/resources/logback.xml
@@ -10,8 +10,6 @@
-
-
diff --git a/statsnode/src/main/resources/logback.xml b/statsnode/src/main/resources/logback.xml
index 4a14ad263d5..914b9d3d4ba 100644
--- a/statsnode/src/main/resources/logback.xml
+++ b/statsnode/src/main/resources/logback.xml
@@ -11,6 +11,5 @@
-