Merge 6a167325f0 into merged_master (Bitcoin PR bitcoin/bitcoin#27279)

This commit is contained in:
Byron Hambly 2025-04-12 17:24:00 +02:00
commit dbf777c321
11 changed files with 132 additions and 34 deletions

View file

@ -0,0 +1,10 @@
Wallet
------
- In the createwallet, loadwallet, unloadwallet, and restorewallet RPCs, the
"warning" string field is deprecated in favor of a "warnings" field that
returns a JSON array of strings to better handle multiple warning messages and
for consistency with other wallet RPCs. The "warning" field will be fully
removed from these RPCs in v26. It can be temporarily re-enabled during the
deprecation period by launching bitcoind with the configuration option
`-deprecatedrpc=walletwarningfield`. (#27279)

View file

@ -189,12 +189,12 @@ static RPCHelpMan createmultisig()
result.pushKV("redeemScript", HexStr(inner));
result.pushKV("descriptor", descriptor->ToString());
UniValue warnings(UniValue::VARR);
if (descriptor->GetOutputType() != output_type) {
// Only warns if the user has explicitly chosen an address type we cannot generate
warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present.");
}
if (!warnings.empty()) result.pushKV("warnings", warnings);
UniValue warnings(UniValue::VARR);
if (descriptor->GetOutputType() != output_type) {
// Only warns if the user has explicitly chosen an address type we cannot generate
warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present.");
}
PushWarnings(warnings, result);
return result;
},

View file

@ -1387,3 +1387,26 @@ UniValue AmountMapToUniv(const CAmountMap& balanceOrig, std::string strasset)
// END ELEMENTS
//
/** Convert a vector of bilingual strings to a UniValue::VARR containing their original untranslated values. */
[[nodiscard]] static UniValue BilingualStringsToUniValue(const std::vector<bilingual_str>& bilingual_strings)
{
CHECK_NONFATAL(!bilingual_strings.empty());
UniValue result{UniValue::VARR};
for (const auto& s : bilingual_strings) {
result.push_back(s.original);
}
return result;
}
void PushWarnings(const UniValue& warnings, UniValue& obj)
{
if (warnings.empty()) return;
obj.pushKV("warnings", warnings);
}
void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj)
{
if (warnings.empty()) return;
obj.pushKV("warnings", BilingualStringsToUniValue(warnings));
}

View file

@ -386,4 +386,13 @@ private:
const RPCExamples m_examples;
};
/**
* Push warning messages to an RPC "warnings" field as a JSON array of strings.
*
* @param[in] warnings Warning messages to push.
* @param[out] obj UniValue object to push the warnings array object to.
*/
void PushWarnings(const UniValue& warnings, UniValue& obj);
void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj);
#endif // BITCOIN_RPC_UTIL_H

View file

@ -318,7 +318,7 @@ RPCHelpMan addmultisigaddress()
// Only warns if the user has explicitly chosen an address type we cannot generate
warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present.");
}
if (!warnings.empty()) result.pushKV("warnings", warnings);
PushWarnings(warnings, result);
return result;
},

View file

