aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xci/test/00_setup_env.sh4
-rw-r--r--ci/test/00_setup_env_native_asan.sh1
-rw-r--r--ci/test/00_setup_env_native_tsan.sh2
-rw-r--r--ci/test/00_setup_env_native_valgrind.sh2
-rwxr-xr-xci/test/06_script_b.sh2
-rw-r--r--doc/release-notes-15937.md12
-rw-r--r--src/Makefile.am7
-rw-r--r--src/Makefile.test.include2
-rw-r--r--src/bitcoin-tx.cpp10
-rw-r--r--src/dummywallet.cpp1
-rw-r--r--src/index/disktxpos.h37
-rw-r--r--src/index/txindex.cpp24
-rw-r--r--src/interfaces/chain.cpp21
-rw-r--r--src/interfaces/chain.h7
-rw-r--r--src/protocol.h3
-rw-r--r--src/rpc/client.cpp3
-rw-r--r--src/script/standard.cpp12
-rw-r--r--src/script/standard.h10
-rw-r--r--src/test/fuzz/script.cpp2
-rw-r--r--src/test/script_standard_tests.cpp17
-rw-r--r--src/test/sigopcount_tests.cpp10
-rw-r--r--src/test/transaction_tests.cpp44
-rw-r--r--src/test/txvalidationcache_tests.cpp2
-rw-r--r--src/wallet/coinselection.cpp43
-rw-r--r--src/wallet/coinselection.h8
-rw-r--r--src/wallet/init.cpp12
-rw-r--r--src/wallet/load.cpp25
-rw-r--r--src/wallet/load.h6
-rw-r--r--src/wallet/rpcwallet.cpp38
-rw-r--r--src/wallet/wallet.cpp85
-rw-r--r--src/wallet/wallet.h13
-rwxr-xr-xtest/functional/p2p_feefilter.py42
-rwxr-xr-xtest/functional/test_framework/test_node.py4
-rwxr-xr-xtest/functional/test_runner.py1
-rwxr-xr-xtest/functional/wallet_basic.py5
-rwxr-xr-xtest/functional/wallet_groups.py50
-rwxr-xr-xtest/functional/wallet_startup.py48
37 files changed, 460 insertions, 155 deletions
diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh
index 2413cfca9f..702e881862 100755
--- a/ci/test/00_setup_env.sh
+++ b/ci/test/00_setup_env.sh
@@ -35,6 +35,10 @@ export USE_BUSY_BOX=${USE_BUSY_BOX:-false}
export RUN_UNIT_TESTS=${RUN_UNIT_TESTS:-true}
export RUN_FUNCTIONAL_TESTS=${RUN_FUNCTIONAL_TESTS:-true}
export RUN_SECURITY_TESTS=${RUN_SECURITY_TESTS:-false}
+# By how much to scale the test_runner timeouts (option --timeout-factor).
+# This is needed because some ci machines have slow CPU or disk, so sanitizers
+# might be slow or a reindex might be waiting on disk IO.
+export TEST_RUNNER_TIMEOUT_FACTOR=${TEST_RUNNER_TIMEOUT_FACTOR:-4}
export TEST_RUNNER_ENV=${TEST_RUNNER_ENV:-}
export RUN_FUZZ_TESTS=${RUN_FUZZ_TESTS:-false}
export EXPECTED_TESTS_DURATION_IN_SECONDS=${EXPECTED_TESTS_DURATION_IN_SECONDS:-1000}
diff --git a/ci/test/00_setup_env_native_asan.sh b/ci/test/00_setup_env_native_asan.sh
index 5995964f17..251ece7984 100644
--- a/ci/test/00_setup_env_native_asan.sh
+++ b/ci/test/00_setup_env_native_asan.sh
@@ -10,6 +10,5 @@ export CONTAINER_NAME=ci_native_asan
export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libqrencode-dev"
export DOCKER_NAME_TAG=ubuntu:20.04
export NO_DEPENDS=1
-export TEST_RUNNER_EXTRA="--timeout-factor=4" # Increase timeout because sanitizers slow down
export GOAL="install"
export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=address,integer,undefined CC=clang CXX=clang++ --with-boost-process"
diff --git a/ci/test/00_setup_env_native_tsan.sh b/ci/test/00_setup_env_native_tsan.sh
index fc18483425..b14a46562c 100644
--- a/ci/test/00_setup_env_native_tsan.sh
+++ b/ci/test/00_setup_env_native_tsan.sh
@@ -10,6 +10,6 @@ export CONTAINER_NAME=ci_native_tsan
export DOCKER_NAME_TAG=ubuntu:20.04
export PACKAGES="clang llvm libc++abi-dev libc++-dev python3-zmq"
export DEP_OPTS="CC=clang CXX='clang++ -stdlib=libc++'"
-export TEST_RUNNER_EXTRA="--exclude feature_block --timeout-factor=4" # Increase timeout because sanitizers slow down. Low memory on Travis machines, exclude feature_block.
+export TEST_RUNNER_EXTRA="--exclude feature_block" # Low memory on Travis machines, exclude feature_block.
export GOAL="install"
export BITCOIN_CONFIG="--enable-zmq --with-gui=no CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' CXXFLAGS='-g' --with-sanitizers=thread CC=clang CXX='clang++ -stdlib=libc++' --with-boost-process"
diff --git a/ci/test/00_setup_env_native_valgrind.sh b/ci/test/00_setup_env_native_valgrind.sh
index 0041122f1e..710d9e1011 100644
--- a/ci/test/00_setup_env_native_valgrind.sh
+++ b/ci/test/00_setup_env_native_valgrind.sh
@@ -10,6 +10,6 @@ export CONTAINER_NAME=ci_native_valgrind
export PACKAGES="valgrind clang llvm python3-zmq libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev"
export USE_VALGRIND=1
export NO_DEPENDS=1
-export TEST_RUNNER_EXTRA="--exclude rpc_bind --timeout-factor=4" # Excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547
+export TEST_RUNNER_EXTRA="--exclude rpc_bind" # Excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547
export GOAL="install"
export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=no CC=clang CXX=clang++" # TODO enable GUI
diff --git a/ci/test/06_script_b.sh b/ci/test/06_script_b.sh
index 96d44328b8..6c14a3dfbe 100755
--- a/ci/test/06_script_b.sh
+++ b/ci/test/06_script_b.sh
@@ -35,7 +35,7 @@ fi
if [ "$RUN_FUNCTIONAL_TESTS" = "true" ]; then
BEGIN_FOLD functional-tests
- DOCKER_EXEC LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib ${TEST_RUNNER_ENV} test/functional/test_runner.py --ci $MAKEJOBS --tmpdirprefix "${BASE_SCRATCH_DIR}/test_runner/" --ansi --combinedlogslen=4000 ${TEST_RUNNER_EXTRA} --quiet --failfast
+ DOCKER_EXEC LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib ${TEST_RUNNER_ENV} test/functional/test_runner.py --ci $MAKEJOBS --tmpdirprefix "${BASE_SCRATCH_DIR}/test_runner/" --ansi --combinedlogslen=4000 --timeout-factor=${TEST_RUNNER_TIMEOUT_FACTOR} ${TEST_RUNNER_EXTRA} --quiet --failfast
END_FOLD
fi
diff --git a/doc/release-notes-15937.md b/doc/release-notes-15937.md
new file mode 100644
index 0000000000..ec7d355dfa
--- /dev/null
+++ b/doc/release-notes-15937.md
@@ -0,0 +1,12 @@
+Configuration
+-------------
+
+The `createwallet`, `loadwallet`, and `unloadwallet` RPCs now accept
+`load_on_startup` options that modify bitcoin's dynamic configuration in
+`\<datadir\>/settings.json`, and can add or remove a wallet from the list of
+wallets automatically loaded at startup. Unless these options are explicitly
+set to true or false, the load on startup wallet list is not modified, so this
+change is backwards compatible.
+
+In the future, the GUI will start updating the same startup wallet list as the
+RPCs to automatically reopen wallets previously opened in the GUI.
diff --git a/src/Makefile.am b/src/Makefile.am
index cd3cc95707..175501d4a6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -140,6 +140,7 @@ BITCOIN_CORE_H = \
httpserver.h \
index/base.h \
index/blockfilterindex.h \
+ index/disktxpos.h \
index/txindex.h \
indirectmap.h \
init.h \
@@ -676,12 +677,18 @@ CLEANFILES = $(EXTRA_LIBRARIES)
CLEANFILES += *.gcda *.gcno
CLEANFILES += compat/*.gcda compat/*.gcno
CLEANFILES += consensus/*.gcda consensus/*.gcno
+CLEANFILES += crc32c/src/*.gcda crc32c/src/*.gcno
CLEANFILES += crypto/*.gcda crypto/*.gcno
+CLEANFILES += index/*.gcda index/*.gcno
+CLEANFILES += interfaces/*.gcda interfaces/*.gcno
+CLEANFILES += node/*.gcda node/*.gcno
CLEANFILES += policy/*.gcda policy/*.gcno
CLEANFILES += primitives/*.gcda primitives/*.gcno
+CLEANFILES += rpc/*.gcda rpc/*.gcno
CLEANFILES += script/*.gcda script/*.gcno
CLEANFILES += support/*.gcda support/*.gcno
CLEANFILES += univalue/*.gcda univalue/*.gcno
+CLEANFILES += util/*.gcda util/*.gcno
CLEANFILES += wallet/*.gcda wallet/*.gcno
CLEANFILES += wallet/test/*.gcda wallet/test/*.gcno
CLEANFILES += zmq/*.gcda zmq/*.gcno
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index c3e46c0def..0068c94070 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -1208,7 +1208,7 @@ nodist_test_test_bitcoin_SOURCES = $(GENERATED_TEST_FILES)
$(BITCOIN_TESTS): $(GENERATED_TEST_FILES)
-CLEAN_BITCOIN_TEST = test/*.gcda test/*.gcno test/fuzz/*.gcda test/fuzz/*.gcno $(GENERATED_TEST_FILES) $(BITCOIN_TESTS:=.log)
+CLEAN_BITCOIN_TEST = test/*.gcda test/*.gcno test/fuzz/*.gcda test/fuzz/*.gcno test/util/*.gcda test/util/*.gcno $(GENERATED_TEST_FILES) $(BITCOIN_TESTS:=.log)
CLEANFILES += $(CLEAN_BITCOIN_TEST)
diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp
index 56afcb6ded..a9119d5144 100644
--- a/src/bitcoin-tx.cpp
+++ b/src/bitcoin-tx.cpp
@@ -320,8 +320,8 @@ static void MutateTxAddOutPubKey(CMutableTransaction& tx, const std::string& str
if (!pubkey.IsCompressed()) {
throw std::runtime_error("Uncompressed pubkeys are not useable for SegWit outputs");
}
- // Call GetScriptForWitness() to build a P2WSH scriptPubKey
- scriptPubKey = GetScriptForWitness(scriptPubKey);
+ // Build a P2WPKH script
+ scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(pubkey));
}
if (bScriptHash) {
// Get the ID for the script, and then construct a P2SH destination for it.
@@ -390,8 +390,8 @@ static void MutateTxAddOutMultiSig(CMutableTransaction& tx, const std::string& s
throw std::runtime_error("Uncompressed pubkeys are not useable for SegWit outputs");
}
}
- // Call GetScriptForWitness() to build a P2WSH scriptPubKey
- scriptPubKey = GetScriptForWitness(scriptPubKey);
+ // Build a P2WSH with the multisig script
+ scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(scriptPubKey));
}
if (bScriptHash) {
if (scriptPubKey.size() > MAX_SCRIPT_ELEMENT_SIZE) {
@@ -464,7 +464,7 @@ static void MutateTxAddOutScript(CMutableTransaction& tx, const std::string& str
}
if (bSegWit) {
- scriptPubKey = GetScriptForWitness(scriptPubKey);
+ scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(scriptPubKey));
}
if (bScriptHash) {
if (scriptPubKey.size() > MAX_SCRIPT_ELEMENT_SIZE) {
diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp
index 18dc7a69e2..380d4eb8ac 100644
--- a/src/dummywallet.cpp
+++ b/src/dummywallet.cpp
@@ -35,6 +35,7 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const
"-discardfee=<amt>",
"-fallbackfee=<amt>",
"-keypool=<n>",
+ "-maxapsfee=<n>",
"-maxtxfee=<amt>",
"-mintxfee=<amt>",
"-paytxfee=<amt>",
diff --git a/src/index/disktxpos.h b/src/index/disktxpos.h
new file mode 100644
index 0000000000..8cd2270028
--- /dev/null
+++ b/src/index/disktxpos.h
@@ -0,0 +1,37 @@
+// 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.
+
+#ifndef BITCOIN_INDEX_DISKTXPOS_H
+#define BITCOIN_INDEX_DISKTXPOS_H
+
+#include <chain.h>
+#include <flatfile.h>
+#include <primitives/block.h>
+#include <primitives/transaction.h>
+
+struct CDiskTxPos : public FlatFilePos
+{
+ unsigned int nTxOffset; // after header
+
+ SERIALIZE_METHODS(CDiskTxPos, obj)
+ {
+ READWRITEAS(FlatFilePos, obj);
+ READWRITE(VARINT(obj.nTxOffset));
+ }
+
+ CDiskTxPos(const FlatFilePos &blockIn, unsigned int nTxOffsetIn) : FlatFilePos(blockIn.nFile, blockIn.nPos), nTxOffset(nTxOffsetIn) {
+ }
+
+ CDiskTxPos() {
+ SetNull();
+ }
+
+ void SetNull() {
+ FlatFilePos::SetNull();
+ nTxOffset = 0;
+ }
+};
+
+
+#endif // BITCOIN_INDEX_DISKTXPOS_H
diff --git a/src/index/txindex.cpp b/src/index/txindex.cpp
index 64472714cc..104b05d4ba 100644
--- a/src/index/txindex.cpp
+++ b/src/index/txindex.cpp
@@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <index/disktxpos.h>
#include <index/txindex.h>
#include <node/ui_interface.h>
#include <shutdown.h>
@@ -15,29 +16,6 @@ constexpr char DB_TXINDEX_BLOCK = 'T';
std::unique_ptr<TxIndex> g_txindex;
-struct CDiskTxPos : public FlatFilePos
-{
- unsigned int nTxOffset; // after header
-
- SERIALIZE_METHODS(CDiskTxPos, obj)
- {
- READWRITEAS(FlatFilePos, obj);
- READWRITE(VARINT(obj.nTxOffset));
- }
-
- CDiskTxPos(const FlatFilePos &blockIn, unsigned int nTxOffsetIn) : FlatFilePos(blockIn.nFile, blockIn.nPos), nTxOffset(nTxOffsetIn) {
- }
-
- CDiskTxPos() {
- SetNull();
- }
-
- void SetNull() {
- FlatFilePos::SetNull();
- nTxOffset = 0;
- }
-};
-
/**
* Access to the txindex database (indexes/txindex/)
*
diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp
index d49e4454af..313c1265de 100644
--- a/src/interfaces/chain.cpp
+++ b/src/interfaces/chain.cpp
@@ -372,6 +372,27 @@ public:
RPCRunLater(name, std::move(fn), seconds);
}
int rpcSerializationFlags() override { return RPCSerializationFlags(); }
+ util::SettingsValue getRwSetting(const std::string& name) override
+ {
+ util::SettingsValue result;
+ gArgs.LockSettings([&](const util::Settings& settings) {
+ if (const util::SettingsValue* value = util::FindKey(settings.rw_settings, name)) {
+ result = *value;
+ }
+ });
+ return result;
+ }
+ bool updateRwSetting(const std::string& name, const util::SettingsValue& value) override
+ {
+ gArgs.LockSettings([&](util::Settings& settings) {
+ if (value.isNull()) {
+ settings.rw_settings.erase(name);
+ } else {
+ settings.rw_settings[name] = value;
+ }
+ });
+ return gArgs.WriteSettingsFile();
+ }
void requestMempoolTransactions(Notifications& notifications) override
{
LOCK2(::cs_main, ::mempool.cs);
diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h
index bbeb0fa801..053d40335f 100644
--- a/src/interfaces/chain.h
+++ b/src/interfaces/chain.h
@@ -7,6 +7,7 @@
#include <optional.h> // For Optional and nullopt
#include <primitives/transaction.h> // For CTransactionRef
+#include <util/settings.h> // For util::SettingsValue
#include <functional>
#include <memory>
@@ -269,6 +270,12 @@ public:
//! Current RPC serialization flags.
virtual int rpcSerializationFlags() = 0;
+ //! Return <datadir>/settings.json setting value.
+ virtual util::SettingsValue getRwSetting(const std::string& name) = 0;
+
+ //! Write a setting to <datadir>/settings.json.
+ virtual bool updateRwSetting(const std::string& name, const util::SettingsValue& value) = 0;
+
//! Synchronously send transactionAddedToMempool notifications about all
//! current mempool transactions to the specified handler and return after
//! the last one is sent. These notifications aren't coordinated with async
diff --git a/src/protocol.h b/src/protocol.h
index 0bef12ee62..9565f5ff96 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -374,9 +374,10 @@ public:
READWRITEAS(CService, obj);
}
- ServiceFlags nServices{NODE_NONE};
// disk and network only
uint32_t nTime{TIME_INIT};
+
+ ServiceFlags nServices{NODE_NONE};
};
/** getdata message type flags */
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index d61e02aee2..4d08671bd2 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -173,6 +173,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createwallet", 2, "blank"},
{ "createwallet", 4, "avoid_reuse"},
{ "createwallet", 5, "descriptors"},
+ { "createwallet", 6, "load_on_startup"},
+ { "loadwallet", 1, "load_on_startup"},
+ { "unloadwallet", 1, "load_on_startup"},
{ "getnodeaddresses", 0, "count"},
{ "addpeeraddress", 1, "port"},
{ "stop", 0, "wait" },
diff --git a/src/script/standard.cpp b/src/script/standard.cpp
index 3a4882f280..96a3d311a6 100644
--- a/src/script/standard.cpp
+++ b/src/script/standard.cpp
@@ -313,18 +313,6 @@ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys)
return script;
}
-CScript GetScriptForWitness(const CScript& redeemscript)
-{
- std::vector<std::vector<unsigned char> > vSolutions;
- TxoutType typ = Solver(redeemscript, vSolutions);
- if (typ == TxoutType::PUBKEY) {
- return GetScriptForDestination(WitnessV0KeyHash(Hash160(vSolutions[0])));
- } else if (typ == TxoutType::PUBKEYHASH) {
- return GetScriptForDestination(WitnessV0KeyHash(uint160{vSolutions[0]}));
- }
- return GetScriptForDestination(WitnessV0ScriptHash(redeemscript));
-}
-
bool IsValidDestination(const CTxDestination& dest) {
return dest.which() != 0;
}
diff --git a/src/script/standard.h b/src/script/standard.h
index 992e37675f..6dbcd04968 100644
--- a/src/script/standard.h
+++ b/src/script/standard.h
@@ -263,14 +263,4 @@ CScript GetScriptForRawPubKey(const CPubKey& pubkey);
/** Generate a multisig script. */
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
-/**
- * Generate a pay-to-witness script for the given redeem script. If the redeem
- * script is P2PK or P2PKH, this returns a P2WPKH script, otherwise it returns a
- * P2WSH script.
- *
- * TODO: replace calls to GetScriptForWitness with GetScriptForDestination using
- * the various witness-specific CTxDestination subtypes.
- */
-CScript GetScriptForWitness(const CScript& redeemscript);
-
#endif // BITCOIN_SCRIPT_STANDARD_H
diff --git a/src/test/fuzz/script.cpp b/src/test/fuzz/script.cpp
index 85aac6ac7a..4274fa4351 100644
--- a/src/test/fuzz/script.cpp
+++ b/src/test/fuzz/script.cpp
@@ -63,8 +63,6 @@ void test_one_input(const std::vector<uint8_t>& buffer)
int required_ret;
(void)ExtractDestinations(script, type_ret, addresses, required_ret);
- (void)GetScriptForWitness(script);
-
const FlatSigningProvider signing_provider;
(void)InferDescriptor(script, signing_provider);
diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp
index 87678af4d1..1d6bcadf69 100644
--- a/src/test/script_standard_tests.cpp
+++ b/src/test/script_standard_tests.cpp
@@ -349,21 +349,16 @@ BOOST_AUTO_TEST_CASE(script_standard_GetScriptFor_)
result = GetScriptForMultisig(2, std::vector<CPubKey>(pubkeys, pubkeys + 3));
BOOST_CHECK(result == expected);
- // GetScriptForWitness
- CScript witnessScript;
-
- witnessScript << ToByteVector(pubkeys[0]) << OP_CHECKSIG;
+ // WitnessV0KeyHash
expected.clear();
expected << OP_0 << ToByteVector(pubkeys[0].GetID());
- result = GetScriptForWitness(witnessScript);
+ result = GetScriptForDestination(WitnessV0KeyHash(Hash160(ToByteVector(pubkeys[0]))));
BOOST_CHECK(result == expected);
-
- witnessScript.clear();
- witnessScript << OP_DUP << OP_HASH160 << ToByteVector(pubkeys[0].GetID()) << OP_EQUALVERIFY << OP_CHECKSIG;
- result = GetScriptForWitness(witnessScript);
+ result = GetScriptForDestination(WitnessV0KeyHash(pubkeys[0].GetID()));
BOOST_CHECK(result == expected);
- witnessScript.clear();
+ // WitnessV0ScriptHash (multisig)
+ CScript witnessScript;
witnessScript << OP_1 << ToByteVector(pubkeys[0]) << OP_1 << OP_CHECKMULTISIG;
uint256 scriptHash;
@@ -372,7 +367,7 @@ BOOST_AUTO_TEST_CASE(script_standard_GetScriptFor_)
expected.clear();
expected << OP_0 << ToByteVector(scriptHash);
- result = GetScriptForWitness(witnessScript);
+ result = GetScriptForDestination(WitnessV0ScriptHash(witnessScript));
BOOST_CHECK(result == expected);
}
diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp
index 6e36bce7a1..7e5274450d 100644
--- a/src/test/sigopcount_tests.cpp
+++ b/src/test/sigopcount_tests.cpp
@@ -154,8 +154,7 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
// P2WPKH witness program
{
- CScript p2pk = CScript() << ToByteVector(pubkey) << OP_CHECKSIG;
- CScript scriptPubKey = GetScriptForWitness(p2pk);
+ CScript scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(pubkey));
CScript scriptSig = CScript();
CScriptWitness scriptWitness;
scriptWitness.stack.push_back(std::vector<unsigned char>(0));
@@ -183,8 +182,7 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
// P2WPKH nested in P2SH
{
- CScript p2pk = CScript() << ToByteVector(pubkey) << OP_CHECKSIG;
- CScript scriptSig = GetScriptForWitness(p2pk);
+ CScript scriptSig = GetScriptForDestination(WitnessV0KeyHash(pubkey));
CScript scriptPubKey = GetScriptForDestination(ScriptHash(scriptSig));
scriptSig = CScript() << ToByteVector(scriptSig);
CScriptWitness scriptWitness;
@@ -199,7 +197,7 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
// P2WSH witness program
{
CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY;
- CScript scriptPubKey = GetScriptForWitness(witnessScript);
+ CScript scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript));
CScript scriptSig = CScript();
CScriptWitness scriptWitness;
scriptWitness.stack.push_back(std::vector<unsigned char>(0));
@@ -215,7 +213,7 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
// P2WSH nested in P2SH
{
CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY;
- CScript redeemScript = GetScriptForWitness(witnessScript);
+ CScript redeemScript = GetScriptForDestination(WitnessV0ScriptHash(witnessScript));
CScript scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript));
CScript scriptSig = CScript() << ToByteVector(redeemScript);
CScriptWitness scriptWitness;
diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp
index c30f44292c..94b5dba913 100644
--- a/src/test/transaction_tests.cpp
+++ b/src/test/transaction_tests.cpp
@@ -501,13 +501,19 @@ BOOST_AUTO_TEST_CASE(test_witness)
BOOST_CHECK(keystore.AddCScript(scriptPubkey1L));
BOOST_CHECK(keystore.AddCScript(scriptPubkey2L));
BOOST_CHECK(keystore.AddCScript(scriptMulti));
- BOOST_CHECK(keystore.AddCScript(GetScriptForWitness(scriptPubkey1)));
- BOOST_CHECK(keystore.AddCScript(GetScriptForWitness(scriptPubkey2)));
- BOOST_CHECK(keystore.AddCScript(GetScriptForWitness(scriptPubkey1L)));
- BOOST_CHECK(keystore.AddCScript(GetScriptForWitness(scriptPubkey2L)));
- BOOST_CHECK(keystore.AddCScript(GetScriptForWitness(scriptMulti)));
+ CScript destination_script_1, destination_script_2, destination_script_1L, destination_script_2L, destination_script_multi;
+ destination_script_1 = GetScriptForDestination(WitnessV0KeyHash(pubkey1));
+ destination_script_2 = GetScriptForDestination(WitnessV0KeyHash(pubkey2));
+ destination_script_1L = GetScriptForDestination(WitnessV0KeyHash(pubkey1L));
+ destination_script_2L = GetScriptForDestination(WitnessV0KeyHash(pubkey2L));
+ destination_script_multi = GetScriptForDestination(WitnessV0ScriptHash(scriptMulti));
+ BOOST_CHECK(keystore.AddCScript(destination_script_1));
+ BOOST_CHECK(keystore.AddCScript(destination_script_2));
+ BOOST_CHECK(keystore.AddCScript(destination_script_1L));
+ BOOST_CHECK(keystore.AddCScript(destination_script_2L));
+ BOOST_CHECK(keystore.AddCScript(destination_script_multi));
BOOST_CHECK(keystore2.AddCScript(scriptMulti));
- BOOST_CHECK(keystore2.AddCScript(GetScriptForWitness(scriptMulti)));
+ BOOST_CHECK(keystore2.AddCScript(destination_script_multi));
BOOST_CHECK(keystore2.AddKeyPubKey(key3, pubkey3));
CTransactionRef output1, output2;
@@ -539,8 +545,8 @@ BOOST_AUTO_TEST_CASE(test_witness)
CheckWithFlag(output1, input2, STANDARD_SCRIPT_VERIFY_FLAGS, false);
// Witness pay-to-compressed-pubkey (v0).
- CreateCreditAndSpend(keystore, GetScriptForWitness(scriptPubkey1), output1, input1);
- CreateCreditAndSpend(keystore, GetScriptForWitness(scriptPubkey2), output2, input2);
+ CreateCreditAndSpend(keystore, destination_script_1, output1, input1);
+ CreateCreditAndSpend(keystore, destination_script_2, output2, input2);
CheckWithFlag(output1, input1, 0, true);
CheckWithFlag(output1, input1, SCRIPT_VERIFY_P2SH, true);
CheckWithFlag(output1, input1, SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH, true);
@@ -551,9 +557,9 @@ BOOST_AUTO_TEST_CASE(test_witness)
CheckWithFlag(output1, input2, STANDARD_SCRIPT_VERIFY_FLAGS, false);
// P2SH witness pay-to-compressed-pubkey (v0).
- CreateCreditAndSpend(keystore, GetScriptForDestination(ScriptHash(GetScriptForWitness(scriptPubkey1))), output1, input1);
- CreateCreditAndSpend(keystore, GetScriptForDestination(ScriptHash(GetScriptForWitness(scriptPubkey2))), output2, input2);
- ReplaceRedeemScript(input2.vin[0].scriptSig, GetScriptForWitness(scriptPubkey1));
+ CreateCreditAndSpend(keystore, GetScriptForDestination(ScriptHash(destination_script_1)), output1, input1);
+ CreateCreditAndSpend(keystore, GetScriptForDestination(ScriptHash(destination_script_2)), output2, input2);
+ ReplaceRedeemScript(input2.vin[0].scriptSig, destination_script_1);
CheckWithFlag(output1, input1, 0, true);
CheckWithFlag(output1, input1, SCRIPT_VERIFY_P2SH, true);
CheckWithFlag(output1, input1, SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH, true);
@@ -589,12 +595,12 @@ BOOST_AUTO_TEST_CASE(test_witness)
CheckWithFlag(output1, input2, STANDARD_SCRIPT_VERIFY_FLAGS, false);
// Signing disabled for witness pay-to-uncompressed-pubkey (v1).
- CreateCreditAndSpend(keystore, GetScriptForWitness(scriptPubkey1L), output1, input1, false);
- CreateCreditAndSpend(keystore, GetScriptForWitness(scriptPubkey2L), output2, input2, false);
+ CreateCreditAndSpend(keystore, destination_script_1L, output1, input1, false);
+ CreateCreditAndSpend(keystore, destination_script_2L, output2, input2, false);
// Signing disabled for P2SH witness pay-to-uncompressed-pubkey (v1).
- CreateCreditAndSpend(keystore, GetScriptForDestination(ScriptHash(GetScriptForWitness(scriptPubkey1L))), output1, input1, false);
- CreateCreditAndSpend(keystore, GetScriptForDestination(ScriptHash(GetScriptForWitness(scriptPubkey2L))), output2, input2, false);
+ CreateCreditAndSpend(keystore, GetScriptForDestination(ScriptHash(destination_script_1L)), output1, input1, false);
+ CreateCreditAndSpend(keystore, GetScriptForDestination(ScriptHash(destination_script_2L)), output2, input2, false);
// Normal 2-of-2 multisig
CreateCreditAndSpend(keystore, scriptMulti, output1, input1, false);
@@ -618,10 +624,10 @@ BOOST_AUTO_TEST_CASE(test_witness)
CheckWithFlag(output1, input1, STANDARD_SCRIPT_VERIFY_FLAGS, true);
// Witness 2-of-2 multisig
- CreateCreditAndSpend(keystore, GetScriptForWitness(scriptMulti), output1, input1, false);
+ CreateCreditAndSpend(keystore, destination_script_multi, output1, input1, false);
CheckWithFlag(output1, input1, 0, true);
CheckWithFlag(output1, input1, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false);
- CreateCreditAndSpend(keystore2, GetScriptForWitness(scriptMulti), output2, input2, false);
+ CreateCreditAndSpend(keystore2, destination_script_multi, output2, input2, false);
CheckWithFlag(output2, input2, 0, true);
CheckWithFlag(output2, input2, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false);
BOOST_CHECK(*output1 == *output2);
@@ -630,10 +636,10 @@ BOOST_AUTO_TEST_CASE(test_witness)
CheckWithFlag(output1, input1, STANDARD_SCRIPT_VERIFY_FLAGS, true);
// P2SH witness 2-of-2 multisig
- CreateCreditAndSpend(keystore, GetScriptForDestination(ScriptHash(GetScriptForWitness(scriptMulti))), output1, input1, false);
+ CreateCreditAndSpend(keystore, GetScriptForDestination(ScriptHash(destination_script_multi)), output1, input1, false);
CheckWithFlag(output1, input1, SCRIPT_VERIFY_P2SH, true);
CheckWithFlag(output1, input1, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false);
- CreateCreditAndSpend(keystore2, GetScriptForDestination(ScriptHash(GetScriptForWitness(scriptMulti))), output2, input2, false);
+ CreateCreditAndSpend(keystore2, GetScriptForDestination(ScriptHash(destination_script_multi)), output2, input2, false);
CheckWithFlag(output2, input2, SCRIPT_VERIFY_P2SH, true);
CheckWithFlag(output2, input2, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false);
BOOST_CHECK(*output1 == *output2);
diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp
index cdef7dcc3c..034577aa2c 100644
--- a/src/test/txvalidationcache_tests.cpp
+++ b/src/test/txvalidationcache_tests.cpp
@@ -157,7 +157,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup)
CScript p2pk_scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
CScript p2sh_scriptPubKey = GetScriptForDestination(ScriptHash(p2pk_scriptPubKey));
CScript p2pkh_scriptPubKey = GetScriptForDestination(PKHash(coinbaseKey.GetPubKey()));
- CScript p2wpkh_scriptPubKey = GetScriptForWitness(p2pkh_scriptPubKey);
+ CScript p2wpkh_scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(coinbaseKey.GetPubKey()));
FillableSigningProvider keystore;
BOOST_CHECK(keystore.AddKey(coinbaseKey));
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 079a5d3d53..1a45a2b313 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -5,6 +5,7 @@
#include <wallet/coinselection.h>
#include <optional.h>
+#include <policy/feerate.h>
#include <util/system.h>
#include <util/moneystr.h>
@@ -302,7 +303,7 @@ bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& group
void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants) {
m_outputs.push_back(output);
m_from_me &= from_me;
- m_value += output.effective_value;
+ m_value += output.txout.nValue;
m_depth = std::min(m_depth, depth);
// ancestors here express the number of ancestors the new coin will end up having, which is
// the sum, rather than the max; this will overestimate in the cases where multiple inputs
@@ -311,15 +312,19 @@ void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size
// descendants is the count as seen from the top ancestor, not the descendants as seen from the
// coin itself; thus, this value is counted as the max, not the sum
m_descendants = std::max(m_descendants, descendants);
- effective_value = m_value;
+ effective_value += output.effective_value;
+ fee += output.m_fee;
+ long_term_fee += output.m_long_term_fee;
}
std::vector<CInputCoin>::iterator OutputGroup::Discard(const CInputCoin& output) {
auto it = m_outputs.begin();
while (it != m_outputs.end() && it->outpoint != output.outpoint) ++it;
if (it == m_outputs.end()) return it;
- m_value -= output.effective_value;
+ m_value -= output.txout.nValue;
effective_value -= output.effective_value;
+ fee -= output.m_fee;
+ long_term_fee -= output.m_long_term_fee;
return m_outputs.erase(it);
}
@@ -329,3 +334,35 @@ bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_f
&& m_ancestors <= eligibility_filter.max_ancestors
&& m_descendants <= eligibility_filter.max_descendants;
}
+
+void OutputGroup::SetFees(const CFeeRate effective_feerate, const CFeeRate long_term_feerate)
+{
+ fee = 0;
+ long_term_fee = 0;
+ effective_value = 0;
+ for (CInputCoin& coin : m_outputs) {
+ coin.m_fee = coin.m_input_bytes < 0 ? 0 : effective_feerate.GetFee(coin.m_input_bytes);
+ fee += coin.m_fee;
+
+ coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes);
+ long_term_fee += coin.m_long_term_fee;
+
+ coin.effective_value = coin.txout.nValue - coin.m_fee;
+ effective_value += coin.effective_value;
+ }
+}
+
+OutputGroup OutputGroup::GetPositiveOnlyGroup()
+{
+ OutputGroup group(*this);
+ for (auto it = group.m_outputs.begin(); it != group.m_outputs.end(); ) {
+ const CInputCoin& coin = *it;
+ // Only include outputs that are positive effective value (i.e. not dust)
+ if (coin.effective_value <= 0) {
+ it = group.Discard(coin);
+ } else {
+ ++it;
+ }
+ }
+ return group;
+}
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index 5348401f45..49c1134ec6 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -9,6 +9,8 @@
#include <primitives/transaction.h>
#include <random.h>
+class CFeeRate;
+
//! target minimum change amount
static constexpr CAmount MIN_CHANGE{COIN / 100};
//! final minimum change amount after paying for fees
@@ -36,6 +38,8 @@ public:
COutPoint outpoint;
CTxOut txout;
CAmount effective_value;
+ CAmount m_fee{0};
+ CAmount m_long_term_fee{0};
/** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
int m_input_bytes{-1};
@@ -91,6 +95,10 @@ struct OutputGroup
void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants);
std::vector<CInputCoin>::iterator Discard(const CInputCoin& output);
bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
+
+ //! Update the OutputGroup's fee, long_term_fee, and effective_value based on the given feerates
+ void SetFees(const CFeeRate effective_feerate, const CFeeRate long_term_feerate);
+ OutputGroup GetPositiveOnlyGroup();
};
bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount not_input_fees);
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 52162ab521..bf05ef844a 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -9,6 +9,7 @@
#include <node/context.h>
#include <node/ui_interface.h>
#include <outputtype.h>
+#include <univalue.h>
#include <util/check.h>
#include <util/moneystr.h>
#include <util/system.h>
@@ -48,6 +49,7 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
argsman.AddArg("-fallbackfee=<amt>", strprintf("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data. 0 to entirely disable the fallbackfee feature. (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-keypool=<n>", strprintf("Set key pool size to <n> (default: %u). Warning: Smaller sizes may increase the risk of losing funds when restoring from an old backup, if none of the addresses in the original keypool have been used.", DEFAULT_KEYPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
+ argsman.AddArg("-maxapsfee=<n>", strprintf("Spend up to this amount in additional (absolute) fees (in %s) if it allows the use of partial spend avoidance (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_MAX_AVOIDPARTIALSPEND_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-maxtxfee=<amt>", strprintf("Maximum total fees (in %s) to use in a single wallet transaction; setting this too low may abort large transactions (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-mintxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)",
@@ -118,6 +120,14 @@ void WalletInit::Construct(NodeContext& node) const
LogPrintf("Wallet disabled!\n");
return;
}
- args.SoftSetArg("-wallet", "");
+ // If there's no -wallet setting with a list of wallets to load, set it to
+ // load the default "" wallet.
+ if (!args.IsArgSet("wallet")) {
+ args.LockSettings([&](util::Settings& settings) {
+ util::SettingsValue wallets(util::SettingsValue::VARR);
+ wallets.push_back(""); // Default wallet name is ""
+ settings.rw_settings["wallet"] = wallets;
+ });
+ }
node.chain_clients.emplace_back(interfaces::MakeWalletClient(*node.chain, args, args.GetArgs("-wallet")));
}
diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp
index 2a81d30133..ae14769edb 100644
--- a/src/wallet/load.cpp
+++ b/src/wallet/load.cpp
@@ -13,6 +13,8 @@
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
+#include <univalue.h>
+
bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files)
{
if (gArgs.IsArgSet("-walletdir")) {
@@ -120,3 +122,26 @@ void UnloadWallets()
UnloadWallet(std::move(wallet));
}
}
+
+bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name)
+{
+ util::SettingsValue setting_value = chain.getRwSetting("wallet");
+ if (!setting_value.isArray()) setting_value.setArray();
+ for (const util::SettingsValue& value : setting_value.getValues()) {
+ if (value.isStr() && value.get_str() == wallet_name) return true;
+ }
+ setting_value.push_back(wallet_name);
+ return chain.updateRwSetting("wallet", setting_value);
+}
+
+bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name)
+{
+ util::SettingsValue setting_value = chain.getRwSetting("wallet");
+ if (!setting_value.isArray()) return true;
+ util::SettingsValue new_value(util::SettingsValue::VARR);
+ for (const util::SettingsValue& value : setting_value.getValues()) {
+ if (!value.isStr() || value.get_str() != wallet_name) new_value.push_back(value);
+ }
+ if (new_value.size() == setting_value.size()) return true;
+ return chain.updateRwSetting("wallet", new_value);
+}
diff --git a/src/wallet/load.h b/src/wallet/load.h
index ff4f5b4b23..30f1a4c90d 100644
--- a/src/wallet/load.h
+++ b/src/wallet/load.h
@@ -34,4 +34,10 @@ void StopWallets();
//! Close all wallets.
void UnloadWallets();
+//! Add wallet name to persistent configuration so it will be loaded on startup.
+bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
+
+//! Remove wallet name from persistent configuration so it will not be loaded on startup.
+bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
+
#endif // BITCOIN_WALLET_LOAD_H
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 58f3777da5..7a03c4f544 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -30,6 +30,7 @@
#include <wallet/coincontrol.h>
#include <wallet/context.h>
#include <wallet/feebumper.h>
+#include <wallet/load.h>
#include <wallet/rpcwallet.h>
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
@@ -229,6 +230,18 @@ static void SetFeeEstimateMode(const CWallet* pwallet, CCoinControl& cc, const U
}
}
+static void UpdateWalletSetting(interfaces::Chain& chain,
+ const std::string& wallet_name,
+ const UniValue& load_on_startup,
+ std::vector<bilingual_str>& warnings)
+{
+ if (load_on_startup.isTrue() && !AddWalletSetting(chain, wallet_name)) {
+ warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may not be loaded next node startup."));
+ } else if (load_on_startup.isFalse() && !RemoveWalletSetting(chain, wallet_name)) {
+ warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may still be loaded next node startup."));
+ }
+}
+
static UniValue getnewaddress(const JSONRPCRequest& request)
{
RPCHelpMan{"getnewaddress",
@@ -2484,6 +2497,7 @@ static UniValue loadwallet(const JSONRPCRequest& request)
"\napplied to the new wallet (eg -zapwallettxes, rescan, etc).\n",
{
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."},
+ {"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -2516,6 +2530,8 @@ static UniValue loadwallet(const JSONRPCRequest& request)
std::shared_ptr<CWallet> const wallet = LoadWallet(*context.chain, location, error, warnings);
if (!wallet) throw JSONRPCError(RPC_WALLET_ERROR, error.original);
+ UpdateWalletSetting(*context.chain, location.GetName(), request.params[1], warnings);
+
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
@@ -2600,6 +2616,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "false", "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
{"descriptors", RPCArg::Type::BOOL, /* default */ "false", "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation"},
+ {"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -2655,6 +2672,8 @@ static UniValue createwallet(const JSONRPCRequest& request)
// no default case, so the compiler can warn about missing cases
}
+ UpdateWalletSetting(*context.chain, request.params[0].get_str(), request.params[6], warnings);
+
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
@@ -2669,8 +2688,11 @@ static UniValue unloadwallet(const JSONRPCRequest& request)
"Specifying the wallet name on a wallet endpoint is invalid.",
{
{"wallet_name", RPCArg::Type::STR, /* default */ "the wallet name from the RPC request", "The name of the wallet to unload."},
+ {"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
},
- RPCResult{RPCResult::Type::NONE, "", ""},
+ RPCResult{RPCResult::Type::OBJ, "", "", {
+ {RPCResult::Type::STR, "warning", "Warning message if wallet was not unloaded cleanly."},
+ }},
RPCExamples{
HelpExampleCli("unloadwallet", "wallet_name")
+ HelpExampleRpc("unloadwallet", "wallet_name")
@@ -2698,9 +2720,15 @@ static UniValue unloadwallet(const JSONRPCRequest& request)
throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
}
+ interfaces::Chain& chain = wallet->chain();
+ std::vector<bilingual_str> warnings;
+
UnloadWallet(std::move(wallet));
+ UpdateWalletSetting(chain, wallet_name, request.params[1], warnings);
- return NullUniValue;
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("warning", Join(warnings, Untranslated("\n")).original);
+ return result;
}
static UniValue listunspent(const JSONRPCRequest& request)
@@ -4158,7 +4186,7 @@ static const CRPCCommand commands[] =
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
{ "wallet", "psbtbumpfee", &psbtbumpfee, {"txid", "options"} },
- { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors"} },
+ { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors", "load_on_startup"} },
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
@@ -4191,7 +4219,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
{ "wallet", "listwalletdir", &listwalletdir, {} },
{ "wallet", "listwallets", &listwallets, {} },
- { "wallet", "loadwallet", &loadwallet, {"filename"} },
+ { "wallet", "loadwallet", &loadwallet, {"filename", "load_on_startup"} },
{ "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} },
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
@@ -4203,7 +4231,7 @@ static const CRPCCommand commands[] =
{ "wallet", "setwalletflag", &setwalletflag, {"flag","value"} },
{ "wallet", "signmessage", &signmessage, {"address","message"} },
{ "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
- { "wallet", "unloadwallet", &unloadwallet, {"wallet_name"} },
+ { "wallet", "unloadwallet", &unloadwallet, {"wallet_name", "load_on_startup"} },
{ "wallet", "upgradewallet", &upgradewallet, {"version"} },
{ "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs"} },
{ "wallet", "walletlock", &walletlock, {} },
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 54f9ff7a1f..ac03b3ca9c 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -2320,27 +2320,15 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil
for (OutputGroup& group : groups) {
if (!group.EligibleForSpending(eligibility_filter)) continue;
- group.fee = 0;
- group.long_term_fee = 0;
- group.effective_value = 0;
- for (auto it = group.m_outputs.begin(); it != group.m_outputs.end(); ) {
- const CInputCoin& coin = *it;
- CAmount effective_value = coin.txout.nValue - (coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes));
- // Only include outputs that are positive effective value (i.e. not dust)
- if (effective_value > 0) {
- group.fee += coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes);
- group.long_term_fee += coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes);
- if (coin_selection_params.m_subtract_fee_outputs) {
- group.effective_value += coin.txout.nValue;
- } else {
- group.effective_value += effective_value;
- }
- ++it;
- } else {
- it = group.Discard(coin);
- }
+ if (coin_selection_params.m_subtract_fee_outputs) {
+ // Set the effective feerate to 0 as we don't want to use the effective value since the fees will be deducted from the output
+ group.SetFees(CFeeRate(0) /* effective_feerate */, long_term_feerate);
+ } else {
+ group.SetFees(coin_selection_params.effective_fee, long_term_feerate);
}
- if (group.effective_value > 0) utxo_pool.push_back(group);
+
+ OutputGroup pos_group = group.GetPositiveOnlyGroup();
+ if (pos_group.effective_value > 0) utxo_pool.push_back(pos_group);
}
// Calculate the fees for things that aren't inputs
CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size);
@@ -2689,7 +2677,14 @@ OutputType CWallet::TransactionChangeType(const Optional<OutputType>& change_typ
return m_default_address_type;
}
-bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, bool sign)
+bool CWallet::CreateTransactionInternal(
+ const std::vector<CRecipient>& vecSend,
+ CTransactionRef& tx,
+ CAmount& nFeeRet,
+ int& nChangePosInOut,
+ bilingual_str& error,
+ const CCoinControl& coin_control,
+ bool sign)
{
CAmount nValue = 0;
const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
@@ -3044,6 +3039,39 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac
return true;
}
+bool CWallet::CreateTransaction(
+ const std::vector<CRecipient>& vecSend,
+ CTransactionRef& tx,
+ CAmount& nFeeRet,
+ int& nChangePosInOut,
+ bilingual_str& error,
+ const CCoinControl& coin_control,
+ bool sign)
+{
+ int nChangePosIn = nChangePosInOut;
+ CTransactionRef tx2 = tx;
+ bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, sign);
+ // try with avoidpartialspends unless it's enabled already
+ if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
+ CCoinControl tmp_cc = coin_control;
+ tmp_cc.m_avoid_partial_spends = true;
+ CAmount nFeeRet2;
+ int nChangePosInOut2 = nChangePosIn;
+ bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
+ if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, sign)) {
+ // if fee of this alternative one is within the range of the max fee, we use this one
+ const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee;
+ WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped");
+ if (use_aps) {
+ tx = tx2;
+ nFeeRet = nFeeRet2;
+ nChangePosInOut = nChangePosInOut2;
+ }
+ }
+ }
+ return res;
+}
+
void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm)
{
LOCK(cs_wallet);
@@ -3861,6 +3889,21 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
walletInstance->m_min_fee = CFeeRate(n);
}
+ if (gArgs.IsArgSet("-maxapsfee")) {
+ CAmount n = 0;
+ if (gArgs.GetArg("-maxapsfee", "") == "-1") {
+ n = -1;
+ } else if (!ParseMoney(gArgs.GetArg("-maxapsfee", ""), n)) {
+ error = AmountErrMsg("maxapsfee", gArgs.GetArg("-maxapsfee", ""));
+ return nullptr;
+ }
+ if (n > HIGH_APS_FEE) {
+ warnings.push_back(AmountHighWarn("-maxapsfee") + Untranslated(" ") +
+ _("This is the maximum transaction fee you pay to prioritize partial spend avoidance over regular coin selection."));
+ }
+ walletInstance->m_max_aps_fee = n;
+ }
+
if (gArgs.IsArgSet("-fallbackfee")) {
CAmount nFeePerK = 0;
if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) {
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index a761caf38c..6d9512f59a 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -72,6 +72,16 @@ static const CAmount DEFAULT_FALLBACK_FEE = 0;
static const CAmount DEFAULT_DISCARD_FEE = 10000;
//! -mintxfee default
static const CAmount DEFAULT_TRANSACTION_MINFEE = 1000;
+/**
+ * maximum fee increase allowed to do partial spend avoidance, even for nodes with this feature disabled by default
+ *
+ * A value of -1 disables this feature completely.
+ * A value of 0 (current default) means to attempt to do partial spend avoidance, and use its results if the fees remain *unchanged*
+ * A value > 0 means to do partial spend avoidance if the fee difference against a regular coin selection instance is in the range [0..value].
+ */
+static const CAmount DEFAULT_MAX_AVOIDPARTIALSPEND_FEE = 0;
+//! discourage APS fee higher than this amount
+constexpr CAmount HIGH_APS_FEE{COIN / 10000};
//! minimum recommended increment for BIP 125 replacement txs
static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000;
//! Default for -spendzeroconfchange
@@ -719,6 +729,8 @@ private:
// ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal structure
std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers;
+ bool CreateTransactionInternal(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, bool sign);
+
public:
/*
* Main wallet lock.
@@ -1008,6 +1020,7 @@ public:
*/
CFeeRate m_fallback_fee{DEFAULT_FALLBACK_FEE};
CFeeRate m_discard_rate{DEFAULT_DISCARD_FEE};
+ CAmount m_max_aps_fee{DEFAULT_MAX_AVOIDPARTIALSPEND_FEE}; //!< note: this is absolute fee, not fee rate
OutputType m_default_address_type{DEFAULT_ADDRESS_TYPE};
/**
* Default output type for change outputs. When unset, automatically choose type
diff --git a/test/functional/p2p_feefilter.py b/test/functional/p2p_feefilter.py
index 0b51d8f4bb..3a9b8dfbd7 100755
--- a/test/functional/p2p_feefilter.py
+++ b/test/functional/p2p_feefilter.py
@@ -5,7 +5,6 @@
"""Test processing of feefilter messages."""
from decimal import Decimal
-import time
from test_framework.messages import MSG_TX, MSG_WTX, msg_feefilter
from test_framework.mininode import mininode_lock, P2PInterface
@@ -17,16 +16,6 @@ def hashToHex(hash):
return format(hash, '064x')
-# Wait up to 60 secs to see if the testnode has received all the expected invs
-def allInvsMatch(invsExpected, testnode):
- for _ in range(60):
- with mininode_lock:
- if (sorted(invsExpected) == sorted(testnode.txinvs)):
- return True
- time.sleep(1)
- return False
-
-
class FeefilterConn(P2PInterface):
feefilter_received = False
@@ -48,6 +37,10 @@ class TestP2PConn(P2PInterface):
if (i.type == MSG_TX) or (i.type == MSG_WTX):
self.txinvs.append(hashToHex(i.hash))
+ def wait_for_invs_to_match(self, invs_expected):
+ invs_expected.sort()
+ self.wait_until(lambda: invs_expected == sorted(self.txinvs), timeout=60)
+
def clear_invs(self):
with mininode_lock:
self.txinvs = []
@@ -61,7 +54,12 @@ class FeeFilterTest(BitcoinTestFramework):
# mempool and wallet feerate calculation based on GetFee
# rounding down 3 places, leading to stranded transactions.
# See issue #16499
- self.extra_args = [["-minrelaytxfee=0.00000100", "-mintxfee=0.00000100"]] * self.num_nodes
+ # grant noban permission to all peers to speed up tx relay / mempool sync
+ self.extra_args = [[
+ "-minrelaytxfee=0.00000100",
+ "-mintxfee=0.00000100",
+ "-whitelist=noban@127.0.0.1",
+ ]] * self.num_nodes
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -88,24 +86,22 @@ class FeeFilterTest(BitcoinTestFramework):
conn = self.nodes[0].add_p2p_connection(TestP2PConn())
- # Test that invs are received by test connection for all txs at
- # feerate of .2 sat/byte
+ self.log.info("Test txs paying 0.2 sat/byte are received by test connection")
node1.settxfee(Decimal("0.00000200"))
txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for _ in range(3)]
- assert allInvsMatch(txids, conn)
+ conn.wait_for_invs_to_match(txids)
conn.clear_invs()
- # Set a filter of .15 sat/byte on test connection
+ # Set a fee filter of 0.15 sat/byte on test connection
conn.send_and_ping(msg_feefilter(150))
- # Test that txs are still being received by test connection (paying .15 sat/byte)
+ self.log.info("Test txs paying 0.15 sat/byte are received by test connection")
node1.settxfee(Decimal("0.00000150"))
txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for _ in range(3)]
- assert allInvsMatch(txids, conn)
+ conn.wait_for_invs_to_match(txids)
conn.clear_invs()
- # Change tx fee rate to .1 sat/byte and test they are no longer received
- # by the test connection
+ self.log.info("Test txs paying 0.1 sat/byte are no longer received by test connection")
node1.settxfee(Decimal("0.00000100"))
[node1.sendtoaddress(node1.getnewaddress(), 1) for _ in range(3)]
self.sync_mempools() # must be sure node 0 has received all txs
@@ -119,13 +115,13 @@ class FeeFilterTest(BitcoinTestFramework):
# as well.
node0.settxfee(Decimal("0.00020000"))
txids = [node0.sendtoaddress(node0.getnewaddress(), 1)]
- assert allInvsMatch(txids, conn)
+ conn.wait_for_invs_to_match(txids)
conn.clear_invs()
- # Remove fee filter and check that txs are received again
+ self.log.info("Remove fee filter and check txs are received again")
conn.send_and_ping(msg_feefilter(0))
txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for _ in range(3)]
- assert allInvsMatch(txids, conn)
+ conn.wait_for_invs_to_match(txids)
conn.clear_invs()
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 8f0d45c7f9..5eba554a42 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -650,10 +650,10 @@ class RPCOverloadWrapper():
def __getattr__(self, name):
return getattr(self.rpc, name)
- def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase='', avoid_reuse=None, descriptors=None):
+ def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase='', avoid_reuse=None, descriptors=None, load_on_startup=None):
if descriptors is None:
descriptors = self.descriptors
- return self.__getattr__('createwallet')(wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors)
+ return self.__getattr__('createwallet')(wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup)
def importprivkey(self, privkey, label=None, rescan=None):
wallet_info = self.getwalletinfo()
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 67b9050123..01232bda3c 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -244,6 +244,7 @@ BASE_SCRIPTS = [
'p2p_node_network_limited.py',
'p2p_permissions.py',
'feature_blocksdir.py',
+ 'wallet_startup.py',
'feature_config_args.py',
'feature_settings.py',
'rpc_getdescriptorinfo.py',
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index d9a8b58a84..71a1a3f4f6 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -12,7 +12,6 @@ from test_framework.util import (
assert_fee_amount,
assert_raises_rpc_error,
connect_nodes,
- wait_until,
)
from test_framework.wallet_util import test_address
@@ -540,7 +539,7 @@ class WalletTest(BitcoinTestFramework):
self.start_node(2, [m, "-limitancestorcount=" + str(chainlimit)])
if m == '-reindex':
# reindex will leave rpc warm up "early"; Wait for it to finish
- wait_until(lambda: [block_count] * 3 == [self.nodes[i].getblockcount() for i in range(3)])
+ self.wait_until(lambda: [block_count] * 3 == [self.nodes[i].getblockcount() for i in range(3)])
assert_equal(balance_nodes, [self.nodes[i].getbalance() for i in range(3)])
# Exercise listsinceblock with the last two blocks
@@ -589,7 +588,7 @@ class WalletTest(BitcoinTestFramework):
self.start_node(0, extra_args=extra_args)
# wait until the wallet has submitted all transactions to the mempool
- wait_until(lambda: len(self.nodes[0].getrawmempool()) == chainlimit * 2)
+ self.wait_until(lambda: len(self.nodes[0].getrawmempool()) == chainlimit * 2)
node0_balance = self.nodes[0].getbalance()
# With walletrejectlongchains we will not create the tx and store it in our wallet.
diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py
index b6fe295127..cfdc2d51e6 100755
--- a/test/functional/wallet_groups.py
+++ b/test/functional/wallet_groups.py
@@ -15,8 +15,8 @@ from test_framework.util import (
class WalletGroupTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
- self.num_nodes = 3
- self.extra_args = [[], [], ['-avoidpartialspends']]
+ self.num_nodes = 4
+ self.extra_args = [[], [], ['-avoidpartialspends'], ["-maxapsfee=0.0001"]]
self.rpc_timeout = 480
def skip_test_if_missing_module(self):
@@ -64,6 +64,52 @@ class WalletGroupTest(BitcoinTestFramework):
assert_approx(v[0], 0.2)
assert_approx(v[1], 1.3, 0.0001)
+ # Test 'avoid partial if warranted, even if disabled'
+ self.sync_all()
+ self.nodes[0].generate(1)
+ # Nodes 1-2 now have confirmed UTXOs (letters denote destinations):
+ # Node #1: Node #2:
+ # - A 1.0 - D0 1.0
+ # - B0 1.0 - D1 0.5
+ # - B1 0.5 - E0 1.0
+ # - C0 1.0 - E1 0.5
+ # - C1 0.5 - F ~1.3
+ # - D ~0.3
+ assert_approx(self.nodes[1].getbalance(), 4.3, 0.0001)
+ assert_approx(self.nodes[2].getbalance(), 4.3, 0.0001)
+ # Sending 1.4 btc should pick one 1.0 + one more. For node #1,
+ # this could be (A / B0 / C0) + (B1 / C1 / D). We ensure that it is
+ # B0 + B1 or C0 + C1, because this avoids partial spends while not being
+ # detrimental to transaction cost
+ txid3 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.4)
+ tx3 = self.nodes[1].getrawtransaction(txid3, True)
+ # tx3 should have 2 inputs and 2 outputs
+ assert_equal(2, len(tx3["vin"]))
+ assert_equal(2, len(tx3["vout"]))
+ # the accumulated value should be 1.5, so the outputs should be
+ # ~0.1 and 1.4 and should come from the same destination
+ values = [vout["value"] for vout in tx3["vout"]]
+ values.sort()
+ assert_approx(values[0], 0.1, 0.0001)
+ assert_approx(values[1], 1.4)
+
+ input_txids = [vin["txid"] for vin in tx3["vin"]]
+ input_addrs = [self.nodes[1].gettransaction(txid)['details'][0]['address'] for txid in input_txids]
+ assert_equal(input_addrs[0], input_addrs[1])
+ # Node 2 enforces avoidpartialspends so needs no checking here
+
+ # Test wallet option maxapsfee with Node 3
+ addr_aps = self.nodes[3].getnewaddress()
+ self.nodes[0].sendtoaddress(addr_aps, 1.0)
+ self.nodes[0].sendtoaddress(addr_aps, 1.0)
+ self.nodes[0].generate(1)
+ txid4 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 0.1)
+ tx4 = self.nodes[3].getrawtransaction(txid4, True)
+ # tx4 should have 2 inputs and 2 outputs although one output would
+ # have been enough and the transaction caused higher fees
+ assert_equal(2, len(tx4["vin"]))
+ assert_equal(2, len(tx4["vout"]))
+
# Empty out node2's wallet
self.nodes[2].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=self.nodes[2].getbalance(), subtractfeefromamount=True)
self.sync_all()
diff --git a/test/functional/wallet_startup.py b/test/functional/wallet_startup.py
new file mode 100755
index 0000000000..cfc4edb8ee
--- /dev/null
+++ b/test/functional/wallet_startup.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+# Copyright (c) 2017-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.
+"""Test wallet load on startup.
+
+Verify that a bitcoind node can maintain list of wallets loading on startup
+"""
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+)
+
+
+class WalletStartupTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+ self.supports_cli = True
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def setup_nodes(self):
+ self.add_nodes(self.num_nodes)
+ self.start_nodes()
+
+ def run_test(self):
+ self.nodes[0].createwallet(wallet_name='w0', load_on_startup=True)
+ self.nodes[0].createwallet(wallet_name='w1', load_on_startup=False)
+ self.nodes[0].createwallet(wallet_name='w2', load_on_startup=True)
+ self.nodes[0].createwallet(wallet_name='w3', load_on_startup=False)
+ self.nodes[0].createwallet(wallet_name='w4', load_on_startup=False)
+ self.nodes[0].unloadwallet(wallet_name='w0', load_on_startup=False)
+ self.nodes[0].unloadwallet(wallet_name='w4', load_on_startup=False)
+ self.nodes[0].loadwallet(filename='w4', load_on_startup=True)
+ assert_equal(set(self.nodes[0].listwallets()), set(('', 'w1', 'w2', 'w3', 'w4')))
+ self.restart_node(0)
+ assert_equal(set(self.nodes[0].listwallets()), set(('', 'w2', 'w4')))
+ self.nodes[0].unloadwallet(wallet_name='', load_on_startup=False)
+ self.nodes[0].unloadwallet(wallet_name='w4', load_on_startup=False)
+ self.nodes[0].loadwallet(filename='w3', load_on_startup=True)
+ self.nodes[0].loadwallet(filename='')
+ self.restart_node(0)
+ assert_equal(set(self.nodes[0].listwallets()), set(('w2', 'w3')))
+
+if __name__ == '__main__':
+ WalletStartupTest().main()