aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configure.ac6
-rw-r--r--depends/Makefile2
-rw-r--r--doc/release-notes.md4
-rw-r--r--src/bitcoin-cli.cpp69
-rw-r--r--src/bitcoin-wallet.cpp69
-rw-r--r--src/init.cpp4
-rw-r--r--src/net.h1
-rw-r--r--src/net_processing.cpp12
-rw-r--r--src/test/fuzz/system.cpp2
-rw-r--r--src/util/system.cpp53
-rw-r--r--src/util/system.h24
-rw-r--r--src/validation.cpp14
-rw-r--r--src/validation.h4
-rw-r--r--src/wallet/wallettool.cpp10
-rw-r--r--src/wallet/wallettool.h2
-rw-r--r--test/functional/README.md11
-rwxr-xr-xtest/functional/example_test.py3
-rwxr-xr-xtest/functional/feature_asmap.py1
-rwxr-xr-xtest/functional/feature_backwards_compatibility.py134
-rwxr-xr-xtest/functional/feature_dbcrash.py1
-rwxr-xr-xtest/functional/feature_filelock.py22
-rwxr-xr-xtest/functional/feature_includeconf.py1
-rwxr-xr-xtest/functional/feature_notifications.py34
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py2
-rwxr-xr-xtest/functional/interface_zmq.py2
-rwxr-xr-xtest/functional/p2p_add_connections.py1
-rwxr-xr-xtest/functional/p2p_addr_relay.py1
-rwxr-xr-xtest/functional/p2p_blocksonly.py1
-rwxr-xr-xtest/functional/p2p_filter.py1
-rwxr-xr-xtest/functional/p2p_getaddr_caching.py1
-rwxr-xr-xtest/functional/p2p_invalid_locator.py1
-rwxr-xr-xtest/functional/p2p_tx_download.py1
-rwxr-xr-xtest/functional/rpc_estimatefee.py1
-rwxr-xr-xtest/functional/rpc_net.py7
-rwxr-xr-xtest/functional/rpc_psbt.py1
-rw-r--r--test/functional/test-shell.md2
-rwxr-xr-xtest/functional/test_framework/test_framework.py32
-rwxr-xr-xtest/functional/test_runner.py71
-rwxr-xr-xtest/functional/tool_wallet.py11
-rwxr-xr-xtest/functional/wallet_avoidreuse.py1
-rwxr-xr-xtest/functional/wallet_createwallet.py1
-rwxr-xr-xtest/functional/wallet_descriptor.py13
-rwxr-xr-xtest/functional/wallet_send.py101
-rwxr-xr-xtest/functional/wallet_upgradewallet.py1
-rwxr-xr-xtest/functional/wallet_watchonly.py1
45 files changed, 476 insertions, 261 deletions
diff --git a/configure.ac b/configure.ac
index dca4c5edd8..c16eae8ba1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1254,8 +1254,10 @@ if test "x$enable_fuzz" = "xyes"; then
#include <cstdint>
#include <cstddef>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { return 0; }
- /* unterminated comment to remove the main function ...
- ]],[[]])])
+ /* comment to remove the main function ...
+ ]],[[
+ */ int not_main() {
+ ]])])
else
BITCOIN_QT_INIT
diff --git a/depends/Makefile b/depends/Makefile
index 596a46d4a2..016b73a5cb 100644
--- a/depends/Makefile
+++ b/depends/Makefile
@@ -265,7 +265,7 @@ install: check-packages $(host_prefix)/share/config.site
download-one: check-sources $(all_sources)
download-osx:
- @$(MAKE) -s HOST=x86_64-apple-darwin14 download-one
+ @$(MAKE) -s HOST=x86_64-apple-darwin download-one
download-linux:
@$(MAKE) -s HOST=x86_64-unknown-linux-gnu download-one
download-win:
diff --git a/doc/release-notes.md b/doc/release-notes.md
index 8f1e03e16b..0f248494c7 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -93,6 +93,10 @@ Tools and Utilities
Wallet
------
+- A new `listdescriptors` RPC is available to inspect the contents of descriptor-enabled wallets.
+ The RPC returns public versions of all imported descriptors, including their timestamp and flags.
+ For ranged descriptors, it also returns the range boundaries and the next index to generate addresses from. (#20226)
+
GUI changes
-----------
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index fa41208a31..a9a0ec6f5d 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -300,9 +300,12 @@ class NetinfoRequestHandler : public BaseRequestHandler
{
private:
static constexpr int8_t UNKNOWN_NETWORK{-1};
- static constexpr uint8_t m_networks_size{3};
- const std::array<std::string, m_networks_size> m_networks{{"ipv4", "ipv6", "onion"}};
- std::array<std::array<uint16_t, m_networks_size + 2>, 3> m_counts{{{}}}; //!< Peer counts by (in/out/total, networks/total/block-relay)
+ static constexpr int8_t NET_I2P{3}; // pos of "i2p" in m_networks
+ static constexpr uint8_t m_networks_size{4};
+ const std::array<std::string, m_networks_size> m_networks{{"ipv4", "ipv6", "onion", "i2p"}};
+ std::array<std::array<uint16_t, m_networks_size + 1>, 3> m_counts{{{}}}; //!< Peer counts by (in/out/total, networks/total)
+ uint8_t m_block_relay_peers_count{0};
+ uint8_t m_manual_peers_count{0};
int8_t NetworkStringToId(const std::string& str) const
{
for (uint8_t i = 0; i < m_networks_size; ++i) {
@@ -316,12 +319,14 @@ private:
bool IsAddressSelected() const { return m_details_level == 2 || m_details_level == 4; }
bool IsVersionSelected() const { return m_details_level == 3 || m_details_level == 4; }
bool m_is_asmap_on{false};
+ bool m_is_i2p_on{false};
size_t m_max_addr_length{0};
- size_t m_max_age_length{4};
+ size_t m_max_age_length{3};
size_t m_max_id_length{2};
struct Peer {
std::string addr;
std::string sub_version;
+ std::string conn_type;
std::string network;
std::string age;
double min_ping;
@@ -333,6 +338,8 @@ private:
int id;
int mapped_as;
int version;
+ bool is_bip152_hb_from;
+ bool is_bip152_hb_to;
bool is_block_relay;
bool is_outbound;
bool operator<(const Peer& rhs) const { return std::tie(is_outbound, min_ping) < std::tie(rhs.is_outbound, rhs.min_ping); }
@@ -351,6 +358,14 @@ private:
const double milliseconds{round(1000 * seconds)};
return milliseconds > 999999 ? "-" : ToString(milliseconds);
}
+ std::string ConnectionTypeForNetinfo(const std::string& conn_type) const
+ {
+ if (conn_type == "outbound-full-relay") return "full";
+ if (conn_type == "block-relay-only") return "block";
+ if (conn_type == "manual" || conn_type == "feeler") return conn_type;
+ if (conn_type == "addr-fetch") return "addr";
+ return "";
+ }
const UniValue NetinfoHelp()
{
return std::string{
@@ -379,6 +394,9 @@ private:
" type Type of peer connection\n"
" \"full\" - full relay, the default\n"
" \"block\" - block relay; like full relay but does not relay transactions or addresses\n"
+ " \"manual\" - peer we manually added using RPC addnode or the -addnode/-connect config options\n"
+ " \"feeler\" - short-lived connection for testing addresses\n"
+ " \"addr\" - address fetch; short-lived connection for requesting addresses\n"
" net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", or \"cjdns\")\n"
" mping Minimum observed ping time, in milliseconds (ms)\n"
" ping Last observed ping time, in milliseconds (ms)\n"
@@ -386,6 +404,9 @@ private:
" recv Time since last message received from the peer, in seconds\n"
" txn Time since last novel transaction received from the peer and accepted into our mempool, in minutes\n"
" blk Time since last novel block passing initial validity checks received from the peer, in minutes\n"
+ " hb High-bandwidth BIP152 compact block relay\n"
+ " \".\" (to) - we selected the peer as a high-bandwidth peer\n"
+ " \"*\" (from) - the peer selected us as a high-bandwidth peer\n"
" age Duration of connection to the peer, in minutes\n"
" asmap Mapped AS (Autonomous System) number in the BGP route to the peer, used for diversifying\n"
" peer selection (only displayed if the -asmap config option is set)\n"
@@ -393,7 +414,7 @@ private:
" address IP address and port of the peer\n"
" version Peer version and subversion concatenated, e.g. \"70016/Satoshi:21.0.0/\"\n\n"
"* The connection counts table displays the number of peers by direction, network, and the totals\n"
- " for each, as well as a column for block relay peers.\n\n"
+ " for each, as well as two special outbound columns for block relay peers and manual peers.\n\n"
"* The local addresses table lists each local address broadcast by the node, the port, and the score.\n\n"
"Examples:\n\n"
"Connection counts and local addresses only\n"
@@ -450,16 +471,16 @@ public:
const std::string network{peer["network"].get_str()};
const int8_t network_id{NetworkStringToId(network)};
if (network_id == UNKNOWN_NETWORK) continue;
+ m_is_i2p_on |= (network_id == NET_I2P);
const bool is_outbound{!peer["inbound"].get_bool()};
const bool is_block_relay{!peer["relaytxes"].get_bool()};
+ const std::string conn_type{peer["connection_type"].get_str()};
++m_counts.at(is_outbound).at(network_id); // in/out by network
++m_counts.at(is_outbound).at(m_networks_size); // in/out overall
++m_counts.at(2).at(network_id); // total by network
++m_counts.at(2).at(m_networks_size); // total overall
- if (is_block_relay) {
- ++m_counts.at(is_outbound).at(m_networks_size + 1); // in/out block-relay
- ++m_counts.at(2).at(m_networks_size + 1); // total block-relay
- }
+ if (conn_type == "block-relay-only") ++m_block_relay_peers_count;
+ if (conn_type == "manual") ++m_manual_peers_count;
if (DetailsRequested()) {
// Push data for this peer to the peers vector.
const int peer_id{peer["id"].get_int()};
@@ -475,7 +496,9 @@ public:
const std::string addr{peer["addr"].get_str()};
const std::string age{conn_time == 0 ? "" : ToString((m_time_now - conn_time) / 60)};
const std::string sub_version{peer["subver"].get_str()};
- m_peers.push_back({addr, sub_version, network, age, min_ping, ping, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_block_relay, is_outbound});
+ const bool is_bip152_hb_from{peer["bip152_hb_from"].get_bool()};
+ const bool is_bip152_hb_to{peer["bip152_hb_to"].get_bool()};
+ m_peers.push_back({addr, sub_version, conn_type, network, age, min_ping, ping, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_bip152_hb_from, is_bip152_hb_to, is_block_relay, is_outbound});
m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length);
m_max_age_length = std::max(age.length(), m_max_age_length);
m_max_id_length = std::max(ToString(peer_id).length(), m_max_id_length);
@@ -489,15 +512,15 @@ public:
// Report detailed peer connections list sorted by direction and minimum ping time.
if (DetailsRequested() && !m_peers.empty()) {
std::sort(m_peers.begin(), m_peers.end());
- result += strprintf("<-> relay net mping ping send recv txn blk %*s ", m_max_age_length, "age");
+ result += strprintf("<-> type net mping ping send recv txn blk hb %*s ", m_max_age_length, "age");
if (m_is_asmap_on) result += " asmap ";
result += strprintf("%*s %-*s%s\n", m_max_id_length, "id", IsAddressSelected() ? m_max_addr_length : 0, IsAddressSelected() ? "address" : "", IsVersionSelected() ? "version" : "");
for (const Peer& peer : m_peers) {
std::string version{ToString(peer.version) + peer.sub_version};
result += strprintf(
- "%3s %5s %5s%7s%7s%5s%5s%5s%5s %*s%*i %*s %-*s%s\n",
+ "%3s %6s %5s%7s%7s%5s%5s%5s%5s %2s %*s%*i %*s %-*s%s\n",
peer.is_outbound ? "out" : "in",
- peer.is_block_relay ? "block" : "full",
+ ConnectionTypeForNetinfo(peer.conn_type),
peer.network,
PingTimeToString(peer.min_ping),
PingTimeToString(peer.ping),
@@ -505,6 +528,7 @@ public:
peer.last_recv == 0 ? "" : ToString(m_time_now - peer.last_recv),
peer.last_trxn == 0 ? "" : ToString((m_time_now - peer.last_trxn) / 60),
peer.last_blck == 0 ? "" : ToString((m_time_now - peer.last_blck) / 60),
+ strprintf("%s%s", peer.is_bip152_hb_to ? "." : " ", peer.is_bip152_hb_from ? "*" : " "),
m_max_age_length, // variable spacing
peer.age,
m_is_asmap_on ? 7 : 0, // variable spacing
@@ -515,18 +539,27 @@ public:
IsAddressSelected() ? peer.addr : "",
IsVersionSelected() && version != "0" ? version : "");
}
- result += strprintf(" ms ms sec sec min min %*s\n\n", m_max_age_length, "min");
+ result += strprintf(" ms ms sec sec min min %*s\n\n", m_max_age_length, "min");
}
// Report peer connection totals by type.
- result += " ipv4 ipv6 onion total block-relay\n";
+ result += " ipv4 ipv6 onion";
+ if (m_is_i2p_on) result += " i2p";
+ result += " total block";
+ if (m_manual_peers_count) result += " manual";
const std::array<std::string, 3> rows{{"in", "out", "total"}};
- for (uint8_t i = 0; i < m_networks_size; ++i) {
- result += strprintf("%-5s %5i %5i %5i %5i %5i\n", rows.at(i), m_counts.at(i).at(0), m_counts.at(i).at(1), m_counts.at(i).at(2), m_counts.at(i).at(m_networks_size), m_counts.at(i).at(m_networks_size + 1));
+ for (uint8_t i = 0; i < 3; ++i) {
+ result += strprintf("\n%-5s %5i %5i %5i", rows.at(i), m_counts.at(i).at(0), m_counts.at(i).at(1), m_counts.at(i).at(2)); // ipv4/ipv6/onion peers counts
+ if (m_is_i2p_on) result += strprintf(" %5i", m_counts.at(i).at(3)); // i2p peers count
+ result += strprintf(" %5i", m_counts.at(i).at(m_networks_size)); // total peers count
+ if (i == 1) { // the outbound row has two extra columns for block relay and manual peer counts
+ result += strprintf(" %5i", m_block_relay_peers_count);
+ if (m_manual_peers_count) result += strprintf(" %5i", m_manual_peers_count);
+ }
}
// Report local addresses, ports, and scores.
- result += "\nLocal addresses";
+ result += "\n\nLocal addresses";
const std::vector<UniValue>& local_addrs{networkinfo["localaddresses"].getValues()};
if (local_addrs.empty()) {
result += ": n/a\n";
diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp
index 3e8e5fc7bc..b84d909b07 100644
--- a/src/bitcoin-wallet.cpp
+++ b/src/bitcoin-wallet.cpp
@@ -33,51 +33,52 @@ static void SetupWalletToolArgs(ArgsManager& argsman)
argsman.AddArg("-format=<format>", "The format of the wallet file to create. Either \"bdb\" or \"sqlite\". Only used with 'createfromdump'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
- argsman.AddArg("info", "Get wallet info", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
- argsman.AddArg("create", "Create new wallet file", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
- argsman.AddArg("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
- argsman.AddArg("dump", "Print out all of the wallet key-value records", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
- argsman.AddArg("createfromdump", "Create new wallet file from dumped records", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
+ argsman.AddCommand("info", "Get wallet info", OptionsCategory::COMMANDS);
+ argsman.AddCommand("create", "Create new wallet file", OptionsCategory::COMMANDS);
+ argsman.AddCommand("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental.", OptionsCategory::COMMANDS);
+ argsman.AddCommand("dump", "Print out all of the wallet key-value records", OptionsCategory::COMMANDS);
+ argsman.AddCommand("createfromdump", "Create new wallet file from dumped records", OptionsCategory::COMMANDS);
}
-static bool WalletAppInit(int argc, char* argv[])
+static bool WalletAppInit(ArgsManager& args, int argc, char* argv[])
{
- SetupWalletToolArgs(gArgs);
+ SetupWalletToolArgs(args);
std::string error_message;
- if (!gArgs.ParseParameters(argc, argv, error_message)) {
+ if (!args.ParseParameters(argc, argv, error_message)) {
tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error_message);
return false;
}
- if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
+ if (argc < 2 || HelpRequested(args) || args.IsArgSet("-version")) {
std::string strUsage = strprintf("%s bitcoin-wallet version", PACKAGE_NAME) + " " + FormatFullVersion() + "\n";
- if (!gArgs.IsArgSet("-version")) {
- strUsage += "\n"
- "bitcoin-wallet is an offline tool for creating and interacting with " PACKAGE_NAME " wallet files.\n"
- "By default bitcoin-wallet will act on wallets in the default mainnet wallet directory in the datadir.\n"
- "To change the target wallet, use the -datadir, -wallet and -testnet/-regtest arguments.\n\n"
- "Usage:\n"
- " bitcoin-wallet [options] <command>\n";
- strUsage += "\n" + gArgs.GetHelpMessage();
- }
+ if (!args.IsArgSet("-version")) {
+ strUsage += "\n"
+ "bitcoin-wallet is an offline tool for creating and interacting with " PACKAGE_NAME " wallet files.\n"
+ "By default bitcoin-wallet will act on wallets in the default mainnet wallet directory in the datadir.\n"
+ "To change the target wallet, use the -datadir, -wallet and -testnet/-regtest arguments.\n\n"
+ "Usage:\n"
+ " bitcoin-wallet [options] <command>\n";
+ strUsage += "\n" + args.GetHelpMessage();
+ }
tfm::format(std::cout, "%s", strUsage);
return false;
}
// check for printtoconsole, allow -debug
- LogInstance().m_print_to_console = gArgs.GetBoolArg("-printtoconsole", gArgs.GetBoolArg("-debug", false));
+ LogInstance().m_print_to_console = args.GetBoolArg("-printtoconsole", args.GetBoolArg("-debug", false));
if (!CheckDataDirOption()) {
- tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", ""));
+ tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", args.GetArg("-datadir", ""));
return false;
}
// Check for chain settings (Params() calls are only valid after this clause)
- SelectParams(gArgs.GetChainName());
+ SelectParams(args.GetChainName());
return true;
}
int main(int argc, char* argv[])
{
+ ArgsManager& args = gArgs;
#ifdef WIN32
util::WinCmdLineArgs winArgs;
std::tie(argc, argv) = winArgs.get();
@@ -85,7 +86,7 @@ int main(int argc, char* argv[])
SetupEnvironment();
RandomInit();
try {
- if (!WalletAppInit(argc, argv)) return EXIT_FAILURE;
+ if (!WalletAppInit(args, argc, argv)) return EXIT_FAILURE;
} catch (const std::exception& e) {
PrintExceptionContinue(&e, "WalletAppInit()");
return EXIT_FAILURE;
@@ -94,33 +95,19 @@ int main(int argc, char* argv[])
return EXIT_FAILURE;
}
- std::string method {};
- for(int i = 1; i < argc; ++i) {
- if (!IsSwitchChar(argv[i][0])) {
- if (!method.empty()) {
- tfm::format(std::cerr, "Error: two methods provided (%s and %s). Only one method should be provided.\n", method, argv[i]);
- return EXIT_FAILURE;
- }
- method = argv[i];
- }
- }
-
- if (method.empty()) {
+ const auto command = args.GetCommand();
+ if (!command) {
tfm::format(std::cerr, "No method provided. Run `bitcoin-wallet -help` for valid methods.\n");
return EXIT_FAILURE;
}
-
- // A name must be provided when creating a file
- if (method == "create" && !gArgs.IsArgSet("-wallet")) {
- tfm::format(std::cerr, "Wallet name must be provided when creating a new wallet.\n");
+ if (command->args.size() != 0) {
+ tfm::format(std::cerr, "Error: Additional arguments provided (%s). Methods do not take arguments. Please refer to `-help`.\n", Join(command->args, ", "));
return EXIT_FAILURE;
}
- std::string name = gArgs.GetArg("-wallet", "");
-
ECCVerifyHandle globalVerifyHandle;
ECC_Start();
- if (!WalletTool::ExecuteWalletToolFunc(gArgs, method, name)) {
+ if (!WalletTool::ExecuteWalletToolFunc(args, command->command)) {
return EXIT_FAILURE;
}
ECC_Stop();
diff --git a/src/init.cpp b/src/init.cpp
index 5d5c9b65b0..a77e2cf1cb 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -454,8 +454,8 @@ void SetupServerArgs(NodeContext& node)
argsman.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION);
- argsman.AddArg("-timeout=<n>", strprintf("Specify connection timeout in milliseconds (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
- argsman.AddArg("-peertimeout=<n>", strprintf("Specify p2p connection timeout in seconds. This option determines the amount of time a peer may be inactive before the connection to it is dropped. (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
+ argsman.AddArg("-timeout=<n>", strprintf("Specify socket connection timeout in milliseconds. If an initial attempt to connect is unsuccessful after this amount of time, drop it (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
+ argsman.AddArg("-peertimeout=<n>", strprintf("Specify a p2p connection timeout delay in seconds. After connecting to a peer, wait this amount of time before considering disconnection based on inactivity (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::CONNECTION);
#ifdef USE_UPNP
diff --git a/src/net.h b/src/net.h
index 2c47d5e557..a0aa157058 100644
--- a/src/net.h
+++ b/src/net.h
@@ -445,6 +445,7 @@ public:
* messages, implying a preference to receive ADDRv2 instead of ADDR ones.
*/
std::atomic_bool m_wants_addrv2{false};
+ /** fSuccessfullyConnected is set to true on receiving VERACK from the peer. */
std::atomic_bool fSuccessfullyConnected{false};
// Setting fDisconnect to true will cause the node to be disconnected the
// next time DisconnectNodes() runs
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 2dffbd7620..a246c8fa1c 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2745,12 +2745,11 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return;
}
- // Feature negotiation of wtxidrelay must happen between VERSION and VERACK
- // to avoid relay problems from switching after a connection is up.
+ // BIP339 defines feature negotiation of wtxidrelay, which must happen between
+ // VERSION and VERACK to avoid relay problems from switching after a connection is up.
if (msg_type == NetMsgType::WTXIDRELAY) {
if (pfrom.fSuccessfullyConnected) {
- // Disconnect peers that send wtxidrelay message after VERACK; this
- // must be negotiated between VERSION and VERACK.
+ // Disconnect peers that send a wtxidrelay message after VERACK.
LogPrint(BCLog::NET, "wtxidrelay received after verack from peer=%d; disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
return;
@@ -2769,10 +2768,11 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return;
}
+ // BIP155 defines feature negotiation of addrv2 and sendaddrv2, which must happen
+ // between VERSION and VERACK.
if (msg_type == NetMsgType::SENDADDRV2) {
if (pfrom.fSuccessfullyConnected) {
- // Disconnect peers that send SENDADDRV2 message after VERACK; this
- // must be negotiated between VERSION and VERACK.
+ // Disconnect peers that send a SENDADDRV2 message after VERACK.
LogPrint(BCLog::NET, "sendaddrv2 received after verack from peer=%d; disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
return;
diff --git a/src/test/fuzz/system.cpp b/src/test/fuzz/system.cpp
index 47b38b6d23..3621702e45 100644
--- a/src/test/fuzz/system.cpp
+++ b/src/test/fuzz/system.cpp
@@ -54,7 +54,7 @@ FUZZ_TARGET(system)
if (args_manager.GetArgFlags(argument_name) != nullopt) {
return;
}
- args_manager.AddArg(argument_name, fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeIntegral<unsigned int>(), options_category);
+ args_manager.AddArg(argument_name, fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeIntegral<unsigned int>() & ~ArgsManager::COMMAND, options_category);
},
[&] {
// Avoid hitting:
diff --git a/src/util/system.cpp b/src/util/system.cpp
index d1fb921642..9fe9d67411 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -3,7 +3,6 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include <sync.h>
#include <util/system.h>
#ifdef HAVE_BOOST_PROCESS
@@ -11,6 +10,8 @@
#endif // HAVE_BOOST_PROCESS
#include <chainparamsbase.h>
+#include <sync.h>
+#include <util/check.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/translation.h>
@@ -310,8 +311,22 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin
key[0] = '-';
#endif
- if (key[0] != '-')
+ if (key[0] != '-') {
+ if (!m_accept_any_command && m_command.empty()) {
+ // The first non-dash arg is a registered command
+ Optional<unsigned int> flags = GetArgFlags(key);
+ if (!flags || !(*flags & ArgsManager::COMMAND)) {
+ error = strprintf("Invalid command '%s'", argv[i]);
+ return false;
+ }
+ }
+ m_command.push_back(key);
+ while (++i < argc) {
+ // The remaining args are command args
+ m_command.push_back(argv[i]);
+ }
break;
+ }
// Transform --foo to -foo
if (key.length() > 1 && key[1] == '-')
@@ -359,6 +374,26 @@ Optional<unsigned int> ArgsManager::GetArgFlags(const std::string& name) const
return nullopt;
}
+std::optional<const ArgsManager::Command> ArgsManager::GetCommand() const
+{
+ Command ret;
+ LOCK(cs_args);
+ auto it = m_command.begin();
+ if (it == m_command.end()) {
+ // No command was passed
+ return std::nullopt;
+ }
+ if (!m_accept_any_command) {
+ // The registered command
+ ret.command = *(it++);
+ }
+ while (it != m_command.end()) {
+ // The unregistered command and args (if any)
+ ret.args.push_back(*(it++));
+ }
+ return ret;
+}
+
std::vector<std::string> ArgsManager::GetArgs(const std::string& strArg) const
{
std::vector<std::string> result;
@@ -504,8 +539,22 @@ void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strV
m_settings.forced_settings[SettingName(strArg)] = strValue;
}
+void ArgsManager::AddCommand(const std::string& cmd, const std::string& help, const OptionsCategory& cat)
+{
+ Assert(cmd.find('=') == std::string::npos);
+ Assert(cmd.at(0) != '-');
+
+ LOCK(cs_args);
+ m_accept_any_command = false; // latch to false
+ std::map<std::string, Arg>& arg_map = m_available_args[cat];
+ auto ret = arg_map.emplace(cmd, Arg{"", help, ArgsManager::COMMAND});
+ Assert(ret.second); // Fail on duplicate commands
+}
+
void ArgsManager::AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat)
{
+ Assert((flags & ArgsManager::COMMAND) == 0); // use AddCommand
+
// Split arg name from its help param
size_t eq_index = name.find('=');
if (eq_index == std::string::npos) {
diff --git a/src/util/system.h b/src/util/system.h
index d06c30bfa7..5959bc4196 100644
--- a/src/util/system.h
+++ b/src/util/system.h
@@ -166,7 +166,7 @@ struct SectionInfo
class ArgsManager
{
public:
- enum Flags {
+ enum Flags : uint32_t {
// Boolean options can accept negation syntax -noOPTION or -noOPTION=1
ALLOW_BOOL = 0x01,
ALLOW_INT = 0x02,
@@ -181,6 +181,7 @@ public:
NETWORK_ONLY = 0x200,
// This argument's value is sensitive (such as a password).
SENSITIVE = 0x400,
+ COMMAND = 0x800,
};
protected:
@@ -193,9 +194,11 @@ protected:
mutable RecursiveMutex cs_args;
util::Settings m_settings GUARDED_BY(cs_args);
+ std::vector<std::string> m_command GUARDED_BY(cs_args);
std::string m_network GUARDED_BY(cs_args);
std::set<std::string> m_network_only_args GUARDED_BY(cs_args);
std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args);
+ bool m_accept_any_command GUARDED_BY(cs_args){true};
std::list<SectionInfo> m_config_sections GUARDED_BY(cs_args);
[[nodiscard]] bool ReadConfigStream(std::istream& stream, const std::string& filepath, std::string& error, bool ignore_invalid_keys = false);
@@ -246,6 +249,20 @@ public:
*/
const std::list<SectionInfo> GetUnrecognizedSections() const;
+ struct Command {
+ /** The command (if one has been registered with AddCommand), or empty */
+ std::string command;
+ /**
+ * If command is non-empty: Any args that followed it
+ * If command is empty: The unregistered command and any args that followed it
+ */
+ std::vector<std::string> args;
+ };
+ /**
+ * Get the command and command args (returns std::nullopt if no command provided)
+ */
+ std::optional<const Command> GetCommand() const;
+
/**
* Return a vector of strings of the given argument
*
@@ -332,6 +349,11 @@ public:
void AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat);
/**
+ * Add subcommand
+ */
+ void AddCommand(const std::string& cmd, const std::string& help, const OptionsCategory& cat);
+
+ /**
* Add many hidden arguments
*/
void AddHiddenArgs(const std::vector<std::string>& args);
diff --git a/src/validation.cpp b/src/validation.cpp
index 38df71b994..8f7d36bfd3 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -5159,7 +5159,7 @@ double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex *pin
}
Optional<uint256> ChainstateManager::SnapshotBlockhash() const {
- LOCK(::cs_main); // for m_active_chainstate access
+ LOCK(::cs_main);
if (m_active_chainstate != nullptr) {
// If a snapshot chainstate exists, it will always be our active.
return m_active_chainstate->m_from_snapshot_blockhash;
@@ -5169,6 +5169,7 @@ Optional<uint256> ChainstateManager::SnapshotBlockhash() const {
std::vector<CChainState*> ChainstateManager::GetAll()
{
+ LOCK(::cs_main);
std::vector<CChainState*> out;
if (!IsSnapshotValidated() && m_ibd_chainstate) {
@@ -5213,11 +5214,13 @@ CChainState& ChainstateManager::ActiveChainstate() const
bool ChainstateManager::IsSnapshotActive() const
{
- return m_snapshot_chainstate && WITH_LOCK(::cs_main, return m_active_chainstate) == m_snapshot_chainstate.get();
+ LOCK(::cs_main);
+ return m_snapshot_chainstate && m_active_chainstate == m_snapshot_chainstate.get();
}
CChainState& ChainstateManager::ValidatedChainstate() const
{
+ LOCK(::cs_main);
if (m_snapshot_chainstate && IsSnapshotValidated()) {
return *m_snapshot_chainstate.get();
}
@@ -5227,6 +5230,7 @@ CChainState& ChainstateManager::ValidatedChainstate() const
bool ChainstateManager::IsBackgroundIBD(CChainState* chainstate) const
{
+ LOCK(::cs_main);
return (m_snapshot_chainstate && chainstate == m_ibd_chainstate.get());
}
@@ -5242,12 +5246,10 @@ void ChainstateManager::Unload()
void ChainstateManager::Reset()
{
+ LOCK(::cs_main);
m_ibd_chainstate.reset();
m_snapshot_chainstate.reset();
- {
- LOCK(::cs_main);
- m_active_chainstate = nullptr;
- }
+ m_active_chainstate = nullptr;
m_snapshot_validated = false;
}
diff --git a/src/validation.h b/src/validation.h
index fc7add85b7..e85c7bbf1a 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -802,7 +802,7 @@ private:
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
//! that call.
- std::unique_ptr<CChainState> m_ibd_chainstate;
+ std::unique_ptr<CChainState> m_ibd_chainstate GUARDED_BY(::cs_main);
//! A chainstate initialized on the basis of a UTXO snapshot. If this is
//! non-null, it is always our active chainstate.
@@ -815,7 +815,7 @@ private:
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
//! that call.
- std::unique_ptr<CChainState> m_snapshot_chainstate;
+ std::unique_ptr<CChainState> m_snapshot_chainstate GUARDED_BY(::cs_main);
//! Points to either the ibd or snapshot chainstate; indicates our
//! most-work chain.
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index bc90491a2c..b2cb0bf479 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -103,10 +103,8 @@ static void WalletShowInfo(CWallet* wallet_instance)
tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->m_address_book.size());
}
-bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command, const std::string& name)
+bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
{
- const fs::path path = fsbridge::AbsPathJoin(GetWalletDir(), name);
-
if (args.IsArgSet("-format") && command != "createfromdump") {
tfm::format(std::cerr, "The -format option can only be used with the \"createfromdump\" command.\n");
return false;
@@ -119,6 +117,12 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command,
tfm::format(std::cerr, "The -descriptors option can only be used with the 'create' command.\n");
return false;
}
+ if (command == "create" && !args.IsArgSet("-wallet")) {
+ tfm::format(std::cerr, "Wallet name must be provided when creating a new wallet.\n");
+ return false;
+ }
+ const std::string name = args.GetArg("-wallet", "");
+ const fs::path path = fsbridge::AbsPathJoin(GetWalletDir(), name);
if (command == "create") {
DatabaseOptions options;
diff --git a/src/wallet/wallettool.h b/src/wallet/wallettool.h
index f544a6f727..f4516bb5bc 100644
--- a/src/wallet/wallettool.h
+++ b/src/wallet/wallettool.h
@@ -10,7 +10,7 @@
namespace WalletTool {
void WalletShowInfo(CWallet* wallet_instance);
-bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command, const std::string& file);
+bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command);
} // namespace WalletTool
diff --git a/test/functional/README.md b/test/functional/README.md
index 2d04413eb2..d830ba0334 100644
--- a/test/functional/README.md
+++ b/test/functional/README.md
@@ -63,10 +63,13 @@ don't have test cases for.
- Avoid stop-starting the nodes multiple times during the test if possible. A
stop-start takes several seconds, so doing it several times blows up the
runtime of the test.
-- Set the `self.setup_clean_chain` variable in `set_test_params()` to control whether
- or not to use the cached data directories. The cached data directories
- contain a 200-block pre-mined blockchain and wallets for four nodes. Each node
- has 25 mature blocks (25x50=1250 BTC) in its wallet.
+- Set the `self.setup_clean_chain` variable in `set_test_params()` to `True` to
+ initialize an empty blockchain and start from the Genesis block, rather than
+ load a premined blockchain from cache with the default value of `False`. The
+ cached data directories contain a 200-block pre-mined blockchain with the
+ spendable mining rewards being split between four nodes. Each node has 25
+ mature block subsidies (25x50=1250 BTC) in its wallet. Using them is much more
+ efficient than mining blocks in your test.
- When calling RPCs with lots of arguments, consider using named keyword
arguments instead of positional arguments to make the intent of the call
clear to readers.
diff --git a/test/functional/example_test.py b/test/functional/example_test.py
index 97f24e1b6e..a0eb213a78 100755
--- a/test/functional/example_test.py
+++ b/test/functional/example_test.py
@@ -76,6 +76,9 @@ class ExampleTest(BitcoinTestFramework):
"""Override test parameters for your individual test.
This method must be overridden and num_nodes must be explicitly set."""
+ # By default every test loads a pre-mined chain of 200 blocks from cache.
+ # Set setup_clean_chain to True to skip this and start from the Genesis
+ # block.
self.setup_clean_chain = True
self.num_nodes = 3
# Use self.extra_args to change command-line arguments for the nodes
diff --git a/test/functional/feature_asmap.py b/test/functional/feature_asmap.py
index 2c6553fbe2..5fcecb4882 100755
--- a/test/functional/feature_asmap.py
+++ b/test/functional/feature_asmap.py
@@ -36,7 +36,6 @@ def expected_messages(filename):
class AsmapTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def test_without_asmap_arg(self):
diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py
index b161c71a85..e6a53b52db 100755
--- a/test/functional/feature_backwards_compatibility.py
+++ b/test/functional/feature_backwards_compatibility.py
@@ -354,73 +354,75 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
hdkeypath = v17_info["hdkeypath"]
pubkey = v17_info["pubkey"]
- # Copy the 0.16 wallet to the last Bitcoin Core version and open it:
- shutil.copyfile(
- os.path.join(node_v16_wallets_dir, "wallets/u1_v16"),
- os.path.join(node_master_wallets_dir, "u1_v16")
- )
- load_res = node_master.loadwallet("u1_v16")
- # Make sure this wallet opens without warnings. See https://github.com/bitcoin/bitcoin/pull/19054
- assert_equal(load_res['warning'], '')
- wallet = node_master.get_wallet_rpc("u1_v16")
- info = wallet.getaddressinfo(v16_addr)
- descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + v16_pubkey + ")"
- assert_equal(info["desc"], descsum_create(descriptor))
-
- # Now copy that same wallet back to 0.16 to make sure no automatic upgrade breaks it
- os.remove(os.path.join(node_v16_wallets_dir, "wallets/u1_v16"))
- shutil.copyfile(
- os.path.join(node_master_wallets_dir, "u1_v16"),
- os.path.join(node_v16_wallets_dir, "wallets/u1_v16")
- )
- self.start_node(-1, extra_args=["-wallet=u1_v16"])
- wallet = node_v16.get_wallet_rpc("u1_v16")
- info = wallet.validateaddress(v16_addr)
- assert_equal(info, v16_info)
-
- # 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"),
- os.path.join(node_master_wallets_dir, "u1_v17")
- )
- node_master.loadwallet("u1_v17")
- wallet = node_master.get_wallet_rpc("u1_v17")
- info = wallet.getaddressinfo(address)
- descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + pubkey + ")"
- assert_equal(info["desc"], descsum_create(descriptor))
-
- # Now copy that same wallet back to 0.17 to make sure no automatic upgrade breaks it
- node_master.unloadwallet("u1_v17")
- shutil.rmtree(os.path.join(node_v17_wallets_dir, "u1_v17"))
- shutil.copytree(
- os.path.join(node_master_wallets_dir, "u1_v17"),
- os.path.join(node_v17_wallets_dir, "u1_v17")
- )
- node_v17.loadwallet("u1_v17")
- wallet = node_v17.get_wallet_rpc("u1_v17")
- info = wallet.getaddressinfo(address)
- assert_equal(info, v17_info)
-
- # 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 self.is_bdb_compiled():
+ # Old wallets are BDB and will only work if BDB is compiled
+ # Copy the 0.16 wallet to the last Bitcoin Core version and open it:
+ shutil.copyfile(
+ os.path.join(node_v16_wallets_dir, "wallets/u1_v16"),
+ os.path.join(node_master_wallets_dir, "u1_v16")
+ )
+ load_res = node_master.loadwallet("u1_v16")
+ # Make sure this wallet opens without warnings. See https://github.com/bitcoin/bitcoin/pull/19054
+ assert_equal(load_res['warning'], '')
+ wallet = node_master.get_wallet_rpc("u1_v16")
+ info = wallet.getaddressinfo(v16_addr)
+ descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + v16_pubkey + ")"
+ assert_equal(info["desc"], descsum_create(descriptor))
+
+ # Now copy that same wallet back to 0.16 to make sure no automatic upgrade breaks it
+ os.remove(os.path.join(node_v16_wallets_dir, "wallets/u1_v16"))
+ shutil.copyfile(
+ os.path.join(node_master_wallets_dir, "u1_v16"),
+ os.path.join(node_v16_wallets_dir, "wallets/u1_v16")
+ )
+ self.start_node(-1, extra_args=["-wallet=u1_v16"])
+ wallet = node_v16.get_wallet_rpc("u1_v16")
+ info = wallet.validateaddress(v16_addr)
+ assert_equal(info, v16_info)
- # Now copy that same wallet back to 0.19 to make sure no automatic upgrade breaks it
- node_master.unloadwallet("w1_v19")
- shutil.rmtree(os.path.join(node_v19_wallets_dir, "w1_v19"))
- shutil.copytree(
- os.path.join(node_master_wallets_dir, "w1_v19"),
- os.path.join(node_v19_wallets_dir, "w1_v19")
- )
- node_v19.loadwallet("w1_v19")
- wallet = node_v19.get_wallet_rpc("w1_v19")
- assert wallet.getaddressinfo(address_18075)["solvable"]
+ # 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"),
+ os.path.join(node_master_wallets_dir, "u1_v17")
+ )
+ node_master.loadwallet("u1_v17")
+ wallet = node_master.get_wallet_rpc("u1_v17")
+ info = wallet.getaddressinfo(address)
+ descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + pubkey + ")"
+ assert_equal(info["desc"], descsum_create(descriptor))
+
+ # Now copy that same wallet back to 0.17 to make sure no automatic upgrade breaks it
+ node_master.unloadwallet("u1_v17")
+ shutil.rmtree(os.path.join(node_v17_wallets_dir, "u1_v17"))
+ shutil.copytree(
+ os.path.join(node_master_wallets_dir, "u1_v17"),
+ os.path.join(node_v17_wallets_dir, "u1_v17")
+ )
+ node_v17.loadwallet("u1_v17")
+ wallet = node_v17.get_wallet_rpc("u1_v17")
+ info = wallet.getaddressinfo(address)
+ assert_equal(info, v17_info)
+
+ # 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"]
+
+ # Now copy that same wallet back to 0.19 to make sure no automatic upgrade breaks it
+ node_master.unloadwallet("w1_v19")
+ shutil.rmtree(os.path.join(node_v19_wallets_dir, "w1_v19"))
+ shutil.copytree(
+ os.path.join(node_master_wallets_dir, "w1_v19"),
+ os.path.join(node_v19_wallets_dir, "w1_v19")
+ )
+ node_v19.loadwallet("w1_v19")
+ wallet = node_v19.get_wallet_rpc("w1_v19")
+ assert wallet.getaddressinfo(address_18075)["solvable"]
if __name__ == '__main__':
BackwardsCompatibilityTest().main()
diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py
index f9ece244fb..2b56bc78f5 100755
--- a/test/functional/feature_dbcrash.py
+++ b/test/functional/feature_dbcrash.py
@@ -49,7 +49,6 @@ from test_framework.util import (
class ChainstateWriteCrashTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
- self.setup_clean_chain = False
self.rpc_timeout = 480
self.supports_cli = False
diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py
index 7de9a589be..2798d11b0a 100755
--- a/test/functional/feature_filelock.py
+++ b/test/functional/feature_filelock.py
@@ -4,6 +4,8 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Check that it's not possible to start a second bitcoind instance using the same datadir or wallet."""
import os
+import random
+import string
from test_framework.test_framework import BitcoinTestFramework
from test_framework.test_node import ErrorMatch
@@ -27,11 +29,21 @@ class FilelockTest(BitcoinTestFramework):
self.nodes[1].assert_start_raises_init_error(extra_args=['-datadir={}'.format(self.nodes[0].datadir), '-noserver'], expected_msg=expected_msg)
if self.is_wallet_compiled():
- self.nodes[0].createwallet(self.default_wallet_name)
- wallet_dir = os.path.join(datadir, 'wallets')
- self.log.info("Check that we can't start a second bitcoind instance using the same wallet")
- expected_msg = "Error: Error initializing wallet database environment"
- self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-wallet=' + self.default_wallet_name, '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX)
+ def check_wallet_filelock(descriptors):
+ wallet_name = ''.join([random.choice(string.ascii_lowercase) for _ in range(6)])
+ self.nodes[0].createwallet(wallet_name=wallet_name, descriptors=descriptors)
+ wallet_dir = os.path.join(datadir, 'wallets')
+ self.log.info("Check that we can't start a second bitcoind instance using the same wallet")
+ if descriptors:
+ expected_msg = "Error: SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another bitcoind?"
+ else:
+ expected_msg = "Error: Error initializing wallet database environment"
+ self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-wallet=' + wallet_name, '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX)
+
+ if self.is_bdb_compiled():
+ check_wallet_filelock(False)
+ if self.is_sqlite_compiled():
+ check_wallet_filelock(True)
if __name__ == '__main__':
FilelockTest().main()
diff --git a/test/functional/feature_includeconf.py b/test/functional/feature_includeconf.py
index 6f1a0cd348..f22b7f266a 100755
--- a/test/functional/feature_includeconf.py
+++ b/test/functional/feature_includeconf.py
@@ -20,7 +20,6 @@ from test_framework.test_framework import BitcoinTestFramework
class IncludeConfTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def setup_chain(self):
diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py
index f2313bac13..b068ce612c 100755
--- a/test/functional/feature_notifications.py
+++ b/test/functional/feature_notifications.py
@@ -5,11 +5,11 @@
"""Test the -alertnotify, -blocknotify and -walletnotify options."""
import os
-from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE, keyhash_to_p2pkh
+from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE
+from test_framework.descriptors import descsum_create
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- hex_str_to_bytes,
)
# Linux allow all characters other than \x00
@@ -49,6 +49,31 @@ class NotificationsTest(BitcoinTestFramework):
super().setup_network()
def run_test(self):
+ if self.is_wallet_compiled():
+ # Setup the descriptors to be imported to the wallet
+ seed = "cTdGmKFWpbvpKQ7ejrdzqYT2hhjyb3GPHnLAK7wdi5Em67YLwSm9"
+ xpriv = "tprv8ZgxMBicQKsPfHCsTwkiM1KT56RXbGGTqvc2hgqzycpwbHqqpcajQeMRZoBD35kW4RtyCemu6j34Ku5DEspmgjKdt2qe4SvRch5Kk8B8A2v"
+ desc_imports = [{
+ "desc": descsum_create("wpkh(" + xpriv + "/0/*)"),
+ "timestamp": 0,
+ "active": True,
+ "keypool": True,
+ },{
+ "desc": descsum_create("wpkh(" + xpriv + "/1/*)"),
+ "timestamp": 0,
+ "active": True,
+ "keypool": True,
+ "internal": True,
+ }]
+ # Make the wallets and import the descriptors
+ # Ensures that node 0 and node 1 share the same wallet for the conflicting transaction tests below.
+ for i, name in enumerate(self.wallet_names):
+ self.nodes[i].createwallet(wallet_name=name, descriptors=self.options.descriptors, blank=True, load_on_startup=True)
+ if self.options.descriptors:
+ self.nodes[i].importdescriptors(desc_imports)
+ else:
+ self.nodes[i].sethdseed(True, seed)
+
self.log.info("test -blocknotify")
block_count = 10
blocks = self.nodes[1].generatetoaddress(block_count, self.nodes[1].getnewaddress() if self.is_wallet_compiled() else ADDRESS_BCRT1_UNSPENDABLE)
@@ -84,11 +109,10 @@ class NotificationsTest(BitcoinTestFramework):
for tx_file in os.listdir(self.walletnotify_dir):
os.remove(os.path.join(self.walletnotify_dir, tx_file))
- # Conflicting transactions tests. Give node 0 same wallet seed as
- # node 1, generate spends from node 0, and check notifications
+ # Conflicting transactions tests.
+ # Generate spends from node 0, and check notifications
# triggered by node 1
self.log.info("test -walletnotify with conflicting transactions")
- self.nodes[0].sethdseed(seed=self.nodes[1].dumpprivkey(keyhash_to_p2pkh(hex_str_to_bytes(self.nodes[1].getwalletinfo()['hdseedid'])[::-1])))
self.nodes[0].rescanblockchain()
self.nodes[0].generatetoaddress(100, ADDRESS_BCRT1_UNSPENDABLE)
self.sync_blocks()
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index 1257dff1ae..2cf0ef2251 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -29,6 +29,8 @@ class TestBitcoinCli(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
+ if self.is_wallet_compiled():
+ self.requires_wallet = True
def skip_test_if_missing_module(self):
self.skip_if_no_cli()
diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py
index 946bfa51d4..e9f61be4d4 100755
--- a/test/functional/interface_zmq.py
+++ b/test/functional/interface_zmq.py
@@ -62,6 +62,8 @@ class ZMQSubscriber:
class ZMQTest (BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
+ if self.is_wallet_compiled():
+ self.requires_wallet = True
def skip_test_if_missing_module(self):
self.skip_if_no_py3_zmq()
diff --git a/test/functional/p2p_add_connections.py b/test/functional/p2p_add_connections.py
index a63c3a3287..a04ba5db2d 100755
--- a/test/functional/p2p_add_connections.py
+++ b/test/functional/p2p_add_connections.py
@@ -17,7 +17,6 @@ def check_node_connections(*, node, num_in, num_out):
class P2PAddConnections(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 2
def setup_network(self):
diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py
index 91fbd722cf..69821763bd 100755
--- a/test/functional/p2p_addr_relay.py
+++ b/test/functional/p2p_addr_relay.py
@@ -46,7 +46,6 @@ class AddrReceiver(P2PInterface):
class AddrTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def run_test(self):
diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py
index c592ab52b1..6584efae79 100755
--- a/test/functional/p2p_blocksonly.py
+++ b/test/functional/p2p_blocksonly.py
@@ -15,7 +15,6 @@ from test_framework.util import assert_equal
class P2PBlocksOnly(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
self.extra_args = [["-blocksonly"]]
diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py
index 642a217047..458e5235b6 100755
--- a/test/functional/p2p_filter.py
+++ b/test/functional/p2p_filter.py
@@ -81,7 +81,6 @@ class P2PBloomFilter(P2PInterface):
class FilterTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
self.extra_args = [[
'-peerbloomfilters',
diff --git a/test/functional/p2p_getaddr_caching.py b/test/functional/p2p_getaddr_caching.py
index 2b75ad5175..d375af6fe1 100755
--- a/test/functional/p2p_getaddr_caching.py
+++ b/test/functional/p2p_getaddr_caching.py
@@ -41,7 +41,6 @@ class AddrReceiver(P2PInterface):
class AddrTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def run_test(self):
diff --git a/test/functional/p2p_invalid_locator.py b/test/functional/p2p_invalid_locator.py
index e4fc9fd178..f884cf90ff 100755
--- a/test/functional/p2p_invalid_locator.py
+++ b/test/functional/p2p_invalid_locator.py
@@ -13,7 +13,6 @@ from test_framework.test_framework import BitcoinTestFramework
class InvalidLocatorTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
- self.setup_clean_chain = False
def run_test(self):
node = self.nodes[0] # convenience reference to the node
diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py
index 8a751c6b54..4bf96cb0e6 100755
--- a/test/functional/p2p_tx_download.py
+++ b/test/functional/p2p_tx_download.py
@@ -56,7 +56,6 @@ MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + INBOUND_PEER_TX_DELAY + TXID_RE
class TxDownloadTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 2
def test_tx_requests(self):
diff --git a/test/functional/rpc_estimatefee.py b/test/functional/rpc_estimatefee.py
index 81862ac69e..51b7efb4c3 100755
--- a/test/functional/rpc_estimatefee.py
+++ b/test/functional/rpc_estimatefee.py
@@ -14,7 +14,6 @@ from test_framework.util import assert_raises_rpc_error
class EstimateFeeTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def run_test(self):
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index de0b7f303f..cf46616681 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -25,6 +25,7 @@ from test_framework.util import (
assert_raises_rpc_error,
p2p_port,
)
+from test_framework.wallet import MiniWallet
def assert_net_servicesnames(servicesflag, servicenames):
@@ -48,6 +49,9 @@ class NetTest(BitcoinTestFramework):
self.supports_cli = False
def run_test(self):
+ # We need miniwallet to make a transaction
+ self.wallet = MiniWallet(self.nodes[0])
+ self.wallet.generate(1)
# Get out of IBD for the minfeefilter and getpeerinfo tests.
self.nodes[0].generate(101)
@@ -74,8 +78,7 @@ class NetTest(BitcoinTestFramework):
def test_getpeerinfo(self):
self.log.info("Test getpeerinfo")
# Create a few getpeerinfo last_block/last_transaction values.
- if self.is_wallet_compiled():
- self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1)
+ self.wallet.send_self_transfer(from_node=self.nodes[0]) # Make a transaction so we can see it in the getpeerinfo results
self.nodes[1].generate(1)
self.sync_all()
time_now = int(time.time())
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index b364077a9a..ed6abaed78 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -24,7 +24,6 @@ MAX_BIP125_RBF_SEQUENCE = 0xfffffffd
class PSBTTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 3
self.extra_args = [
["-walletrbf=1"],
diff --git a/test/functional/test-shell.md b/test/functional/test-shell.md
index f6ea9ef682..b8e899d675 100644
--- a/test/functional/test-shell.md
+++ b/test/functional/test-shell.md
@@ -178,7 +178,7 @@ can be called after the TestShell is shut down.
| `num_nodes` | `1` | Sets the number of initialized bitcoind processes. |
| `perf` | False | Profiles running nodes with `perf` for the duration of the test if set to `True`. |
| `rpc_timeout` | `60` | Sets the RPC server timeout for the underlying bitcoind processes. |
-| `setup_clean_chain` | `False` | Initializes an empty blockchain by default. A 199-block-long chain is initialized if set to `True`. |
+| `setup_clean_chain` | `False` | A 200-block-long chain is initialized from cache by default. Instead, `setup_clean_chain` initializes an empty blockchain if set to `True`. |
| `randomseed` | Random Integer | `TestShell.options.randomseed` is a member of `TestShell` which can be accessed during a test to seed a random generator. User can override default with a constant value for reproducible test runs. |
| `supports_cli` | `False` | Whether the bitcoin-cli utility is compiled and available for the test. |
| `tmpdir` | `"/var/folders/.../"` | Sets directory for test logs. Will be deleted upon a successful test run unless `nocleanup` is set to `True` |
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 4bda73599d..70a9798449 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -108,6 +108,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
# skipped. If list is truncated, wallet creation is skipped and keys
# are not imported.
self.wallet_names = None
+ # By default the wallet is not required. Set to true by skip_if_no_wallet().
+ # When False, we ignore wallet_names regardless of what it is.
+ self.requires_wallet = False
self.set_test_params()
assert self.wallet_names is None or len(self.wallet_names) <= self.num_nodes
if self.options.timeout_factor == 0 :
@@ -184,15 +187,30 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
parser.add_argument('--timeout-factor', dest="timeout_factor", type=float, default=1.0, help='adjust test timeouts by a factor. Setting it to 0 disables all timeouts')
group = parser.add_mutually_exclusive_group()
- group.add_argument("--descriptors", default=False, action="store_true",
+ group.add_argument("--descriptors", action='store_const', const=True,
help="Run test using a descriptor wallet", dest='descriptors')
- group.add_argument("--legacy-wallet", default=False, action="store_false",
+ group.add_argument("--legacy-wallet", action='store_const', const=False,
help="Run test using legacy wallets", dest='descriptors')
self.add_options(parser)
self.options = parser.parse_args()
self.options.previous_releases_path = previous_releases_path
+ config = configparser.ConfigParser()
+ config.read_file(open(self.options.configfile))
+ self.config = config
+
+ if self.options.descriptors is None:
+ # Prefer BDB unless it isn't available
+ if self.is_bdb_compiled():
+ self.options.descriptors = False
+ elif self.is_sqlite_compiled():
+ self.options.descriptors = True
+ else:
+ # If neither are compiled, tests requiring a wallet will be skipped and the value of self.options.descriptors won't matter
+ # It still needs to exist and be None in order for tests to work however.
+ self.options.descriptors = None
+
def setup(self):
"""Call this method to start up the test framework object with options set."""
@@ -202,9 +220,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.options.cachedir = os.path.abspath(self.options.cachedir)
- config = configparser.ConfigParser()
- config.read_file(open(self.options.configfile))
- self.config = config
+ config = self.config
+
fname_bitcoind = os.path.join(
config["environment"]["BUILDDIR"],
"src",
@@ -377,7 +394,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
extra_args = self.extra_args
self.add_nodes(self.num_nodes, extra_args)
self.start_nodes()
- if self.is_wallet_compiled():
+ if self.requires_wallet:
self.import_deterministic_coinbase_privkeys()
if not self.setup_clean_chain:
for n in self.nodes:
@@ -769,10 +786,13 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def skip_if_no_wallet(self):
"""Skip the running test if wallet has not been compiled."""
+ self.requires_wallet = True
if not self.is_wallet_compiled():
raise SkipTest("wallet has not been compiled.")
if self.options.descriptors:
self.skip_if_no_sqlite()
+ else:
+ self.skip_if_no_bdb()
def skip_if_no_sqlite(self):
"""Skip the running test if sqlite has not been compiled."""
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 0e6340b69d..1e7d9e4b0d 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -86,29 +86,29 @@ EXTENDED_SCRIPTS = [
BASE_SCRIPTS = [
# Scripts that are run by default.
# Longest test should go first, to favor running tests in parallel
- 'wallet_hd.py',
+ 'wallet_hd.py --legacy-wallet',
'wallet_hd.py --descriptors',
- 'wallet_backup.py',
+ 'wallet_backup.py --legacy-wallet',
'wallet_backup.py --descriptors',
# vv Tests less than 5m vv
'mining_getblocktemplate_longpoll.py',
'feature_maxuploadtarget.py',
'feature_block.py',
- 'rpc_fundrawtransaction.py',
+ 'rpc_fundrawtransaction.py --legacy-wallet',
'rpc_fundrawtransaction.py --descriptors',
'p2p_compactblocks.py',
'feature_segwit.py --legacy-wallet',
# vv Tests less than 2m vv
- 'wallet_basic.py',
+ 'wallet_basic.py --legacy-wallet',
'wallet_basic.py --descriptors',
- 'wallet_labels.py',
+ 'wallet_labels.py --legacy-wallet',
'wallet_labels.py --descriptors',
'p2p_segwit.py',
'p2p_timeouts.py',
'p2p_tx_download.py',
'mempool_updatefromblock.py',
'wallet_dump.py --legacy-wallet',
- 'wallet_listtransactions.py',
+ 'wallet_listtransactions.py --legacy-wallet',
'wallet_listtransactions.py --descriptors',
'feature_taproot.py',
# vv Tests less than 60s vv
@@ -116,21 +116,21 @@ BASE_SCRIPTS = [
'wallet_importmulti.py --legacy-wallet',
'mempool_limit.py',
'rpc_txoutproof.py',
- 'wallet_listreceivedby.py',
+ 'wallet_listreceivedby.py --legacy-wallet',
'wallet_listreceivedby.py --descriptors',
- 'wallet_abandonconflict.py',
+ 'wallet_abandonconflict.py --legacy-wallet',
'wallet_abandonconflict.py --descriptors',
'feature_csv_activation.py',
- 'rpc_rawtransaction.py',
+ 'rpc_rawtransaction.py --legacy-wallet',
'rpc_rawtransaction.py --descriptors',
- 'wallet_address_types.py',
+ 'wallet_address_types.py --legacy-wallet',
'wallet_address_types.py --descriptors',
'feature_bip68_sequence.py',
'p2p_feefilter.py',
'feature_reindex.py',
'feature_abortnode.py',
# vv Tests less than 30s vv
- 'wallet_keypool_topup.py',
+ 'wallet_keypool_topup.py --legacy-wallet',
'wallet_keypool_topup.py --descriptors',
'feature_fee_estimation.py',
'interface_zmq.py',
@@ -138,7 +138,7 @@ BASE_SCRIPTS = [
'interface_bitcoin_cli.py',
'mempool_resurrect.py',
'wallet_txn_doublespend.py --mineblock',
- 'tool_wallet.py',
+ 'tool_wallet.py --legacy-wallet',
'tool_wallet.py --descriptors',
'wallet_txn_clone.py',
'wallet_txn_clone.py --segwit',
@@ -146,14 +146,14 @@ BASE_SCRIPTS = [
'rpc_misc.py',
'interface_rest.py',
'mempool_spend_coinbase.py',
- 'wallet_avoidreuse.py',
+ 'wallet_avoidreuse.py --legacy-wallet',
'wallet_avoidreuse.py --descriptors',
'mempool_reorg.py',
'mempool_persist.py',
- 'wallet_multiwallet.py',
+ 'wallet_multiwallet.py --legacy-wallet',
'wallet_multiwallet.py --descriptors',
'wallet_multiwallet.py --usecli',
- 'wallet_createwallet.py',
+ 'wallet_createwallet.py --legacy-wallet',
'wallet_createwallet.py --usecli',
'wallet_createwallet.py --descriptors',
'wallet_watchonly.py --legacy-wallet',
@@ -161,27 +161,27 @@ BASE_SCRIPTS = [
'wallet_reorgsrestore.py',
'interface_http.py',
'interface_rpc.py',
- 'rpc_psbt.py',
+ 'rpc_psbt.py --legacy-wallet',
'rpc_psbt.py --descriptors',
'rpc_users.py',
'rpc_whitelist.py',
'feature_proxy.py',
- 'rpc_signrawtransaction.py',
+ 'rpc_signrawtransaction.py --legacy-wallet',
'rpc_signrawtransaction.py --descriptors',
- 'wallet_groups.py',
+ 'wallet_groups.py --legacy-wallet',
'p2p_addrv2_relay.py',
'wallet_groups.py --descriptors',
'p2p_disconnect_ban.py',
'rpc_decodescript.py',
'rpc_blockchain.py',
'rpc_deprecated.py',
- 'wallet_disable.py',
+ 'wallet_disable.py --legacy-wallet',
'wallet_disable.py --descriptors',
'p2p_addr_relay.py',
'p2p_getaddr_caching.py',
'p2p_getdata.py',
'rpc_net.py',
- 'wallet_keypool.py',
+ 'wallet_keypool.py --legacy-wallet',
'wallet_keypool.py --descriptors',
'wallet_descriptor.py --descriptors',
'p2p_nobloomfilter_messages.py',
@@ -195,9 +195,9 @@ BASE_SCRIPTS = [
'p2p_invalid_tx.py',
'feature_assumevalid.py',
'example_test.py',
- 'wallet_txn_doublespend.py',
+ 'wallet_txn_doublespend.py --legacy-wallet',
'wallet_txn_doublespend.py --descriptors',
- 'feature_backwards_compatibility.py',
+ 'feature_backwards_compatibility.py --legacy-wallet',
'feature_backwards_compatibility.py --descriptors',
'wallet_txn_clone.py --mineblock',
'feature_notifications.py',
@@ -206,59 +206,60 @@ BASE_SCRIPTS = [
'feature_rbf.py',
'mempool_packages.py',
'mempool_package_onemore.py',
- 'rpc_createmultisig.py',
+ 'rpc_createmultisig.py --legacy-wallet',
'rpc_createmultisig.py --descriptors',
'feature_versionbits_warning.py',
'rpc_preciousblock.py',
- 'wallet_importprunedfunds.py',
+ 'wallet_importprunedfunds.py --legacy-wallet',
'wallet_importprunedfunds.py --descriptors',
'p2p_leak_tx.py',
'p2p_eviction.py',
'rpc_signmessage.py',
'rpc_generateblock.py',
'rpc_generate.py',
- 'wallet_balance.py',
+ 'wallet_balance.py --legacy-wallet',
'wallet_balance.py --descriptors',
- 'feature_nulldummy.py',
+ 'feature_nulldummy.py --legacy-wallet',
'feature_nulldummy.py --descriptors',
'mempool_accept.py',
'mempool_expiry.py',
'wallet_import_rescan.py --legacy-wallet',
'wallet_import_with_label.py --legacy-wallet',
'wallet_importdescriptors.py --descriptors',
- 'wallet_upgradewallet.py',
+ 'wallet_upgradewallet.py --legacy-wallet',
'rpc_bind.py --ipv4',
'rpc_bind.py --ipv6',
'rpc_bind.py --nonloopback',
'mining_basic.py',
'feature_signet.py',
- 'wallet_bumpfee.py',
+ 'wallet_bumpfee.py --legacy-wallet',
'wallet_bumpfee.py --descriptors',
'wallet_implicitsegwit.py --legacy-wallet',
'rpc_named_arguments.py',
- 'wallet_listsinceblock.py',
+ 'wallet_listsinceblock.py --legacy-wallet',
'wallet_listsinceblock.py --descriptors',
'wallet_listdescriptors.py --descriptors',
'p2p_leak.py',
- 'wallet_encryption.py',
+ 'wallet_encryption.py --legacy-wallet',
'wallet_encryption.py --descriptors',
'feature_dersig.py',
'feature_cltv.py',
'rpc_uptime.py',
- 'wallet_resendwallettransactions.py',
+ 'wallet_resendwallettransactions.py --legacy-wallet',
'wallet_resendwallettransactions.py --descriptors',
- 'wallet_fallbackfee.py',
+ 'wallet_fallbackfee.py --legacy-wallet',
'wallet_fallbackfee.py --descriptors',
'rpc_dumptxoutset.py',
'feature_minchainwork.py',
'rpc_estimatefee.py',
'rpc_getblockstats.py',
- 'wallet_create_tx.py',
- 'wallet_send.py',
+ 'wallet_create_tx.py --legacy-wallet',
+ 'wallet_send.py --legacy-wallet',
+ 'wallet_send.py --descriptors',
'wallet_create_tx.py --descriptors',
'p2p_fingerprint.py',
'feature_uacomment.py',
- 'wallet_coinbase_category.py',
+ 'wallet_coinbase_category.py --legacy-wallet',
'wallet_coinbase_category.py --descriptors',
'feature_filelock.py',
'feature_loadblock.py',
diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py
index 8a1af24dcf..28103793df 100755
--- a/test/functional/tool_wallet.py
+++ b/test/functional/tool_wallet.py
@@ -183,11 +183,13 @@ class ToolWalletTest(BitcoinTestFramework):
def test_invalid_tool_commands_and_args(self):
self.log.info('Testing that various invalid commands raise with specific error messages')
- self.assert_raises_tool_error('Invalid command: foo', 'foo')
+ self.assert_raises_tool_error("Error parsing command line arguments: Invalid command 'foo'", 'foo')
# `bitcoin-wallet help` raises an error. Use `bitcoin-wallet -help`.
- self.assert_raises_tool_error('Invalid command: help', 'help')
- self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create')
+ self.assert_raises_tool_error("Error parsing command line arguments: Invalid command 'help'", 'help')
+ self.assert_raises_tool_error('Error: Additional arguments provided (create). Methods do not take arguments. Please refer to `-help`.', 'info', 'create')
self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo')
+ self.assert_raises_tool_error('No method provided. Run `bitcoin-wallet -help` for valid methods.')
+ self.assert_raises_tool_error('Wallet name must be provided when creating a new wallet.', 'create')
locked_dir = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets")
error = 'Error initializing wallet database environment "{}"!'.format(locked_dir)
if self.options.descriptors:
@@ -348,7 +350,8 @@ class ToolWalletTest(BitcoinTestFramework):
self.log.info('Checking createfromdump')
self.do_tool_createfromdump("load", "wallet.dump")
- self.do_tool_createfromdump("load-bdb", "wallet.dump", "bdb")
+ if self.is_bdb_compiled():
+ self.do_tool_createfromdump("load-bdb", "wallet.dump", "bdb")
if self.is_sqlite_compiled():
self.do_tool_createfromdump("load-sqlite", "wallet.dump", "sqlite")
diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py
index 229c134a4b..bc4fa90e83 100755
--- a/test/functional/wallet_avoidreuse.py
+++ b/test/functional/wallet_avoidreuse.py
@@ -65,7 +65,6 @@ def assert_balances(node, mine):
class AvoidReuseTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 2
# This test isn't testing txn relay/timing, so set whitelist on the
# peers for instant txn relay. This speeds up the test run time 2-3x.
diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py
index cf3317121f..16a0a50b07 100755
--- a/test/functional/wallet_createwallet.py
+++ b/test/functional/wallet_createwallet.py
@@ -17,7 +17,6 @@ from test_framework.wallet_util import bytes_to_wif, generate_wif_key
class CreateWalletTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def skip_test_if_missing_module(self):
diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py
index 1de41a5f96..c04a15a67c 100755
--- a/test/functional/wallet_descriptor.py
+++ b/test/functional/wallet_descriptor.py
@@ -23,11 +23,14 @@ class WalletDescriptorTest(BitcoinTestFramework):
self.skip_if_no_sqlite()
def run_test(self):
- # Make a legacy wallet and check it is BDB
- self.nodes[0].createwallet(wallet_name="legacy1", descriptors=False)
- wallet_info = self.nodes[0].getwalletinfo()
- assert_equal(wallet_info['format'], 'bdb')
- self.nodes[0].unloadwallet("legacy1")
+ if self.is_bdb_compiled():
+ # Make a legacy wallet and check it is BDB
+ self.nodes[0].createwallet(wallet_name="legacy1", descriptors=False)
+ wallet_info = self.nodes[0].getwalletinfo()
+ assert_equal(wallet_info['format'], 'bdb')
+ self.nodes[0].unloadwallet("legacy1")
+ else:
+ self.log.warning("Skipping BDB test")
# Make a descriptor wallet
self.log.info("Making a descriptor wallet")
diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py
index 9835c5a2af..880341fdd9 100755
--- a/test/functional/wallet_send.py
+++ b/test/functional/wallet_send.py
@@ -8,6 +8,7 @@ from decimal import Decimal, getcontext
from itertools import product
from test_framework.authproxy import JSONRPCException
+from test_framework.descriptors import descsum_create
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -168,49 +169,91 @@ class WalletSendTest(BitcoinTestFramework):
self.nodes[1].createwallet(wallet_name="w1")
w1 = self.nodes[1].get_wallet_rpc("w1")
# w2 contains the private keys for w3
- self.nodes[1].createwallet(wallet_name="w2")
+ self.nodes[1].createwallet(wallet_name="w2", blank=True)
w2 = self.nodes[1].get_wallet_rpc("w2")
+ xpriv = "tprv8ZgxMBicQKsPfHCsTwkiM1KT56RXbGGTqvc2hgqzycpwbHqqpcajQeMRZoBD35kW4RtyCemu6j34Ku5DEspmgjKdt2qe4SvRch5Kk8B8A2v"
+ xpub = "tpubD6NzVbkrYhZ4YkEfMbRJkQyZe7wTkbTNRECozCtJPtdLRn6cT1QKb8yHjwAPcAr26eHBFYs5iLiFFnCbwPRsncCKUKCfubHDMGKzMVcN1Jg"
+ if self.options.descriptors:
+ w2.importdescriptors([{
+ "desc": descsum_create("wpkh(" + xpriv + "/0/0/*)"),
+ "timestamp": "now",
+ "range": [0, 100],
+ "active": True
+ },{
+ "desc": descsum_create("wpkh(" + xpriv + "/0/1/*)"),
+ "timestamp": "now",
+ "range": [0, 100],
+ "active": True,
+ "internal": True
+ }])
+ else:
+ w2.sethdseed(True)
+
# w3 is a watch-only wallet, based on w2
self.nodes[1].createwallet(wallet_name="w3", disable_private_keys=True)
w3 = self.nodes[1].get_wallet_rpc("w3")
- for _ in range(3):
- a2_receive = w2.getnewaddress()
- a2_change = w2.getrawchangeaddress() # doesn't actually use change derivation
- res = w3.importmulti([{
- "desc": w2.getaddressinfo(a2_receive)["desc"],
+ if self.options.descriptors:
+ # Match the privkeys in w2 for descriptors
+ res = w3.importdescriptors([{
+ "desc": descsum_create("wpkh(" + xpub + "/0/0/*)"),
"timestamp": "now",
+ "range": [0, 100],
"keypool": True,
+ "active": True,
"watchonly": True
},{
- "desc": w2.getaddressinfo(a2_change)["desc"],
+ "desc": descsum_create("wpkh(" + xpub + "/0/1/*)"),
"timestamp": "now",
+ "range": [0, 100],
"keypool": True,
+ "active": True,
"internal": True,
"watchonly": True
}])
assert_equal(res, [{"success": True}, {"success": True}])
- w0.sendtoaddress(a2_receive, 10) # fund w3
- self.nodes[0].generate(1)
- self.sync_blocks()
-
- # w4 has private keys enabled, but only contains watch-only keys (from w2)
- self.nodes[1].createwallet(wallet_name="w4", disable_private_keys=False)
- w4 = self.nodes[1].get_wallet_rpc("w4")
for _ in range(3):
a2_receive = w2.getnewaddress()
- res = w4.importmulti([{
- "desc": w2.getaddressinfo(a2_receive)["desc"],
- "timestamp": "now",
- "keypool": False,
- "watchonly": True
- }])
- assert_equal(res, [{"success": True}])
+ if not self.options.descriptors:
+ # Because legacy wallets use exclusively hardened derivation, we can't do a ranged import like we do for descriptors
+ a2_change = w2.getrawchangeaddress() # doesn't actually use change derivation
+ res = w3.importmulti([{
+ "desc": w2.getaddressinfo(a2_receive)["desc"],
+ "timestamp": "now",
+ "keypool": True,
+ "watchonly": True
+ },{
+ "desc": w2.getaddressinfo(a2_change)["desc"],
+ "timestamp": "now",
+ "keypool": True,
+ "internal": True,
+ "watchonly": True
+ }])
+ assert_equal(res, [{"success": True}, {"success": True}])
- w0.sendtoaddress(a2_receive, 10) # fund w4
+ w0.sendtoaddress(a2_receive, 10) # fund w3
self.nodes[0].generate(1)
self.sync_blocks()
+ if not self.options.descriptors:
+ # w4 has private keys enabled, but only contains watch-only keys (from w2)
+ # This is legacy wallet behavior only as descriptor wallets don't allow watchonly and non-watchonly things in the same wallet.
+ self.nodes[1].createwallet(wallet_name="w4", disable_private_keys=False)
+ w4 = self.nodes[1].get_wallet_rpc("w4")
+ for _ in range(3):
+ a2_receive = w2.getnewaddress()
+ res = w4.importmulti([{
+ "desc": w2.getaddressinfo(a2_receive)["desc"],
+ "timestamp": "now",
+ "keypool": False,
+ "watchonly": True
+ }])
+ assert_equal(res, [{"success": True}])
+
+ w0.sendtoaddress(a2_receive, 10) # fund w4
+ self.nodes[0].generate(1)
+ self.sync_blocks()
+
self.log.info("Send to address...")
self.test_send(from_wallet=w0, to_wallet=w1, amount=1)
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=True)
@@ -241,11 +284,15 @@ class WalletSendTest(BitcoinTestFramework):
res = w2.walletprocesspsbt(res["psbt"])
assert res["complete"]
- self.log.info("Create PSBT from wallet w4 with watch-only keys, sign with w2...")
- self.test_send(from_wallet=w4, to_wallet=w1, amount=1, expect_error=(-4, "Insufficient funds"))
- res = self.test_send(from_wallet=w4, to_wallet=w1, amount=1, include_watching=True, add_to_wallet=False)
- res = w2.walletprocesspsbt(res["psbt"])
- assert res["complete"]
+ if not self.options.descriptors:
+ # Descriptor wallets do not allow mixed watch-only and non-watch-only things in the same wallet.
+ # This is specifically testing that w4 ignores its own private keys and creates a psbt with send
+ # which is not something that needs to be tested in descriptor wallets.
+ self.log.info("Create PSBT from wallet w4 with watch-only keys, sign with w2...")
+ self.test_send(from_wallet=w4, to_wallet=w1, amount=1, expect_error=(-4, "Insufficient funds"))
+ res = self.test_send(from_wallet=w4, to_wallet=w1, amount=1, include_watching=True, add_to_wallet=False)
+ res = w2.walletprocesspsbt(res["psbt"])
+ assert res["complete"]
self.log.info("Create OP_RETURN...")
self.test_send(from_wallet=w0, to_wallet=w1, amount=1)
diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py
index d0bb6135a8..fbc0f995d2 100755
--- a/test/functional/wallet_upgradewallet.py
+++ b/test/functional/wallet_upgradewallet.py
@@ -57,6 +57,7 @@ class UpgradeWalletTest(BitcoinTestFramework):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
+ self.skip_if_no_bdb()
self.skip_if_no_previous_releases()
def setup_network(self):
diff --git a/test/functional/wallet_watchonly.py b/test/functional/wallet_watchonly.py
index b0c41b2738..24799fe5f2 100755
--- a/test/functional/wallet_watchonly.py
+++ b/test/functional/wallet_watchonly.py
@@ -14,7 +14,6 @@ from test_framework.util import (
class CreateWalletWatchonlyTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def skip_test_if_missing_module(self):