@ -1261,7 +1261,7 @@ static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64
result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields"));
}
if (warnings.size()) result.pushKV("warnings", warnings);
PushWarnings(warnings, result);
return result;
}
@ -1616,7 +1616,7 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
result.pushKV("success", UniValue(false));
result.pushKV("error", e);
}
if (warnings.size()) result.pushKV("warnings", warnings);
PushWarnings(warnings, result);
return result;
}
@ -1940,7 +1940,11 @@ RPCHelpMan restorewallet()
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
{RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
{RPCResult::Type::STR, "warning", /*optional=*/true, "Warning messages, if any, related to restoring the wallet. Multiple messages will be delimited by newlines. (DEPRECATED, returned only if config option -deprecatedrpc=walletwarningfield is passed.)"},
{RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring the wallet.",
{
{RPCResult::Type::STR, "", ""},
}},
}
},
RPCExamples{
@ -1970,7 +1974,10 @@ RPCHelpMan restorewallet()
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
if (wallet->chain().rpcEnableDeprecated("walletwarningfield")) {
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
}
PushWarnings(warnings, obj);
return obj;

View file

@ -12,6 +12,8 @@
#include <wallet/receive.h>
#include <wallet/rpc/wallet.h>
#include <wallet/rpc/util.h>
#include <wallet/wallet.h>
#include <wallet/walletutil.h>
#include <optional>
@ -19,6 +21,14 @@
namespace wallet {
static const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS{
{WALLET_FLAG_AVOID_REUSE,
"You need to rescan the blockchain in order to correctly mark used "
"destinations in the past. Until this is done, some destinations may "
"be considered unused, even if the opposite is the case."},
};
/** Checks if a CKey is in the given CWallet compressed or otherwise*/
bool HaveKey(const SigningProvider& wallet, const CKey& key)
{
@ -215,7 +225,11 @@ static RPCHelpMan loadwallet()
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
{RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
{RPCResult::Type::STR, "warning", /*optional=*/true, "Warning messages, if any, related to loading the wallet. Multiple messages will be delimited by newlines. (DEPRECATED, returned only if config option -deprecatedrpc=walletwarningfield is passed.)"},
{RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
{
{RPCResult::Type::STR, "", ""},
}},
}
},
RPCExamples{
@ -248,7 +262,10 @@ static RPCHelpMan loadwallet()
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
if (wallet->chain().rpcEnableDeprecated("walletwarningfield")) {
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
}
PushWarnings(warnings, obj);
return obj;
},
@ -343,7 +360,11 @@ static RPCHelpMan createwallet()
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "name", "The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path."},
{RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
{RPCResult::Type::STR, "warning", /*optional=*/true, "Warning messages, if any, related to creating the wallet. Multiple messages will be delimited by newlines. (DEPRECATED, returned only if config option -deprecatedrpc=walletwarningfield is passed.)"},
{RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to creating the wallet.",
{
{RPCResult::Type::STR, "", ""},
}},
}
},
RPCExamples{
@ -413,7 +434,10 @@ static RPCHelpMan createwallet()
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
if (wallet->chain().rpcEnableDeprecated("walletwarningfield")) {
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
}
PushWarnings(warnings, obj);
return obj;
},
@ -430,7 +454,11 @@ static RPCHelpMan unloadwallet()
{"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "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, "", "", {
{RPCResult::Type::STR, "warning", "Warning message if wallet was not unloaded cleanly."},
{RPCResult::Type::STR, "warning", /*optional=*/true, "Warning messages, if any, related to unloading the wallet. Multiple messages will be delimited by newlines. (DEPRECATED, returned only if config option -deprecatedrpc=walletwarningfield is passed.)"},
{RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to unloading the wallet.",
{
{RPCResult::Type::STR, "", ""},
}},
}},
RPCExamples{
HelpExampleCli("unloadwallet", "wallet_name")
@ -468,11 +496,13 @@ static RPCHelpMan unloadwallet()
throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
}
}
UniValue result(UniValue::VOBJ);
if (wallet->chain().rpcEnableDeprecated("walletwarningfield")) {
result.pushKV("warning", Join(warnings, Untranslated("\n")).original);
}
PushWarnings(warnings, result);
UnloadWallet(std::move(wallet));
UniValue result(UniValue::VOBJ);
result.pushKV("warning", Join(warnings, Untranslated("\n")).original);
return result;
},
};

View file

