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:
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:
…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.
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 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.