Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

backport: merge bitcoin#21207, #22008, #22686, #22742, #19101, #22183, #22009, #22938, #23288, #24592 (wallet backports: part 2) #6529

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/bench/coin_selection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ static void CoinSelection(benchmark::Bench& bench)
NodeContext node;
auto chain = interfaces::MakeChain(node);
CWallet wallet(chain.get(), /*coinjoin_loader=*/ nullptr, "", CreateDummyWalletDatabase());
wallet.SetupLegacyScriptPubKeyMan();
std::vector<std::unique_ptr<CWalletTx>> wtxs;
LOCK(wallet.cs_wallet);

Expand All @@ -55,7 +54,7 @@ static void CoinSelection(benchmark::Bench& bench)
bench.run([&] {
std::set<CInputCoin> setCoinsRet;
CAmount nValueRet;
bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, coins, setCoinsRet, nValueRet, coin_selection_params);
bool success = wallet.AttemptSelection(1003 * COIN, filter_standard, coins, setCoinsRet, nValueRet, coin_selection_params);
assert(success);
assert(nValueRet == 1003 * COIN);
assert(setCoinsRet.size() == 2);
Expand Down
16 changes: 8 additions & 8 deletions src/bench/wallet_balance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,21 @@

#include <optional>

static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const bool add_watchonly, const bool add_mine, const uint32_t epoch_iters)
static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const bool add_mine, const uint32_t epoch_iters)
{
const auto test_setup = MakeNoLogFileContext<const TestingSetup>();
const auto& ADDRESS_WATCHONLY = ADDRESS_B58T_UNSPENDABLE;

CWallet wallet{test_setup->m_node.chain.get(), test_setup->m_node.coinjoin_loader.get(), "", CreateMockWalletDatabase()};
{
wallet.SetupLegacyScriptPubKeyMan();
LOCK(wallet.cs_wallet);
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet.SetupDescriptorScriptPubKeyMans();
if (wallet.LoadWallet() != DBErrors::LOAD_OK) assert(false);
}
auto handler = test_setup->m_node.chain->handleNotifications({&wallet, [](CWallet*) {}});

const std::optional<std::string> address_mine{add_mine ? std::optional<std::string>{getnewaddress(wallet)} : std::nullopt};
if (add_watchonly) importaddress(wallet, ADDRESS_WATCHONLY);

for (int i = 0; i < 100; ++i) {
generatetoaddress(test_setup->m_node, address_mine.value_or(ADDRESS_WATCHONLY));
Expand All @@ -40,14 +41,13 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b
if (set_dirty) wallet.MarkDirty();
bal = wallet.GetBalance();
if (add_mine) assert(bal.m_mine_trusted > 0);
if (add_watchonly) assert(bal.m_watchonly_trusted > 0);
});
}

static void WalletBalanceDirty(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ true, /* add_watchonly */ true, /* add_mine */ true, 2500); }
static void WalletBalanceClean(benchmark::Bench& bench) {WalletBalance(bench, /* set_dirty */ false, /* add_watchonly */ true, /* add_mine */ true, 8000); }
static void WalletBalanceMine(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ false, /* add_watchonly */ false, /* add_mine */ true, 16000); }
static void WalletBalanceWatch(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ false, /* add_watchonly */ true, /* add_mine */ false, 8000); }
static void WalletBalanceDirty(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ true, /* add_mine */ true, 2500); }
static void WalletBalanceClean(benchmark::Bench& bench) {WalletBalance(bench, /* set_dirty */ false, /* add_mine */ true, 8000); }
static void WalletBalanceMine(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ false, /* add_mine */ true, 16000); }
static void WalletBalanceWatch(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ false, /* add_mine */ false, 8000); }

BENCHMARK(WalletBalanceDirty);
BENCHMARK(WalletBalanceClean);
Expand Down
2 changes: 1 addition & 1 deletion src/bitcoin-cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ static void GetWalletBalances(UniValue& result)
}