@ -60,15 +60,8 @@
using interfaces::FoundBlock;
namespace wallet {
isminetype InputIsMine(const CWallet& wallet, const COutPoint& txin); // ELEMENTS: forward declaration to remove circular dep on receive.h
const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS{
{WALLET_FLAG_AVOID_REUSE,
"You need to rescan the blockchain in order to correctly mark used "
"destinations in the past. Until this is done, some destinations may "
"be considered unused, even if the opposite is the case."
},
};
isminetype InputIsMine(const CWallet& wallet, const COutPoint& txin); // ELEMENTS: forward declaration to remove circular dep on receive.h
bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name)
{
util::SettingsValue setting_value = chain.getRwSetting("wallet");

View file

@ -186,8 +186,6 @@ static const std::map<std::string,WalletFlags> WALLET_FLAG_MAP{
{"external_signer", WALLET_FLAG_EXTERNAL_SIGNER}
};
extern const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS;
/** A wrapper to reserve an address from a wallet
*
* ReserveDestination is used to reserve an address.

View file

@ -265,7 +265,12 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
)
load_res = node_master.loadwallet("u1_v16")
# Make sure this wallet opens without warnings. See https://github.com/bitcoin/bitcoin/pull/19054
assert_equal(load_res['warning'], '')
if int(node_master.getnetworkinfo()["version"]) >= 249900:
# loadwallet#warnings (added in v25) -- only present if there is a warning
assert "warnings" not in load_res
else:
# loadwallet#warning (deprecated in v25) -- always present, but empty string if no warning
assert_equal(load_res["warning"], '')
wallet = node_master.get_wallet_rpc("u1_v16")
info = wallet.getaddressinfo(v16_addr)
descriptor = f"wpkh([{info['hdmasterfingerprint']}{hdkeypath[1:]}]{v16_pubkey})"

View file

@ -15,12 +15,17 @@ from test_framework.util import (
)
from test_framework.wallet_util import bytes_to_wif, generate_wif_key
EMPTY_PASSPHRASE_MSG = "Empty string given as passphrase, wallet will not be encrypted."
LEGACY_WALLET_MSG = "Wallet created successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future."
class CreateWalletTest(BitcoinTestFramework):
def add_options(self, parser):
self.add_wallet_options(parser)
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [["-deprecatedrpc=walletwarningfield"]]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@ -55,7 +60,7 @@ class CreateWalletTest(BitcoinTestFramework):
else:
result = w1.importmulti([{'scriptPubKey': {'address': key_to_p2wpkh(eckey.get_pubkey().get_bytes())}, 'timestamp': 'now', 'keys': [privkey]}])
assert not result[0]['success']
assert 'warning' not in result[0]
assert 'warnings' not in result[0]
assert_equal(result[0]['error']['code'], -4)
assert_equal(result[0]['error']['message'], 'Cannot import private keys to a wallet with private keys disabled')
@ -159,7 +164,9 @@ class CreateWalletTest(BitcoinTestFramework):
assert_equal(walletinfo['keypoolsize_hd_internal'], keys)
# Allow empty passphrase, but there should be a warning
resp = self.nodes[0].createwallet(wallet_name='w7', disable_private_keys=False, blank=False, passphrase='')
assert 'Empty string given as passphrase, wallet will not be encrypted.' in resp['warning']
assert_equal(resp["warning"], EMPTY_PASSPHRASE_MSG if self.options.descriptors else f"{EMPTY_PASSPHRASE_MSG}\n{LEGACY_WALLET_MSG}")
assert_equal(resp["warnings"], [EMPTY_PASSPHRASE_MSG] if self.options.descriptors else [EMPTY_PASSPHRASE_MSG, LEGACY_WALLET_MSG])
w7 = node.get_wallet_rpc('w7')
assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 60)
@ -174,8 +181,24 @@ class CreateWalletTest(BitcoinTestFramework):
if self.is_bdb_compiled():
self.log.info("Test legacy wallet deprecation")
res = self.nodes[0].createwallet(wallet_name="legacy_w0", descriptors=False, passphrase=None)
assert_equal(res["warning"], "Wallet created successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future.")
result = self.nodes[0].createwallet(wallet_name="legacy_w0", descriptors=False, passphrase=None)
assert_equal(result, {
"name": "legacy_w0",
"warning": LEGACY_WALLET_MSG,
"warnings": [LEGACY_WALLET_MSG],
})
result = self.nodes[0].createwallet(wallet_name="legacy_w1", descriptors=False, passphrase="")
assert_equal(result, {
"name": "legacy_w1",
"warning": f"{EMPTY_PASSPHRASE_MSG}\n{LEGACY_WALLET_MSG}",
"warnings": [EMPTY_PASSPHRASE_MSG, LEGACY_WALLET_MSG],
})
self.log.info('Test "warning" field deprecation, i.e. not returned without -deprecatedrpc=walletwarningfield')
self.restart_node(0, extra_args=[])
result = self.nodes[0].createwallet(wallet_name="w7_again", disable_private_keys=False, blank=False, passphrase="")
assert "warning" not in result
if __name__ == '__main__':
CreateWalletTest().main()