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 @@ -