Hi everyone! Welcome to the latest episode of Inside Bitcoin Code!
Sorry for the delay, but my fiat-mining job is getting a bit tiresome lately and I have just a bit of energy at the end of the day. However, trying to stay consistent here!
Today we are going to talk about the start-up of the application, done by the AppInit()
function. Since the function is quite long and complex its explanation will be done in two parts.
Let’s dive in together in part 1!
AppInit()
: Overall function
Here is the function signature:
static bool AppInit(NodeContext& node)
The function takes as input a NodeContext1
object by reference2, and returns a static3 Boolean variable. This means that the function returns a value which be either TRUE or FALSE, according to the good success of the processing done here.
Here is the whole code for the AppInit()
function:
static bool AppInit(NodeContext& node)
{
bool fRet = false;
ArgsManager& args = *Assert(node.args);
#if HAVE_DECL_FORK
// Communication with parent after daemonizing. This is used for signalling in the following ways:
// - a boolean token is sent when the initialization process (all the Init* functions) have finished to indicate
// that the parent process can quit, and whether it was successful/unsuccessful.
// - an unexpected shutdown of the child process creates an unexpected end of stream at the parent
// end, which is interpreted as failure to start.
TokenPipeEnd daemon_ep;
#endif
std::any context{&node};
try
{
// -server defaults to true for bitcoind but not for the GUI so do this here
args.SoftSetBoolArg("-server", true);
// Set this early so that parameter interactions go to console
InitLogging(args);
InitParameterInteraction(args);
if (!AppInitBasicSetup(args, node.exit_status)) {
// InitError will have been called with detailed error, which ends up on console
return false;
}
if (!AppInitParameterInteraction(args)) {
// InitError will have been called with detailed error, which ends up on console
return false;
}
node.kernel = std::make_unique<kernel::Context>();
if (!AppInitSanityChecks(*node.kernel))
{
// InitError will have been called with detailed error, which ends up on console
return false;
}
if (args.GetBoolArg("-daemon", DEFAULT_DAEMON) || args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) {
#if HAVE_DECL_FORK
tfm::format(std::cout, PACKAGE_NAME " starting\n");
// Daemonize
switch (fork_daemon(1, 0, daemon_ep)) { // don't chdir (1), do close FDs (0)
case 0: // Child: continue.
// If -daemonwait is not enabled, immediately send a success token the parent.
if (!args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) {
daemon_ep.TokenWrite(1);
daemon_ep.Close();
}
break;
case -1: // Error happened.
return InitError(Untranslated(strprintf("fork_daemon() failed: %s", SysErrorString(errno))));
default: { // Parent: wait and exit.
int token = daemon_ep.TokenRead();
if (token) { // Success
exit(EXIT_SUCCESS);
} else { // fRet = false or token read error (premature exit).
tfm::format(std::cerr, "Error during initialization - check debug.log for details\n");
exit(EXIT_FAILURE);
}
}
}
#else
return InitError(Untranslated("-daemon is not supported on this operating system"));
#endif // HAVE_DECL_FORK
}
// Lock data directory after daemonization
if (!AppInitLockDataDirectory())
{
// If locking the data directory failed, exit immediately
return false;
}
fRet = AppInitInterfaces(node) && AppInitMain(node);
}
catch (const std::exception& e) {
PrintExceptionContinue(&e, "AppInit()");
} catch (...) {
PrintExceptionContinue(nullptr, "AppInit()");
}
#if HAVE_DECL_FORK
if (daemon_ep.IsOpen()) {
// Signal initialization status to parent, then close pipe.
daemon_ep.TokenWrite(fRet);
daemon_ep.Close();
}
#endif
SetSyscallSandboxPolicy(SyscallSandboxPolicy::SHUTOFF);
return fRet;
}
One thing that can be noted in this function is that there are two different “ways” the function can work; that is because of the declaration of the preprocessor directive #if HAVE_DECL_FORK
, which allows to include or to exclude some parts of the code from compilation. In particular, in case it is included the code launches a custom daemon, which basically is a program that runs as a background process, without being under control of an interactive user4.
AppInit()
: Step-by-step
At the beginning of the Function, the return value fRet
is declared and initialized to false; moreover, the command line arguments are recovered from the NodeContext
object. It can be noted that, when recovering the command line arguments, the Assert()
function is called; this function is defined in check.h5, and causes the overall program to abort in case the final result is FALSE (in this case, if the command line arguments are not defined).
bool fRet = false;
ArgsManager& args = *Assert(node.args);
Then, in case the HAVE_DECL_FORK
is defined, a “token pipe” is created. A “token” is a data frame that is transmitted across different points of a network, while the “pipe” is the technique that allows to send this token from a process to another in the program. On the other hand, if HAVE_DECL_FORK
is FALSE, this part is skipped in compilation (the same will be true for all the other parts of the code which reside inside an #if/#endif
block).
#if HAVE_DECL_FORK
TokenPipeEnd daemon_ep;
#endif
Next, a std::any
variable is defined, taking as initial value the address in memory of the NodeContext
. std::any
is a container that can basically take the form of any type, as the name suggests; in this case, it is taking the information of the NodeContext
structure.
std::any context{&node};
The following parts of the code can be found inside a try/catch
block; this kind of instruction allows to try
a piece of code and possibly catch
any problem that may arise.
First, the “-server”
command line argument is set to TRUE, in case it does not have a default value. Then, the logging infrastructure is initialized, together with the parameter interaction, which are changed according to the rules specified in the command line arguments.
try
{
// -server defaults to true for bitcoind but not for the GUI so do this here
args.SoftSetBoolArg("-server", true);
// Set this early so that parameter interactions go to console
InitLogging(args);
InitParameterInteraction(args);
Then, three different IF clauses check if all the various initializations are done correctly:
AppInitBasicSetup()
: Initializes the basic setupAppInitParameterInteraction()
: Initializes the parameters interactionAppInitSanityChecks()
: Initializes sanity checks, basic tests to check the correctness of what has been done.
Before this last initialization the kernel
member of the NodeContext
class is initialized.
In case one of the three IF clauses fails, the function returns FALSE, causing the application to stop immediately.
if (!AppInitBasicSetup(args, node.exit_status)) {
// InitError will have been called with detailed error, which ends up on console
return false;
}
if (!AppInitParameterInteraction(args)) {
// InitError will have been called with detailed error, which ends up on console
return false;
}
node.kernel = std::make_unique<kernel::Context>();
if (!AppInitSanityChecks(*node.kernel))
{
// InitError will have been called with detailed error, which ends up on console
return false;
}
Enough for today, this function is very long and I do not want to run too much!
I hope that everything I explained is clear. Otherwise, please leave a comment on this post so that I can try to help you out!
See you soon!
Tuma
Fiat Mining is a drain, but sometimes necessary. Keep up the great work! Stay motivated. We appreciate your hard work.