aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml5
-rw-r--r--build_msvc/.gitignore14
-rw-r--r--ci/test/00_setup_env_arm.sh2
-rw-r--r--ci/test/00_setup_env_native_fuzz_with_valgrind.sh18
-rwxr-xr-xci/test/06_script_b.sh2
-rw-r--r--configure.ac1
-rw-r--r--contrib/gitian-descriptors/gitian-win.yml4
-rw-r--r--doc/build-osx.md1
-rw-r--r--doc/developer-notes.md3
-rw-r--r--src/Makefile.test.include5
-rw-r--r--src/chainparams.cpp4
-rw-r--r--src/chainparams.h3
-rw-r--r--src/init.cpp19
-rw-r--r--src/node/context.cpp1
-rw-r--r--src/node/context.h2
-rw-r--r--src/outputtype.cpp14
-rw-r--r--src/rpc/blockchain.cpp222
-rw-r--r--src/rpc/client.cpp1
-rw-r--r--src/rpc/mining.cpp30
-rw-r--r--src/rpc/misc.cpp61
-rw-r--r--src/rpc/net.cpp126
-rw-r--r--src/rpc/rawtransaction.cpp54
-rw-r--r--src/rpc/server.cpp6
-rw-r--r--src/rpc/util.cpp1
-rw-r--r--src/rpc/util.h6
-rw-r--r--src/scheduler.cpp22
-rw-r--r--src/scheduler.h7
-rw-r--r--src/script/descriptor.cpp49
-rw-r--r--src/script/descriptor.h5
-rw-r--r--src/script/signingprovider.h46
-rw-r--r--src/test/bswap_tests.cpp20
-rw-r--r--src/test/denialofservice_tests.cpp10
-rw-r--r--src/test/descriptor_tests.cpp74
-rw-r--r--src/test/scheduler_tests.cpp43
-rw-r--r--src/test/transaction_tests.cpp14
-rw-r--r--src/test/util/setup_common.cpp7
-rw-r--r--src/test/util/setup_common.h1
-rw-r--r--src/test/validation_flush_tests.cpp26
-rw-r--r--src/util/system.cpp10
-rw-r--r--src/util/system.h3
-rw-r--r--src/util/time.cpp14
-rw-r--r--src/wallet/init.cpp2
-rw-r--r--src/wallet/rpcwallet.cpp170
-rw-r--r--src/wallet/scriptpubkeyman.cpp24
-rw-r--r--src/wallet/test/scriptpubkeyman_tests.cpp43
-rw-r--r--src/wallet/wallet.cpp18
-rw-r--r--src/wallet/wallet.h4
-rw-r--r--src/zmq/zmqrpc.cpp6
-rwxr-xr-xtest/functional/feature_backwards_compatibility.py14
-rwxr-xr-xtest/functional/feature_notifications.py18
-rwxr-xr-xtest/functional/mempool_expiry.py100
-rwxr-xr-xtest/functional/test_runner.py1
-rwxr-xr-xtest/fuzz/test_runner.py39
54 files changed, 932 insertions, 465 deletions
diff --git a/.gitignore b/.gitignore
index bc8751c805..1c487f43a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -90,7 +90,7 @@ src/qt/bitcoin-qt.includes
*.qm
Makefile
!depends/Makefile
-bitcoin-qt
+src/qt/bitcoin-qt
Bitcoin-Qt.app
background.tiff*
diff --git a/.travis.yml b/.travis.yml
index c5b8c476b7..9a111c03ad 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -135,6 +135,11 @@ jobs:
FILE_ENV="./ci/test/00_setup_env_native_fuzz.sh"
- stage: test
+ name: 'x86_64 Linux [GOAL: install] [bionic] [no depends, only system libs, fuzzers under valgrind]'
+ env: >-
+ FILE_ENV="./ci/test/00_setup_env_native_fuzz_with_valgrind.sh"
+
+ - stage: test
name: 'x86_64 Linux [GOAL: install] [bionic] [no wallet]'
env: >-
FILE_ENV="./ci/test/00_setup_env_native_nowallet.sh"
diff --git a/build_msvc/.gitignore b/build_msvc/.gitignore
index 4d4aef7e35..3e71c7b8d3 100644
--- a/build_msvc/.gitignore
+++ b/build_msvc/.gitignore
@@ -8,7 +8,19 @@ packages/*
*/Release
*/x64
*.vcxproj.user
-*.vcxproj
+
+# .vcxproj files that are auto-generated by the msvc-autogen.py script.
+libbitcoin_cli/libbitcoin_cli.vcxproj
+libbitcoin_common/libbitcoin_common.vcxproj
+libbitcoin_crypto/libbitcoin_crypto.vcxproj
+libbitcoin_server/libbitcoin_server.vcxproj
+libbitcoin_util/libbitcoin_util.vcxproj
+libbitcoin_wallet_tool/libbitcoin_wallet_tool.vcxproj
+libbitcoin_wallet/libbitcoin_wallet.vcxproj
+libbitcoin_zmq/libbitcoin_zmq.vcxproj
+bench_bitcoin/bench_bitcoin.vcxproj
+libtest_util/libtest_util.vcxproj
+
*/Win32
libbitcoin_qt/QtGeneratedFiles/*
test_bitcoin-qt/QtGeneratedFiles/*
diff --git a/ci/test/00_setup_env_arm.sh b/ci/test/00_setup_env_arm.sh
index 86dd423eab..2b30b4a5e3 100644
--- a/ci/test/00_setup_env_arm.sh
+++ b/ci/test/00_setup_env_arm.sh
@@ -25,4 +25,4 @@ export RUN_FUNCTIONAL_TESTS=true
export GOAL="install"
# -Wno-psabi is to disable ABI warnings: "note: parameter passing for argument of type ... changed in GCC 7.1"
# This could be removed once the ABI change warning does not show up by default
-export BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports CXXFLAGS=-Wno-psabi"
+export BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports CXXFLAGS=-Wno-psabi --enable-werror"
diff --git a/ci/test/00_setup_env_native_fuzz_with_valgrind.sh b/ci/test/00_setup_env_native_fuzz_with_valgrind.sh
new file mode 100644
index 0000000000..45b13a669d
--- /dev/null
+++ b/ci/test/00_setup_env_native_fuzz_with_valgrind.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2019 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+export LC_ALL=C.UTF-8
+
+export CONTAINER_NAME=ci_native_fuzz_valgrind
+export PACKAGES="clang-8 llvm-8 python3 libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev valgrind"
+export NO_DEPENDS=1
+export RUN_UNIT_TESTS=false
+export RUN_FUNCTIONAL_TESTS=false
+export RUN_FUZZ_TESTS=true
+export FUZZ_TESTS_CONFIG="--valgrind"
+export GOAL="install"
+export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer CC=clang-8 CXX=clang++-8"
+# Use clang-8, instead of default clang on bionic, which is clang-6 and does not come with libfuzzer on aarch64
diff --git a/ci/test/06_script_b.sh b/ci/test/06_script_b.sh
index 537493a710..3b32513353 100755
--- a/ci/test/06_script_b.sh
+++ b/ci/test/06_script_b.sh
@@ -36,6 +36,6 @@ fi
if [ "$RUN_FUZZ_TESTS" = "true" ]; then
BEGIN_FOLD fuzz-tests
- DOCKER_EXEC test/fuzz/test_runner.py -l DEBUG ${DIR_FUZZ_IN}
+ DOCKER_EXEC test/fuzz/test_runner.py ${FUZZ_TESTS_CONFIG} -l DEBUG ${DIR_FUZZ_IN}
END_FOLD
fi
diff --git a/configure.ac b/configure.ac
index dc233c8423..d0fcf48713 100644
--- a/configure.ac
+++ b/configure.ac
@@ -330,6 +330,7 @@ if test "x$enable_werror" = "xyes"; then
AX_CHECK_COMPILE_FLAG([-Werror=thread-safety-analysis],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=thread-safety-analysis"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Werror=unused-variable],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=unused-variable"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Werror=date-time],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=date-time"],,[[$CXXFLAG_WERROR]])
+ AX_CHECK_COMPILE_FLAG([-Werror=return-type],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=return-type"],,[[$CXXFLAG_WERROR]])
fi
if test "x$CXXFLAGS_overridden" = "xno"; then
diff --git a/contrib/gitian-descriptors/gitian-win.yml b/contrib/gitian-descriptors/gitian-win.yml
index de2e45190a..a69439369e 100644
--- a/contrib/gitian-descriptors/gitian-win.yml
+++ b/contrib/gitian-descriptors/gitian-win.yml
@@ -34,8 +34,8 @@ script: |
CONFIGFLAGS="--enable-reduce-exports --disable-bench --disable-gui-tests"
FAKETIME_HOST_PROGS="ar ranlib nm windres strip objcopy"
FAKETIME_PROGS="date makensis zip"
- HOST_CFLAGS="-O2 -g"
- HOST_CXXFLAGS="-O2 -g"
+ HOST_CFLAGS="-O2 -g -fno-ident"
+ HOST_CXXFLAGS="-O2 -g -fno-ident"
export QT_RCC_TEST=1
export QT_RCC_SOURCE_DATE_OVERRIDE=1
diff --git a/doc/build-osx.md b/doc/build-osx.md
index 86b5c5b602..7b76117c8b 100644
--- a/doc/build-osx.md
+++ b/doc/build-osx.md
@@ -22,6 +22,7 @@ Then install [Homebrew](https://brew.sh).
brew install automake berkeley-db4 libtool boost miniupnpc pkg-config python qt libevent qrencode
```
+If you run into issues, check [Homebrew's troubleshooting page](https://docs.brew.sh/Troubleshooting).
See [dependencies.md](dependencies.md) for a complete overview.
If you want to build the disk image with `make deploy` (.dmg / optional), you need RSVG:
diff --git a/doc/developer-notes.md b/doc/developer-notes.md
index a82ecee7d8..d106aab3e4 100644
--- a/doc/developer-notes.md
+++ b/doc/developer-notes.md
@@ -1089,7 +1089,8 @@ A few guidelines for introducing and reviewing new RPC interfaces:
new RPC is replacing a deprecated RPC, to avoid both RPCs confusingly
showing up in the command list.
-- Use *invalid* bech32 addresses for `RPCExamples` help documentation.
+- Use *invalid* bech32 addresses (e.g. the constant `EXAMPLE_ADDRESS`) for
+ `RPCExamples` help documentation.
- *Rationale*: Prevent accidental transactions by users and encourage the use
of bech32 addresses by default.
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index c76f30de8e..8992d22b34 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -209,7 +209,8 @@ BITCOIN_TESTS += \
wallet/test/wallet_crypto_tests.cpp \
wallet/test/coinselector_tests.cpp \
wallet/test/init_tests.cpp \
- wallet/test/ismine_tests.cpp
+ wallet/test/ismine_tests.cpp \
+ wallet/test/scriptpubkeyman_tests.cpp
BITCOIN_TEST_SUITE += \
wallet/test/wallet_test_fixture.cpp \
@@ -639,7 +640,7 @@ endif
%.cpp.test: %.cpp
@echo Running tests: `cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1` from $<
- $(AM_V_at)$(TEST_BINARY) -l test_suite -t "`cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1`" > $<.log 2>&1 || (cat $<.log && false)
+ $(AM_V_at)$(TEST_BINARY) --catch_system_errors=no -l test_suite -t "`cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1`" > $<.log 2>&1 || (cat $<.log && false)
%.json.h: %.json
@$(MKDIR_P) $(@D)
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index 31592b0f0a..a9183ac970 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -135,6 +135,7 @@ public:
fDefaultConsistencyChecks = false;
fRequireStandard = true;
m_is_test_chain = false;
+ m_is_mockable_chain = false;
checkpointData = {
{
@@ -231,7 +232,7 @@ public:
fDefaultConsistencyChecks = false;
fRequireStandard = false;
m_is_test_chain = true;
-
+ m_is_mockable_chain = false;
checkpointData = {
{
@@ -303,6 +304,7 @@ public:
fDefaultConsistencyChecks = true;
fRequireStandard = true;
m_is_test_chain = true;
+ m_is_mockable_chain = true;
checkpointData = {
{
diff --git a/src/chainparams.h b/src/chainparams.h
index 63398e587e..379c75e4be 100644
--- a/src/chainparams.h
+++ b/src/chainparams.h
@@ -68,6 +68,8 @@ public:
bool RequireStandard() const { return fRequireStandard; }
/** If this chain is exclusively used for testing */
bool IsTestChain() const { return m_is_test_chain; }
+ /** If this chain allows time to be mocked */
+ bool IsMockableChain() const { return m_is_mockable_chain; }
uint64_t PruneAfterHeight() const { return nPruneAfterHeight; }
/** Minimum free space (in GB) needed for data directory */
uint64_t AssumedBlockchainSize() const { return m_assumed_blockchain_size; }
@@ -102,6 +104,7 @@ protected:
bool fDefaultConsistencyChecks;
bool fRequireStandard;
bool m_is_test_chain;
+ bool m_is_mockable_chain;
CCheckpointData checkpointData;
ChainTxData chainTxData;
};
diff --git a/src/init.cpp b/src/init.cpp
index 90d2624c7f..24a1fd27db 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -157,7 +157,6 @@ NODISCARD static bool CreatePidFile()
static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle;
static boost::thread_group threadGroup;
-static CScheduler scheduler;
void Interrupt(NodeContext& node)
{
@@ -295,6 +294,7 @@ void Shutdown(NodeContext& node)
globalVerifyHandle.reset();
ECC_Stop();
if (node.mempool) node.mempool = nullptr;
+ node.scheduler.reset();
LogPrintf("%s: done\n", __func__);
}
@@ -1268,16 +1268,19 @@ bool AppInitMain(NodeContext& node)
}
}
+ assert(!node.scheduler);
+ node.scheduler = MakeUnique<CScheduler>();
+
// Start the lightweight task scheduler thread
- CScheduler::Function serviceLoop = std::bind(&CScheduler::serviceQueue, &scheduler);
+ CScheduler::Function serviceLoop = [&node]{ node.scheduler->serviceQueue(); };
threadGroup.create_thread(std::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop));
// Gather some entropy once per minute.
- scheduler.scheduleEvery([]{
+ node.scheduler->scheduleEvery([]{
RandAddPeriodic();
}, 60000);
- GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);
+ GetMainSignals().RegisterBackgroundSignalScheduler(*node.scheduler);
// Create client interfaces for wallets that are supposed to be loaded
// according to -wallet and -disablewallet options. This only constructs
@@ -1327,7 +1330,7 @@ bool AppInitMain(NodeContext& node)
assert(!node.connman);
node.connman = std::unique_ptr<CConnman>(new CConnman(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max())));
- node.peer_logic.reset(new PeerLogicValidation(node.connman.get(), node.banman.get(), scheduler));
+ node.peer_logic.reset(new PeerLogicValidation(node.connman.get(), node.banman.get(), *node.scheduler));
RegisterValidationInterface(node.peer_logic.get());
// sanitize comments per BIP-0014, format user agent and check total size
@@ -1819,7 +1822,7 @@ bool AppInitMain(NodeContext& node)
connOptions.m_specified_outgoing = connect;
}
}
- if (!node.connman->Start(scheduler, connOptions)) {
+ if (!node.connman->Start(*node.scheduler, connOptions)) {
return false;
}
@@ -1848,11 +1851,11 @@ bool AppInitMain(NodeContext& node)
uiInterface.InitMessage(_("Done loading").translated);
for (const auto& client : node.chain_clients) {
- client->start(scheduler);
+ client->start(*node.scheduler);
}
BanMan* banman = node.banman.get();
- scheduler.scheduleEvery([banman]{
+ node.scheduler->scheduleEvery([banman]{
banman->DumpBanlist();
}, DUMP_BANS_INTERVAL * 1000);
diff --git a/src/node/context.cpp b/src/node/context.cpp
index 26a01420c8..5b19a41bd4 100644
--- a/src/node/context.cpp
+++ b/src/node/context.cpp
@@ -8,6 +8,7 @@
#include <interfaces/chain.h>
#include <net.h>
#include <net_processing.h>
+#include <scheduler.h>
NodeContext::NodeContext() {}
NodeContext::~NodeContext() {}
diff --git a/src/node/context.h b/src/node/context.h
index dab5b5d048..1c592b456b 100644
--- a/src/node/context.h
+++ b/src/node/context.h
@@ -10,6 +10,7 @@
class BanMan;
class CConnman;
+class CScheduler;
class CTxMemPool;
class PeerLogicValidation;
namespace interfaces {
@@ -34,6 +35,7 @@ struct NodeContext {
std::unique_ptr<BanMan> banman;
std::unique_ptr<interfaces::Chain> chain;
std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients;
+ std::unique_ptr<CScheduler> scheduler;
//! Declare default constructor and destructor that are not inline, so code
//! instantiating the NodeContext struct doesn't need to #include class
diff --git a/src/outputtype.cpp b/src/outputtype.cpp
index 567eecb5c9..71b5cba01c 100644
--- a/src/outputtype.cpp
+++ b/src/outputtype.cpp
@@ -82,30 +82,22 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore,
{
// Add script to keystore
keystore.AddCScript(script);
- ScriptHash sh(script);
// Note that scripts over 520 bytes are not yet supported.
switch (type) {
case OutputType::LEGACY:
- keystore.AddCScript(GetScriptForDestination(sh));
- return sh;
+ return ScriptHash(script);
case OutputType::P2SH_SEGWIT:
case OutputType::BECH32: {
CTxDestination witdest = WitnessV0ScriptHash(script);
CScript witprog = GetScriptForDestination(witdest);
// Check if the resulting program is solvable (i.e. doesn't use an uncompressed key)
- if (!IsSolvable(keystore, witprog)) {
- // Since the wsh is invalid, add and return the sh instead.
- keystore.AddCScript(GetScriptForDestination(sh));
- return sh;
- }
+ if (!IsSolvable(keystore, witprog)) return ScriptHash(script);
// Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours.
keystore.AddCScript(witprog);
if (type == OutputType::BECH32) {
return witdest;
} else {
- ScriptHash sh_w = ScriptHash(witprog);
- keystore.AddCScript(GetScriptForDestination(sh_w));
- return sh_w;
+ return ScriptHash(witprog);
}
}
default: assert(false);
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index eb5148eebd..9b06aba22b 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -229,7 +229,7 @@ static UniValue waitfornewblock(const JSONRPCRequest& request)
RPCResult{
"{ (json object)\n"
" \"hash\" : { (string) The blockhash\n"
- " \"height\" : { (int) Block height\n"
+ " \"height\" : { (numeric) Block height\n"
"}\n"
},
RPCExamples{
@@ -269,7 +269,7 @@ static UniValue waitforblock(const JSONRPCRequest& request)
RPCResult{
"{ (json object)\n"
" \"hash\" : { (string) The blockhash\n"
- " \"height\" : { (int) Block height\n"
+ " \"height\" : { (numeric) Block height\n"
"}\n"
},
RPCExamples{
@@ -313,7 +313,7 @@ static UniValue waitforblockheight(const JSONRPCRequest& request)
RPCResult{
"{ (json object)\n"
" \"hash\" : { (string) The blockhash\n"
- " \"height\" : { (int) Block height\n"
+ " \"height\" : { (numeric) Block height\n"
"}\n"
},
RPCExamples{
@@ -400,10 +400,10 @@ static std::string EntryDescriptionString()
" \"ancestor\" : n, (numeric) modified fees (see above) of in-mempool ancestors (including this one) in " + CURRENCY_UNIT + "\n"
" \"descendant\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one) in " + CURRENCY_UNIT + "\n"
" }\n"
- " \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n"
+ " \"depends\" : [ (json array) unconfirmed transactions used as inputs for this transaction\n"
" \"transactionid\", (string) parent transaction id\n"
" ... ]\n"
- " \"spentby\" : [ (array) unconfirmed transactions spending outputs from this transaction\n"
+ " \"spentby\" : [ (json array) unconfirmed transactions spending outputs from this transaction\n"
" \"transactionid\", (string) child transaction id\n"
" ... ]\n"
" \"bip125-replaceable\" : true|false, (boolean) Whether this transaction could be replaced due to BIP125 (replace-by-fee)\n";
@@ -984,14 +984,14 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
{},
RPCResult{
"{\n"
- " \"height\":n, (numeric) The current block height (index)\n"
- " \"bestblock\": \"hex\", (string) The hash of the block at the tip of the chain\n"
- " \"transactions\": n, (numeric) The number of transactions with unspent outputs\n"
- " \"txouts\": n, (numeric) The number of unspent transaction outputs\n"
- " \"bogosize\": n, (numeric) A meaningless metric for UTXO set size\n"
+ " \"height\" : n, (numeric) The current block height (index)\n"
+ " \"bestblock\" : \"hex\", (string) The hash of the block at the tip of the chain\n"
+ " \"transactions\" : n, (numeric) The number of transactions with unspent outputs\n"
+ " \"txouts\" : n, (numeric) The number of unspent transaction outputs\n"
+ " \"bogosize\" : n, (numeric) A meaningless metric for UTXO set size\n"
" \"hash_serialized_2\": \"hash\", (string) The serialized hash\n"
- " \"disk_size\": n, (numeric) The estimated size of the chainstate on disk\n"
- " \"total_amount\": x.xxx (numeric) The total amount\n"
+ " \"disk_size\" : n, (numeric) The estimated size of the chainstate on disk\n"
+ " \"total_amount\" : x.xxx (numeric) The total amount\n"
"}\n"
},
RPCExamples{
@@ -1032,7 +1032,7 @@ UniValue gettxout(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
- " \"bestblock\": \"hash\", (string) The hash of the block at the tip of the chain\n"
+ " \"bestblock\" : \"hash\", (string) The hash of the block at the tip of the chain\n"
" \"confirmations\" : n, (numeric) The number of confirmations\n"
" \"value\" : x.xxx, (numeric) The transaction value in " + CURRENCY_UNIT + "\n"
" \"scriptPubKey\" : { (json object)\n"
@@ -1204,39 +1204,39 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
{},
RPCResult{
"{\n"
- " \"chain\": \"xxxx\", (string) current network name (main, test, regtest)\n"
- " \"blocks\": xxxxxx, (numeric) the height of the most-work fully-validated chain. The genesis block has height 0\n"
- " \"headers\": xxxxxx, (numeric) the current number of headers we have validated\n"
- " \"bestblockhash\": \"...\", (string) the hash of the currently best block\n"
- " \"difficulty\": xxxxxx, (numeric) the current difficulty\n"
- " \"mediantime\": xxxxxx, (numeric) median time for the current best block\n"
- " \"verificationprogress\": xxxx, (numeric) estimate of verification progress [0..1]\n"
- " \"initialblockdownload\": xxxx, (bool) (debug information) estimate of whether this node is in Initial Block Download mode.\n"
- " \"chainwork\": \"xxxx\" (string) total amount of work in active chain, in hexadecimal\n"
- " \"size_on_disk\": xxxxxx, (numeric) the estimated size of the block and undo files on disk\n"
- " \"pruned\": xx, (boolean) if the blocks are subject to pruning\n"
- " \"pruneheight\": xxxxxx, (numeric) lowest-height complete block stored (only present if pruning is enabled)\n"
- " \"automatic_pruning\": xx, (boolean) whether automatic pruning is enabled (only present if pruning is enabled)\n"
- " \"prune_target_size\": xxxxxx, (numeric) the target size used by pruning (only present if automatic pruning is enabled)\n"
- " \"softforks\": { (object) status of softforks\n"
+ " \"chain\" : \"xxxx\", (string) current network name (main, test, regtest)\n"
+ " \"blocks\" : xxxxxx, (numeric) the height of the most-work fully-validated chain. The genesis block has height 0\n"
+ " \"headers\" : xxxxxx, (numeric) the current number of headers we have validated\n"
+ " \"bestblockhash\" : \"...\", (string) the hash of the currently best block\n"
+ " \"difficulty\" : xxxxxx, (numeric) the current difficulty\n"
+ " \"mediantime\" : xxxxxx, (numeric) median time for the current best block\n"
+ " \"verificationprogress\" : xxxx, (numeric) estimate of verification progress [0..1]\n"
+ " \"initialblockdownload\" : xxxx, (boolean) (debug information) estimate of whether this node is in Initial Block Download mode.\n"
+ " \"chainwork\" : \"xxxx\" (string) total amount of work in active chain, in hexadecimal\n"
+ " \"size_on_disk\" : xxxxxx, (numeric) the estimated size of the block and undo files on disk\n"
+ " \"pruned\" : xx, (boolean) if the blocks are subject to pruning\n"
+ " \"pruneheight\" : xxxxxx, (numeric) lowest-height complete block stored (only present if pruning is enabled)\n"
+ " \"automatic_pruning\" : xx, (boolean) whether automatic pruning is enabled (only present if pruning is enabled)\n"
+ " \"prune_target_size\" : xxxxxx, (numeric) the target size used by pruning (only present if automatic pruning is enabled)\n"
+ " \"softforks\" : { (json object) status of softforks\n"
" \"xxxx\" : { (string) name of the softfork\n"
- " \"type\": \"xxxx\", (string) one of \"buried\", \"bip9\"\n"
- " \"bip9\": { (object) status of bip9 softforks (only for \"bip9\" type)\n"
- " \"status\": \"xxxx\", (string) one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\"\n"
- " \"bit\": xx, (numeric) the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)\n"
- " \"start_time\": xx, (numeric) the minimum median time past of a block at which the bit gains its meaning\n"
- " \"timeout\": xx, (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in\n"
- " \"since\": xx, (numeric) height of the first block to which the status applies\n"
- " \"statistics\": { (object) numeric statistics about BIP9 signalling for a softfork\n"
- " \"period\": xx, (numeric) the length in blocks of the BIP9 signalling period \n"
- " \"threshold\": xx, (numeric) the number of blocks with the version bit set required to activate the feature \n"
- " \"elapsed\": xx, (numeric) the number of blocks elapsed since the beginning of the current period \n"
- " \"count\": xx, (numeric) the number of blocks with the version bit set in the current period \n"
- " \"possible\": xx (boolean) returns false if there are not enough blocks left in this period to pass activation threshold \n"
+ " \"type\" : \"xxxx\", (string) one of \"buried\", \"bip9\"\n"
+ " \"bip9\": { (json object) status of bip9 softforks (only for \"bip9\" type)\n"
+ " \"status\" : \"xxxx\", (string) one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\"\n"
+ " \"bit\" : xx, (numeric) the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)\n"
+ " \"start_time\" : xx, (numeric) the minimum median time past of a block at which the bit gains its meaning\n"
+ " \"timeout\" : xx, (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in\n"
+ " \"since\" : xx, (numeric) height of the first block to which the status applies\n"
+ " \"statistics\" : { (json object) numeric statistics about BIP9 signalling for a softfork\n"
+ " \"period\" : xx, (numeric) the length in blocks of the BIP9 signalling period \n"
+ " \"threshold\" : xx, (numeric) the number of blocks with the version bit set required to activate the feature \n"
+ " \"elapsed\" : xx, (numeric) the number of blocks elapsed since the beginning of the current period \n"
+ " \"count\" : xx, (numeric) the number of blocks with the version bit set in the current period \n"
+ " \"possible\" : xx (boolean) returns false if there are not enough blocks left in this period to pass activation threshold \n"
" }\n"
" },\n"
- " \"height\": \"xxxxxx\", (numeric) height of the first block which the rules are or will be enforced (only for \"buried\" type, or \"bip9\" type with \"active\" status)\n"
- " \"active\": xx, (boolean) true if the rules are enforced for the mempool and the next block\n"
+ " \"height\" : \"xxxxxx\", (numeric) height of the first block which the rules are or will be enforced (only for \"buried\" type, or \"bip9\" type with \"active\" status)\n"
+ " \"active\" : xx, (boolean) true if the rules are enforced for the mempool and the next block\n"
" }\n"
" }\n"
" \"warnings\" : \"...\", (string) any network and blockchain warnings.\n"
@@ -1318,16 +1318,16 @@ static UniValue getchaintips(const JSONRPCRequest& request)
RPCResult{
"[\n"
" {\n"
- " \"height\": xxxx, (numeric) height of the chain tip\n"
- " \"hash\": \"xxxx\", (string) block hash of the tip\n"
- " \"branchlen\": 0 (numeric) zero for main chain\n"
- " \"status\": \"active\" (string) \"active\" for the main chain\n"
+ " \"height\" : xxxx, (numeric) height of the chain tip\n"
+ " \"hash\" : \"xxxx\", (string) block hash of the tip\n"
+ " \"branchlen\" : 0 (numeric) zero for main chain\n"
+ " \"status\" : \"active\" (string) \"active\" for the main chain\n"
" },\n"
" {\n"
- " \"height\": xxxx,\n"
- " \"hash\": \"xxxx\",\n"
- " \"branchlen\": 1 (numeric) length of branch connecting the tip to the main chain\n"
- " \"status\": \"xxxx\" (string) status of the chain (active, valid-fork, valid-headers, headers-only, invalid)\n"
+ " \"height\" : xxxx,\n"
+ " \"hash\" : \"xxxx\",\n"
+ " \"branchlen\" : 1 (numeric) length of branch connecting the tip to the main chain\n"
+ " \"status\" : \"xxxx\" (string) status of the chain (active, valid-fork, valid-headers, headers-only, invalid)\n"
" }\n"
"]\n"
"Possible values for status:\n"
@@ -1437,13 +1437,13 @@ static UniValue getmempoolinfo(const JSONRPCRequest& request)
{},
RPCResult{
"{\n"
- " \"loaded\": true|false (boolean) True if the mempool is fully loaded\n"
- " \"size\": xxxxx, (numeric) Current tx count\n"
- " \"bytes\": xxxxx, (numeric) Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted\n"
- " \"usage\": xxxxx, (numeric) Total memory usage for the mempool\n"
- " \"maxmempool\": xxxxx, (numeric) Maximum memory usage for the mempool\n"
- " \"mempoolminfee\": xxxxx (numeric) Minimum fee rate in " + CURRENCY_UNIT + "/kB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee\n"
- " \"minrelaytxfee\": xxxxx (numeric) Current minimum relay fee for transactions\n"
+ " \"loaded\" : true|false (boolean) True if the mempool is fully loaded\n"
+ " \"size\" : xxxxx, (numeric) Current tx count\n"
+ " \"bytes\" : xxxxx, (numeric) Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted\n"
+ " \"usage\" : xxxxx, (numeric) Total memory usage for the mempool\n"
+ " \"maxmempool\" : xxxxx, (numeric) Maximum memory usage for the mempool\n"
+ " \"mempoolminfee\" : xxxxx (numeric) Minimum fee rate in " + CURRENCY_UNIT + "/kB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee\n"
+ " \"minrelaytxfee\" : xxxxx (numeric) Current minimum relay fee for transactions\n"
"}\n"
},
RPCExamples{
@@ -1577,14 +1577,14 @@ static UniValue getchaintxstats(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
- " \"time\": xxxxx, (numeric) The timestamp for the final block in the window, expressed in " + UNIX_EPOCH_TIME + ".\n"
- " \"txcount\": xxxxx, (numeric) The total number of transactions in the chain up to that point.\n"
- " \"window_final_block_hash\": \"...\", (string) The hash of the final block in the window.\n"
- " \"window_final_block_height\": xxxxx, (numeric) The height of the final block in the window.\n"
- " \"window_block_count\": xxxxx, (numeric) Size of the window in number of blocks.\n"
- " \"window_tx_count\": xxxxx, (numeric) The number of transactions in the window. Only returned if \"window_block_count\" is > 0.\n"
- " \"window_interval\": xxxxx, (numeric) The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0.\n"
- " \"txrate\": x.xx, (numeric) The average rate of transactions per second in the window. Only returned if \"window_interval\" is > 0.\n"
+ " \"time\" : xxxxx, (numeric) The timestamp for the final block in the window, expressed in " + UNIX_EPOCH_TIME + ".\n"
+ " \"txcount\" : xxxxx, (numeric) The total number of transactions in the chain up to that point.\n"
+ " \"window_final_block_hash\" : \"...\", (string) The hash of the final block in the window.\n"
+ " \"window_final_block_height\" : xxxxx, (numeric) The height of the final block in the window.\n"
+ " \"window_block_count\" : xxxxx, (numeric) Size of the window in number of blocks.\n"
+ " \"window_tx_count\" : xxxxx, (numeric) The number of transactions in the window. Only returned if \"window_block_count\" is > 0.\n"
+ " \"window_interval\" : xxxxx, (numeric) The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0.\n"
+ " \"txrate\" : x.xx, (numeric) The average rate of transactions per second in the window. Only returned if \"window_interval\" is > 0.\n"
"}\n"
},
RPCExamples{
@@ -1716,41 +1716,41 @@ static UniValue getblockstats(const JSONRPCRequest& request)
},
RPCResult{
"{ (json object)\n"
- " \"avgfee\": xxxxx, (numeric) Average fee in the block\n"
- " \"avgfeerate\": xxxxx, (numeric) Average feerate (in satoshis per virtual byte)\n"
- " \"avgtxsize\": xxxxx, (numeric) Average transaction size\n"
- " \"blockhash\": xxxxx, (string) The block hash (to check for potential reorgs)\n"
- " \"feerate_percentiles\": [ (array of numeric) Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)\n"
+ " \"avgfee\" : xxxxx, (numeric) Average fee in the block\n"
+ " \"avgfeerate\" : xxxxx, (numeric) Average feerate (in satoshis per virtual byte)\n"
+ " \"avgtxsize\" : xxxxx, (numeric) Average transaction size\n"
+ " \"blockhash\" : xxxxx, (string) The block hash (to check for potential reorgs)\n"
+ " \"feerate_percentiles\" : [ (array of numeric) Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)\n"
" \"10th_percentile_feerate\", (numeric) The 10th percentile feerate\n"
" \"25th_percentile_feerate\", (numeric) The 25th percentile feerate\n"
" \"50th_percentile_feerate\", (numeric) The 50th percentile feerate\n"
" \"75th_percentile_feerate\", (numeric) The 75th percentile feerate\n"
" \"90th_percentile_feerate\", (numeric) The 90th percentile feerate\n"
" ],\n"
- " \"height\": xxxxx, (numeric) The height of the block\n"
- " \"ins\": xxxxx, (numeric) The number of inputs (excluding coinbase)\n"
- " \"maxfee\": xxxxx, (numeric) Maximum fee in the block\n"
- " \"maxfeerate\": xxxxx, (numeric) Maximum feerate (in satoshis per virtual byte)\n"
- " \"maxtxsize\": xxxxx, (numeric) Maximum transaction size\n"
- " \"medianfee\": xxxxx, (numeric) Truncated median fee in the block\n"
- " \"mediantime\": xxxxx, (numeric) The block median time past\n"
- " \"mediantxsize\": xxxxx, (numeric) Truncated median transaction size\n"
- " \"minfee\": xxxxx, (numeric) Minimum fee in the block\n"
- " \"minfeerate\": xxxxx, (numeric) Minimum feerate (in satoshis per virtual byte)\n"
- " \"mintxsize\": xxxxx, (numeric) Minimum transaction size\n"
- " \"outs\": xxxxx, (numeric) The number of outputs\n"
- " \"subsidy\": xxxxx, (numeric) The block subsidy\n"
- " \"swtotal_size\": xxxxx, (numeric) Total size of all segwit transactions\n"
- " \"swtotal_weight\": xxxxx, (numeric) Total weight of all segwit transactions divided by segwit scale factor (4)\n"
- " \"swtxs\": xxxxx, (numeric) The number of segwit transactions\n"
- " \"time\": xxxxx, (numeric) The block time\n"
- " \"total_out\": xxxxx, (numeric) Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee])\n"
- " \"total_size\": xxxxx, (numeric) Total size of all non-coinbase transactions\n"
- " \"total_weight\": xxxxx, (numeric) Total weight of all non-coinbase transactions divided by segwit scale factor (4)\n"
- " \"totalfee\": xxxxx, (numeric) The fee total\n"
- " \"txs\": xxxxx, (numeric) The number of transactions (excluding coinbase)\n"
- " \"utxo_increase\": xxxxx, (numeric) The increase/decrease in the number of unspent outputs\n"
- " \"utxo_size_inc\": xxxxx, (numeric) The increase/decrease in size for the utxo index (not discounting op_return and similar)\n"
+ " \"height\" : xxxxx, (numeric) The height of the block\n"
+ " \"ins\" : xxxxx, (numeric) The number of inputs (excluding coinbase)\n"
+ " \"maxfee\" : xxxxx, (numeric) Maximum fee in the block\n"
+ " \"maxfeerate\" : xxxxx, (numeric) Maximum feerate (in satoshis per virtual byte)\n"
+ " \"maxtxsize\" : xxxxx, (numeric) Maximum transaction size\n"
+ " \"medianfee\" : xxxxx, (numeric) Truncated median fee in the block\n"
+ " \"mediantime\" : xxxxx, (numeric) The block median time past\n"
+ " \"mediantxsize\" : xxxxx, (numeric) Truncated median transaction size\n"
+ " \"minfee\" : xxxxx, (numeric) Minimum fee in the block\n"
+ " \"minfeerate\" : xxxxx, (numeric) Minimum feerate (in satoshis per virtual byte)\n"
+ " \"mintxsize\" : xxxxx, (numeric) Minimum transaction size\n"
+ " \"outs\" : xxxxx, (numeric) The number of outputs\n"
+ " \"subsidy\" : xxxxx, (numeric) The block subsidy\n"
+ " \"swtotal_size\" : xxxxx, (numeric) Total size of all segwit transactions\n"
+ " \"swtotal_weight\" : xxxxx, (numeric) Total weight of all segwit transactions divided by segwit scale factor (4)\n"
+ " \"swtxs\" : xxxxx, (numeric) The number of segwit transactions\n"
+ " \"time\" : xxxxx, (numeric) The block time\n"
+ " \"total_out\" : xxxxx, (numeric) Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee])\n"
+ " \"total_size\" : xxxxx, (numeric) Total size of all non-coinbase transactions\n"
+ " \"total_weight\" : xxxxx, (numeric) Total weight of all non-coinbase transactions divided by segwit scale factor (4)\n"
+ " \"totalfee\" : xxxxx, (numeric) The fee total\n"
+ " \"txs\" : xxxxx, (numeric) The number of transactions (excluding coinbase)\n"
+ " \"utxo_increase\" : xxxxx, (numeric) The increase/decrease in the number of unspent outputs\n"
+ " \"utxo_size_inc\" : xxxxx, (numeric) The increase/decrease in size for the utxo index (not discounting op_return and similar)\n"
"}\n"
},
RPCExamples{
@@ -2076,21 +2076,21 @@ UniValue scantxoutset(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
- " \"success\": true|false, (boolean) Whether the scan was completed\n"
- " \"txouts\": n, (numeric) The number of unspent transaction outputs scanned\n"
- " \"height\": n, (numeric) The current block height (index)\n"
- " \"bestblock\": \"hex\", (string) The hash of the block at the tip of the chain\n"
- " \"unspents\": [\n"
+ " \"success\" : true|false, (boolean) Whether the scan was completed\n"
+ " \"txouts\" : n, (numeric) The number of unspent transaction outputs scanned\n"
+ " \"height\" : n, (numeric) The current block height (index)\n"
+ " \"bestblock\" : \"hex\", (string) The hash of the block at the tip of the chain\n"
+ " \"unspents\" : [\n"
" {\n"
- " \"txid\": \"hash\", (string) The transaction id\n"
- " \"vout\": n, (numeric) The vout value\n"
- " \"scriptPubKey\": \"script\", (string) The script key\n"
- " \"desc\": \"descriptor\", (string) A specialized descriptor for the matched scriptPubKey\n"
- " \"amount\": x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n"
- " \"height\": n, (numeric) Height of the unspent transaction output\n"
+ " \"txid\" : \"hash\", (string) The transaction id\n"
+ " \"vout\" : n, (numeric) The vout value\n"
+ " \"scriptPubKey\" : \"script\", (string) The script key\n"
+ " \"desc\" : \"descriptor\", (string) A specialized descriptor for the matched scriptPubKey\n"
+ " \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n"
+ " \"height\" : n, (numeric) Height of the unspent transaction output\n"
" }\n"
" ,...],\n"
- " \"total_amount\": x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n"
+ " \"total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n"
"]\n"
},
RPCExamples{""},
@@ -2284,10 +2284,10 @@ UniValue dumptxoutset(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
- " \"coins_written\": n, (numeric) the number of coins written in the snapshot\n"
- " \"base_hash\": \"...\", (string) the hash of the base of the snapshot\n"
- " \"base_height\": n, (string) the height of the base of the snapshot\n"
- " \"path\": \"...\" (string) the absolute path that the snapshot was written to\n"
+ " \"coins_written\" : n, (numeric) the number of coins written in the snapshot\n"
+ " \"base_hash\" : \"...\", (string) the hash of the base of the snapshot\n"
+ " \"base_height\" : n, (string) the height of the base of the snapshot\n"
+ " \"path\" : \"...\" (string) the absolute path that the snapshot was written to\n"
"]\n"
},
RPCExamples{
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 2eaa3427eb..c1762483e9 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -27,6 +27,7 @@ public:
static const CRPCConvertParam vRPCConvertParams[] =
{
{ "setmocktime", 0, "timestamp" },
+ { "mockscheduler", 0, "delta_time" },
{ "utxoupdatepsbt", 1, "descriptors" },
{ "generatetoaddress", 0, "nblocks" },
{ "generatetoaddress", 2, "maxtries" },
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index 69885546c8..ab5d830b2a 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -154,7 +154,7 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
{"maxtries", RPCArg::Type::NUM, /* default */ "1000000", "How many iterations to try."},
},
RPCResult{
- "[ blockhashes ] (array) hashes of blocks generated\n"},
+ "[ blockhashes ] (json array) hashes of blocks generated\n"},
RPCExamples{
"\nGenerate 11 blocks to mydesc\n" + HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")},
}
@@ -196,7 +196,7 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
{"maxtries", RPCArg::Type::NUM, /* default */ "1000000", "How many iterations to try."},
},
RPCResult{
- "[ blockhashes ] (array) hashes of blocks generated\n"
+ "[ blockhashes ] (json array) hashes of blocks generated\n"
},
RPCExamples{
"\nGenerate 11 blocks to myaddress\n"
@@ -231,14 +231,14 @@ static UniValue getmininginfo(const JSONRPCRequest& request)
{},
RPCResult{
"{\n"
- " \"blocks\": nnn, (numeric) The current block\n"
- " \"currentblockweight\": nnn, (numeric, optional) The block weight of the last assembled block (only present if a block was ever assembled)\n"
- " \"currentblocktx\": nnn, (numeric, optional) The number of block transactions of the last assembled block (only present if a block was ever assembled)\n"
- " \"difficulty\": xxx.xxxxx (numeric) The current difficulty\n"
- " \"networkhashps\": nnn, (numeric) The network hashes per second\n"
- " \"pooledtx\": n (numeric) The size of the mempool\n"
- " \"chain\": \"xxxx\", (string) current network name (main, test, regtest)\n"
- " \"warnings\": \"...\" (string) any network and blockchain warnings\n"
+ " \"blocks\" : nnn, (numeric) The current block\n"
+ " \"currentblockweight\" : nnn, (numeric, optional) The block weight of the last assembled block (only present if a block was ever assembled)\n"
+ " \"currentblocktx\" : nnn, (numeric, optional) The number of block transactions of the last assembled block (only present if a block was ever assembled)\n"
+ " \"difficulty\" : xxx.xxxxx (numeric) The current difficulty\n"
+ " \"networkhashps\" : nnn, (numeric) The network hashes per second\n"
+ " \"pooledtx\" : n (numeric) The size of the mempool\n"
+ " \"chain\" : \"xxxx\", (string) current network name (main, test, regtest)\n"
+ " \"warnings\" : \"...\" (string) any network and blockchain warnings\n"
"}\n"
},
RPCExamples{
@@ -365,16 +365,16 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
" },\n"
" \"vbrequired\" : n, (numeric) bit mask of versionbits the server requires set in submissions\n"
" \"previousblockhash\" : \"xxxx\", (string) The hash of current highest block\n"
- " \"transactions\" : [ (array) contents of non-coinbase transactions that should be included in the next block\n"
+ " \"transactions\" : [ (json array) contents of non-coinbase transactions that should be included in the next block\n"
" {\n"
" \"data\" : \"xxxx\", (string) transaction data encoded in hexadecimal (byte-for-byte)\n"
" \"txid\" : \"xxxx\", (string) transaction id encoded in little-endian hexadecimal\n"
" \"hash\" : \"xxxx\", (string) hash encoded in little-endian hexadecimal (including witness data)\n"
- " \"depends\" : [ (array) array of numbers \n"
+ " \"depends\" : [ (json array) array of numbers \n"
" n (numeric) transactions before this one (by 1-based index in 'transactions' list) that must be present in the final block if this one is\n"
" ,...\n"
" ],\n"
- " \"fee\": n, (numeric) difference in value between transaction inputs and outputs (in satoshis); for coinbase transactions, this is a negative Number of the total collected block fees (ie, not including the block subsidy); if key is not present, fee is unknown and clients MUST NOT assume there isn't one\n"
+ " \"fee\" : n, (numeric) difference in value between transaction inputs and outputs (in satoshis); for coinbase transactions, this is a negative Number of the total collected block fees (ie, not including the block subsidy); if key is not present, fee is unknown and clients MUST NOT assume there isn't one\n"
" \"sigops\" : n, (numeric) total SigOps cost, as counted for purposes of block limits; if key is not present, sigop cost is unknown and clients MUST NOT assume it is zero\n"
" \"weight\" : n, (numeric) total transaction weight, as counted for purposes of block limits\n"
" }\n"
@@ -850,7 +850,7 @@ static UniValue estimatesmartfee(const JSONRPCRequest& request)
RPCResult{
"{\n"
" \"feerate\" : x.x, (numeric, optional) estimate fee rate in " + CURRENCY_UNIT + "/kB\n"
- " \"errors\": [ str... ] (json array of strings, optional) Errors encountered during processing\n"
+ " \"errors\" : [ str... ] (json array of strings, optional) Errors encountered during processing\n"
" \"blocks\" : n (numeric) block number where estimate was found\n"
"}\n"
"\n"
@@ -924,7 +924,7 @@ static UniValue estimaterawfee(const JSONRPCRequest& request)
" \"fail\" : { (json object, optional) information about the highest range of feerates to fail to meet the threshold\n"
" ...\n"
" },\n"
- " \"errors\": [ (json array, optional) Errors encountered during processing\n"
+ " \"errors\" : [ (json array, optional) Errors encountered during processing\n"
" \"str\", (string)\n"
" ...\n"
" ],\n"
diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp
index e77dad6bfa..4279756f4d 100644
--- a/src/rpc/misc.cpp
+++ b/src/rpc/misc.cpp
@@ -5,10 +5,12 @@
#include <httpserver.h>
#include <key_io.h>
+#include <node/context.h>
#include <outputtype.h>
#include <rpc/blockchain.h>
#include <rpc/server.h>
#include <rpc/util.h>
+#include <scheduler.h>
#include <script/descriptor.h>
#include <util/check.h>
#include <util/message.h> // For MessageSign(), MessageVerify()
@@ -42,8 +44,8 @@ static UniValue validateaddress(const JSONRPCRequest& request)
"}\n"
},
RPCExamples{
- HelpExampleCli("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")
- + HelpExampleRpc("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")
+ HelpExampleCli("validateaddress", EXAMPLE_ADDRESS) +
+ HelpExampleRpc("validateaddress", EXAMPLE_ADDRESS)
},
}.Check(request);
@@ -81,9 +83,9 @@ static UniValue createmultisig(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
- " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n"
- " \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n"
- " \"descriptor\":\"descriptor\" (string) The descriptor for this multisig\n"
+ " \"address\" : \"multisigaddress\", (string) The value of the new multisig address.\n"
+ " \"redeemScript\" : \"script\" (string) The string value of the hex-encoded redemption script.\n"
+ " \"descriptor\" : \"descriptor\" (string) The descriptor for this multisig\n"
"}\n"
},
RPCExamples{
@@ -187,7 +189,7 @@ UniValue deriveaddresses(const JSONRPCRequest& request)
{"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."},
},
RPCResult{
- "[ address ] (array) the derived addresses\n"
+ "[ address ] (json array) the derived addresses\n"
},
RPCExamples{
"First three native segwit receive addresses\n" +
@@ -359,6 +361,36 @@ static UniValue setmocktime(const JSONRPCRequest& request)
return NullUniValue;
}
+static UniValue mockscheduler(const JSONRPCRequest& request)
+{
+ RPCHelpMan{"mockscheduler",
+ "\nBump the scheduler into the future (-regtest only)\n",
+ {
+ {"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." },
+ },
+ RPCResults{},
+ RPCExamples{""},
+ }.Check(request);
+
+ if (!Params().IsMockableChain()) {
+ throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only");
+ }
+
+ // check params are valid values
+ RPCTypeCheck(request.params, {UniValue::VNUM});
+ int64_t delta_seconds = request.params[0].get_int64();
+ if ((delta_seconds <= 0) || (delta_seconds > 3600)) {
+ throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)");
+ }
+
+ // protect against null pointer dereference
+ CHECK_NONFATAL(g_rpc_node);
+ CHECK_NONFATAL(g_rpc_node->scheduler);
+ g_rpc_node->scheduler->MockForward(boost::chrono::seconds(delta_seconds));
+
+ return NullUniValue;
+}
+
static UniValue RPCLockedMemoryInfo()
{
LockedPool::Stats stats = LockedPoolManager::Instance().stats();
@@ -406,13 +438,13 @@ static UniValue getmemoryinfo(const JSONRPCRequest& request)
{
RPCResult{"mode \"stats\"",
"{\n"
- " \"locked\": { (json object) Information about locked memory manager\n"
- " \"used\": xxxxx, (numeric) Number of bytes used\n"
- " \"free\": xxxxx, (numeric) Number of bytes available in current arenas\n"
- " \"total\": xxxxxxx, (numeric) Total number of bytes managed\n"
- " \"locked\": xxxxxx, (numeric) Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk.\n"
- " \"chunks_used\": xxxxx, (numeric) Number allocated chunks\n"
- " \"chunks_free\": xxxxx, (numeric) Number unused chunks\n"
+ " \"locked\" : { (json object) Information about locked memory manager\n"
+ " \"used\" : xxxxx, (numeric) Number of bytes used\n"
+ " \"free\" : xxxxx, (numeric) Number of bytes available in current arenas\n"
+ " \"total\" : xxxxxxx, (numeric) Total number of bytes managed\n"
+ " \"locked\" : xxxxxx, (numeric) Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk.\n"
+ " \"chunks_used\" : xxxxx, (numeric) Number allocated chunks\n"
+ " \"chunks_free\" : xxxxx, (numeric) Number unused chunks\n"
" }\n"
"}\n"
},
@@ -485,7 +517,7 @@ UniValue logging(const JSONRPCRequest& request)
},
RPCResult{
"{ (json object where keys are the logging categories, and values indicates its status\n"
- " \"category\": true|false, (bool) if being debug logged or not. false:inactive, true:active\n"
+ " \"category\" : true|false, (boolean) if being debug logged or not. false:inactive, true:active\n"
" ...\n"
"}\n"
},
@@ -563,6 +595,7 @@ static const CRPCCommand commands[] =
/* Not shown in help */
{ "hidden", "setmocktime", &setmocktime, {"timestamp"}},
+ { "hidden", "mockscheduler", &mockscheduler, {"delta_time"}},
{ "hidden", "echo", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
{ "hidden", "echojson", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
};
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 42aec08b45..92542539df 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -79,48 +79,48 @@ static UniValue getpeerinfo(const JSONRPCRequest& request)
RPCResult{
"[\n"
" {\n"
- " \"id\": n, (numeric) Peer index\n"
- " \"addr\":\"host:port\", (string) The IP address and port of the peer\n"
- " \"addrbind\":\"ip:port\", (string) Bind address of the connection to the peer\n"
- " \"addrlocal\":\"ip:port\", (string) Local address as reported by the peer\n"
- " \"mapped_as\":\"mapped_as\", (string) The AS in the BGP route to the peer used for diversifying peer selection\n"
- " \"services\":\"xxxxxxxxxxxxxxxx\", (string) The services offered\n"
- " \"servicesnames\":[ (array) the services offered, in human-readable form\n"
+ " \"id\" : n, (numeric) Peer index\n"
+ " \"addr\" : \"host:port\", (string) The IP address and port of the peer\n"
+ " \"addrbind\" : \"ip:port\", (string) Bind address of the connection to the peer\n"
+ " \"addrlocal\" : \"ip:port\", (string) Local address as reported by the peer\n"
+ " \"mapped_as\" : \"mapped_as\", (string) The AS in the BGP route to the peer used for diversifying peer selection\n"
+ " \"services\" : \"xxxxxxxxxxxxxxxx\", (string) The services offered\n"
+ " \"servicesnames\" : [ (json array) the services offered, in human-readable form\n"
" \"SERVICE_NAME\", (string) the service name if it is recognised\n"
" ...\n"
" ],\n"
- " \"relaytxes\":true|false, (boolean) Whether peer has asked us to relay transactions to it\n"
- " \"lastsend\": ttt, (numeric) The " + UNIX_EPOCH_TIME + " of the last send\n"
- " \"lastrecv\": ttt, (numeric) The " + UNIX_EPOCH_TIME + " of the last receive\n"
- " \"bytessent\": n, (numeric) The total bytes sent\n"
- " \"bytesrecv\": n, (numeric) The total bytes received\n"
- " \"conntime\": ttt, (numeric) The " + UNIX_EPOCH_TIME + " of the connection\n"
- " \"timeoffset\": ttt, (numeric) The time offset in seconds\n"
- " \"pingtime\": n, (numeric) ping time (if available)\n"
- " \"minping\": n, (numeric) minimum observed ping time (if any at all)\n"
- " \"pingwait\": n, (numeric) ping wait (if non-zero)\n"
- " \"version\": v, (numeric) The peer version, such as 70001\n"
- " \"subver\": \"/Satoshi:0.8.5/\", (string) The string version\n"
- " \"inbound\": true|false, (boolean) Inbound (true) or Outbound (false)\n"
- " \"addnode\": true|false, (boolean) Whether connection was due to addnode/-connect or if it was an automatic/inbound connection\n"
- " \"startingheight\": n, (numeric) The starting height (block) of the peer\n"
- " \"banscore\": n, (numeric) The ban score\n"
- " \"synced_headers\": n, (numeric) The last header we have in common with this peer\n"
- " \"synced_blocks\": n, (numeric) The last block we have in common with this peer\n"
- " \"inflight\": [\n"
+ " \"relaytxes\" : true|false, (boolean) Whether peer has asked us to relay transactions to it\n"
+ " \"lastsend\" : ttt, (numeric) The " + UNIX_EPOCH_TIME + " of the last send\n"
+ " \"lastrecv\" : ttt, (numeric) The " + UNIX_EPOCH_TIME + " of the last receive\n"
+ " \"bytessent\" : n, (numeric) The total bytes sent\n"
+ " \"bytesrecv\" : n, (numeric) The total bytes received\n"
+ " \"conntime\" : ttt, (numeric) The " + UNIX_EPOCH_TIME + " of the connection\n"
+ " \"timeoffset\" : ttt, (numeric) The time offset in seconds\n"
+ " \"pingtime\" : n, (numeric) ping time (if available)\n"
+ " \"minping\" : n, (numeric) minimum observed ping time (if any at all)\n"
+ " \"pingwait\" : n, (numeric) ping wait (if non-zero)\n"
+ " \"version\" : v, (numeric) The peer version, such as 70001\n"
+ " \"subver\" : \"/Satoshi:0.8.5/\", (string) The string version\n"
+ " \"inbound\" : true|false, (boolean) Inbound (true) or Outbound (false)\n"
+ " \"addnode\" : true|false, (boolean) Whether connection was due to addnode/-connect or if it was an automatic/inbound connection\n"
+ " \"startingheight\" : n, (numeric) The starting height (block) of the peer\n"
+ " \"banscore\" : n, (numeric) The ban score\n"
+ " \"synced_headers\" : n, (numeric) The last header we have in common with this peer\n"
+ " \"synced_blocks\" : n, (numeric) The last block we have in common with this peer\n"
+ " \"inflight\" : [\n"
" n, (numeric) The heights of blocks we're currently asking from this peer\n"
" ...\n"
" ],\n"
- " \"whitelisted\": true|false, (boolean) Whether the peer is whitelisted\n"
- " \"minfeefilter\": n, (numeric) The minimum fee rate for transactions this peer accepts\n"
- " \"bytessent_per_msg\": {\n"
- " \"msg\": n, (numeric) The total bytes sent aggregated by message type\n"
+ " \"whitelisted\" : true|false, (boolean) Whether the peer is whitelisted\n"
+ " \"minfeefilter\" : n, (numeric) The minimum fee rate for transactions this peer accepts\n"
+ " \"bytessent_per_msg\" : {\n"
+ " \"msg\" : n, (numeric) The total bytes sent aggregated by message type\n"
" When a message type is not listed in this json object, the bytes sent are 0.\n"
" Only known message types can appear as keys in the object.\n"
" ...\n"
" },\n"
- " \"bytesrecv_per_msg\": {\n"
- " \"msg\": n, (numeric) The total bytes received aggregated by message type\n"
+ " \"bytesrecv_per_msg\" : {\n"
+ " \"msg\" : n, (numeric) The total bytes received aggregated by message type\n"
" When a message type is not listed in this json object, the bytes received are 0.\n"
" Only known message types can appear as keys in the object and all bytes received of unknown message types are listed under '"+NET_MESSAGE_COMMAND_OTHER+"'.\n"
" ...\n"
@@ -387,17 +387,17 @@ static UniValue getnettotals(const JSONRPCRequest& request)
{},
RPCResult{
"{\n"
- " \"totalbytesrecv\": n, (numeric) Total bytes received\n"
- " \"totalbytessent\": n, (numeric) Total bytes sent\n"
- " \"timemillis\": t, (numeric) Current UNIX time in milliseconds\n"
- " \"uploadtarget\":\n"
+ " \"totalbytesrecv\" : n, (numeric) Total bytes received\n"
+ " \"totalbytessent\" : n, (numeric) Total bytes sent\n"
+ " \"timemillis\" : t, (numeric) Current UNIX time in milliseconds\n"
+ " \"uploadtarget\" : \n"
" {\n"
- " \"timeframe\": n, (numeric) Length of the measuring timeframe in seconds\n"
- " \"target\": n, (numeric) Target in bytes\n"
- " \"target_reached\": true|false, (boolean) True if target is reached\n"
- " \"serve_historical_blocks\": true|false, (boolean) True if serving historical blocks\n"
- " \"bytes_left_in_cycle\": t, (numeric) Bytes left in current time cycle\n"
- " \"time_left_in_cycle\": t (numeric) Seconds left in current time cycle\n"
+ " \"timeframe\" : n, (numeric) Length of the measuring timeframe in seconds\n"
+ " \"target\" : n, (numeric) Target in bytes\n"
+ " \"target_reached\" : true|false, (boolean) True if target is reached\n"
+ " \"serve_historical_blocks\" : true|false, (boolean) True if serving historical blocks\n"
+ " \"bytes_left_in_cycle\" : t, (numeric) Bytes left in current time cycle\n"
+ " \"time_left_in_cycle\" : t (numeric) Seconds left in current time cycle\n"
" }\n"
"}\n"
},
@@ -453,35 +453,35 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request)
{},
RPCResult{
"{ (json object)\n"
- " \"version\": xxxxx, (numeric) the server version\n"
+ " \"version\" : xxxxx, (numeric) the server version\n"
" \"subversion\" : \"str\", (string) the server subversion string\n"
- " \"protocolversion\": xxxxx, (numeric) the protocol version\n"
+ " \"protocolversion\" : xxxxx, (numeric) the protocol version\n"
" \"localservices\" : \"hex\", (string) the services we offer to the network\n"
- " \"localservicesnames\": [ (array) the services we offer to the network, in human-readable form\n"
+ " \"localservicesnames\" : [ (json array) the services we offer to the network, in human-readable form\n"
" \"SERVICE_NAME\", (string) the service name\n"
" ...\n"
" ],\n"
- " \"localrelay\": true|false, (bool) true if transaction relay is requested from peers\n"
- " \"timeoffset\": xxxxx, (numeric) the time offset\n"
- " \"connections\": xxxxx, (numeric) the number of connections\n"
- " \"networkactive\": true|false, (bool) whether p2p networking is enabled\n"
- " \"networks\": [ (array) information per network\n"
+ " \"localrelay\" : true|false, (boolean) true if transaction relay is requested from peers\n"
+ " \"timeoffset\" : xxxxx, (numeric) the time offset\n"
+ " \"connections\" : xxxxx, (numeric) the number of connections\n"
+ " \"networkactive\" : true|false, (boolean) whether p2p networking is enabled\n"
+ " \"networks\" : [ (json array) information per network\n"
" { (json object)\n"
- " \"name\": \"str\", (string) network (ipv4, ipv6 or onion)\n"
- " \"limited\": true|false, (boolean) is the network limited using -onlynet?\n"
- " \"reachable\": true|false, (boolean) is the network reachable?\n"
+ " \"name\" : \"str\", (string) network (ipv4, ipv6 or onion)\n"
+ " \"limited\" : true|false, (boolean) is the network limited using -onlynet?\n"
+ " \"reachable\" : true|false, (boolean) is the network reachable?\n"
" \"proxy\" : \"str\" (string) (\"host:port\") the proxy that is used for this network, or empty if none\n"
- " \"proxy_randomize_credentials\" : true|false, (bool) Whether randomized credentials are used\n"
+ " \"proxy_randomize_credentials\" : true|false, (boolean) Whether randomized credentials are used\n"
" },\n"
" ...\n"
" ],\n"
- " \"relayfee\": x.xxxxxxxx, (numeric) minimum relay fee for transactions in " + CURRENCY_UNIT + "/kB\n"
- " \"incrementalfee\": x.xxxxxxxx, (numeric) minimum fee increment for mempool limiting or BIP 125 replacement in " + CURRENCY_UNIT + "/kB\n"
- " \"localaddresses\": [ (array) list of local addresses\n"
+ " \"relayfee\" : x.xxxxxxxx, (numeric) minimum relay fee for transactions in " + CURRENCY_UNIT + "/kB\n"
+ " \"incrementalfee\" : x.xxxxxxxx, (numeric) minimum fee increment for mempool limiting or BIP 125 replacement in " + CURRENCY_UNIT + "/kB\n"
+ " \"localaddresses\" : [ (json array) list of local addresses\n"
" { (json object)\n"
" \"address\" : \"xxxx\", (string) network address\n"
- " \"port\": xxx, (numeric) network port\n"
- " \"score\": xxx (numeric) relative score\n"
+ " \"port\" : xxx, (numeric) network port\n"
+ " \"score\" : xxx (numeric) relative score\n"
" },\n"
" ...\n"
" ],\n"
@@ -695,10 +695,10 @@ static UniValue getnodeaddresses(const JSONRPCRequest& request)
RPCResult{
"[\n"
" {\n"
- " \"time\": ttt, (numeric) The " + UNIX_EPOCH_TIME + " of when the node was last seen\n"
- " \"services\": n, (numeric) The services offered\n"
- " \"address\": \"host\", (string) The address of the node\n"
- " \"port\": n (numeric) The port of the node\n"
+ " \"time\" : ttt, (numeric) The " + UNIX_EPOCH_TIME + " of when the node was last seen\n"
+ " \"services\" : n, (numeric) The services offered\n"
+ " \"address\" : \"host\", (string) The address of the node\n"
+ " \"port\" : n (numeric) The port of the node\n"
" }\n"
" ,....\n"
"]\n"
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 972809a65b..cd1c657c26 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -98,7 +98,7 @@ static UniValue getrawtransaction(const JSONRPCRequest& request)
},
RPCResult{"if verbose is set to true",
"{\n"
- " \"in_active_chain\": b, (bool) Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)\n"
+ " \"in_active_chain\" : b, (boolean) Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)\n"
" \"hex\" : \"data\", (string) The serialized, hex-encoded data for 'txid'\n"
" \"txid\" : \"id\", (string) The transaction id (same as provided)\n"
" \"hash\" : \"id\", (string) The transaction hash (differs from txid for witness transactions)\n"
@@ -109,14 +109,14 @@ static UniValue getrawtransaction(const JSONRPCRequest& request)
" \"locktime\" : ttt, (numeric) The lock time\n"
" \"vin\" : [ (array of json objects)\n"
" {\n"
- " \"txid\": \"id\", (string) The transaction id\n"
- " \"vout\": n, (numeric) \n"
- " \"scriptSig\": { (json object) The script\n"
- " \"asm\": \"asm\", (string) asm\n"
- " \"hex\": \"hex\" (string) hex\n"
+ " \"txid\" : \"id\", (string) The transaction id\n"
+ " \"vout\" : n, (numeric) \n"
+ " \"scriptSig\" : { (json object) The script\n"
+ " \"asm\" : \"asm\", (string) asm\n"
+ " \"hex\" : \"hex\" (string) hex\n"
" },\n"
- " \"sequence\": n (numeric) The script sequence number\n"
- " \"txinwitness\": [\"hex\", ...] (array of string) hex-encoded witness data (if any)\n"
+ " \"sequence\" : n (numeric) The script sequence number\n"
+ " \"txinwitness\" : [\"hex\", ...] (array of string) hex-encoded witness data (if any)\n"
" }\n"
" ,...\n"
" ],\n"
@@ -442,14 +442,14 @@ static UniValue decoderawtransaction(const JSONRPCRequest& request)
" \"locktime\" : ttt, (numeric) The lock time\n"
" \"vin\" : [ (array of json objects)\n"
" {\n"
- " \"txid\": \"id\", (string) The transaction id\n"
- " \"vout\": n, (numeric) The output number\n"
- " \"scriptSig\": { (json object) The script\n"
- " \"asm\": \"asm\", (string) asm\n"
- " \"hex\": \"hex\" (string) hex\n"
+ " \"txid\" : \"id\", (string) The transaction id\n"
+ " \"vout\" : n, (numeric) The output number\n"
+ " \"scriptSig\" : { (json object) The script\n"
+ " \"asm\" : \"asm\", (string) asm\n"
+ " \"hex\" : \"hex\" (string) hex\n"
" },\n"
- " \"txinwitness\": [\"hex\", ...] (array of string) hex-encoded witness data (if any)\n"
- " \"sequence\": n (numeric) The script sequence number\n"
+ " \"txinwitness\" : [\"hex\", ...] (array of string) hex-encoded witness data (if any)\n"
+ " \"sequence\" : n (numeric) The script sequence number\n"
" }\n"
" ,...\n"
" ],\n"
@@ -514,20 +514,20 @@ static UniValue decodescript(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
- " \"asm\":\"asm\", (string) Script public key\n"
- " \"type\":\"type\", (string) The output type (e.g. "+GetAllOutputTypes()+")\n"
- " \"reqSigs\": n, (numeric) The required signatures\n"
- " \"addresses\": [ (json array of string)\n"
+ " \"asm\" : \"asm\", (string) Script public key\n"
+ " \"type\" : \"type\", (string) The output type (e.g. "+GetAllOutputTypes()+")\n"
+ " \"reqSigs\" : n, (numeric) The required signatures\n"
+ " \"addresses\" : [ (json array of string)\n"
" \"address\" (string) bitcoin address\n"
" ,...\n"
" ],\n"
" \"p2sh\":\"str\" (string) address of P2SH script wrapping this redeem script (not returned if the script is already a P2SH).\n"
- " \"segwit\": { (json object) Result of a witness script public key wrapping this redeem script (not returned if the script is a P2SH or witness).\n"
- " \"asm\":\"str\", (string) String representation of the script public key\n"
- " \"hex\":\"hexstr\", (string) Hex string of the script public key\n"
- " \"type\":\"str\", (string) The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)\n"
- " \"reqSigs\": n, (numeric) The required signatures (always 1)\n"
- " \"addresses\": [ (json array of string) (always length 1)\n"
+ " \"segwit\" : { (json object) Result of a witness script public key wrapping this redeem script (not returned if the script is a P2SH or witness).\n"
+ " \"asm\" : \"str\", (string) String representation of the script public key\n"
+ " \"hex\" : \"hexstr\", (string) Hex string of the script public key\n"
+ " \"type\" : \"str\", (string) The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)\n"
+ " \"reqSigs\" : n, (numeric) The required signatures (always 1)\n"
+ " \"addresses\" : [ (json array of string) (always length 1)\n"
" \"address\" (string) segwit address\n"
" ,...\n"
" ],\n"
@@ -846,7 +846,7 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
{"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()), "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
},
RPCResult{
- "[ (array) The result of the mempool acceptance test for each raw transaction in the input array.\n"
+ "[ (json array) The result of the mempool acceptance test for each raw transaction in the input array.\n"
" Length is exactly one for now.\n"
" {\n"
" \"txid\" (string) The transaction hash in hex\n"
@@ -998,7 +998,7 @@ UniValue decodepsbt(const JSONRPCRequest& request)
" \"asm\" : \"asm\", (string) The asm\n"
" \"hex\" : \"hex\", (string) The hex\n"
" }\n"
- " \"final_scriptwitness\": [\"hex\", ...] (array of string) hex-encoded witness data (if any)\n"
+ " \"final_scriptwitness\" : [\"hex\", ...] (array of string) hex-encoded witness data (if any)\n"
" \"unknown\" : { (json object) The unknown global fields\n"
" \"key\" : \"value\" (key-value pair) An unknown key-value pair\n"
" ...\n"
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index df8e687d82..b62490ed29 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -198,14 +198,14 @@ static UniValue getrpcinfo(const JSONRPCRequest& request)
{},
RPCResult{
"{\n"
- " \"active_commands\" (array) All active commands\n"
+ " \"active_commands\" (json array) All active commands\n"
" [\n"
- " { (object) Information about an active command\n"
+ " { (json object) Information about an active command\n"
" \"method\" (string) The name of the RPC command \n"
" \"duration\" (numeric) The running time in microseconds\n"
" },...\n"
" ],\n"
- " \"logpath\": \"xxx\" (string) The complete file path to the debug log\n"
+ " \"logpath\" : \"xxx\" (string) The complete file path to the debug log\n"
"}\n"
},
RPCExamples{
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 78586c22f9..7cefb2db96 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -14,6 +14,7 @@
#include <tuple>
const std::string UNIX_EPOCH_TIME = "UNIX epoch time";
+const std::string EXAMPLE_ADDRESS = "\"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl\"";
void RPCTypeCheck(const UniValue& params,
const std::list<UniValueType>& typesExpected,
diff --git a/src/rpc/util.h b/src/rpc/util.h
index 065a992a88..f5edf69713 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -28,6 +28,12 @@
*/
extern const std::string UNIX_EPOCH_TIME;
+/**
+ * Example bech32 address used in multiple RPCExamples. The address is intentionally
+ * invalid to prevent accidental transactions by users.
+ */
+extern const std::string EXAMPLE_ADDRESS;
+
class FillableSigningProvider;
class CPubKey;
class CScript;
diff --git a/src/scheduler.cpp b/src/scheduler.cpp
index 927a3f3820..72cca89d99 100644
--- a/src/scheduler.cpp
+++ b/src/scheduler.cpp
@@ -114,6 +114,28 @@ void CScheduler::scheduleFromNow(CScheduler::Function f, int64_t deltaMilliSecon
schedule(f, boost::chrono::system_clock::now() + boost::chrono::milliseconds(deltaMilliSeconds));
}
+void CScheduler::MockForward(boost::chrono::seconds delta_seconds)
+{
+ assert(delta_seconds.count() > 0 && delta_seconds < boost::chrono::hours{1});
+
+ {
+ boost::unique_lock<boost::mutex> lock(newTaskMutex);
+
+ // use temp_queue to maintain updated schedule
+ std::multimap<boost::chrono::system_clock::time_point, Function> temp_queue;
+
+ for (const auto& element : taskQueue) {
+ temp_queue.emplace_hint(temp_queue.cend(), element.first - delta_seconds, element.second);
+ }
+
+ // point taskQueue to temp_queue
+ taskQueue = std::move(temp_queue);
+ }
+
+ // notify that the taskQueue needs to be processed
+ newTaskScheduled.notify_one();
+}
+
static void Repeat(CScheduler* s, CScheduler::Function f, int64_t deltaMilliSeconds)
{
f();
diff --git a/src/scheduler.h b/src/scheduler.h
index 7080adf34c..d18be0ea5e 100644
--- a/src/scheduler.h
+++ b/src/scheduler.h
@@ -55,6 +55,13 @@ public:
// need more accurate scheduling, don't use this method.
void scheduleEvery(Function f, int64_t deltaMilliSeconds);
+ /**
+ * Mock the scheduler to fast forward in time.
+ * Iterates through items on taskQueue and reschedules them
+ * to be delta_seconds sooner.
+ */
+ void MockForward(boost::chrono::seconds delta_seconds);
+
// To keep things as simple as possible, there is no unschedule.
// Services the queue 'forever'. Should be run in a thread,
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 773d6a55c4..b1d9a5bda7 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -339,14 +339,15 @@ class DescriptorImpl : public Descriptor
{
//! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size for Multisig).
const std::vector<std::unique_ptr<PubkeyProvider>> m_pubkey_args;
+ //! The string name of the descriptor function.
+ const std::string m_name;
+
+protected:
//! The sub-descriptor argument (nullptr for everything but SH and WSH).
//! In doc/descriptors.m this is referred to as SCRIPT expressions sh(SCRIPT)
//! and wsh(SCRIPT), and distinct from KEY expressions and ADDR expressions.
const std::unique_ptr<DescriptorImpl> m_subdescriptor_arg;
- //! The string name of the descriptor function.
- const std::string m_name;
-protected:
//! Return a serialization of anything except pubkey and script arguments, to be prepended to those.
virtual std::string ToStringExtra() const { return ""; }
@@ -364,7 +365,7 @@ protected:
virtual std::vector<CScript> MakeScripts(const std::vector<CPubKey>& pubkeys, const CScript* script, FlatSigningProvider& out) const = 0;
public:
- DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_subdescriptor_arg(std::move(script)), m_name(name) {}
+ DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_arg(std::move(script)) {}
bool IsSolvable() const override
{
@@ -500,6 +501,8 @@ public:
out = Merge(out, subprovider);
}
}
+
+ Optional<OutputType> GetOutputType() const override { return nullopt; }
};
/** A parsed addr(A) descriptor. */
@@ -512,6 +515,19 @@ protected:
public:
AddressDescriptor(CTxDestination destination) : DescriptorImpl({}, {}, "addr"), m_destination(std::move(destination)) {}
bool IsSolvable() const final { return false; }
+
+ Optional<OutputType> GetOutputType() const override
+ {
+ switch (m_destination.which()) {
+ case 1 /* PKHash */:
+ case 2 /* ScriptHash */: return OutputType::LEGACY;
+ case 3 /* WitnessV0ScriptHash */:
+ case 4 /* WitnessV0KeyHash */:
+ case 5 /* WitnessUnknown */: return OutputType::BECH32;
+ case 0 /* CNoDestination */:
+ default: return nullopt;
+ }
+ }
};
/** A parsed raw(H) descriptor. */
@@ -524,6 +540,21 @@ protected:
public:
RawDescriptor(CScript script) : DescriptorImpl({}, {}, "raw"), m_script(std::move(script)) {}
bool IsSolvable() const final { return false; }
+
+ Optional<OutputType> GetOutputType() const override
+ {
+ CTxDestination dest;
+ ExtractDestination(m_script, dest);
+ switch (dest.which()) {
+ case 1 /* PKHash */:
+ case 2 /* ScriptHash */: return OutputType::LEGACY;
+ case 3 /* WitnessV0ScriptHash */:
+ case 4 /* WitnessV0KeyHash */:
+ case 5 /* WitnessUnknown */: return OutputType::BECH32;
+ case 0 /* CNoDestination */:
+ default: return nullopt;
+ }
+ }
};
/** A parsed pk(P) descriptor. */
@@ -547,6 +578,7 @@ protected:
}
public:
PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "pkh") {}
+ Optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; }
};
/** A parsed wpkh(P) descriptor. */
@@ -561,6 +593,7 @@ protected:
}
public:
WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "wpkh") {}
+ Optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
};
/** A parsed combo(P) descriptor. */
@@ -612,6 +645,13 @@ protected:
std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript* script, FlatSigningProvider&) const override { return Vector(GetScriptForDestination(ScriptHash(*script))); }
public:
SHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "sh") {}
+
+ Optional<OutputType> GetOutputType() const override
+ {
+ assert(m_subdescriptor_arg);
+ if (m_subdescriptor_arg->GetOutputType() == OutputType::BECH32) return OutputType::P2SH_SEGWIT;
+ return OutputType::LEGACY;
+ }
};
/** A parsed wsh(...) descriptor. */
@@ -621,6 +661,7 @@ protected:
std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript* script, FlatSigningProvider&) const override { return Vector(GetScriptForDestination(WitnessV0ScriptHash(*script))); }
public:
WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {}
+ Optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
};
////////////////////////////////////////////////////////////////////////////
diff --git a/src/script/descriptor.h b/src/script/descriptor.h
index a5a41d78dd..58b920c681 100644
--- a/src/script/descriptor.h
+++ b/src/script/descriptor.h
@@ -5,6 +5,8 @@
#ifndef BITCOIN_SCRIPT_DESCRIPTOR_H
#define BITCOIN_SCRIPT_DESCRIPTOR_H
+#include <optional.h>
+#include <outputtype.h>
#include <script/script.h>
#include <script/sign.h>
#include <script/signingprovider.h>
@@ -71,6 +73,9 @@ struct Descriptor {
* @param[out] out Any private keys available for the specified `pos`.
*/
virtual void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const = 0;
+
+ /** @return The OutputType of the scriptPubKey(s) produced by this descriptor. Or nullopt if indeterminate (multiple or none) */
+ virtual Optional<OutputType> GetOutputType() const = 0;
};
/** Parse a `descriptor` string. Included private keys are put in `out`.
diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h
index 6ad20480a7..76f31d2f6f 100644
--- a/src/script/signingprovider.h
+++ b/src/script/signingprovider.h
@@ -66,7 +66,53 @@ protected:
using KeyMap = std::map<CKeyID, CKey>;
using ScriptMap = std::map<CScriptID, CScript>;
+ /**
+ * Map of key id to unencrypted private keys known by the signing provider.
+ * Map may be empty if the provider has another source of keys, like an
+ * encrypted store.
+ */
KeyMap mapKeys GUARDED_BY(cs_KeyStore);
+
+ /**
+ * Map of script id to scripts known by the signing provider.
+ *
+ * This map originally just held P2SH redeemScripts, and was used by wallet
+ * code to look up script ids referenced in "OP_HASH160 <script id>
+ * OP_EQUAL" P2SH outputs. Later in 605e8473a7d it was extended to hold
+ * P2WSH witnessScripts as well, and used to look up nested scripts
+ * referenced in "OP_0 <script hash>" P2WSH outputs. Later in commits
+ * f4691ab3a9d and 248f3a76a82, it was extended once again to hold segwit
+ * "OP_0 <key or script hash>" scriptPubKeys, in order to give the wallet a
+ * way to distinguish between segwit outputs that it generated addresses for
+ * and wanted to receive payments from, and segwit outputs that it never
+ * generated addresses for, but it could spend just because of having keys.
+ * (Before segwit activation it was also important to not treat segwit
+ * outputs to arbitrary wallet keys as payments, because these could be
+ * spent by anyone without even needing to sign with the keys.)
+ *
+ * Some of the scripts stored in mapScripts are memory-only and
+ * intentionally not saved to disk. Specifically, scripts added by
+ * ImplicitlyLearnRelatedKeyScripts(pubkey) calls are not written to disk so
+ * future wallet code can have flexibility to be more selective about what
+ * transaction outputs it recognizes as payments, instead of having to treat
+ * all outputs spending to keys it knows as payments. By contrast,
+ * mapScripts entries added by AddCScript(script),
+ * LearnRelatedScripts(pubkey, type), and LearnAllRelatedScripts(pubkey)
+ * calls are saved because they are all intentionally used to receive
+ * payments.
+ *
+ * The FillableSigningProvider::mapScripts script map should not be confused
+ * with LegacyScriptPubKeyMan::setWatchOnly script set. The two collections
+ * can hold the same scripts, but they serve different purposes. The
+ * setWatchOnly script set is intended to expand the set of outputs the
+ * wallet considers payments. Every output with a script it contains is
+ * considered to belong to the wallet, regardless of whether the script is
+ * solvable or signable. By contrast, the scripts in mapScripts are only
+ * used for solving, and to restrict which outputs are considered payments
+ * by the wallet. An output with a script in mapScripts, unlike
+ * setWatchOnly, is not automatically considered to belong to the wallet if
+ * it can't be solved and signed for.
+ */
ScriptMap mapScripts GUARDED_BY(cs_KeyStore);
void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
diff --git a/src/test/bswap_tests.cpp b/src/test/bswap_tests.cpp
index d5e2344a8b..0b4bfdb019 100644
--- a/src/test/bswap_tests.cpp
+++ b/src/test/bswap_tests.cpp
@@ -11,16 +11,16 @@ BOOST_FIXTURE_TEST_SUITE(bswap_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(bswap_tests)
{
- // Sibling in bitcoin/src/qt/test/compattests.cpp
- uint16_t u1 = 0x1234;
- uint32_t u2 = 0x56789abc;
- uint64_t u3 = 0xdef0123456789abc;
- uint16_t e1 = 0x3412;
- uint32_t e2 = 0xbc9a7856;
- uint64_t e3 = 0xbc9a78563412f0de;
- BOOST_CHECK(bswap_16(u1) == e1);
- BOOST_CHECK(bswap_32(u2) == e2);
- BOOST_CHECK(bswap_64(u3) == e3);
+ // Sibling in bitcoin/src/qt/test/compattests.cpp
+ uint16_t u1 = 0x1234;
+ uint32_t u2 = 0x56789abc;
+ uint64_t u3 = 0xdef0123456789abc;
+ uint16_t e1 = 0x3412;
+ uint32_t e2 = 0xbc9a7856;
+ uint64_t e3 = 0xbc9a78563412f0de;
+ BOOST_CHECK(bswap_16(u1) == e1);
+ BOOST_CHECK(bswap_32(u2) == e2);
+ BOOST_CHECK(bswap_64(u3) == e3);
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp
index 2c2b3035e3..e5d51ab83b 100644
--- a/src/test/denialofservice_tests.cpp
+++ b/src/test/denialofservice_tests.cpp
@@ -78,7 +78,7 @@ BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup)
BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
{
auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
- auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, scheduler);
+ auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, *m_node.scheduler);
// Mock an outbound peer
CAddress addr1(ip(0xa0b0c001), NODE_NONE);
@@ -148,7 +148,7 @@ static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerLogicValidat
BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
{
auto connman = MakeUnique<CConnmanTest>(0x1337, 0x1337);
- auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, scheduler);
+ auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, *m_node.scheduler);
const Consensus::Params& consensusParams = Params().GetConsensus();
constexpr int max_outbound_full_relay = 8;
@@ -221,7 +221,7 @@ BOOST_AUTO_TEST_CASE(DoS_banning)
{
auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);
auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
- auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), scheduler);
+ auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler);
banman->ClearBanned();
CAddress addr1(ip(0xa0b0c001), NODE_NONE);
@@ -276,7 +276,7 @@ BOOST_AUTO_TEST_CASE(DoS_banscore)
{
auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);
auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
- auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), scheduler);
+ auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler);
banman->ClearBanned();
gArgs.ForceSetArg("-banscore", "111"); // because 11 is my favorite number
@@ -323,7 +323,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
{
auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);
auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
- auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), scheduler);
+ auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler);
banman->ClearBanned();
int64_t nStartTime = GetTime();
diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
index bcce8854e3..e99aee724e 100644
--- a/src/test/descriptor_tests.cpp
+++ b/src/test/descriptor_tests.cpp
@@ -62,7 +62,7 @@ std::string UseHInsteadOfApostrophe(const std::string& desc)
const std::set<std::vector<uint32_t>> ONLY_EMPTY{{}};
-void DoCheck(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY,
+void DoCheck(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const Optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY,
bool replace_apostrophe_with_h_in_prv=false, bool replace_apostrophe_with_h_in_pub=false)
{
FlatSigningProvider keys_priv, keys_pub;
@@ -86,6 +86,10 @@ void DoCheck(const std::string& prv, const std::string& pub, int flags, const st
BOOST_CHECK(parse_priv);
BOOST_CHECK(parse_pub);
+ // Check that the correct OutputType is inferred
+ BOOST_CHECK(parse_priv->GetOutputType() == type);
+ BOOST_CHECK(parse_pub->GetOutputType() == type);
+
// Check private keys are extracted from the private version but not the public one.
BOOST_CHECK(keys_priv.keys.size());
BOOST_CHECK(!keys_pub.keys.size());
@@ -181,29 +185,29 @@ void DoCheck(const std::string& prv, const std::string& pub, int flags, const st
BOOST_CHECK_MESSAGE(left_paths.empty(), "Not all expected key paths found: " + prv);
}
-void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY)
+void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const Optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY)
{
bool found_apostrophes_in_prv = false;
bool found_apostrophes_in_pub = false;
// Do not replace apostrophes with 'h' in prv and pub
- DoCheck(prv, pub, flags, scripts, paths);
+ DoCheck(prv, pub, flags, scripts, type, paths);
// Replace apostrophes with 'h' in prv but not in pub, if apostrophes are found in prv
if (prv.find('\'') != std::string::npos) {
found_apostrophes_in_prv = true;
- DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */false);
+ DoCheck(prv, pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */false);
}
// Replace apostrophes with 'h' in pub but not in prv, if apostrophes are found in pub
if (pub.find('\'') != std::string::npos) {
found_apostrophes_in_pub = true;
- DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */false, /*replace_apostrophe_with_h_in_pub = */true);
+ DoCheck(prv, pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */false, /*replace_apostrophe_with_h_in_pub = */true);
}
// Replace apostrophes with 'h' both in prv and in pub, if apostrophes are found in both
if (found_apostrophes_in_prv && found_apostrophes_in_pub) {
- DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true);
+ DoCheck(prv, pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true);
}
}
@@ -214,50 +218,50 @@ BOOST_FIXTURE_TEST_SUITE(descriptor_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(descriptor_test)
{
// Basic single-key compressed
- Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}});
- Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}});
- Check("pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, {{1,0x80000002UL,3,0x80000004UL}});
- Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}});
- Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}});
+ Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, nullopt);
+ Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}}, nullopt);
+ Check("pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, OutputType::LEGACY, {{1,0x80000002UL,3,0x80000004UL}});
+ Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}, OutputType::BECH32);
+ Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, OutputType::P2SH_SEGWIT);
CheckUnparsable("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY2))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5))", "Pubkey '03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5' is invalid"); // Invalid pubkey
CheckUnparsable("pkh(deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "Key origin start '[ character expected but not found, got 'd' instead"); // Missing start bracket in key origin
CheckUnparsable("pkh([deadbeef]/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef]/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "Multiple ']' characters found for a single pubkey"); // Multiple end brackets in key origin
// Basic single-key uncompressed
- Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}});
- Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}});
- Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}});
+ Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, nullopt);
+ Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}}, nullopt);
+ Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, OutputType::LEGACY);
CheckUnparsable("wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Uncompressed keys are not allowed"); // No uncompressed keys in witness
CheckUnparsable("wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness
CheckUnparsable("sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness
// Some unconventional single-key constructions
- Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}});
- Check("sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}});
- Check("wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa"}});
- Check("wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b"}});
- Check("sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787"}});
- Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}});
+ Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}}, OutputType::LEGACY);
+ Check("sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}}, OutputType::LEGACY);
+ Check("wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa"}}, OutputType::BECH32);
+ Check("wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b"}}, OutputType::BECH32);
+ Check("sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787"}}, OutputType::P2SH_SEGWIT);
+ Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}}, OutputType::P2SH_SEGWIT);
// Versions with BIP32 derivations
- Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}});
- Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, {{0}});
- Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, {{0xFFFFFFFFUL,0}});
- Check("wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}, {{0x8000000DUL, 1, 2, 0}, {0x8000000DUL, 1, 2, 1}, {0x8000000DUL, 1, 2, 2}});
- Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}, {{10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}});
- Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, {{0}, {1}});
+ Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}, nullopt);
+ Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, nullopt, {{0}});
+ Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, OutputType::LEGACY, {{0xFFFFFFFFUL,0}});
+ Check("wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}, OutputType::BECH32, {{0x8000000DUL, 1, 2, 0}, {0x8000000DUL, 1, 2, 1}, {0x8000000DUL, 1, 2, 2}});
+ Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}, OutputType::P2SH_SEGWIT, {{10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}});
+ Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, nullopt, {{0}, {1}});
CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long key fingerprint
CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)", "Key path value 2147483648 is out of range"); // BIP 32 path element overflow
CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1aa)", "Key path value '1aa' is not a valid uint32"); // Path is not valid uint
// Multisig constructions
- Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}});
- Check("sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}});
- Check("sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}});
- Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}});
- Check("sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", RANGE, {{"5221025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b62102fbd47cc8034098f0e6a94c6aeee8528abf0a2153a5d8e46d325b7284c046784652ae"}, {"52210264fd4d1f5dea8ded94c61e9641309349b62f27fbffe807291f664e286bfbe6472103f4ece6dfccfa37b211eb3d0af4d0c61dba9ef698622dc17eecdf764beeb005a652ae"}, {"5221022ccabda84c30bad578b13c89eb3b9544ce149787e5b538175b1d1ba259cbb83321024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c52ae"}}, {{0}, {1}, {2}, {0, 0, 0}, {0, 0, 1}, {0, 0, 2}});
- Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}, {{0xFFFFFFFFUL,0}, {1,2,0}, {1,2,1}, {1,2,2}, {10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}});
- Check("sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}});
+ Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt);
+ Check("sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt);
+ Check("sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt);
+ Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
+ Check("sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", RANGE, {{"5221025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b62102fbd47cc8034098f0e6a94c6aeee8528abf0a2153a5d8e46d325b7284c046784652ae"}, {"52210264fd4d1f5dea8ded94c61e9641309349b62f27fbffe807291f664e286bfbe6472103f4ece6dfccfa37b211eb3d0af4d0c61dba9ef698622dc17eecdf764beeb005a652ae"}, {"5221022ccabda84c30bad578b13c89eb3b9544ce149787e5b538175b1d1ba259cbb83321024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c52ae"}}, nullopt, {{0}, {1}, {2}, {0, 0, 0}, {0, 0, 1}, {0, 0, 2}});
+ Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}, OutputType::BECH32, {{0xFFFFFFFFUL,0}, {1,2,0}, {1,2,1}, {1,2,2}, {10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}});
+ Check("sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}}, OutputType::P2SH_SEGWIT);
CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))", "P2SH script is too large, 547 bytes is larger than 520 bytes"); // P2SH does not fit 16 compressed pubkeys in a redeemscript
CheckUnparsable("wsh(multi(2,[aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multiple ']' characters found for a single pubkey"); // Double key origin descriptor
CheckUnparsable("wsh(multi(2,[aaaagaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaagaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Fingerprint 'aaagaaaa' is not hex"); // Non hex fingerprint
@@ -280,8 +284,8 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
CheckUnparsable("wsh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "Cannot have wsh within wsh"); // Cannot embed P2WSH inside P2WSH
// Checksums
- Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}});
- Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}});
+ Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
+ Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#", "Expected 8 character checksum, not 0 characters"); // Empty checksum
CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfyq", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5tq", "Expected 8 character checksum, not 9 characters"); // Too long checksum
CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxf", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5", "Expected 8 character checksum, not 7 characters"); // Too short checksum
diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp
index b292d5b0d0..a6cb34cf28 100644
--- a/src/test/scheduler_tests.cpp
+++ b/src/test/scheduler_tests.cpp
@@ -5,8 +5,6 @@
#include <random.h>
#include <scheduler.h>
-#include <test/util/setup_common.h>
-
#include <boost/thread.hpp>
#include <boost/test/unit_test.hpp>
@@ -155,4 +153,45 @@ BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered)
BOOST_CHECK_EQUAL(counter2, 100);
}
+BOOST_AUTO_TEST_CASE(mockforward)
+{
+ CScheduler scheduler;
+
+ int counter{0};
+ CScheduler::Function dummy = [&counter]{counter++;};
+
+ // schedule jobs for 2, 5 & 8 minutes into the future
+ int64_t min_in_milli = 60*1000;
+ scheduler.scheduleFromNow(dummy, 2*min_in_milli);
+ scheduler.scheduleFromNow(dummy, 5*min_in_milli);
+ scheduler.scheduleFromNow(dummy, 8*min_in_milli);
+
+ // check taskQueue
+ boost::chrono::system_clock::time_point first, last;
+ size_t num_tasks = scheduler.getQueueInfo(first, last);
+ BOOST_CHECK_EQUAL(num_tasks, 3ul);
+
+ std::thread scheduler_thread([&]() { scheduler.serviceQueue(); });
+
+ // bump the scheduler forward 5 minutes
+ scheduler.MockForward(boost::chrono::seconds(5*60));
+
+ // ensure scheduler has chance to process all tasks queued for before 1 ms from now.
+ scheduler.scheduleFromNow([&scheduler]{ scheduler.stop(false); }, 1);
+ scheduler_thread.join();
+
+ // check that the queue only has one job remaining
+ num_tasks = scheduler.getQueueInfo(first, last);
+ BOOST_CHECK_EQUAL(num_tasks, 1ul);
+
+ // check that the dummy function actually ran
+ BOOST_CHECK_EQUAL(counter, 2);
+
+ // check that the time of the remaining job has been updated
+ boost::chrono::system_clock::time_point now = boost::chrono::system_clock::now();
+ int delta = boost::chrono::duration_cast<boost::chrono::seconds>(first - now).count();
+ // should be between 2 & 3 minutes from now
+ BOOST_CHECK(delta > 2*60 && delta < 3*60);
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp
index 0352d2d95a..bcf4033fe4 100644
--- a/src/test/transaction_tests.cpp
+++ b/src/test/transaction_tests.cpp
@@ -122,10 +122,9 @@ BOOST_AUTO_TEST_CASE(tx_valid)
std::map<COutPoint, int64_t> mapprevOutValues;
UniValue inputs = test[0].get_array();
bool fValid = true;
- for (unsigned int inpIdx = 0; inpIdx < inputs.size(); inpIdx++) {
- const UniValue& input = inputs[inpIdx];
- if (!input.isArray())
- {
+ for (unsigned int inpIdx = 0; inpIdx < inputs.size(); inpIdx++) {
+ const UniValue& input = inputs[inpIdx];
+ if (!input.isArray()) {
fValid = false;
break;
}
@@ -209,10 +208,9 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
std::map<COutPoint, int64_t> mapprevOutValues;
UniValue inputs = test[0].get_array();
bool fValid = true;
- for (unsigned int inpIdx = 0; inpIdx < inputs.size(); inpIdx++) {
- const UniValue& input = inputs[inpIdx];
- if (!input.isArray())
- {
+ for (unsigned int inpIdx = 0; inpIdx < inputs.size(); inpIdx++) {
+ const UniValue& input = inputs[inpIdx];
+ if (!input.isArray()) {
fValid = false;
break;
}
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index ccb3064d59..360377e58a 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -103,10 +103,12 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha
g_rpc_node = &m_node;
RegisterAllCoreRPCCommands(tableRPC);
+ m_node.scheduler = MakeUnique<CScheduler>();
+
// We have to run a scheduler thread to prevent ActivateBestChain
// from blocking due to queue overrun.
- threadGroup.create_thread(std::bind(&CScheduler::serviceQueue, &scheduler));
- GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);
+ threadGroup.create_thread([&]{ m_node.scheduler->serviceQueue(); });
+ GetMainSignals().RegisterBackgroundSignalScheduler(*g_rpc_node->scheduler);
pblocktree.reset(new CBlockTreeDB(1 << 20, true));
g_chainstate = MakeUnique<CChainState>();
@@ -147,6 +149,7 @@ TestingSetup::~TestingSetup()
m_node.connman.reset();
m_node.banman.reset();
m_node.mempool = nullptr;
+ m_node.scheduler.reset();
UnloadBlockIndex();
g_chainstate.reset();
pblocktree.reset();
diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h
index 6741be8480..56ad62eb24 100644
--- a/src/test/util/setup_common.h
+++ b/src/test/util/setup_common.h
@@ -85,7 +85,6 @@ private:
struct TestingSetup : public BasicTestingSetup {
NodeContext m_node;
boost::thread_group threadGroup;
- CScheduler scheduler;
explicit TestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
~TestingSetup();
diff --git a/src/test/validation_flush_tests.cpp b/src/test/validation_flush_tests.cpp
index ab8b957f7d..c24164528f 100644
--- a/src/test/validation_flush_tests.cpp
+++ b/src/test/validation_flush_tests.cpp
@@ -2,10 +2,10 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
-#include <txmempool.h>
-#include <validation.h>
#include <sync.h>
#include <test/util/setup_common.h>
+#include <txmempool.h>
+#include <validation.h>
#include <boost/test/unit_test.hpp>
@@ -85,12 +85,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate)
// This is contingent not only on the dynamic memory usage of the Coins
// that we're adding (COIN_SIZE bytes per), but also on how much memory the
// cacheCoins (unordered_map) preallocates.
- //
- // I came up with the count by examining the printed memory usage of the
- // CCoinsCacheView, so it's sort of arbitrary - but it shouldn't change
- // unless we somehow change the way the cacheCoins map allocates memory.
- //
- constexpr int COINS_UNTIL_CRITICAL = is_64_bit ? 4 : 5;
+ constexpr int COINS_UNTIL_CRITICAL{3};
for (int i{0}; i < COINS_UNTIL_CRITICAL; ++i) {
COutPoint res = add_coin(view);
@@ -101,17 +96,14 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate)
CoinsCacheSizeState::OK);
}
- // Adding an additional coin will push us over the edge to CRITICAL.
- add_coin(view);
- print_view_mem_usage(view);
-
- auto size_state = chainstate.GetCoinsCacheSizeState(
- tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0);
-
- if (!is_64_bit && size_state == CoinsCacheSizeState::LARGE) {
- // On 32 bit hosts, we may hit LARGE before CRITICAL.
+ // Adding some additional coins will push us over the edge to CRITICAL.
+ for (int i{0}; i < 4; ++i) {
add_coin(view);
print_view_mem_usage(view);
+ if (chainstate.GetCoinsCacheSizeState(tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0) ==
+ CoinsCacheSizeState::CRITICAL) {
+ break;
+ }
}
BOOST_CHECK_EQUAL(
diff --git a/src/util/system.cpp b/src/util/system.cpp
index 13ff99c663..b0a538b527 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -63,6 +63,7 @@
#include <malloc.h>
#endif
+#include <boost/algorithm/string/replace.hpp>
#include <thread>
#include <typeinfo>
#include <univalue.h>
@@ -1047,6 +1048,15 @@ fs::path GetSpecialFolderPath(int nFolder, bool fCreate)
}
#endif
+#ifndef WIN32
+std::string ShellEscape(const std::string& arg)
+{
+ std::string escaped = arg;
+ boost::replace_all(escaped, "'", "'\"'\"'");
+ return "'" + escaped + "'";
+}
+#endif
+
#if HAVE_SYSTEM
void runCommand(const std::string& strCommand)
{
diff --git a/src/util/system.h b/src/util/system.h
index bb69181de9..3138522b5c 100644
--- a/src/util/system.h
+++ b/src/util/system.h
@@ -81,6 +81,9 @@ fs::path GetConfigFile(const std::string& confPath);
#ifdef WIN32
fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
#endif
+#ifndef WIN32
+std::string ShellEscape(const std::string& arg);
+#endif
#if HAVE_SYSTEM
void runCommand(const std::string& strCommand);
#endif
diff --git a/src/util/time.cpp b/src/util/time.cpp
index 2afff2626b..f33966f149 100644
--- a/src/util/time.cpp
+++ b/src/util/time.cpp
@@ -94,10 +94,12 @@ std::string FormatISO8601DateTime(int64_t nTime) {
struct tm ts;
time_t time_val = nTime;
#ifdef _MSC_VER
- gmtime_s(&ts, &time_val);
+ if (gmtime_s(&ts, &time_val) != 0) {
#else
- gmtime_r(&time_val, &ts);
+ if (gmtime_r(&time_val, &ts) == nullptr) {
#endif
+ return {};
+ }
return strprintf("%04i-%02i-%02iT%02i:%02i:%02iZ", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec);
}
@@ -105,10 +107,12 @@ std::string FormatISO8601Date(int64_t nTime) {
struct tm ts;
time_t time_val = nTime;
#ifdef _MSC_VER
- gmtime_s(&ts, &time_val);
+ if (gmtime_s(&ts, &time_val) != 0) {
#else
- gmtime_r(&time_val, &ts);
+ if (gmtime_r(&time_val, &ts) == nullptr) {
#endif
+ return {};
+ }
return strprintf("%04i-%02i-%02i", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday);
}
@@ -124,4 +128,4 @@ int64_t ParseISO8601DateTime(const std::string& str)
if (ptime.is_not_a_date_time() || epoch > ptime)
return 0;
return (ptime - epoch).total_seconds();
-} \ No newline at end of file
+}
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 2ebc9aba39..50f064b305 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -62,7 +62,7 @@ void WalletInit::AddWalletOptions() const
gArgs.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
#if HAVE_SYSTEM
- gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
+ gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes. %s in cmd is replaced by TxID and %w is replaced by wallet name. %w is not currently implemented on windows. On systems where %w is supported, it should NOT be quoted because this would break shell escaping used to invoke the command.", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
#endif
gArgs.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
gArgs.AddArg("-zapwallettxes=<mode>", "Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup"
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 6a42137ddd..65fffe6487 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -970,9 +970,9 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
- " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n"
- " \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n"
- " \"descriptor\":\"descriptor\" (string) The descriptor for this multisig\n"
+ " \"address\" : \"multisigaddress\", (string) The value of the new multisig address.\n"
+ " \"redeemScript\" : \"script\" (string) The string value of the hex-encoded redemption script.\n"
+ " \"descriptor\" : \"descriptor\" (string) The descriptor for this multisig\n"
"}\n"
},
RPCExamples{
@@ -1204,12 +1204,12 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request)
RPCResult{
"[\n"
" {\n"
- " \"involvesWatchonly\" : true, (bool) Only returns true if imported addresses were involved in transaction.\n"
+ " \"involvesWatchonly\" : true, (boolean) Only returns true if imported addresses were involved in transaction.\n"
" \"address\" : \"receivingaddress\", (string) The receiving address\n"
" \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " received by the address\n"
" \"confirmations\" : n, (numeric) The number of confirmations of the most recent transaction included\n"
" \"label\" : \"label\", (string) The label of the receiving address. The default label is \"\".\n"
- " \"txids\": [\n"
+ " \"txids\" : [\n"
" \"txid\", (string) The ids of transactions received with the address \n"
" ...\n"
" ]\n"
@@ -1254,7 +1254,7 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request)
RPCResult{
"[\n"
" {\n"
- " \"involvesWatchonly\" : true, (bool) Only returns true if imported addresses were involved in transaction.\n"
+ " \"involvesWatchonly\" : true, (boolean) Only returns true if imported addresses were involved in transaction.\n"
" \"amount\" : x.xxx, (numeric) The total amount received by addresses with this label\n"
" \"confirmations\" : n, (numeric) The number of confirmations of the most recent transaction included\n"
" \"label\" : \"label\" (string) The label of the receiving address. The default label is \"\".\n"
@@ -1376,20 +1376,20 @@ static const std::string TransactionDescriptionString()
{
return " \"confirmations\": n, (numeric) The number of confirmations for the transaction. Negative confirmations means the\n"
" transaction conflicted that many blocks ago.\n"
- " \"generated\": xxx, (bool) Only present if transaction only input is a coinbase one.\n"
- " \"trusted\": xxx, (bool) Only present if we consider transaction to be trusted and so safe to spend from.\n"
- " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction.\n"
- " \"blockheight\": n, (numeric) The block height containing the transaction.\n"
- " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it.\n"
- " \"blocktime\": xxx, (numeric) The block time expressed in " + UNIX_EPOCH_TIME + ".\n"
- " \"txid\": \"transactionid\", (string) The transaction id.\n"
- " \"walletconflicts\": [ (array) Conflicting transaction ids.\n"
+ " \"generated\" : xxx, (boolean) Only present if transaction only input is a coinbase one.\n"
+ " \"trusted\" : xxx, (boolean) Only present if we consider transaction to be trusted and so safe to spend from.\n"
+ " \"blockhash\" : \"hashvalue\", (string) The block hash containing the transaction.\n"
+ " \"blockheight\" : n, (numeric) The block height containing the transaction.\n"
+ " \"blockindex\" : n, (numeric) The index of the transaction in the block that includes it.\n"
+ " \"blocktime\" : xxx, (numeric) The block time expressed in " + UNIX_EPOCH_TIME + ".\n"
+ " \"txid\" : \"transactionid\", (string) The transaction id.\n"
+ " \"walletconflicts\" : [ (json array) Conflicting transaction ids.\n"
" \"txid\", (string) The transaction id.\n"
" ...\n"
" ],\n"
- " \"time\": xxx, (numeric) The transaction time expressed in " + UNIX_EPOCH_TIME + ".\n"
- " \"timereceived\": xxx, (numeric) The time received expressed in " + UNIX_EPOCH_TIME + ".\n"
- " \"comment\": \"...\", (string) If a comment is associated with the transaction, only present if not empty.\n"
+ " \"time\" : xxx, (numeric) The transaction time expressed in " + UNIX_EPOCH_TIME + ".\n"
+ " \"timereceived\" : xxx, (numeric) The time received expressed in " + UNIX_EPOCH_TIME + ".\n"
+ " \"comment\" : \"...\", (string) If a comment is associated with the transaction, only present if not empty.\n"
" \"bip125-replaceable\" : \"str\", (string) (\"yes|no|unknown\") Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
" may be unknown for unconfirmed transactions not in the mempool\n";
}
@@ -1416,22 +1416,22 @@ UniValue listtransactions(const JSONRPCRequest& request)
RPCResult{
"[\n"
" {\n"
- " \"involvesWatchonly\": xxx, (bool) Only returns true if imported addresses were involved in transaction.\n"
- " \"address\":\"address\", (string) The bitcoin address of the transaction.\n"
- " \"category\": (string) The transaction category.\n"
+ " \"involvesWatchonly\" : xxx, (boolean) Only returns true if imported addresses were involved in transaction.\n"
+ " \"address\" : \"address\", (string) The bitcoin address of the transaction.\n"
+ " \"category\" : (string) The transaction category.\n"
" \"send\" Transactions sent.\n"
" \"receive\" Non-coinbase transactions received.\n"
" \"generate\" Coinbase transactions received with more than 100 confirmations.\n"
" \"immature\" Coinbase transactions received with 100 or fewer confirmations.\n"
" \"orphan\" Orphaned coinbase transactions received.\n"
- " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
+ " \"amount\" : x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
" for all other categories\n"
- " \"label\": \"label\", (string) A comment for the address/transaction, if any\n"
- " \"vout\": n, (numeric) the vout value\n"
- " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
+ " \"label\" : \"label\", (string) A comment for the address/transaction, if any\n"
+ " \"vout\" : n, (numeric) the vout value\n"
+ " \"fee\" : x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
" 'send' category of transactions.\n"
+ TransactionDescriptionString()
- + " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
+ + " \"abandoned\": xxx (boolean) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
" 'send' category of transactions.\n"
" }\n"
"]\n"
@@ -1528,7 +1528,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
"{ (json object)\n"
" \"transactions\" : [ (json array)\n"
" { (json object)\n"
- " \"involvesWatchonly\": xxx, (bool) Only returns true if imported addresses were involved in transaction.\n"
+ " \"involvesWatchonly\" : xxx, (boolean) Only returns true if imported addresses were involved in transaction.\n"
" \"address\" : \"str\", (string) The bitcoin address of the transaction.\n"
" \"category\" : \"str\", (string) The transaction category.\n"
" \"send\" Transactions sent.\n"
@@ -1536,22 +1536,22 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
" \"generate\" Coinbase transactions received with more than 100 confirmations.\n"
" \"immature\" Coinbase transactions received with 100 or fewer confirmations.\n"
" \"orphan\" Orphaned coinbase transactions received.\n"
- " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
+ " \"amount\" : x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
" for all other categories\n"
" \"vout\" : n, (numeric) the vout value\n"
- " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the 'send' category of transactions.\n"
+ " \"fee\" : x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the 'send' category of transactions.\n"
+ TransactionDescriptionString()
- + " \"abandoned\": xxx, (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the 'send' category of transactions.\n"
+ + " \"abandoned\": xxx, (boolean) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the 'send' category of transactions.\n"
" \"label\" : \"label\" (string) A comment for the address/transaction, if any\n"
- " \"to\": \"...\", (string) If a comment to is associated with the transaction.\n"
+ " \"to\" : \"...\", (string) If a comment to is associated with the transaction.\n"
" },\n"
" ...\n"
" ],\n"
- " \"removed\": [ (json array)\n"
+ " \"removed\" : [ (json array)\n"
" <structure is the same as \"transactions\" above, only present if include_removed=true>\n"
" Note: transactions that were re-added in the active chain will appear as-is in this array, and may thus have a positive confirmation count.\n"
" ],\n"
- " \"lastblock\": \"hex\" (string) The hash of the block (target_confirmations-1) from the best block on the main chain. This is typically used to feed back into listsinceblock the next time you call it. So you would generally use a target_confirmations of say 6, so you will be continually re-notified of transactions until they've reached 6 confirmations plus any new ones\n"
+ " \"lastblock\" : \"hex\" (string) The hash of the block (target_confirmations-1) from the best block on the main chain. This is typically used to feed back into listsinceblock the next time you call it. So you would generally use a target_confirmations of say 6, so you will be continually re-notified of transactions until they've reached 6 confirmations plus any new ones\n"
"}\n"
},
RPCExamples{
@@ -1662,12 +1662,12 @@ static UniValue gettransaction(const JSONRPCRequest& request)
RPCResult{
"{\n"
" \"amount\" : x.xxx, (numeric) The transaction amount in " + CURRENCY_UNIT + "\n"
- " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
+ " \"fee\" : x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
" 'send' category of transactions.\n"
+ TransactionDescriptionString()
+ " \"details\" : [\n"
" {\n"
- " \"involvesWatchonly\": xxx, (bool) Only returns true if imported addresses were involved in transaction.\n"
+ " \"involvesWatchonly\" : xxx, (boolean) Only returns true if imported addresses were involved in transaction.\n"
" \"address\" : \"address\", (string) The bitcoin address involved in the transaction\n"
" \"category\" : (string) The transaction category.\n"
" \"send\" Transactions sent.\n"
@@ -1678,9 +1678,9 @@ static UniValue gettransaction(const JSONRPCRequest& request)
" \"amount\" : x.xxx, (numeric) The amount in " + CURRENCY_UNIT + "\n"
" \"label\" : \"label\", (string) A comment for the address/transaction, if any\n"
" \"vout\" : n, (numeric) the vout value\n"
- " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
+ " \"fee\" : x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
" 'send' category of transactions.\n"
- " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
+ " \"abandoned\" : xxx (boolean) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
" 'send' category of transactions.\n"
" }\n"
" ,...\n"
@@ -2352,16 +2352,16 @@ static UniValue getbalances(const JSONRPCRequest& request)
{},
RPCResult{
"{\n"
- " \"mine\": { (object) balances from outputs that the wallet can sign\n"
- " \"trusted\": xxx (numeric) trusted balance (outputs created by the wallet or confirmed outputs)\n"
- " \"untrusted_pending\": xxx (numeric) untrusted pending balance (outputs created by others that are in the mempool)\n"
- " \"immature\": xxx (numeric) balance from immature coinbase outputs\n"
- " \"used\": xxx (numeric) (only present if avoid_reuse is set) balance from coins sent to addresses that were previously spent from (potentially privacy violating)\n"
+ " \"mine\" : { (json object) balances from outputs that the wallet can sign\n"
+ " \"trusted\" : xxx (numeric) trusted balance (outputs created by the wallet or confirmed outputs)\n"
+ " \"untrusted_pending\" : xxx (numeric) untrusted pending balance (outputs created by others that are in the mempool)\n"
+ " \"immature\" : xxx (numeric) balance from immature coinbase outputs\n"
+ " \"used\" : xxx (numeric) (only present if avoid_reuse is set) balance from coins sent to addresses that were previously spent from (potentially privacy violating)\n"
" },\n"
- " \"watchonly\": { (object) watchonly balances (not present if wallet does not watch anything)\n"
- " \"trusted\": xxx (numeric) trusted balance (outputs created by the wallet or confirmed outputs)\n"
- " \"untrusted_pending\": xxx (numeric) untrusted pending balance (outputs created by others that are in the mempool)\n"
- " \"immature\": xxx (numeric) balance from immature coinbase outputs\n"
+ " \"watchonly\" : { (json object) watchonly balances (not present if wallet does not watch anything)\n"
+ " \"trusted\" : xxx (numeric) trusted balance (outputs created by the wallet or confirmed outputs)\n"
+ " \"untrusted_pending\" : xxx (numeric) untrusted pending balance (outputs created by others that are in the mempool)\n"
+ " \"immature\" : xxx (numeric) balance from immature coinbase outputs\n"
" },\n"
"}\n"},
RPCExamples{
@@ -2418,21 +2418,21 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
{},
RPCResult{
"{\n"
- " \"walletname\": xxxxx, (string) the wallet name\n"
- " \"walletversion\": xxxxx, (numeric) the wallet version\n"
- " \"balance\": xxxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.trusted\n"
- " \"unconfirmed_balance\": xxx, (numeric) DEPRECATED. Identical to getbalances().mine.untrusted_pending\n"
- " \"immature_balance\": xxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.immature\n"
- " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
- " \"keypoololdest\": xxxxxx, (numeric) the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool\n"
- " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n"
- " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n"
- " \"unlocked_until\": ttt, (numeric) the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
- " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
- " \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n"
- " \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n"
- " \"avoid_reuse\": true|false (boolean) whether this wallet tracks clean/dirty coins in terms of reuse\n"
- " \"scanning\": (json object) current scanning details, or false if no scan is in progress\n"
+ " \"walletname\" : xxxxx, (string) the wallet name\n"
+ " \"walletversion\" : xxxxx, (numeric) the wallet version\n"
+ " \"balance\" : xxxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.trusted\n"
+ " \"unconfirmed_balance\" : xxx, (numeric) DEPRECATED. Identical to getbalances().mine.untrusted_pending\n"
+ " \"immature_balance\" : xxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.immature\n"
+ " \"txcount\" : xxxxxxx, (numeric) the total number of transactions in the wallet\n"
+ " \"keypoololdest\" : xxxxxx, (numeric) the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool\n"
+ " \"keypoolsize\" : xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n"
+ " \"keypoolsize_hd_internal\" : xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n"
+ " \"unlocked_until\" : ttt, (numeric) the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
+ " \"paytxfee\" : x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
+ " \"hdseedid\" : \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n"
+ " \"private_keys_enabled\" : true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n"
+ " \"avoid_reuse\" : true|false (boolean) whether this wallet tracks clean/dirty coins in terms of reuse\n"
+ " \"scanning\" : (json object) current scanning details, or false if no scan is in progress\n"
" {\n"
" \"duration\" : xxxx (numeric) elapsed seconds since scan start\n"
" \"progress\" : x.xxxx, (numeric) scanning progress percentage [0.0, 1.0]\n"
@@ -2625,9 +2625,9 @@ static UniValue setwalletflag(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
- " \"flag_name\": string (string) The name of the flag that was modified\n"
- " \"flag_state\": bool (bool) The new state of the flag\n"
- " \"warnings\": string (string) Any warnings associated with the change\n"
+ " \"flag_name\" : string (string) The name of the flag that was modified\n"
+ " \"flag_state\" : bool (boolean) The new state of the flag\n"
+ " \"warnings\" : string (string) Any warnings associated with the change\n"
"}\n"
},
RPCExamples{
@@ -2824,11 +2824,11 @@ static UniValue listunspent(const JSONRPCRequest& request)
" \"confirmations\" : n, (numeric) The number of confirmations\n"
" \"redeemScript\" : \"script\" (string) The redeemScript if scriptPubKey is P2SH\n"
" \"witnessScript\" : \"script\" (string) witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH\n"
- " \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n"
- " \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n"
- " \"reused\" : xxx, (bool) (only present if avoid_reuse is set) Whether this output is reused/dirty (sent to an address that was previously spent from)\n"
+ " \"spendable\" : xxx, (boolean) Whether we have the private keys to spend this output\n"
+ " \"solvable\" : xxx, (boolean) Whether we know how to spend this output, ignoring the lack of keys\n"
+ " \"reused\" : xxx, (boolean) (only present if avoid_reuse is set) Whether this output is reused/dirty (sent to an address that was previously spent from)\n"
" \"desc\" : xxx, (string, only when solvable) A descriptor for spending this output\n"
- " \"safe\" : xxx (bool) Whether this output is considered safe to spend. Unconfirmed transactions\n"
+ " \"safe\" : xxx (boolean) Whether this output is considered safe to spend. Unconfirmed transactions\n"
" from outside keys and unconfirmed replacement transactions are considered unsafe\n"
" and are not eligible for spending by fundrawtransaction and sendtoaddress.\n"
" }\n"
@@ -2923,7 +2923,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
CTxDestination address;
const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
bool fValidAddress = ExtractDestination(scriptPubKey, address);
- bool reused = avoid_reuse && pwallet->IsUsedDestination(out.tx->GetHash(), out.i);
+ bool reused = avoid_reuse && pwallet->IsSpentKey(out.tx->GetHash(), out.i);
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
continue;
@@ -3172,9 +3172,9 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
- " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n"
- " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n"
- " \"changepos\": n (numeric) The position of the added change output, or -1\n"
+ " \"hex\" : \"value\", (string) The resulting raw transaction (hex-encoded string)\n"
+ " \"fee\" : n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n"
+ " \"changepos\" : n (numeric) The position of the added change output, or -1\n"
"}\n"
},
RPCExamples{
@@ -3361,11 +3361,11 @@ static UniValue bumpfee(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
- " \"psbt\": \"psbt\", (string) The base64-encoded unsigned PSBT of the new transaction. Only returned when wallet private keys are disabled.\n"
- " \"txid\": \"value\", (string) The id of the new transaction. Only returned when wallet private keys are enabled.\n"
- " \"origfee\": n, (numeric) The fee of the replaced transaction.\n"
- " \"fee\": n, (numeric) The fee of the new transaction.\n"
- " \"errors\": [ str... ] (json array of strings) Errors encountered during processing (may be empty).\n"
+ " \"psbt\" : \"psbt\", (string) The base64-encoded unsigned PSBT of the new transaction. Only returned when wallet private keys are disabled.\n"
+ " \"txid\" : \"value\", (string) The id of the new transaction. Only returned when wallet private keys are enabled.\n"
+ " \"origfee\" : n, (numeric) The fee of the replaced transaction.\n"
+ " \"fee\" : n, (numeric) The fee of the new transaction.\n"
+ " \"errors\" : [ str... ] (json array of strings) Errors encountered during processing (may be empty).\n"
"}\n"
},
RPCExamples{
@@ -3722,8 +3722,6 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
return NullUniValue;
}
- const std::string example_address = "\"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl\"";
-
RPCHelpMan{"getaddressinfo",
"\nReturn information about the given bitcoin address.\n"
"Some of the information will only be present if the address is in the active wallet.\n",
@@ -3763,7 +3761,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
" \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath, if the key is HD and available.\n"
" \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed.\n"
" \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingerprint of the master key.\n"
- " \"labels\" (array) Array of labels associated with the address. Currently limited to one label but returned\n"
+ " \"labels\" (json array) Array of labels associated with the address. Currently limited to one label but returned\n"
" as an array to keep the API stable if multiple labels are enabled in the future.\n"
" [\n"
" \"label name\" (string) The label name. Defaults to \"\".\n"
@@ -3776,8 +3774,8 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
"}\n"
},
RPCExamples{
- HelpExampleCli("getaddressinfo", example_address) +
- HelpExampleRpc("getaddressinfo", example_address)
+ HelpExampleCli("getaddressinfo", EXAMPLE_ADDRESS) +
+ HelpExampleRpc("getaddressinfo", EXAMPLE_ADDRESS)
},
}.Check(request);
@@ -3871,8 +3869,8 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request)
},
RPCResult{
"{ (json object with addresses as keys)\n"
- " \"address\": { (json object with information about address)\n"
- " \"purpose\": \"string\" (string) Purpose of address (\"send\" for sending address, \"receive\" for receiving address)\n"
+ " \"address\" : { (json object with information about address)\n"
+ " \"purpose\" : \"string\" (string) Purpose of address (\"send\" for sending address, \"receive\" for receiving address)\n"
" },...\n"
"}\n"
},
@@ -4182,9 +4180,9 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
- " \"psbt\": \"value\", (string) The resulting raw transaction (base64-encoded string)\n"
- " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n"
- " \"changepos\": n (numeric) The position of the added change output, or -1\n"
+ " \"psbt\" : \"value\", (string) The resulting raw transaction (base64-encoded string)\n"
+ " \"fee\" : n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n"
+ " \"changepos\" : n (numeric) The position of the added change output, or -1\n"
"}\n"
},
RPCExamples{
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 4c9d88973e..0c95ab29b1 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -70,7 +70,15 @@ bool HaveKeys(const std::vector<valtype>& pubkeys, const LegacyScriptPubKeyMan&
return true;
}
-IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion)
+//! Recursively solve script and return spendable/watchonly/invalid status.
+//!
+//! @param keystore legacy key and script store
+//! @param script script to solve
+//! @param sigversion script type (top-level / redeemscript / witnessscript)
+//! @param recurse_scripthash whether to recurse into nested p2sh and p2wsh
+//! scripts or simply treat any script that has been
+//! stored in the keystore as spendable
+IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion, bool recurse_scripthash=true)
{
IsMineResult ret = IsMineResult::NO;
@@ -129,7 +137,7 @@ IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& s
CScriptID scriptID = CScriptID(uint160(vSolutions[0]));
CScript subscript;
if (keystore.GetCScript(scriptID, subscript)) {
- ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH));
+ ret = std::max(ret, recurse_scripthash ? IsMineInner(keystore, subscript, IsMineSigVersion::P2SH) : IsMineResult::SPENDABLE);
}
break;
}
@@ -147,7 +155,7 @@ IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& s
CScriptID scriptID = CScriptID(hash);
CScript subscript;
if (keystore.GetCScript(scriptID, subscript)) {
- ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0));
+ ret = std::max(ret, recurse_scripthash ? IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0) : IsMineResult::SPENDABLE);
}
break;
}
@@ -476,11 +484,11 @@ std::unique_ptr<SigningProvider> LegacyScriptPubKeyMan::GetSigningProvider(const
bool LegacyScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& sigdata)
{
- if (IsMine(script) != ISMINE_NO) {
- // If it IsMine, we can always provide in some way
- return true;
- } else if (HaveCScript(CScriptID(script))) {
- // We can still provide some stuff if we have the script, but IsMine failed because we don't have keys
+ IsMineResult ismine = IsMineInner(*this, script, IsMineSigVersion::TOP, /* recurse_scripthash= */ false);
+ if (ismine == IsMineResult::SPENDABLE || ismine == IsMineResult::WATCH_ONLY) {
+ // If ismine, it means we recognize keys or script ids in the script, or
+ // are watching the script itself, and we can at least provide metadata
+ // or solving information, even if not able to sign fully.
return true;
} else {
// If, given the stuff in sigdata, we could make a valid sigature, then we can provide for this script
diff --git a/src/wallet/test/scriptpubkeyman_tests.cpp b/src/wallet/test/scriptpubkeyman_tests.cpp
new file mode 100644
index 0000000000..757865ea37
--- /dev/null
+++ b/src/wallet/test/scriptpubkeyman_tests.cpp
@@ -0,0 +1,43 @@
+// Copyright (c) 2020 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <key.h>
+#include <script/standard.h>
+#include <test/util/setup_common.h>
+#include <wallet/scriptpubkeyman.h>
+#include <wallet/wallet.h>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(scriptpubkeyman_tests, BasicTestingSetup)
+
+// Test LegacyScriptPubKeyMan::CanProvide behavior, making sure it returns true
+// for recognized scripts even when keys may not be available for signing.
+BOOST_AUTO_TEST_CASE(CanProvide)
+{
+ // Set up wallet and keyman variables.
+ NodeContext node;
+ std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(node);
+ CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
+ LegacyScriptPubKeyMan& keyman = *wallet.GetOrCreateLegacyScriptPubKeyMan();
+
+ // Make a 1 of 2 multisig script
+ std::vector<CKey> keys(2);
+ std::vector<CPubKey> pubkeys;
+ for (CKey& key : keys) {
+ key.MakeNewKey(true);
+ pubkeys.emplace_back(key.GetPubKey());
+ }
+ CScript multisig_script = GetScriptForMultisig(1, pubkeys);
+ CScript p2sh_script = GetScriptForDestination(ScriptHash(multisig_script));
+ SignatureData data;
+
+ // Verify the p2sh(multisig) script is not recognized until the multisig
+ // script is added to the keystore to make it solvable
+ BOOST_CHECK(!keyman.CanProvide(p2sh_script, data));
+ keyman.AddCScript(multisig_script);
+ BOOST_CHECK(keyman.CanProvide(p2sh_script, data));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 405afb6d8d..02fe59b601 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -714,7 +714,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
return success;
}
-void CWallet::SetUsedDestinationState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations)
+void CWallet::SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations)
{
AssertLockHeld(cs_wallet);
const CWalletTx* srctx = GetWalletTx(hash);
@@ -734,7 +734,7 @@ void CWallet::SetUsedDestinationState(WalletBatch& batch, const uint256& hash, u
}
}
-bool CWallet::IsUsedDestination(const uint256& hash, unsigned int n) const
+bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const
{
AssertLockHeld(cs_wallet);
CTxDestination dst;
@@ -777,7 +777,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
for (const CTxIn& txin : wtxIn.tx->vin) {
const COutPoint& op = txin.prevout;
- SetUsedDestinationState(batch, op.hash, op.n, true, tx_destinations);
+ SetSpentKeyState(batch, op.hash, op.n, true, tx_destinations);
}
MarkDestinationsDirty(tx_destinations);
@@ -847,6 +847,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
if (!strCmd.empty())
{
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
+#ifndef WIN32
+ // Substituting the wallet name isn't currently supported on windows
+ // because windows shell escaping has not been implemented yet:
+ // https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-537384875
+ // A few ways it could be implemented in the future are described in:
+ // https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-461288094
+ boost::replace_all(strCmd, "%w", ShellEscape(GetName()));
+#endif
std::thread t(runCommand, strCmd);
t.detach(); // thread runs free
}
@@ -1870,7 +1878,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter
uint256 hashTx = GetHash();
for (unsigned int i = 0; i < tx->vout.size(); i++)
{
- if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsUsedDestination(hashTx, i))) {
+ if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsSpentKey(hashTx, i))) {
const CTxOut &txout = tx->vout[i];
nCredit += pwallet->GetCredit(txout, filter);
if (!MoneyRange(nCredit))
@@ -2160,7 +2168,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
continue;
}
- if (!allow_used_addresses && IsUsedDestination(wtxid, i)) {
+ if (!allow_used_addresses && IsSpentKey(wtxid, i)) {
continue;
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index a918bb8833..6c1c3040c2 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -817,8 +817,8 @@ public:
bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
// Whether this or any known UTXO with the same single key has been spent.
- bool IsUsedDestination(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- void SetUsedDestinationState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool IsSpentKey(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const;
diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp
index 5652877f3c..9c9b27a413 100644
--- a/src/zmq/zmqrpc.cpp
+++ b/src/zmq/zmqrpc.cpp
@@ -21,9 +21,9 @@ UniValue getzmqnotifications(const JSONRPCRequest& request)
RPCResult{
"[\n"
" { (json object)\n"
- " \"type\": \"pubhashtx\", (string) Type of notification\n"
- " \"address\": \"...\", (string) Address of the publisher\n"
- " \"hwm\": n (numeric) Outbound message high water mark\n"
+ " \"type\" : \"pubhashtx\", (string) Type of notification\n"
+ " \"address\" : \"...\", (string) Address of the publisher\n"
+ " \"hwm\" : n (numeric) Outbound message high water mark\n"
" },\n"
" ...\n"
"]\n"
diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py
index 1ac10f971a..7a6e3df702 100755
--- a/test/functional/feature_backwards_compatibility.py
+++ b/test/functional/feature_backwards_compatibility.py
@@ -122,6 +122,9 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] > 0
+ # Use addmultisigaddress (see #18075)
+ address_18075 = wallet.addmultisigaddress(1, ["0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52", "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073"], "", "legacy")["address"]
+ assert wallet.getaddressinfo(address_18075)["solvable"]
# w1_v18: regular wallet, created with v0.18
node_v18.createwallet(wallet_name="w1_v18")
@@ -319,7 +322,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
hdkeypath = info["hdkeypath"]
pubkey = info["pubkey"]
- # Copy the wallet to the last Bitcoin Core version and open it:
+ # Copy the 0.17 wallet to the last Bitcoin Core version and open it:
node_v17.unloadwallet("u1_v17")
shutil.copytree(
os.path.join(node_v17_wallets_dir, "u1_v17"),
@@ -331,5 +334,14 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + pubkey + ")"
assert_equal(info["desc"], descsum_create(descriptor))
+ # Copy the 0.19 wallet to the last Bitcoin Core version and open it:
+ shutil.copytree(
+ os.path.join(node_v19_wallets_dir, "w1_v19"),
+ os.path.join(node_master_wallets_dir, "w1_v19")
+ )
+ node_master.loadwallet("w1_v19")
+ wallet = node_master.get_wallet_rpc("w1_v19")
+ assert wallet.getaddressinfo(address_18075)["solvable"]
+
if __name__ == '__main__':
BackwardsCompatibilityTest().main()
diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py
index da00b773ad..b110a559c0 100755
--- a/test/functional/feature_notifications.py
+++ b/test/functional/feature_notifications.py
@@ -13,6 +13,16 @@ from test_framework.util import (
connect_nodes,
)
+# Linux allow all characters other than \x00
+# Windows disallow control characters (0-31) and /\?%:|"<>
+FILE_CHAR_START = 32 if os.name == 'nt' else 1
+FILE_CHAR_END = 128
+FILE_CHAR_BLACKLIST = '/\\?%*:|"<>' if os.name == 'nt' else '/'
+
+
+def notify_outputname(walletname, txid):
+ return txid if os.name == 'nt' else '{}_{}'.format(walletname, txid)
+
class NotificationsTest(BitcoinTestFramework):
def set_test_params(self):
@@ -20,6 +30,7 @@ class NotificationsTest(BitcoinTestFramework):
self.setup_clean_chain = True
def setup_network(self):
+ self.wallet = ''.join(chr(i) for i in range(FILE_CHAR_START, FILE_CHAR_END) if chr(i) not in FILE_CHAR_BLACKLIST)
self.alertnotify_dir = os.path.join(self.options.tmpdir, "alertnotify")
self.blocknotify_dir = os.path.join(self.options.tmpdir, "blocknotify")
self.walletnotify_dir = os.path.join(self.options.tmpdir, "walletnotify")
@@ -33,7 +44,8 @@ class NotificationsTest(BitcoinTestFramework):
"-blocknotify=echo > {}".format(os.path.join(self.blocknotify_dir, '%s'))],
["-blockversion=211",
"-rescan",
- "-walletnotify=echo > {}".format(os.path.join(self.walletnotify_dir, '%s'))]]
+ "-wallet={}".format(self.wallet),
+ "-walletnotify=echo > {}".format(os.path.join(self.walletnotify_dir, notify_outputname('%w', '%s')))]]
super().setup_network()
def run_test(self):
@@ -53,7 +65,7 @@ class NotificationsTest(BitcoinTestFramework):
wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10)
# directory content should equal the generated transaction hashes
- txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count)))
+ txids_rpc = list(map(lambda t: notify_outputname(self.wallet, t['txid']), self.nodes[1].listtransactions("*", block_count)))
assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir)))
self.stop_node(1)
for tx_file in os.listdir(self.walletnotify_dir):
@@ -67,7 +79,7 @@ class NotificationsTest(BitcoinTestFramework):
wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10)
# directory content should equal the generated transaction hashes
- txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count)))
+ txids_rpc = list(map(lambda t: notify_outputname(self.wallet, t['txid']), self.nodes[1].listtransactions("*", block_count)))
assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir)))
# TODO: add test for `-alertnotify` large fork notifications
diff --git a/test/functional/mempool_expiry.py b/test/functional/mempool_expiry.py
new file mode 100755
index 0000000000..8b9b7b155a
--- /dev/null
+++ b/test/functional/mempool_expiry.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Tests that a mempool transaction expires after a given timeout and that its
+children are removed as well.
+
+Both the default expiry timeout defined by DEFAULT_MEMPOOL_EXPIRY and a user
+definable expiry timeout via the '-mempoolexpiry=<n>' command line argument
+(<n> is the timeout in hours) are tested.
+"""
+
+from datetime import timedelta
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+ find_vout_for_address,
+)
+
+DEFAULT_MEMPOOL_EXPIRY = 336 # hours
+CUSTOM_MEMPOOL_EXPIRY = 10 # hours
+
+
+class MempoolExpiryTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def test_transaction_expiry(self, timeout):
+ """Tests that a transaction expires after the expiry timeout and its
+ children are removed as well."""
+ node = self.nodes[0]
+
+ # Send a parent transaction that will expire.
+ parent_address = node.getnewaddress()
+ parent_txid = node.sendtoaddress(parent_address, 1.0)
+
+ # Set the mocktime to the arrival time of the parent transaction.
+ entry_time = node.getmempoolentry(parent_txid)['time']
+ node.setmocktime(entry_time)
+
+ # Create child transaction spending the parent transaction
+ vout = find_vout_for_address(node, parent_txid, parent_address)
+ inputs = [{'txid': parent_txid, 'vout': vout}]
+ outputs = {node.getnewaddress(): 0.99}
+ child_raw = node.createrawtransaction(inputs, outputs)
+ child_signed = node.signrawtransactionwithwallet(child_raw)['hex']
+
+ # Let half of the timeout elapse and broadcast the child transaction.
+ half_expiry_time = entry_time + int(60 * 60 * timeout/2)
+ node.setmocktime(half_expiry_time)
+ child_txid = node.sendrawtransaction(child_signed)
+ self.log.info('Broadcast child transaction after {} hours.'.format(
+ timedelta(seconds=(half_expiry_time-entry_time))))
+
+ # Let most of the timeout elapse and check that the parent tx is still
+ # in the mempool.
+ nearly_expiry_time = entry_time + 60 * 60 * timeout - 5
+ node.setmocktime(nearly_expiry_time)
+ # Expiry of mempool transactions is only checked when a new transaction
+ # is added to the to the mempool.
+ node.sendtoaddress(node.getnewaddress(), 1.0)
+ self.log.info('Test parent tx not expired after {} hours.'.format(
+ timedelta(seconds=(nearly_expiry_time-entry_time))))
+ assert_equal(entry_time, node.getmempoolentry(parent_txid)['time'])
+
+ # Transaction should be evicted from the mempool after the expiry time
+ # has passed.
+ expiry_time = entry_time + 60 * 60 * timeout + 5
+ node.setmocktime(expiry_time)
+ # Expiry of mempool transactions is only checked when a new transaction
+ # is added to the to the mempool.
+ node.sendtoaddress(node.getnewaddress(), 1.0)
+ self.log.info('Test parent tx expiry after {} hours.'.format(
+ timedelta(seconds=(expiry_time-entry_time))))
+ assert_raises_rpc_error(-5, 'Transaction not in mempool',
+ node.getmempoolentry, parent_txid)
+
+ # The child transaction should be removed from the mempool as well.
+ self.log.info('Test child tx is evicted as well.')
+ assert_raises_rpc_error(-5, 'Transaction not in mempool',
+ node.getmempoolentry, child_txid)
+
+ def run_test(self):
+ self.log.info('Test default mempool expiry timeout of %d hours.' %
+ DEFAULT_MEMPOOL_EXPIRY)
+ self.test_transaction_expiry(DEFAULT_MEMPOOL_EXPIRY)
+
+ self.log.info('Test custom mempool expiry timeout of %d hours.' %
+ CUSTOM_MEMPOOL_EXPIRY)
+ self.restart_node(0, ['-mempoolexpiry=%d' % CUSTOM_MEMPOOL_EXPIRY])
+ self.test_transaction_expiry(CUSTOM_MEMPOOL_EXPIRY)
+
+
+if __name__ == '__main__':
+ MempoolExpiryTest().main()
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index ea8a340081..06d939afb7 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -175,6 +175,7 @@ BASE_SCRIPTS = [
'wallet_balance.py',
'feature_nulldummy.py',
'mempool_accept.py',
+ 'mempool_expiry.py',
'wallet_import_rescan.py',
'wallet_import_with_label.py',
'rpc_bind.py --ipv4',
diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py
index 50e86cf9dc..a56651cca5 100755
--- a/test/fuzz/test_runner.py
+++ b/test/fuzz/test_runner.py
@@ -47,6 +47,7 @@ FUZZERS_MISSING_CORPORA = [
"tx_out",
]
+
def main():
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
@@ -62,6 +63,16 @@ def main():
help='If true, export coverage information to files in the seed corpus',
)
parser.add_argument(
+ '--valgrind',
+ action='store_true',
+ help='If true, run fuzzing binaries under the valgrind memory error detector',
+ )
+ parser.add_argument(
+ '-x',
+ '--exclude',
+ help="A comma-separated list of targets to exclude",
+ )
+ parser.add_argument(
'seed_dir',
help='The seed corpus to run on (must contain subfolders for each fuzz target).',
)
@@ -95,7 +106,7 @@ def main():
logging.error("No fuzz targets found")
sys.exit(1)
- logging.info("Fuzz targets found: {}".format(test_list_all))
+ logging.debug("{} fuzz target(s) found: {}".format(len(test_list_all), " ".join(sorted(test_list_all))))
args.target = args.target or test_list_all # By default run all
test_list_error = list(set(args.target).difference(set(test_list_all)))
@@ -104,7 +115,15 @@ def main():
test_list_selection = list(set(test_list_all).intersection(set(args.target)))
if not test_list_selection:
logging.error("No fuzz targets selected")
- logging.info("Fuzz targets selected: {}".format(test_list_selection))
+ if args.exclude:
+ for excluded_target in args.exclude.split(","):
+ if excluded_target not in test_list_selection:
+ logging.error("Target \"{}\" not found in current target list.".format(excluded_target))
+ continue
+ test_list_selection.remove(excluded_target)
+ test_list_selection.sort()
+
+ logging.info("{} of {} detected fuzz target(s) selected: {}".format(len(test_list_selection), len(test_list_all), " ".join(test_list_selection)))
try:
help_output = subprocess.run(
@@ -129,10 +148,11 @@ def main():
test_list=test_list_selection,
build_dir=config["environment"]["BUILDDIR"],
export_coverage=args.export_coverage,
+ use_valgrind=args.valgrind,
)
-def run_once(*, corpus, test_list, build_dir, export_coverage):
+def run_once(*, corpus, test_list, build_dir, export_coverage, use_valgrind):
for t in test_list:
corpus_path = os.path.join(corpus, t)
if t in FUZZERS_MISSING_CORPORA:
@@ -140,14 +160,23 @@ def run_once(*, corpus, test_list, build_dir, export_coverage):
args = [
os.path.join(build_dir, 'src', 'test', 'fuzz', t),
'-runs=1',
- '-detect_leaks=0',
corpus_path,
]
+ if use_valgrind:
+ args = ['valgrind', '--quiet', '--error-exitcode=1'] + args
logging.debug('Run {} with args {}'.format(t, args))
result = subprocess.run(args, stderr=subprocess.PIPE, universal_newlines=True)
output = result.stderr
logging.debug('Output: {}'.format(output))
- result.check_returncode()
+ try:
+ result.check_returncode()
+ except subprocess.CalledProcessError as e:
+ if e.stdout:
+ logging.info(e.stdout)
+ if e.stderr:
+ logging.info(e.stderr)
+ logging.info("Target \"{}\" failed with exit code {}: {}".format(t, e.returncode, " ".join(args)))
+ sys.exit(1)
if not export_coverage:
continue
for l in output.splitlines():