diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/core_read.cpp | 2 | ||||
-rw-r--r-- | src/node/blockstorage.cpp | 2 | ||||
-rw-r--r-- | src/qt/rpcconsole.cpp | 2 | ||||
-rw-r--r-- | src/rpc/mining.cpp | 2 | ||||
-rw-r--r-- | src/test/fuzz/locale.cpp | 6 | ||||
-rw-r--r-- | src/test/fuzz/parse_numbers.cpp | 4 | ||||
-rw-r--r-- | src/test/fuzz/string.cpp | 24 | ||||
-rw-r--r-- | src/test/util_tests.cpp | 71 | ||||
-rw-r--r-- | src/torcontrol.cpp | 2 | ||||
-rw-r--r-- | src/util/moneystr.cpp | 3 | ||||
-rw-r--r-- | src/util/strencodings.cpp | 14 | ||||
-rw-r--r-- | src/util/strencodings.h | 30 | ||||
-rw-r--r-- | src/util/system.cpp | 20 | ||||
-rw-r--r-- | src/wallet/coincontrol.h | 29 | ||||
-rw-r--r-- | src/wallet/coinselection.h | 12 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 132 | ||||
-rw-r--r-- | src/wallet/spend.cpp | 81 | ||||
-rw-r--r-- | src/wallet/spend.h | 5 | ||||
-rw-r--r-- | src/wallet/transaction.h | 4 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 23 | ||||
-rw-r--r-- | src/wallet/wallet.h | 9 |
21 files changed, 384 insertions, 93 deletions
diff --git a/src/core_read.cpp b/src/core_read.cpp index 6108961010..320811b9e9 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -69,7 +69,7 @@ CScript ParseScript(const std::string& s) (w->front() == '-' && w->size() > 1 && std::all_of(w->begin()+1, w->end(), ::IsDigit))) { // Number - int64_t n = atoi64(*w); + int64_t n = LocaleIndependentAtoi<int64_t>(*w); //limit the range of numbers ParseScript accepts in decimal //since numbers outside -0xFFFFFFFF...0xFFFFFFFF are illegal in scripts diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 5ddcf95c84..104fe8bf01 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -85,7 +85,7 @@ void CleanupBlockRevFiles() // start removing block files. int nContigCounter = 0; for (const std::pair<const std::string, fs::path>& item : mapBlockFiles) { - if (atoi(item.first) == nContigCounter) { + if (LocaleIndependentAtoi<int>(item.first) == nContigCounter) { nContigCounter++; continue; } diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 1c8ed22ada..3c0dc5aa40 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -250,7 +250,7 @@ bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strRes for(char argch: curarg) if (!IsDigit(argch)) throw std::runtime_error("Invalid result query"); - subelement = lastResult[atoi(curarg.c_str())]; + subelement = lastResult[LocaleIndependentAtoi<int>(curarg)]; } else if (lastResult.isObject()) subelement = find_value(lastResult, curarg); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 066a60b71b..8cad51a710 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -702,7 +702,7 @@ static RPCHelpMan getblocktemplate() std::string lpstr = lpval.get_str(); hashWatchedChain = ParseHashV(lpstr.substr(0, 64), "longpollid"); - nTransactionsUpdatedLastLP = atoi64(lpstr.substr(64)); + nTransactionsUpdatedLastLP = LocaleIndependentAtoi<int64_t>(lpstr.substr(64)); } else { diff --git a/src/test/fuzz/locale.cpp b/src/test/fuzz/locale.cpp index 5b1acae57b..4ad8123554 100644 --- a/src/test/fuzz/locale.cpp +++ b/src/test/fuzz/locale.cpp @@ -50,8 +50,6 @@ FUZZ_TARGET(locale) const bool parseint32_without_locale = ParseInt32(random_string, &parseint32_out_without_locale); int64_t parseint64_out_without_locale; const bool parseint64_without_locale = ParseInt64(random_string, &parseint64_out_without_locale); - const int64_t atoi64_without_locale = atoi64(random_string); - const int atoi_without_locale = atoi(random_string); const int64_t random_int64 = fuzzed_data_provider.ConsumeIntegral<int64_t>(); const std::string tostring_without_locale = ToString(random_int64); // The variable `random_int32` is no longer used, but the harness still needs to @@ -77,10 +75,6 @@ FUZZ_TARGET(locale) if (parseint64_without_locale) { assert(parseint64_out_without_locale == parseint64_out_with_locale); } - const int64_t atoi64_with_locale = atoi64(random_string); - assert(atoi64_without_locale == atoi64_with_locale); - const int atoi_with_locale = atoi(random_string); - assert(atoi_without_locale == atoi_with_locale); const std::string tostring_with_locale = ToString(random_int64); assert(tostring_without_locale == tostring_with_locale); const std::string strprintf_int_with_locale = strprintf("%d", random_int64); diff --git a/src/test/fuzz/parse_numbers.cpp b/src/test/fuzz/parse_numbers.cpp index 33b5004290..85fee062f0 100644 --- a/src/test/fuzz/parse_numbers.cpp +++ b/src/test/fuzz/parse_numbers.cpp @@ -22,13 +22,13 @@ FUZZ_TARGET(parse_numbers) int32_t i32; (void)ParseInt32(random_string, &i32); - (void)atoi(random_string); + (void)LocaleIndependentAtoi<int>(random_string); uint32_t u32; (void)ParseUInt32(random_string, &u32); int64_t i64; - (void)atoi64(random_string); + (void)LocaleIndependentAtoi<int64_t>(random_string); (void)ParseFixedPoint(random_string, 3, &i64); (void)ParseInt64(random_string, &i64); diff --git a/src/test/fuzz/string.cpp b/src/test/fuzz/string.cpp index dc2bf7c860..ab646c68fc 100644 --- a/src/test/fuzz/string.cpp +++ b/src/test/fuzz/string.cpp @@ -122,6 +122,12 @@ bool LegacyParseUInt64(const std::string& str, uint64_t* out) return endp && *endp == 0 && !errno && n <= std::numeric_limits<uint64_t>::max(); } + +// For backwards compatibility checking. +int64_t atoi64_legacy(const std::string& str) +{ + return strtoll(str.c_str(), nullptr, 10); +} }; // namespace FUZZ_TARGET(string) @@ -268,4 +274,22 @@ FUZZ_TARGET(string) assert(u8 == u8_legacy); } } + + { + const int atoi_result = atoi(random_string_1.c_str()); + const int locale_independent_atoi_result = LocaleIndependentAtoi<int>(random_string_1); + const int64_t atoi64_result = atoi64_legacy(random_string_1); + const bool out_of_range = atoi64_result < std::numeric_limits<int>::min() || atoi64_result > std::numeric_limits<int>::max(); + if (out_of_range) { + assert(locale_independent_atoi_result == 0); + } else { + assert(atoi_result == locale_independent_atoi_result); + } + } + + { + const int64_t atoi64_result = atoi64_legacy(random_string_1); + const int64_t locale_independent_atoi_result = LocaleIndependentAtoi<int64_t>(random_string_1); + assert(atoi64_result == locale_independent_atoi_result || locale_independent_atoi_result == 0); + } } diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index dbd94eedfa..51707310a2 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -1565,6 +1565,77 @@ BOOST_AUTO_TEST_CASE(test_ToIntegral) BOOST_CHECK(!ToIntegral<uint8_t>("256")); } +BOOST_AUTO_TEST_CASE(test_LocaleIndependentAtoi) +{ + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("1234"), 1'234); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("0"), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("01234"), 1'234); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("-1234"), -1'234); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>(" 1"), 1); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("1 "), 1); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("1a"), 1); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("1.1"), 1); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("1.9"), 1); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("+01.9"), 1); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("-1"), -1); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>(" -1"), -1); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("-1 "), -1); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>(" -1 "), -1); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("+1"), 1); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>(" +1"), 1); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>(" +1 "), 1); + + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("+-1"), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("-+1"), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("++1"), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("--1"), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>(""), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("aap"), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("0x1"), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("-32482348723847471234"), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("32482348723847471234"), 0); + + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int64_t>("-9223372036854775809"), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int64_t>("-9223372036854775808"), -9'223'372'036'854'775'807LL - 1LL); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int64_t>("9223372036854775807"), 9'223'372'036'854'775'807); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int64_t>("9223372036854775808"), 0); + + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint64_t>("-1"), 0U); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint64_t>("0"), 0U); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint64_t>("18446744073709551615"), 18'446'744'073'709'551'615ULL); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint64_t>("18446744073709551616"), 0U); + + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("-2147483649"), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("-2147483648"), -2'147'483'648LL); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("2147483647"), 2'147'483'647); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int32_t>("2147483648"), 0); + + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint32_t>("-1"), 0U); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint32_t>("0"), 0U); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint32_t>("4294967295"), 4'294'967'295U); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint32_t>("4294967296"), 0U); + + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int16_t>("-32769"), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int16_t>("-32768"), -32'768); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int16_t>("32767"), 32'767); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int16_t>("32768"), 0); + + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint16_t>("-1"), 0U); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint16_t>("0"), 0U); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint16_t>("65535"), 65'535U); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint16_t>("65536"), 0U); + + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int8_t>("-129"), 0); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int8_t>("-128"), -128); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int8_t>("127"), 127); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<int8_t>("128"), 0); + + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint8_t>("-1"), 0U); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint8_t>("0"), 0U); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint8_t>("255"), 255U); + BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint8_t>("256"), 0U); +} + BOOST_AUTO_TEST_CASE(test_ParseInt64) { int64_t n; diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index bb296456ba..4bd19dcf45 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -83,7 +83,7 @@ void TorControlConnection::readcb(struct bufferevent *bev, void *ctx) if (s.size() < 4) // Short line continue; // <status>(-|+| )<data><CRLF> - self->message.code = atoi(s.substr(0,3)); + self->message.code = LocaleIndependentAtoi<int>(s.substr(0,3)); self->message.lines.push_back(s.substr(4)); char ch = s[3]; // '-','+' or ' ' if (ch == ' ') { diff --git a/src/util/moneystr.cpp b/src/util/moneystr.cpp index d3f4029607..95a919ad12 100644 --- a/src/util/moneystr.cpp +++ b/src/util/moneystr.cpp @@ -77,8 +77,7 @@ std::optional<CAmount> ParseMoney(const std::string& money_string) return std::nullopt; if (nUnits < 0 || nUnits > COIN) return std::nullopt; - int64_t nWhole = atoi64(strWhole); - + int64_t nWhole = LocaleIndependentAtoi<int64_t>(strWhole); CAmount value = nWhole * COIN + nUnits; if (!MoneyRange(value)) { diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 53989a8d28..15bd07b374 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -373,20 +373,6 @@ std::string FormatParagraph(const std::string& in, size_t width, size_t indent) return out.str(); } -int64_t atoi64(const std::string& str) -{ -#ifdef _MSC_VER - return _atoi64(str.c_str()); -#else - return strtoll(str.c_str(), nullptr, 10); -#endif -} - -int atoi(const std::string& str) -{ - return atoi(str.c_str()); -} - /** Upper bound for mantissa. * 10^18-1 is the largest arbitrary decimal that will fit in a signed 64-bit integer. * Larger integers cannot consist of arbitrary combinations of 0-9: diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 688707d188..1f7762aeef 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -11,6 +11,7 @@ #include <attributes.h> #include <span.h> +#include <util/string.h> #include <charconv> #include <cstdint> @@ -68,8 +69,33 @@ std::string EncodeBase32(Span<const unsigned char> input, bool pad = true); std::string EncodeBase32(const std::string& str, bool pad = true); void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut); -int64_t atoi64(const std::string& str); -int atoi(const std::string& str); + +// LocaleIndependentAtoi is provided for backwards compatibility reasons. +// +// New code should use the ParseInt64/ParseUInt64/ParseInt32/ParseUInt32 functions +// which provide parse error feedback. +// +// The goal of LocaleIndependentAtoi is to replicate the exact defined behaviour +// of atoi and atoi64 as they behave under the "C" locale. +template <typename T> +T LocaleIndependentAtoi(const std::string& str) +{ + static_assert(std::is_integral<T>::value); + T result; + // Emulate atoi(...) handling of white space and leading +/-. + std::string s = TrimString(str); + if (!s.empty() && s[0] == '+') { + if (s.length() >= 2 && s[1] == '-') { + return 0; + } + s = s.substr(1); + } + auto [_, error_condition] = std::from_chars(s.data(), s.data() + s.size(), result); + if (error_condition != std::errc{}) { + return 0; + } + return result; +} /** * Tests if the given character is a decimal digit. diff --git a/src/util/system.cpp b/src/util/system.cpp index 4defeed4ce..79c08816fa 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -158,16 +158,14 @@ std::streampos GetFileSize(const char* path, std::streamsize max) { /** * Interpret a string argument as a boolean. * - * The definition of atoi() requires that non-numeric string values like "foo", - * return 0. This means that if a user unintentionally supplies a non-integer - * argument here, the return value is always false. This means that -foo=false - * does what the user probably expects, but -foo=true is well defined but does - * not do what they probably expected. + * The definition of LocaleIndependentAtoi<int>() requires that non-numeric string values + * like "foo", return 0. This means that if a user unintentionally supplies a + * non-integer argument here, the return value is always false. This means that + * -foo=false does what the user probably expects, but -foo=true is well defined + * but does not do what they probably expected. * - * The return value of atoi() is undefined when given input not representable as - * an int. On most systems this means string value between "-2147483648" and - * "2147483647" are well defined (this method will return true). Setting - * -txindex=2147483648 on most systems, however, is probably undefined. + * The return value of LocaleIndependentAtoi<int>(...) is zero when given input not + * representable as an int. * * For a more extensive discussion of this topic (and a wide range of opinions * on the Right Way to change this code), see PR12713. @@ -176,7 +174,7 @@ static bool InterpretBool(const std::string& strValue) { if (strValue.empty()) return true; - return (atoi(strValue) != 0); + return (LocaleIndependentAtoi<int>(strValue) != 0); } static std::string SettingName(const std::string& arg) @@ -594,7 +592,7 @@ std::string ArgsManager::GetArg(const std::string& strArg, const std::string& st int64_t ArgsManager::GetIntArg(const std::string& strArg, int64_t nDefault) const { const util::SettingsValue value = GetSetting(strArg); - return value.isNull() ? nDefault : value.isFalse() ? 0 : value.isTrue() ? 1 : value.isNum() ? value.get_int64() : atoi64(value.get_str()); + return value.isNull() ? nDefault : value.isFalse() ? 0 : value.isTrue() ? 1 : value.isNum() ? value.get_int64() : LocaleIndependentAtoi<int64_t>(value.get_str()); } bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index 85cbec76b7..c989512d3e 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -9,9 +9,14 @@ #include <policy/feerate.h> #include <policy/fees.h> #include <primitives/transaction.h> +#include <script/keyorigin.h> +#include <script/signingprovider.h> #include <script/standard.h> #include <optional> +#include <algorithm> +#include <map> +#include <set> const int DEFAULT_MIN_DEPTH = 0; const int DEFAULT_MAX_DEPTH = 9999999; @@ -53,6 +58,8 @@ public: int m_min_depth = DEFAULT_MIN_DEPTH; //! Maximum chain depth value for coin availability int m_max_depth = DEFAULT_MAX_DEPTH; + //! SigningProvider that has pubkeys and scripts to do spend size estimation for external inputs + FlatSigningProvider m_external_provider; CCoinControl(); @@ -66,11 +73,32 @@ public: return (setSelected.count(output) > 0); } + bool IsExternalSelected(const COutPoint& output) const + { + return (m_external_txouts.count(output) > 0); + } + + bool GetExternalOutput(const COutPoint& outpoint, CTxOut& txout) const + { + const auto ext_it = m_external_txouts.find(outpoint); + if (ext_it == m_external_txouts.end()) { + return false; + } + txout = ext_it->second; + return true; + } + void Select(const COutPoint& output) { setSelected.insert(output); } + void Select(const COutPoint& outpoint, const CTxOut& txout) + { + setSelected.insert(outpoint); + m_external_txouts.emplace(outpoint, txout); + } + void UnSelect(const COutPoint& output) { setSelected.erase(output); @@ -88,6 +116,7 @@ public: private: std::set<COutPoint> setSelected; + std::map<COutPoint, CTxOut> m_external_txouts; }; #endif // BITCOIN_WALLET_COINCONTROL_H diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index a28bee622e..78d877a10b 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -37,6 +37,18 @@ public: m_input_bytes = input_bytes; } + CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in) + { + outpoint = outpoint_in; + txout = txout_in; + effective_value = txout.nValue; + } + + CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in, int input_bytes) : CInputCoin(outpoint_in, txout_in) + { + m_input_bytes = input_bytes; + } + COutPoint outpoint; CTxOut txout; CAmount effective_value; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f6cf8868de..aa97b748b1 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -43,6 +43,7 @@ #include <univalue.h> +#include <map> using interfaces::FoundBlock; @@ -3213,6 +3214,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, {"fee_rate", UniValueType()}, // will be checked by AmountFromValue() in SetFeeEstimateMode() {"feeRate", UniValueType()}, // will be checked by AmountFromValue() below {"psbt", UniValueType(UniValue::VBOOL)}, + {"solving_data", UniValueType(UniValue::VOBJ)}, {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)}, {"subtract_fee_from_outputs", UniValueType(UniValue::VARR)}, {"replaceable", UniValueType(UniValue::VBOOL)}, @@ -3289,6 +3291,54 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, coinControl.fAllowWatchOnly = ParseIncludeWatchonly(NullUniValue, wallet); } + if (options.exists("solving_data")) { + UniValue solving_data = options["solving_data"].get_obj(); + if (solving_data.exists("pubkeys")) { + for (const UniValue& pk_univ : solving_data["pubkeys"].get_array().getValues()) { + const std::string& pk_str = pk_univ.get_str(); + if (!IsHex(pk_str)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not hex", pk_str)); + } + const std::vector<unsigned char> data(ParseHex(pk_str)); + CPubKey pubkey(data.begin(), data.end()); + if (!pubkey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not a valid public key", pk_str)); + } + coinControl.m_external_provider.pubkeys.emplace(pubkey.GetID(), pubkey); + // Add witness script for pubkeys + const CScript wit_script = GetScriptForDestination(WitnessV0KeyHash(pubkey)); + coinControl.m_external_provider.scripts.emplace(CScriptID(wit_script), wit_script); + } + } + + if (solving_data.exists("scripts")) { + for (const UniValue& script_univ : solving_data["scripts"].get_array().getValues()) { + const std::string& script_str = script_univ.get_str(); + if (!IsHex(script_str)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not hex", script_str)); + } + std::vector<unsigned char> script_data(ParseHex(script_str)); + const CScript script(script_data.begin(), script_data.end()); + coinControl.m_external_provider.scripts.emplace(CScriptID(script), script); + } + } + + if (solving_data.exists("descriptors")) { + for (const UniValue& desc_univ : solving_data["descriptors"].get_array().getValues()) { + const std::string& desc_str = desc_univ.get_str(); + FlatSigningProvider desc_out; + std::string error; + std::vector<CScript> scripts_temp; + std::unique_ptr<Descriptor> desc = Parse(desc_str, desc_out, error, true); + if (!desc) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unable to parse descriptor '%s': %s", desc_str, error)); + } + desc->Expand(0, desc_out, scripts_temp, desc_out); + coinControl.m_external_provider = Merge(coinControl.m_external_provider, desc_out); + } + } + } + if (tx.vout.size() == 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output"); @@ -3306,6 +3356,19 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, setSubtractFeeFromOutputs.insert(pos); } + // Fetch specified UTXOs from the UTXO set to get the scriptPubKeys and values of the outputs being selected + // and to match with the given solving_data. Only used for non-wallet outputs. + std::map<COutPoint, Coin> coins; + for (const CTxIn& txin : tx.vin) { + coins[txin.prevout]; // Create empty map entry keyed by prevout. + } + wallet.chain().findCoins(coins); + for (const auto& coin : coins) { + if (!coin.second.out.IsNull()) { + coinControl.Select(coin.first, coin.second.out); + } + } + bilingual_str error; if (!FundTransaction(wallet, tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { @@ -3321,8 +3384,9 @@ static RPCHelpMan fundrawtransaction() "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" "The inputs added will not be signed, use signrawtransactionwithkey\n" - " or signrawtransactionwithwallet for that.\n" - "Note that all existing inputs must have their previous output transaction be in the wallet.\n" + "or signrawtransactionwithwallet for that.\n" + "All existing inputs must either have their previous output transaction be in the wallet\n" + "or be in the UTXO set. Solving data must be provided for non-wallet inputs.\n" "Note that all inputs selected must be of standard form and P2SH scripts must be\n" "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" "You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n" @@ -3357,6 +3421,26 @@ static RPCHelpMan fundrawtransaction() {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n" " \"" + FeeModes("\"\n\"") + "\""}, + {"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n" + "Used for fee estimation during coin selection.", + { + {"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.", + { + {"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"}, + }, + }, + {"scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.", + { + {"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"}, + }, + }, + {"descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.", + { + {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"}, + }, + } + } + }, }, "options"}, {"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n" @@ -4202,6 +4286,26 @@ static RPCHelpMan send() }, {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Marks this transaction as BIP125 replaceable.\n" "Allows this transaction to be replaced by a transaction with higher fees"}, + {"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n" + "Used for fee estimation during coin selection.", + { + {"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.", + { + {"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"}, + }, + }, + {"scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.", + { + {"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"}, + }, + }, + {"descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.", + { + {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"}, + }, + } + } + }, }, "options"}, }, @@ -4489,7 +4593,9 @@ static RPCHelpMan walletcreatefundedpsbt() { return RPCHelpMan{"walletcreatefundedpsbt", "\nCreates and funds a transaction in the Partially Signed Transaction format.\n" - "Implements the Creator and Updater roles.\n", + "Implements the Creator and Updater roles.\n" + "All existing inputs must either have their previous output transaction be in the wallet\n" + "or be in the UTXO set. Solving data must be provided for non-wallet inputs.\n", { {"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "Leave empty to add inputs automatically. See add_inputs option.", { @@ -4546,6 +4652,26 @@ static RPCHelpMan walletcreatefundedpsbt() {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n" " \"" + FeeModes("\"\n\"") + "\""}, + {"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n" + "Used for fee estimation during coin selection.", + { + {"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.", + { + {"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"}, + }, + }, + {"scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.", + { + {"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"}, + }, + }, + {"descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.", + { + {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"}, + }, + } + } + }, }, "options"}, {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"}, diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 1724375f4c..43fcc0416d 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -5,6 +5,7 @@ #include <consensus/validation.h> #include <interfaces/chain.h> #include <policy/policy.h> +#include <script/signingprovider.h> #include <util/check.h> #include <util/fees.h> #include <util/moneystr.h> @@ -31,21 +32,27 @@ std::string COutput::ToString() const return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); } -int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig) +int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig) { CMutableTransaction txn; txn.vin.push_back(CTxIn(COutPoint())); - if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) { + if (!provider || !DummySignInput(*provider, txn.vin[0], txout, use_max_sig)) { return -1; } return GetVirtualTransactionInputSize(txn.vin[0]); } +int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig) +{ + const std::unique_ptr<SigningProvider> provider = wallet->GetSolvingProvider(txout.scriptPubKey); + return CalculateMaximumSignedInputSize(txout, provider.get(), use_max_sig); +} + // txouts needs to be in the order of tx.vin -TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig) +TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control) { CMutableTransaction txNew(tx); - if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) { + if (!wallet->DummySignTx(txNew, txouts, coin_control)) { return TxSize{-1, -1}; } CTransaction ctx(txNew); @@ -54,19 +61,27 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle return TxSize{vsize, weight}; } -TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) +TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const CCoinControl* coin_control) { std::vector<CTxOut> txouts; + // Look up the inputs. The inputs are either in the wallet, or in coin_control. for (const CTxIn& input : tx.vin) { const auto mi = wallet->mapWallet.find(input.prevout.hash); // Can not estimate size without knowing the input details - if (mi == wallet->mapWallet.end()) { + if (mi != wallet->mapWallet.end()) { + assert(input.prevout.n < mi->second.tx->vout.size()); + txouts.emplace_back(mi->second.tx->vout.at(input.prevout.n)); + } else if (coin_control) { + CTxOut txout; + if (!coin_control->GetExternalOutput(input.prevout, txout)) { + return TxSize{-1, -1}; + } + txouts.emplace_back(txout); + } else { return TxSize{-1, -1}; } - assert(input.prevout.n < mi->second.tx->vout.size()); - txouts.emplace_back(mi->second.tx->vout[input.prevout.n]); } - return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig); + return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control); } void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) @@ -435,32 +450,40 @@ bool SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCo std::vector<COutPoint> vPresetInputs; coin_control.ListSelected(vPresetInputs); - for (const COutPoint& outpoint : vPresetInputs) - { + for (const COutPoint& outpoint : vPresetInputs) { + int input_bytes = -1; + CTxOut txout; std::map<uint256, CWalletTx>::const_iterator it = wallet.mapWallet.find(outpoint.hash); - if (it != wallet.mapWallet.end()) - { + if (it != wallet.mapWallet.end()) { const CWalletTx& wtx = it->second; // Clearly invalid input, fail if (wtx.tx->vout.size() <= outpoint.n) { return false; } - // Just to calculate the marginal byte size - CInputCoin coin(wtx.tx, outpoint.n, GetTxSpendSize(wallet, wtx, outpoint.n, false)); - nValueFromPresetInputs += coin.txout.nValue; - if (coin.m_input_bytes <= 0) { - return false; // Not solvable, can't estimate size for fee - } - coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes); - if (coin_selection_params.m_subtract_fee_outputs) { - value_to_select -= coin.txout.nValue; - } else { - value_to_select -= coin.effective_value; + input_bytes = GetTxSpendSize(wallet, wtx, outpoint.n, false); + txout = wtx.tx->vout.at(outpoint.n); + } + if (input_bytes == -1) { + // The input is external. We either did not find the tx in mapWallet, or we did but couldn't compute the input size with wallet data + if (!coin_control.GetExternalOutput(outpoint, txout)) { + // Not ours, and we don't have solving data. + return false; } - setPresetCoins.insert(coin); + input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /* use_max_sig */ true); + } + + CInputCoin coin(outpoint, txout, input_bytes); + nValueFromPresetInputs += coin.txout.nValue; + if (coin.m_input_bytes <= 0) { + return false; // Not solvable, can't estimate size for fee + } + coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes); + if (coin_selection_params.m_subtract_fee_outputs) { + value_to_select -= coin.txout.nValue; } else { - return false; // TODO: Allow non-wallet inputs + value_to_select -= coin.effective_value; } + setPresetCoins.insert(coin); } // remove preset inputs from vCoins so that Coin Selection doesn't pick them. @@ -788,10 +811,10 @@ static bool CreateTransactionInternal( } // Calculate the transaction fee - TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, coin_control.fAllowWatchOnly); + TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control); int nBytes = tx_sizes.vsize; if (nBytes < 0) { - error = _("Signing transaction failed"); + error = _("Missing solving data for estimating transaction size"); return false; } nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes); @@ -813,7 +836,7 @@ static bool CreateTransactionInternal( txNew.vout.erase(change_position); // Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those - tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, coin_control.fAllowWatchOnly); + tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control); nBytes = tx_sizes.vsize; fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes); } diff --git a/src/wallet/spend.h b/src/wallet/spend.h index e39f134dc3..3a723d9832 100644 --- a/src/wallet/spend.h +++ b/src/wallet/spend.h @@ -66,6 +66,7 @@ public: //Get the marginal bytes of spending the specified output int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false); +int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* pwallet, bool use_max_sig = false); struct TxSize { int64_t vsize{-1}; @@ -76,8 +77,8 @@ struct TxSize { * Use DummySignatureCreator, which inserts 71 byte signatures everywhere. * NOTE: this requires that all inputs must be in mapWallet (eg the tx should * be AllInputsMine). */ -TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false); -TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); +TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control = nullptr); +TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); /** * populate vCoins with vector of available COutputs. diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h index 0cd91b9ebe..223901586e 100644 --- a/src/wallet/transaction.h +++ b/src/wallet/transaction.h @@ -216,9 +216,9 @@ public: } const auto it_op = mapValue.find("n"); - nOrderPos = (it_op != mapValue.end()) ? atoi64(it_op->second) : -1; + nOrderPos = (it_op != mapValue.end()) ? LocaleIndependentAtoi<int64_t>(it_op->second) : -1; const auto it_ts = mapValue.find("timesmart"); - nTimeSmart = (it_ts != mapValue.end()) ? static_cast<unsigned int>(atoi64(it_ts->second)) : 0; + nTimeSmart = (it_ts != mapValue.end()) ? static_cast<unsigned int>(LocaleIndependentAtoi<int64_t>(it_ts->second)) : 0; mapValue.erase("fromaccount"); mapValue.erase("spent"); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1c18cdb1bc..bf6d8e7510 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1448,19 +1448,13 @@ bool CWallet::AddWalletFlags(uint64_t flags) // Helper for producing a max-sized low-S low-R signature (eg 71 bytes) // or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true -bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig) const +bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig) { // Fill in dummy signatures for fee calculation. const CScript& scriptPubKey = txout.scriptPubKey; SignatureData sigdata; - std::unique_ptr<SigningProvider> provider = GetSolvingProvider(scriptPubKey); - if (!provider) { - // We don't know about this scriptpbuKey; - return false; - } - - if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) { + if (!ProduceSignature(provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) { return false; } UpdateInput(tx_in, sigdata); @@ -1468,14 +1462,21 @@ bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig } // Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes) -bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig) const +bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control) const { // Fill in dummy signatures for fee calculation. int nIn = 0; for (const auto& txout : txouts) { - if (!DummySignInput(txNew.vin[nIn], txout, use_max_sig)) { - return false; + CTxIn& txin = txNew.vin[nIn]; + // Use max sig if watch only inputs were used or if this particular input is an external input + // to ensure a sufficient fee is attained for the requested feerate. + const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(txin.prevout)); + const std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey); + if (!provider || !DummySignInput(*provider, txin, txout, use_max_sig)) { + if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, use_max_sig)) { + return false; + } } nIn++; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index cedccf7d44..b949fe3040 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -576,14 +576,13 @@ public: /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */ bool SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_string, bool relay) const; - bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const + bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const { std::vector<CTxOut> v_txouts(txouts.size()); std::copy(txouts.begin(), txouts.end(), v_txouts.begin()); - return DummySignTx(txNew, v_txouts, use_max_sig); + return DummySignTx(txNew, v_txouts, coin_control); } - bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig = false) const; - bool DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig = false) const; + bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const; bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -928,4 +927,6 @@ 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); +bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig); + #endif // BITCOIN_WALLET_WALLET_H |