Wallet component initialisation

The wallet component is initialized via the WalletInitInterface class as specified in src/walletinitinterface.h. The member functions are marked as virtual in the WalletInitInterface definition, indicating that they are going to be overridden later by a derived class.

Both wallet/init.cpp and dummywallet.cpp include derived classes which override the member functions of WalletInitInterface, depending on whether the wallet is being compiled in or not.

The primary src/Makefile.am describes which of these modules is chosen to override: if ./configure has been run with the wallet feature enabled (default), then wallet/init.cpp is added to the sources, otherwise (./configure --disable-wallet) dummywallet.cpp is added:

src/Makefile.am
if ENABLE_WALLET
libbitcoin_server_a_SOURCES += wallet/init.cpp
endif
if !ENABLE_WALLET
libbitcoin_server_a_SOURCES += dummywallet.cpp
endif

src/walletinitinterface.h declares the global g_wallet_init_interface which will handle the configured WalletInitInterface.

The wallet interface is created when the Construct() method is called on the g_wallet_init_interface object by AppInitMain() in init.cpp. Construct takes a reference to a NodeContext as argument, and then checks that the wallet has not been disabled by a runtime argument before calling interfaces::MakeWalletLoader() on the node. This initialises a new WalletLoader object which is then added to the node object, both to the general list of node.chain_clients (wallet processes or other clients which want chain information from the node) in addition to being assigned as the unique node.wallet_client role, which specifies the particular node.chain_client that should be used to load or create wallets.

The NodeContext struct is defined as the following:

src/node/context.h

…​contains references to chain state and connection state.

…​used by init, rpc, and test code to pass object references around without needing to declare the same variables and parameters repeatedly, or to use globals…​ The struct isn’t intended to have any member functions. It should just be a collection of references that can be used without pulling in unwanted dependencies or functionality.

Wallets and program initialisation

Wallets can optionally be loaded as part of main program startup (i.e. from src/init.cpp). Any wallets loaded during the life cycle of the main program are also unloaded as part of program shutdown.

Specifying wallets loaded at startup

Wallet(s) to be loaded as part of program startup can be specified by passing -wallet= or -walletdir= arguments to bitcoind/bitcoin-qt. If the wallet has been compiled in but no -wallet*= arguments have been passed, then the default wallet directory ($datadir/wallets) will be checked as per GetWalletDir():

Wallets can also be loaded after program startup via the loadwallet RPC.

VerifyWallets

Wallet verification refers to verification of the -wallet arguments as well as the underlying wallet database(s) on disk.

Wallets loaded via program arguments are first verified as part of AppInitMain() which first verifies wallet database integrity by calling VerifyWallets() via the WalletClientImpl override of client→verify().

VerifyWallets() takes an interfaces::Chain object as argument, which is currently used to send init and error messages (about wallet verification) back to the GUI. VerifyWallets() starts by checking that the walletdir supplied by argument, or default of "", is valid. Next it loops through all wallets it finds in the walletdir and adds them to an std::set called wallet_paths, first de-duplicating them by tracking their absolute paths, and then checking that the WalletDatabase for each wallet exists (or is otherwise constructed successfully) and can be verified.

If this check passes for all wallets, then VerifyWallets() is complete and will return true to calling function AppInitMain, otherwise false will be returned. If VerifyWallets() fails and returns false (due to a corrupted wallet database, but notably not due to an incorrect wallet path), the main program process AppInit() will be immediately interrupted and shutdown.

Program shutdown on a potentially-corrupt wallet database is a deliberate design decision. This is so that the wallet cannot display information to the user which is not guaranteed by the database.

LoadWallets

"Startup" wallet(s) are loaded when client→load() is called on each node.chain_client as part of init.cpp.

src/init.cpp#AppInitMain()
for (const auto& client : node.chain_clients) {
    if (!client->load()) {
        return false;
    }
}

The call to load() on the wallet chain_clients has again been overridden, this time by WalletClientImpl's LoadWallets() method. This function works similarly to VerifyWallets(), first creating the WalletDatabase (memory) object for each wallet, although this time skipping the verify step, before creating a CWallet object from the database and adding it to the global list of wallets, the vector vpwallets, by calling AddWallet().

There are a number of steps in init.cpp that happen before the wallet is loaded, notably the blockchain is synced first. This is a safeguard which means that wallet operations cannot be called on a wallet which has been loaded against stale blockchain data.

init.cpp is run on a single thread. This means that calls to wallet code block further initialisation of the node.

The interfaces::Chain object taken as argument by LoadWallets() is used to pass back any error messages, exactly as it was in VerifyWallets(). More information on AddWallet() can be found in src/wallet.cpp.

StartWallets

The wallet is finally ready when (all) chain_clients have been started in init.cpp which calls the overridden client→start() method from the WalletClientImpl class, resulting in src/wallet/load.cpp#StartWallets() being called.

This calls the GetWallets() function which returns a vector of pointers to the interfaces for all loaded CWallet objects, called vpwallets. As part of startup PostInitProcess() is called on each wallet which, after grabbing the main wallet lock cs_wallet, synchronises the wallet and mempool by adding wallet transactions not yet in a block to our mempool, and updating the wallet with any relevant transactions from the mempool.

Also, as part of StartWallets, flushwallet might be scheduled (if configured by argument) scheduling wallet transactions to be re-broadcast every second, although this interval is delayed upstream with a random timer.

FlushWallets

All wallets loaded into the program are "flushed" (to disk) before shutdown. As part of init.cpp#Shutdown() the flush() method is called on each member of node.chain_clients in sequence. WalletClientImpl again overrides this method to call wallet/load.cpp#FlushWallets() which makes sure all wallet changes have been successfully flushed to the wallet database.

Finally the stop() method is called on each member of node.chain_clients which is overridden by StopWallets(), flushing again and this time calling close() on the database file.