/**
* GetProgressBar contructs a progress bar with 5% intervals.
* GetProgressBar constructs a progress bar with 5% intervals.
*
* @param[in] progress The proportion of the progress bar to be filled between 0 and 1.
* @param[out] progress_bar String representation of the progress bar.
Expand Down
20 changes: 7 additions & 13 deletions src/coinjoin/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1914,14 +1914,11 @@ void CCoinJoinClientManager::GetJsonInfo(UniValue& obj) const

void CoinJoinWalletManager::Add(const std::shared_ptr<CWallet>& wallet)
{
{
LOCK(cs_wallet_manager_map);
m_wallet_manager_map.try_emplace(wallet->GetName(),
std::make_unique<CCoinJoinClientManager>(wallet, *this, m_dmnman, m_mn_metaman,
m_mn_sync, m_isman, m_queueman,
m_is_masternode));
}
g_wallet_init_interface.InitCoinJoinSettings(*this);
LOCK(cs_wallet_manager_map);
m_wallet_manager_map.try_emplace(wallet->GetName(),
std::make_unique<CCoinJoinClientManager>(wallet, *this, m_dmnman, m_mn_metaman,
m_mn_sync, m_isman, m_queueman,
m_is_masternode));
}

void CoinJoinWalletManager::DoMaintenance(CConnman& connman)
Expand All @@ -1933,11 +1930,8 @@ void CoinJoinWalletManager::DoMaintenance(CConnman& connman)
}

void CoinJoinWalletManager::Remove(const std::string& name) {
{
LOCK(cs_wallet_manager_map);
m_wallet_manager_map.erase(name);
}
g_wallet_init_interface.InitCoinJoinSettings(*this);
LOCK(cs_wallet_manager_map);
m_wallet_manager_map.erase(name);
}

void CoinJoinWalletManager::Flush(const std::string& name)
Expand Down
23 changes: 19 additions & 4 deletions src/coinjoin/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <coinjoin/client.h>
#include <wallet/wallet.h>
#include <walletinitinterface.h>

#include <memory>
#include <string>
Expand Down Expand Up @@ -62,15 +63,25 @@ class CoinJoinClientImpl : public interfaces::CoinJoin::Client
class CoinJoinLoaderImpl : public interfaces::CoinJoin::Loader
{
CoinJoinWalletManager& m_walletman;
interfaces::WalletLoader& m_wallet_loader;

public:
explicit CoinJoinLoaderImpl(CoinJoinWalletManager& walletman)
: m_walletman(walletman) {}
explicit CoinJoinLoaderImpl(CoinJoinWalletManager& walletman, interfaces::WalletLoader& wallet_loader) :
m_walletman{walletman},
m_wallet_loader{wallet_loader}
{
g_wallet_init_interface.InitCoinJoinSettings(m_wallet_loader, m_walletman);
}

void AddWallet(const std::shared_ptr<CWallet>& wallet) override { m_walletman.Add(wallet); }
void AddWallet(const std::shared_ptr<CWallet>& wallet) override
{
m_walletman.Add(wallet);
g_wallet_init_interface.InitCoinJoinSettings(m_wallet_loader, m_walletman);
}
void RemoveWallet(const std::string& name) override
{
m_walletman.Remove(name);
g_wallet_init_interface.InitCoinJoinSettings(m_wallet_loader, m_walletman);
}
void FlushWallet(const std::string& name) override
{
Expand All @@ -91,5 +102,9 @@ class CoinJoinLoaderImpl : public interfaces::CoinJoin::Loader
} // namespace coinjoin

namespace interfaces {
std::unique_ptr<CoinJoin::Loader> MakeCoinJoinLoader(CoinJoinWalletManager& walletman) { return std::make_unique<coinjoin::CoinJoinLoaderImpl>(walletman); }
std::unique_ptr<CoinJoin::Loader> MakeCoinJoinLoader(CoinJoinWalletManager& walletman,
interfaces::WalletLoader& wallet_loader)
{
return std::make_unique<coinjoin::CoinJoinLoaderImpl>(walletman, wallet_loader);
}
} // namespace interfaces
6 changes: 4 additions & 2 deletions src/dummywallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace interfaces {
class Chain;
class Handler;
class Wallet;
class WalletLoader;
}

class DummyWalletInit : public WalletInitInterface {
Expand All @@ -23,15 +24,16 @@ class DummyWalletInit : public WalletInitInterface {
void Construct(NodeContext& node) const override {LogPrintf("No wallet support compiled in!\n");}

// Dash Specific WalletInitInterface InitCoinJoinSettings
void AutoLockMasternodeCollaterals() const override {}
void InitCoinJoinSettings(const CoinJoinWalletManager& cjwalletman) const override {}
void AutoLockMasternodeCollaterals(interfaces::WalletLoader& wallet_loader) const override {}
void InitCoinJoinSettings(interfaces::WalletLoader& wallet_loader, const CoinJoinWalletManager& cjwalletman) const override {}
bool InitAutoBackup() const override {return true;}
};

void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const
{
argsman.AddHiddenArgs({
"-avoidpartialspends",
"-consolidatefeerate=<amt>",
"-createwalletbackups=<n>",
"-disablewallet",
"-instantsendnotify=<cmd>",
Expand Down
30 changes: 27 additions & 3 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@
#include <httpserver.h>
#include <httprpc.h>
#include <init/common.h>
#include <interfaces/chain.h>
#include <index/blockfilterindex.h>
#include <index/coinstatsindex.h>
#include <index/txindex.h>
#include <interfaces/chain.h>
#include <interfaces/node.h>
#include <interfaces/wallet.h>
#include <mapport.h>
#include <node/miner.h>
#include <net.h>
Expand Down Expand Up @@ -1515,6 +1516,23 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
for (const auto& client : node.chain_clients) {
client->registerRpcs();
}
#ifdef ENABLE_WALLET
// Register non-core wallet-only RPC commands. These are commands that
// aren't a part of the wallet library but heavily rely on wallet logic.
// TODO: Move them to chain client interfaces so they can be called
// with registerRpcs()
if (!args.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
for (const auto& commands : {
GetWalletCoinJoinRPCCommands(),
GetWalletEvoRPCCommands(),
GetWalletGovernanceRPCCommands(),
GetWalletMasternodeRPCCommands(),
}) {
node.wallet_loader->registerOtherRpcs(commands);
}
}
#endif // ENABLE_WALLET

#if ENABLE_ZMQ
RegisterZMQRPCCommands(tableRPC);
#endif
Expand Down Expand Up @@ -2028,8 +2046,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
!ignores_incoming_txs);

#ifdef ENABLE_WALLET
node.coinjoin_loader = interfaces::MakeCoinJoinLoader(*node.cj_ctx->walletman);
g_wallet_init_interface.InitCoinJoinSettings(*node.cj_ctx->walletman);
if (!args.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
node.coinjoin_loader = interfaces::MakeCoinJoinLoader(*node.cj_ctx->walletman, *node.wallet_loader);
}
#endif // ENABLE_WALLET

// ********************************************************* Step 7d: Setup other Dash services
Expand Down Expand Up @@ -2206,6 +2225,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
chainman.m_load_block = std::thread(&util::TraceThread, "loadblk", [=, &args, &chainman, &node] {
ThreadImport(chainman, *node.dmnman, *g_ds_notification_interface, vImportFiles, node.mn_activeman.get(), args);
});
#ifdef ENABLE_WALLET
if (!args.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
g_wallet_init_interface.AutoLockMasternodeCollaterals(*node.wallet_loader);
}
#endif // ENABLE_WALLET
Comment on lines +2228 to +2232
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify wallet initialization state before auto-locking collaterals.

While the conditional check for wallet being enabled is good, we should verify that the wallet is fully initialized before attempting to auto-lock masternode collaterals to prevent potential race conditions.


🏁 Script executed:

#!/bin/bash
# Search for wallet initialization completion checks before auto-locking
rg -A 5 "AutoLockMasternodeCollaterals|wallet.*initialized|wallet.*ready" --type cpp

Length of output: 13042


Potential Race Condition – Validate Wallet Initialization Before Auto-Lock

The current code in src/init.cpp calls the auto-lock function without explicitly verifying that the wallet (and by extension DIP3) is fully initialized. Although the wallet initialization interface (see src/wallet/init.cpp) contains a comment noting that the operation must not occur before DIP3 is ready, there is no guard at the call site. This could lead to a race condition if the wallets aren’t completely set up yet.

  • File: src/init.cpp (Lines 2228-2232)
  • Concern: No explicit check ensuring the wallet is fully initialized before calling
    g_wallet_init_interface.AutoLockMasternodeCollaterals(*node.wallet_loader);
  • Action: Add a condition to verify that the wallet and necessary components (e.g., DIP3) are fully initialized before auto-locking masternode collaterals.


// Wait for genesis block to be processed
{
Expand Down
5 changes: 4 additions & 1 deletion src/interfaces/coinjoin.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

class CoinJoinWalletManager;
class CWallet;
namespace interfaces {
class WalletLoader;
}

namespace interfaces {
namespace CoinJoin {
Expand Down Expand Up @@ -42,7 +45,7 @@ class Loader
};
} // namespace CoinJoin

std::unique_ptr<CoinJoin::Loader> MakeCoinJoinLoader(CoinJoinWalletManager& walletman);
std::unique_ptr<CoinJoin::Loader> MakeCoinJoinLoader(CoinJoinWalletManager& walletman, interfaces::WalletLoader& wallet_loader);

} // namespace interfaces

Expand Down
24 changes: 19 additions & 5 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,22 @@
class CCoinControl;
class CFeeRate;
class CKey;
class CRPCCommand;
class CWallet;
class UniValue;
enum class FeeReason;
enum class TransactionError;
enum isminetype : unsigned int;
struct bilingual_str;
struct CRecipient;
struct NodeContext;
struct PartiallySignedTransaction;
struct WalletContext;
struct bilingual_str;
using isminefilter = std::underlying_type<isminetype>::type;

template <typename T>
class Span;

namespace interfaces {

class Handler;
Expand Down Expand Up @@ -86,6 +90,9 @@ class Wallet
//! Abort a rescan.
virtual void abortRescan() = 0;

//! Lock masternode collaterals
virtual void autoLockMasternodeCollaterals() = 0;

//! Back up wallet.
virtual bool backupWallet(const std::string& filename) = 0;

Expand Down Expand Up @@ -332,8 +339,11 @@ class Wallet
class WalletLoader : public ChainClient
{
public:
//! Create new wallet.
virtual std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
//! Register non-core wallet RPCs
virtual void registerOtherRpcs(const Span<const CRPCCommand>& commands) = 0;

//! Create new wallet.
virtual std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;

//! Load existing wallet.
virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
Expand All @@ -355,6 +365,9 @@ class WalletLoader : public ChainClient
//! loaded at startup or by RPC.
using LoadWalletFn = std::function<void(std::unique_ptr<Wallet> wallet)>;
virtual std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) = 0;

//! Return pointer to internal context, useful for testing.
virtual WalletContext* context() { return nullptr; }
};

//! Information about one wallet address.
Expand Down Expand Up @@ -440,11 +453,12 @@ struct WalletTxOut

//! Return implementation of Wallet interface. This function is defined in
//! dummywallet.cpp and throws if the wallet component is not compiled.
std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet);
std::unique_ptr<Wallet> MakeWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet);

//! Return implementation of ChainClient interface for a wallet loader. This
//! function will be undefined in builds where ENABLE_WALLET is false.
std::unique_ptr<WalletLoader> MakeWalletLoader(Chain& chain, const std::unique_ptr<CoinJoin::Loader>& coinjoin_loader, ArgsManager& args);
std::unique_ptr<WalletLoader> MakeWalletLoader(Chain& chain, ArgsManager& args, NodeContext& node_context,
const std::unique_ptr<interfaces::CoinJoin::Loader>& coinjoin_loader);

} // namespace interfaces

Expand Down
2 changes: 0 additions & 2 deletions src/node/blockstorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -926,7 +926,5 @@ void ThreadImport(ChainstateManager& chainman, CDeterministicMNManager& dmnman,
mn_activeman->Init(chainman.ActiveTip());
}

g_wallet_init_interface.AutoLockMasternodeCollaterals();

chainman.ActiveChainstate().LoadMempool(args);
}
13 changes: 9 additions & 4 deletions src/qt/test/addressbooktests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,12 @@ void TestAddAddressesToSendBook(interfaces::Node& node)
TestChain100Setup test;
node.setContext(&test.m_node);
const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), node.context()->coinjoin_loader.get(), "", CreateMockWalletDatabase());
wallet->SetupLegacyScriptPubKeyMan();
wallet->LoadWallet();
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
{
LOCK(wallet->cs_wallet);
wallet->SetupDescriptorScriptPubKeyMans();
}

auto build_address = [wallet]() {
CKey key;
Expand Down Expand Up @@ -110,9 +114,10 @@ void TestAddAddressesToSendBook(interfaces::Node& node)
// Initialize relevant QT models.
OptionsModel optionsModel;
ClientModel clientModel(node, &optionsModel);
AddWallet(wallet);
WalletModel walletModel(interfaces::MakeWallet(wallet), clientModel);
RemoveWallet(wallet, std::nullopt);
WalletContext& context = *node.walletLoader().context();
AddWallet(context, wallet);
WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel);
RemoveWallet(context, wallet, /*load_on_startup=*/std::nullopt);
EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress);
editAddressDialog.setModel(walletModel.getAddressTableModel());

Expand Down
Loading
Loading