diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 7 | ||||
-rw-r--r-- | src/Makefile.test.include | 2 | ||||
-rw-r--r-- | src/bitcoin-tx.cpp | 10 | ||||
-rw-r--r-- | src/dummywallet.cpp | 1 | ||||
-rw-r--r-- | src/index/disktxpos.h | 37 | ||||
-rw-r--r-- | src/index/txindex.cpp | 24 | ||||
-rw-r--r-- | src/interfaces/chain.cpp | 21 | ||||
-rw-r--r-- | src/interfaces/chain.h | 7 | ||||
-rw-r--r-- | src/protocol.h | 3 | ||||
-rw-r--r-- | src/rpc/client.cpp | 3 | ||||
-rw-r--r-- | src/script/standard.cpp | 12 | ||||
-rw-r--r-- | src/script/standard.h | 10 | ||||
-rw-r--r-- | src/test/fuzz/script.cpp | 2 | ||||
-rw-r--r-- | src/test/script_standard_tests.cpp | 17 | ||||
-rw-r--r-- | src/test/sigopcount_tests.cpp | 10 | ||||
-rw-r--r-- | src/test/transaction_tests.cpp | 44 | ||||
-rw-r--r-- | src/test/txvalidationcache_tests.cpp | 2 | ||||
-rw-r--r-- | src/wallet/coinselection.cpp | 43 | ||||
-rw-r--r-- | src/wallet/coinselection.h | 8 | ||||
-rw-r--r-- | src/wallet/init.cpp | 12 | ||||
-rw-r--r-- | src/wallet/load.cpp | 25 | ||||
-rw-r--r-- | src/wallet/load.h | 6 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 38 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 85 | ||||
-rw-r--r-- | src/wallet/wallet.h | 13 | ||||
-rw-r--r-- | src/wallet/wallettool.cpp | 2 |
26 files changed, 322 insertions, 122 deletions
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 a269d6dc24..2e6c767cdd 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/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index c1cba0fd13..9b51461843 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -130,7 +130,7 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name) std::vector<bilingual_str> warnings; bool ret = RecoverDatabaseFile(path, error, warnings); if (!ret) { - for (const auto warning : warnings) { + for (const auto& warning : warnings) { tfm::format(std::cerr, "%s\n", warning.original); } if (!error.empty()) { |