bitcoind.cpp – Commented Bitcoin source code

bitcoind.cpp – Commented Bitcoin source code
on September 19, 2019

As mentioned in the Bitcoin-Qt commented source code post, there are several approaches one can take to step into the Bitcoin source in approximately the same order as it runs. For instance, tests are a great source of tiny example programs. Then there’s the ubiquitous Bitcoin-Qt client and, of course, bitcoind.

All are good options to begin studying the Bitcoin source.

bitcoind

Today we’ll take a look at bitcoind – the Bitcoin Core daemon.

This program implements a full, verifying, Bitcoin node. In fact, the Bitcoin-Qt graphical user interface could have been implemented as a front-end to bitcoind like Ethereum does (runs geth in the background and some graphical wallet as a front end).

Since bitcoind doesn’t have any GUI-related functionality, the code is much more to the point than the Qt version.

WaitForShutdown

The first function we encounter is a little utility function that performs a busy wait for shutdown.

static void WaitForShutdown()
{
    while (!ShutdownRequested())
    {
        MilliSleep(200);
    }
    Interrupt();
}

It’s a very trivial function, so not much we can comment on. Once called, the while loop executes until shutdown is requested. The loop body consists of a single call to MilliSleepwith 200 ms sleep time.

Once the loop breaks, Interrupt is called to perform system cleanup and exit.

AppInit

Here’s where the heavy lifting happens. AppInit takes the same parameters as C/C++’s main() function does. As we’ll see later main() is simply a stub that calls AppInit.

As in other commented source code articles, my comments begin with a > while the original source comments remain unedited and begin with // or /* */

