From 69f588a99a7a79d1d72300bc0f2c8475f95f6c6a Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Tue, 13 May 2025 14:33:45 -0700 Subject: [PATCH 1/3] wallet: Set upgraded descriptor cache flag for newly created wallets Although WalletBatch::LoadWallet performs the descriptor cache upgrade, because new wallets do not have the descriptor flag set yet, the upgrade does not run and set the flag. Since new wallets will always being using the upgraded cache, there's no reason to wait to set the flag, so set it when the wallet flags are being initialized for new wallets. --- src/wallet/wallet.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4a4aa837eaa..75d34e7215e 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2904,7 +2904,9 @@ std::shared_ptr CWallet::Create(WalletContext& context, const std::stri // ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key walletInstance->SetMinVersion(FEATURE_LATEST); - walletInstance->InitWalletFlags(wallet_creation_flags); + // Init with passed flags. + // Always set the cache upgrade flag as this feature is supported from the beginning. + walletInstance->InitWalletFlags(wallet_creation_flags | WALLET_FLAG_LAST_HARDENED_XPUB_CACHED); // Only descriptor wallets can be created assert(walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); From bc2a26b296238cbead6012c071bc7741c40fbd02 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 12 May 2025 14:00:28 -0700 Subject: [PATCH 2/3] wallet: Add GetWalletFlags --- src/wallet/wallet.cpp | 5 +++++ src/wallet/wallet.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 75d34e7215e..ab04864f9ec 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1739,6 +1739,11 @@ void CWallet::InitWalletFlags(uint64_t flags) if (!LoadWalletFlags(flags)) assert(false); } +uint64_t CWallet::GetWalletFlags() const +{ + return m_wallet_flags; +} + void CWallet::MaybeUpdateBirthTime(int64_t time) { int64_t birthtime = m_birth_time.load(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index fbc3bed2ab6..989db8e79df 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -906,6 +906,8 @@ public: void InitWalletFlags(uint64_t flags); /** Loads the flags into the wallet. (used by LoadWallet) */ bool LoadWalletFlags(uint64_t flags); + //! Retrieve all of the wallet's flags + uint64_t GetWalletFlags() const; /** Returns a bracketed wallet name for displaying in logs, will return [default wallet] if the wallet has no name */ std::string GetDisplayName() const override From 47237cd1938058b29fdec242c3a37611e255fda0 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Thu, 22 May 2025 17:29:21 -0700 Subject: [PATCH 3/3] wallet, rpc: Output wallet flags in getwalletinfo --- src/wallet/rpc/wallet.cpp | 25 ++++++++++++++++++++++--- src/wallet/wallet.h | 26 ++++++++++++++++++-------- test/functional/wallet_avoidreuse.py | 2 ++ test/functional/wallet_createwallet.py | 1 + 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index c2db094fba5..ac371ab004e 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -62,6 +62,10 @@ static RPCHelpMan getwalletinfo() {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"}, {RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"}, {RPCResult::Type::NUM_TIME, "birthtime", /*optional=*/true, "The start time for blocks scanning. It could be modified by (re)importing any descriptor with an earlier timestamp."}, + {RPCResult::Type::ARR, "flags", "The flags currently set on the wallet", + { + {RPCResult::Type::STR, "flag", "The name of the flag"}, + }}, RESULT_LAST_PROCESSED_BLOCK, }}, }, @@ -121,6 +125,21 @@ static RPCHelpMan getwalletinfo() obj.pushKV("birthtime", birthtime); } + // Push known flags + UniValue flags(UniValue::VARR); + uint64_t wallet_flags = pwallet->GetWalletFlags(); + for (uint64_t i = 0; i < 64; ++i) { + uint64_t flag = uint64_t{1} << i; + if (flag & wallet_flags) { + if (flag & KNOWN_WALLET_FLAGS) { + flags.push_back(WALLET_FLAG_TO_STRING.at(WalletFlags{flag})); + } else { + flags.push_back(strprintf("unknown_flag_%u", i)); + } + } + } + obj.pushKV("flags", flags); + AppendLastProcessedBlock(obj, *pwallet); return obj; }, @@ -263,7 +282,7 @@ static RPCHelpMan loadwallet() static RPCHelpMan setwalletflag() { std::string flags; - for (auto& it : WALLET_FLAG_MAP) + for (auto& it : STRING_TO_WALLET_FLAG) if (it.second & MUTABLE_WALLET_FLAGS) flags += (flags == "" ? "" : ", ") + it.first; @@ -294,11 +313,11 @@ static RPCHelpMan setwalletflag() std::string flag_str = request.params[0].get_str(); bool value = request.params[1].isNull() || request.params[1].get_bool(); - if (!WALLET_FLAG_MAP.count(flag_str)) { + if (!STRING_TO_WALLET_FLAG.count(flag_str)) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str)); } - auto flag = WALLET_FLAG_MAP.at(flag_str); + auto flag = STRING_TO_WALLET_FLAG.at(flag_str); if (!(flag & MUTABLE_WALLET_FLAGS)) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str)); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 989db8e79df..51386678687 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -160,14 +160,24 @@ static constexpr uint64_t KNOWN_WALLET_FLAGS = static constexpr uint64_t MUTABLE_WALLET_FLAGS = WALLET_FLAG_AVOID_REUSE; -static const std::map WALLET_FLAG_MAP{ - {"avoid_reuse", WALLET_FLAG_AVOID_REUSE}, - {"blank", WALLET_FLAG_BLANK_WALLET}, - {"key_origin_metadata", WALLET_FLAG_KEY_ORIGIN_METADATA}, - {"last_hardened_xpub_cached", WALLET_FLAG_LAST_HARDENED_XPUB_CACHED}, - {"disable_private_keys", WALLET_FLAG_DISABLE_PRIVATE_KEYS}, - {"descriptor_wallet", WALLET_FLAG_DESCRIPTORS}, - {"external_signer", WALLET_FLAG_EXTERNAL_SIGNER} +static const std::map WALLET_FLAG_TO_STRING{ + {WALLET_FLAG_AVOID_REUSE, "avoid_reuse"}, + {WALLET_FLAG_BLANK_WALLET, "blank"}, + {WALLET_FLAG_KEY_ORIGIN_METADATA, "key_origin_metadata"}, + {WALLET_FLAG_LAST_HARDENED_XPUB_CACHED, "last_hardened_xpub_cached"}, + {WALLET_FLAG_DISABLE_PRIVATE_KEYS, "disable_private_keys"}, + {WALLET_FLAG_DESCRIPTORS, "descriptor_wallet"}, + {WALLET_FLAG_EXTERNAL_SIGNER, "external_signer"} +}; + +static const std::map STRING_TO_WALLET_FLAG{ + {WALLET_FLAG_TO_STRING.at(WALLET_FLAG_AVOID_REUSE), WALLET_FLAG_AVOID_REUSE}, + {WALLET_FLAG_TO_STRING.at(WALLET_FLAG_BLANK_WALLET), WALLET_FLAG_BLANK_WALLET}, + {WALLET_FLAG_TO_STRING.at(WALLET_FLAG_KEY_ORIGIN_METADATA), WALLET_FLAG_KEY_ORIGIN_METADATA}, + {WALLET_FLAG_TO_STRING.at(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED), WALLET_FLAG_LAST_HARDENED_XPUB_CACHED}, + {WALLET_FLAG_TO_STRING.at(WALLET_FLAG_DISABLE_PRIVATE_KEYS), WALLET_FLAG_DISABLE_PRIVATE_KEYS}, + {WALLET_FLAG_TO_STRING.at(WALLET_FLAG_DESCRIPTORS), WALLET_FLAG_DESCRIPTORS}, + {WALLET_FLAG_TO_STRING.at(WALLET_FLAG_EXTERNAL_SIGNER), WALLET_FLAG_EXTERNAL_SIGNER} }; /** A wrapper to reserve an address from a wallet diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py index 2ae153a937e..44fa16ff64c 100755 --- a/test/functional/wallet_avoidreuse.py +++ b/test/functional/wallet_avoidreuse.py @@ -104,7 +104,9 @@ class AvoidReuseTest(BitcoinTestFramework): # Flags should be node1.avoid_reuse=false, node2.avoid_reuse=true assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False) + assert_equal(sorted(self.nodes[0].getwalletinfo()["flags"]), sorted(["descriptor_wallet", "last_hardened_xpub_cached"])) assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True) + assert_equal(sorted(self.nodes[1].getwalletinfo()["flags"]), sorted(["descriptor_wallet", "last_hardened_xpub_cached", "avoid_reuse"])) self.restart_node(1) self.connect_nodes(0, 1) diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py index 312d22fce48..a2e7aae33ea 100755 --- a/test/functional/wallet_createwallet.py +++ b/test/functional/wallet_createwallet.py @@ -44,6 +44,7 @@ class CreateWalletTest(BitcoinTestFramework): assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getrawchangeaddress) import_res = w1.importdescriptors([{"desc": w0.getaddressinfo(address1)['desc'], "timestamp": "now"}]) assert_equal(import_res[0]["success"], True) + assert_equal(sorted(w1.getwalletinfo()["flags"]), sorted(["last_hardened_xpub_cached", "descriptor_wallet", "disable_private_keys"])) self.log.info('Test that private keys cannot be imported') privkey, pubkey = generate_keypair(wif=True)