static bool AppInit(int argc, char* argv[])
{


> InitInterfaces is a small struct that groups together a blockchain and a collection of wallets.
> The chain is represented by interfaces::Chain chain and the wallets by a std::vector of
> interfaces::ChainClient objects. Chain client is a more general concept than a wallet, since
> there are other applications for the blockchain which would be called clients as well.
> InitInterfaces is defined in init.h

    InitInterfaces interfaces;


> interfaces::MakeChain() is defined in interfaces/chain.cpp
> It's just a stub that returns a pointer to a new ChainImpl instance.
> ChainImpl allows clients to access the blockchain state including
> transaction submission, fee estimation, get notifications and so on.

    interfaces.chain = interfaces::MakeChain();

    bool fRet = false;


> Renames the current thread to "init"

    util::ThreadRename("init");

    //
    // Parameters
    //
    // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main()


> SetupServerArgs() parses command line arguments and fills the global gArgs 
> instance of ArgsManager.
> Data directory, which chain to use (test or main), maximum mempool size and
> tens of other options are set in this subroutine.

    SetupServerArgs();
    std::string error;
    if (!gArgs.ParseParameters(argc, argv, error)) {
        return InitError(strprintf("Error parsing command line arguments: %s\n", error));
    }


> If requested, displays help text.

    // Process help and version before taking care about datadir
    if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
        std::string strUsage = PACKAGE_NAME " Daemon version " + FormatFullVersion() + "\n";

        if (gArgs.IsArgSet("-version"))
        {
            strUsage += FormatParagraph(LicenseInfo()) + "\n";
        }
        else
        {
            strUsage += "\nUsage:  bitcoind [options]                     Start " PACKAGE_NAME " Daemon\n";
            strUsage += "\n" + gArgs.GetHelpMessage();
        }

        tfm::format(std::cout, "%s", strUsage.c_str());
        return true;
    }




    try
    {


> Parses the -datadir option, if provided, and checks if the directory exists.

        if (!CheckDataDirOption()) {
            return InitError(strprintf("Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "")));
        }


> Reads and parses config files.

        if (!gArgs.ReadConfigFiles(error, true)) {
            return InitError(strprintf("Error reading configuration file: %s\n", error));
        }


> Original comment is self-explanatory in the next few sections:

        // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
        try {
            SelectParams(gArgs.GetChainName());
        } catch (const std::exception& e) {
            return InitError(strprintf("%s\n", e.what()));
        }

        // Error out when loose non-argument tokens are encountered on command line
        for (int i = 1; i < argc; i++) {
            if (!IsSwitchChar(argv[i][0])) {
                return InitError(strprintf("Command line contains unexpected token '%s', see bitcoind -h for a list of options.\n", argv[i]));
            }
        }

        // -server defaults to true for bitcoind but not for the GUI so do this here
        gArgs.SoftSetBoolArg("-server", true);
        // Set this early so that parameter interactions go to console


> Uses BCLog::Logger which defined in logging.cpp
> LogInstance() is used to get a pointer to g_logger instance of BCLog::Logger
> g_logger is defined to be static, but it's defined within LogInstance()
> Therefore it's not truly a global, although being static gives it the same lifetime
> as a global would have.

        InitLogging();


> InitParameterInteraction() is defined in init.cpp. It works out the 
> effects of parameters on other parameters. For example, if -bind is
> provided then -listen becomes automatically true.

        InitParameterInteraction();


> AppInitBasicSetup() is defined in init.cpp. It sets up OS signal handlers
> for SIGTERM, SIGHUP AND SIGINT among others

        if (!AppInitBasicSetup())
        {
            // InitError will have been called with detailed error, which ends up on console
            return false;
        }


> AppInitParameterInteraction() is also defined in init.cpp and it sets application-specific
> parameter interactions. While InitParameterInteraction() changed other parameters as a result
> of a parameter, AppInitParameterInteraction() changes application variables and configuration.
> Basically, AppInitParameterInteraction() commits all computed parameters to the application.

        if (!AppInitParameterInteraction())
        {
            // InitError will have been called with detailed error, which ends up on console
            return false;
        }


> AppInitSanityChecks() is defined in init.cpp. This subroutine checks whether the data directory
> can be locked. If it can't, sanity check fails. It also performs tests on the local elliptic curve
> functions, random number tests and C language library tests to assure the cryptographic subsystem
> is working perfectly.

        if (!AppInitSanityChecks())
        {
            // InitError will have been called with detailed error, which ends up on console
            return false;
        }


> If -daemon option is provided, then daemonize the process.

        if (gArgs.GetBoolArg("-daemon", false))
        {
#if HAVE_DECL_DAEMON
#if defined(MAC_OSX)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
            tfm::format(std::cout, PACKAGE_NAME " daemon starting\n");

            // Daemonize
            if (daemon(1, 0)) { // don't chdir (1), do close FDs (0)
                return InitError(strprintf("daemon() failed: %s\n", strerror(errno)));
            }
#if defined(MAC_OSX)
#pragma GCC diagnostic pop
#endif
#else
            return InitError("-daemon is not supported on this operating system\n");
#endif // HAVE_DECL_DAEMON
        }


> Data directory is locked throughout the process lifetime

        // Lock data directory after daemonization
        if (!AppInitLockDataDirectory())
        {
            // If locking the data directory failed, exit immediately
            return false;
        }


> Here's where everything else happens. AppInitMain brings the whole system up.
> AppInitMain is thoroughly discussed on the init.cpp article. See link below.

        fRet = AppInitMain(interfaces);
    }
    catch (const std::exception& e) {
        PrintExceptionContinue(&e, "AppInit()");
    } catch (...) {
        PrintExceptionContinue(nullptr, "AppInit()");
    }


> If initialization was not OK, then shut down immediately.
> Otherwise enter the busy wait loop we described earlier on WaitForShutdown()

    if (!fRet)
    {
        Interrupt();
    } else {
        WaitForShutdown();
    }
    Shutdown(interfaces);

    return fRet;
}

As you can seeAppInitis somewhat analog to GuiMain() in the Qt version. Both calls take the argc, argv C/C++ parameters from main() and do some user interface specific startup before entering the same main initialization subroutine at AppInitMain().

Here we’ve reached the common denominator between the Qt and console programs: both reach AppInitMain() from which the program returns only upon shutdown or fatal error.

Just like GuiMain(), when AppInit() returns the program exits immediately since only main() is left in our call stack.