crypto.bi

init.cpp Bitcoin Core startup source code

This is our first exploration of Bitcoin-specific code in this series of articles.

Until now we’ve looked at how the Qt system was set up to run the GUI and Bitcoin services in separate threads and how those threads interconnected using Qt signals and slots.

The BitcoinCore class in qt/bitcoin.cpp is the glue that ties together this code and the Qt GUI.

We get here from a call to appInitMain() in BitcoinCorewhich is aptly named because it calls AppInitMain(InitInterfaces& interfaces) as our entry point into init.cpp.

Following our strategy to step into the execution via the top of the call stack, we now take a look at AppInitMain.

Note that init.cpp functions are mostly defined in the global namespace. There’s no class wrapping AppInitMain

AppInitMain(InitInterfaces& interfaces)

This subroutine is over 600 lines long. So we’ve got a lot of ground to cover here. Let’s get started.

The InitInterfaces& interfaces parameter comes from our Node instance that was created in GuiMain via MakeNode()

MakeNode is a short stub that simply calls the standard C++ macro make_unique<NodeImpl>. Notice that interfaces is passed by reference. NodeImpl does not initialize the interfaces but AppInitMain does.

Now that we know where the parameter comes from, let’s jump into this deep sea of code. This is actually one

bool AppInitMain(InitInterfaces& interfaces)
{


> chainparams is a local reference to the global globalChainParams set on chainparams.cpp
> chainparams is an instance of CChainParams, which is a class that wraps together several
> Bitcoin parameters so that you can switch between test, regression or main blockchains easily
> All the blockchain's critical parameters are kept in chainparams for the rest of this subroutine

    const CChainParams& chainparams = Params();
    // ********************************************************* Step 4a: application initialization


> Next, a PID file is created. This file containes the Bitcoin process ID.
> Logging is set up and a few initialization messages are saved, like which 
> conf file was loaded. 
> Here I snipped about 39 lines of logging setup and code.

    if (!CreatePidFile()) { // ..... snipped 39 lines

// [......]
            gArgs.GetArg("-datadir", ""), fs::current_path().string());
    }


> InitSignatureCache() allocates memory space for at least 2 valid signatures
> The purpose for this cache is to avoid repeating work when checking TX signatures
> If a TX was verified when in mempool, avoid redoing the ECDSA work when the same TX
> gets committed to the blockchain. This is implemented in script/sigcache.cpp

    InitSignatureCache();


> InitScriptExecutionCache has similar purpose to InitSignatureCache
> Here it allocates space for 2 script hashes. Later, in validation.cpp,
> the script hash is checked against this cache. If the script hash is contained there
> it automatically returns true - meaning script success. This avoids running the same
> script more than once (mempool, blockchain).

    InitScriptExecutionCache();



> nScriptCheckThreads is a global int defined in validation.cpp
> It creates nScriptCheckThreads - 1 new threads and add them to threadGroup. 
> The thread creation function is a lambda that captures the loop counter i 
> in the closure and creates a new thread named scriptch.%i where %i is the 
> value of the loop counter.
> There are nScriptCheckThreads - 1 script check threads because one more thread 
> will be created for the serviceLoop, which is a simple scheduler that
> repeatedly runs submitted tasks.
> The script check queue (CCheckQueue) then enters the thread loop waiting
> to process scripts as necessary.


    LogPrintf("Using %u threads for script verificationn", nScriptCheckThreads);
    if (nScriptCheckThreads) {
        for (int i=0; i<nScriptCheckThreads-1; i++)
            threadGroup.create_thread([i]() { return ThreadScriptCheck(i); });
    }

    // Start the lightweight task scheduler thread
    CScheduler::Function serviceLoop = std::bind(&CScheduler::serviceQueue, &scheduler);
    threadGroup.create_thread(std::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop));



> GetMainSignals() is defined in validationinterface.cpp and returns a static global CMainSignals 
> instance called g_signals. CMainSignals handles the main chain validation signals in Bitcoin Core.
> For instance : NewPoWValidBlock, BlockChecked, ChainStateFlushed, BlockDisconnected, TransactionAddedToMempool 
> among others.
> Added comments copied verbatim from validationinterface.h :

    /** Register a CScheduler to give callbacks which should run in the background (may only be called once) */
    GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);

    /** Register with mempool to call TransactionRemovedFromMempool callbacks */
    GetMainSignals().RegisterWithMempoolSignals(mempool);



> The following few sections are extremely well commented. Not adding any additional notes.


    // Create client interfaces for wallets that are supposed to be loaded
    // according to -wallet and -disablewallet options. This only constructs
    // the interfaces, it doesn't load wallet data. Wallets actually get loaded
    // when load() and start() interface methods are called below.
    g_wallet_init_interface.Construct(interfaces);

    /* Register RPC commands regardless of -server setting so they will be
     * available in the GUI RPC console even if external calls are disabled.
     */
    RegisterAllCoreRPCCommands(tableRPC);
    for (const auto& client : interfaces.chain_clients) {
        client->registerRpcs();
    }
    g_rpc_interfaces = &interfaces;
#if ENABLE_ZMQ
    RegisterZMQRPCCommands(tableRPC);
#endif

    /* Start the RPC server already.  It will be started in "warmup" mode
     * and not really process calls already (but it will signify connections
     * that the server is there and will be ready later).  Warmup mode will
     * be disabled when initialisation is finished.
     */
    if (gArgs.GetBoolArg("-server", false))
    {
        uiInterface.InitMessage_connect(SetRPCWarmupStatus);
        if (!AppInitServers())
            return InitError(_("Unable to start HTTP server. See debug log for details.").translated);
    }

> Wallets are called chain clients in the Bitcoin Core source code.
> This is because there could be other kinds of clients that make use of the chain,
> not just wallets. For the time being, though, only wallets get verified here.

    // ********************************************************* Step 5: verify wallet database integrity
    for (const auto& client : interfaces.chain_clients) {
        if (!client->verify()) {
            return false;
        }
    }

    // ********************************************************* Step 6: network initialization
    // Note that we absolutely cannot open any actual connections
    // until the very end ("start node") as the UTXO/block state
    // is not yet setup and may end up being set up twice if we
    // need to reindex later.


> Global objects g_banman (ban manager) and g_connman (connection manager) 
> get instantiated here. At this point they must not have been instantiated
> thus the NULL assertions.

    assert(!g_banman);
    g_banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", &uiInterface, gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME));
    assert(!g_connman);
    g_connman = std::unique_ptr<CConnman>(new CConnman(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max())));


> PeerLogicValidation is implemented in net_processing.h
> The C++ standard library smart pointer method reset() sets the
> newly instantiated object as the new managed unique_ptr in peerLogic.
> peerLogic is a pointer to PeerLogicValidation where most network peer 
> management is performed, including marking bad peers, adding peers to known
> list, getting statistics, managing transactions, announce new blocks to peers,
> and so forth.

    peerLogic.reset(new PeerLogicValidation(g_connman.get(), g_banman.get(), scheduler, gArgs.GetBoolArg("-enablebip61", DEFAULT_ENABLE_BIP61)));
    RegisterValidationInterface(peerLogic.get());


> See https://github.com/bitcoin/bips/blob/master/bip-0014.mediawiki

    // sanitize comments per BIP-0014, format user agent and check total size
    std::vector<std::string> uacomments;
    for (const std::string& cmt : gArgs.GetArgs("-uacomment")) {
        if (cmt != SanitizeString(cmt, SAFE_CHARS_UA_COMMENT))
            return InitError(strprintf(_("User Agent comment (%s) contains unsafe characters.").translated, cmt));
        uacomments.push_back(cmt);
    }
    strSubVersion = FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments);
    if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) {
        return InitError(strprintf(_("Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments.").translated,
            strSubVersion.size(), MAX_SUBVERSION_LENGTH));
    }


> From the docs: -onlynet : Make outbout connections onion, ipv4 or ipv6.
> What this code does is mark hosts on certain nets UNROUTABLE.
> Note this only changes outbound behavior, not inbound. To restrict
> inbound traffic, use a firewall or other OS mechanism.

    if (gArgs.IsArgSet("-onlynet")) {
        std::set<enum Network> nets;
        for (const std::string& snet : gArgs.GetArgs("-onlynet")) {
            enum Network net = ParseNetwork(snet);
            if (net == NET_UNROUTABLE)
                return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'").translated, snet));
            nets.insert(net);
        }
        for (int n = 0; n < NET_MAX; n++) {
            enum Network net = (enum Network)n;
            if (!nets.count(net))
                SetReachable(net, false);
        }
    }


> Check if DNS lookups are allowed. DNS is seen as a security issue by some Bitcoiners.


    // Check for host lookup allowed before parsing any network related parameters
    fNameLookup = gArgs.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);

> Proxy, onion and command line options setup. 
> Not much to comment on here. Intuitive all the way.


    bool proxyRandomize = gArgs.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE);
    // -proxy sets a proxy for all outgoing network traffic
    // -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default
    std::string proxyArg = gArgs.GetArg("-proxy", "");
    SetReachable(NET_ONION, false);
    if (proxyArg != "" && proxyArg != "0") {
        CService proxyAddr;
        if (!Lookup(proxyArg.c_str(), proxyAddr, 9050, fNameLookup)) {
            return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'").translated, proxyArg));
        }

        proxyType addrProxy = proxyType(proxyAddr, proxyRandomize);
        if (!addrProxy.IsValid())
            return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'").translated, proxyArg));

        SetProxy(NET_IPV4, addrProxy);
        SetProxy(NET_IPV6, addrProxy);
        SetProxy(NET_ONION, addrProxy);
        SetNameProxy(addrProxy);
        SetReachable(NET_ONION, true); // by default, -proxy sets onion as reachable, unless -noonion later
    }

    // -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses
    // -noonion (or -onion=0) disables connecting to .onion entirely
    // An empty string is used to not override the onion proxy (in which case it defaults to -proxy set above, or none)
    std::string onionArg = gArgs.GetArg("-onion", "");
    if (onionArg != "") {
        if (onionArg == "0") { // Handle -noonion/-onion=0
            SetReachable(NET_ONION, false);
        } else {
            CService onionProxy;
            if (!Lookup(onionArg.c_str(), onionProxy, 9050, fNameLookup)) {
                return InitError(strprintf(_("Invalid -onion address or hostname: '%s'").translated, onionArg));
            }
            proxyType addrOnion = proxyType(onionProxy, proxyRandomize);
            if (!addrOnion.IsValid())
                return InitError(strprintf(_("Invalid -onion address or hostname: '%s'").translated, onionArg));
            SetProxy(NET_ONION, addrOnion);
            SetReachable(NET_ONION, true);
        }
    }

    // see Step 2: parameter interactions for more information about these
    fListen = gArgs.GetBoolArg("-listen", DEFAULT_LISTEN);
    fDiscover = gArgs.GetBoolArg("-discover", true);
    g_relay_txes = !gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY);

    for (const std::string& strAddr : gArgs.GetArgs("-externalip")) {
        CService addrLocal;
        if (Lookup(strAddr.c_str(), addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid())
            AddLocal(addrLocal, LOCAL_MANUAL);
        else
            return InitError(ResolveErrMsg("externalip", strAddr));
    }

#if ENABLE_ZMQ
    g_zmq_notification_interface = CZMQNotificationInterface::Create();

    if (g_zmq_notification_interface) {
        RegisterValidationInterface(g_zmq_notification_interface);
    }
#endif


> An outbound cycle has a timeframe of at least one block
> Here the timeframe is set to one day (MAX_UPLOAD_TIMEFRAME = 60 * 60 * 24 seconds)

    uint64_t nMaxOutboundLimit = 0; //unlimited unless -maxuploadtarget is set
    uint64_t nMaxOutboundTimeframe = MAX_UPLOAD_TIMEFRAME;

    if (gArgs.IsArgSet("-maxuploadtarget")) {
        nMaxOutboundLimit = gArgs.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET)*1024*1024;
    }

    // ********************************************************* Step 7: load block chain

    fReindex = gArgs.GetBoolArg("-reindex", false);
    bool fReindexChainState = gArgs.GetBoolArg("-reindex-chainstate", false);


> Set up cache sizes. First work out maximum of the minimum, then the
> minimum of the maximum between presets and command line options,
> setting the total amount of cache.

    // cache size calculations
    int64_t nTotalCache = (gArgs.GetArg("-dbcache", nDefaultDbCache) << 20);
    nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache
    nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcache


> Determine cache size for the block tree, then subtract from the total cache.


    int64_t nBlockTreeDBCache = std::min(nTotalCache / 8, nMaxBlockDBCache << 20);
    nTotalCache -= nBlockTreeDBCache;
    int64_t nTxIndexCache = std::min(nTotalCache / 8, gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxTxIndexCache << 20 : 0);
    nTotalCache -= nTxIndexCache;
    int64_t filter_index_cache = 0;


> Work out the filter index cache, then subtract from the total cache.

    if (!g_enabled_filter_types.empty()) {
        size_t n_indexes = g_enabled_filter_types.size();
        int64_t max_cache = std::min(nTotalCache / 8, max_filter_index_cache << 20);
        filter_index_cache = max_cache / n_indexes;
        nTotalCache -= filter_index_cache * n_indexes;
    }
    int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache
    nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cache
    nTotalCache -= nCoinDBCache;
    nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache
    int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
    LogPrintf("Cache configuration:n");
    LogPrintf("* Using %.1f MiB for block index databasen", nBlockTreeDBCache * (1.0 / 1024 / 1024));
    if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
        LogPrintf("* Using %.1f MiB for transaction index databasen", nTxIndexCache * (1.0 / 1024 / 1024));
    }
    for (BlockFilterType filter_type : g_enabled_filter_types) {
        LogPrintf("* Using %.1f MiB for %s block filter index databasen",
                  filter_index_cache * (1.0 / 1024 / 1024), BlockFilterTypeName(filter_type));
    }
    LogPrintf("* Using %.1f MiB for chain state databasen", nCoinDBCache * (1.0 / 1024 / 1024));
    LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)n", nCoinCacheUsage * (1.0 / 1024 / 1024), nMempoolSizeMax * (1.0 / 1024 / 1024));



> If flagLoaded is false and nobody has requested system exit, enter the
> main initialization loop. This is where the blockchain is downloaded 
> where users see "Loading block index..."
> Note the if (ShutdownRequested()) break; statements all over the place.
> The system is asynchronous, so the user can request to exit Bitcoin core
> while the long lasting blockchain startup operation finishes.
> This is probably one of the most important code blocks in Bitcoin Core so
> let's go over the most important parts.


    bool fLoaded = false;
    while (!fLoaded && !ShutdownRequested()) {
        bool fReset = fReindex;
        std::string strLoadError;

        uiInterface.InitMessage(_("Loading block index...").translated);


> Here we begin a single iteration do {} while(false) block.
> The reason a do {} while() block is used is the program can
> break (as in C++ break) out of this kind of structure. Note how break is used instead
> of return. It's a structured jump in case of error.
> This block is where all the heavy lifting is done when initializing Bitcoin Core.

        do {

> Store the block indexing start time.
> This will be used at the last line of the do/while block to calculate
> time delta since the indexing began.
            const int64_t load_block_index_start_time = GetTimeMillis();


> is_coinsview_empty is a bool set by the following statement:
> fReset || fReindexChainState || ::ChainstateActive().CoinsTip().GetBestBlock().IsNull();
> If this is a chain index reset, a reindex or the latest block is null, then call LoadChainTip
> to set the chain tip (latest block) to the longest chain (called best chain) (note: 89 lines down from here).

            bool is_coinsview_empty;
            try {


> LOCK the chainstate mutex, a global defined in validation.cpp
> All mempool and blockchain altering operations happen in critical blocks.

                LOCK(cs_main);

                // This statement makes ::ChainstateActive() usable.
                g_chainstate = MakeUnique<CChainState>();


> Sets chain tip to null, unload block manager, set best header and invalid block to null
> clear mempool, set fHavePruned to false, set nBlockSequenceId to 1 and clear block index candiates.

                UnloadBlockIndex();

                // new CBlockTreeDB tries to delete the existing file, which
                // fails if it's still open from the previous loop. Close it first:
                pblocktree.reset();
                pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, false, fReset));

                if (fReset) {


> Set leveldb chain index DB_REINDEX_FLAG to true


                    pblocktree->WriteReindexing(true);
                    //If we're reindexing in prune mode, wipe away unusable block files and all undo data files
                    if (fPruneMode)
                        CleanupBlockRevFiles();
                }


> We're inside a do/while block, so it's possible to snap out using break.
> Dijkstra would not approve. 
> Or would he?

                if (ShutdownRequested()) break;


> The following original comments are pretty clear.

                // LoadBlockIndex will load fHavePruned if we've ever removed a
                // block file from disk.
                // Note that it also sets fReindex based on the disk flag!
                // From here on out fReindex and fReset mean something different!
                if (!LoadBlockIndex(chainparams)) {
                    if (ShutdownRequested()) break;
                    strLoadError = _("Error loading block database").translated;
                    break;
                }

                // If the loaded chain has a wrong genesis, bail out immediately
                // (we're likely using a testnet datadir, or the other way around).
                if (!::BlockIndex().empty() &&
                        !LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) {
                    return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?").translated);
                }

                // Check for changed -prune state.  What we are concerned about is a user who has pruned blocks
                // in the past, but is now trying to run unpruned.
                if (fHavePruned && !fPruneMode) {
                    strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode.  This will redownload the entire blockchain").translated;
                    break;
                }

                // At this point blocktree args are consistent with what's on disk.
                // If we're not mid-reindex (based on disk + args), add a genesis block on disk
                // (otherwise we use the one already on disk).
                // This is called again in ThreadImport after the reindex completes.
                if (!fReindex && !LoadGenesisBlock(chainparams)) {
                    strLoadError = _("Error initializing block database").translated;
                    break;
                }

                // At this point we're either in reindex or we've loaded a useful
                // block tree into BlockIndex()!


> Initialize m_coins_views in the global g_chainstate

                ::ChainstateActive().InitCoinsDB(
                    /* cache_size_bytes */ nCoinDBCache,
                    /* in_memory */ false,
                    /* should_wipe */ fReset || fReindexChainState);


> Set the read error callback to a lambda that calls back into the UI (GUI or console)
> and shows a message. 

                ::ChainstateActive().CoinsErrorCatcher().AddReadErrCallback([]() {
                    uiInterface.ThreadSafeMessageBox(
                        _("Error reading from database, shutting down.").translated,
                        "", CClientUIInterface::MSG_ERROR);
                });


> Call into CCoinsViewDB::Upgrade() on txdb.cpp
> Upgrade() iterates into a leveldb slice while updating the UTXO database. 

                // If necessary, upgrade from older database format.
                // This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
                if (!::ChainstateActive().CoinsDB().Upgrade()) {
                    strLoadError = _("Error upgrading chainstate database").translated;
                    break;
                }


> ReplayBlocks calls into CChainState::ReplayBlocks on validation.cpp
> It replays each block, updating its effects on the UTXO cache using 
> CChainState::RollforwardBlock also in validation.cpp 

                // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
                if (!ReplayBlocks(chainparams, &::ChainstateActive().CoinsDB())) {
                    strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.").translated;
                    break;
                }


> Calls m_coins_views->InitCache() on the chainstate's CoinsViews instance.
> This keeps the UTXO set cache updated.

                // The on-disk coinsdb is now in a good state, create the cache
                ::ChainstateActive().InitCoinsCache();
                assert(::ChainstateActive().CanFlushToDisk());


> As discussed early on when we entered the do/while block.
> Search for is_coinsview_empty earlier in this article.

                is_coinsview_empty = fReset || fReindexChainState ||
                    ::ChainstateActive().CoinsTip().GetBestBlock().IsNull();
                if (!is_coinsview_empty) {
                    // LoadChainTip sets ::ChainActive() based on CoinsTip()'s best block
                    if (!LoadChainTip(chainparams)) {
                        strLoadError = _("Error initializing block database").translated;
                        break;
                    }
                    assert(::ChainActive().Tip() != nullptr);
                }
            } catch (const std::exception& e) {
                LogPrintf("%sn", e.what());
                strLoadError = _("Error opening block database").translated;
                break;
            }


> If we're not in a reset, then we must rewind the block index.
> A lot of maintenance is performed here. 
> First it erases all segwit blocks that do not have a witness in the mainchain.
> Then it discovers the last block # that must be reorganized by looking for
> the earliest segwit activated block and scanning from there.
> To check whether segwit was active, it calls IsWitnessEnabled in a loop.


            if (!fReset) {
                // Note that RewindBlockIndex MUST run even if we're about to -reindex-chainstate.
                // It both disconnects blocks based on ::ChainActive(), and drops block data in
                // BlockIndex() based on lack of available witness data.
                uiInterface.InitMessage(_("Rewinding blocks...").translated);
                if (!RewindBlockIndex(chainparams)) {
                    strLoadError = _("Unable to rewind the database to a pre-fork state. You will need to redownload the blockchain").translated;
                    break;
                }
            }


> This is the last stage the user sees on the GUI, when the splash screen says 
> "verifying blocks".

            try {
                LOCK(cs_main);
                if (!is_coinsview_empty) {
                    uiInterface.InitMessage(_("Verifying blocks...").translated);
                    if (fHavePruned && gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) {
                        LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocksn",
                            MIN_BLOCKS_TO_KEEP);
                    }


> If blocks appear to be from the future, the computer likely has a skewed clock.
> Bitcoin will not continue if this is the case as the blockchain is a timestamped
> ledger which relies heavily on time of day correctness.

                    CBlockIndex* tip = ::ChainActive().Tip();
                    RPCNotifyBlockChange(true, tip);
                    if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
                        strLoadError = _("The block database contains a block which appears to be from the future. "
                                "This may be due to your computer's date and time being set incorrectly. "
                                "Only rebuild the block database if you are sure that your computer's date and time are correct").translated;
                        break;
                    }


> VerifyDB is defined in validation.cpp and checks for bad blocks, bad undo data,
> various inconsistencies and proper connection between blocks. This is the heavy
> lifting in chain validation.

                    if (!CVerifyDB().VerifyDB(chainparams, &::ChainstateActive().CoinsDB(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
                                  gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) {
                        strLoadError = _("Corrupted block database detected").translated;
                        break;
                    }
                }
            } catch (const std::exception& e) {
                LogPrintf("%sn", e.what());
                strLoadError = _("Error opening block database").translated;
                break;
            }

            fLoaded = true;
            LogPrintf(" block index %15dmsn", GetTimeMillis() - load_block_index_start_time);
        } while(false);


> If the previous section fails, fLoaded will be false and we'll enter this branch.
> It asks the user if they'd like to attempt a reindex. If refused, the system shuts down.

        if (!fLoaded && !ShutdownRequested()) {
            // first suggest a reindex
            if (!fReset) {
                bool fRet = uiInterface.ThreadSafeQuestion(
                    strLoadError + ".nn" + _("Do you want to rebuild the block database now?").translated,
                    strLoadError + ".nPlease restart with -reindex or -reindex-chainstate to recover.",
                    "", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT);
                if (fRet) {
                    fReindex = true;
                    AbortShutdown();
                } else {
                    LogPrintf("Aborted block database rebuild. Exiting.n");
                    return false;
                }
            } else {
                return InitError(strLoadError);
            }
        }
    }

    // As LoadBlockIndex can take several minutes, it's possible the user
    // requested to kill the GUI during the last operation. If so, exit.
    // As the program has not fully started yet, Shutdown() is possibly overkill.
    if (ShutdownRequested()) {
        LogPrintf("Shutdown requested. Exiting.n");
        return false;
    }


> The fees estimate file caches a local estimation of payment fees.
> Here, as well as in other file handling routines, the boost fs library is used.

    fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME;
    CAutoFile est_filein(fsbridge::fopen(est_path, "rb"), SER_DISK, CLIENT_VERSION);
    // Allowed to fail as this file IS missing on first startup.
    if (!est_filein.IsNull())
        ::feeEstimator.Read(est_filein);
    fFeeEstimatesInitialized = true;


> Start the global transaction indexer. TxIndex calls Start() on its superclass BaseIndex
> Defined in base.cpp
> BaseIndex starts a new thread (m_thread_sync) and calls the TraceThread function on
> system.h. TraceThread is a wrapper which calls its last argument (a function).
> The thread function for Start() is BaseIndex::ThreadSync which runs a loop
> scanning to the last index and running BaseIndex::Commit() and logging the results.

    // ********************************************************* Step 8: start indexers
    if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
        g_txindex = MakeUnique<TxIndex>(nTxIndexCache, false, fReindex);
        g_txindex->Start();
    }


> Initialize global variable g_filter_indexes and start the indexer.

    for (const auto& filter_type : g_enabled_filter_types) {
        InitBlockFilterIndex(filter_type, filter_index_cache, false, fReindex);
        GetBlockFilterIndex(filter_type)->Start();
    }


> Load wallets (called chain clients).
> This call will be bound to LoadWallets in wallet/load.cpp which instantiates
> CWallet's from wallet.dat files.
> It then calls AddWallet(newWallet) and adds newWallet to global vpwallets 
> which is a std::vector of CWallet shared pointers

    // ********************************************************* Step 9: load wallet
    for (const auto& client : interfaces.chain_clients) {
        if (!client->load()) {
            return false;
        }
    }

    // ********************************************************* Step 10: data directory maintenance


> Unless we're reindexing, calls CChainState::FlushStateToDisk 
> in validation.cpp which prunes the blockstore and flushes 
> the new state to disk.

    // if pruning, unset the service bit and perform the initial blockstore prune
    // after any wallet rescanning has taken place.
    if (fPruneMode) {
        LogPrintf("Unsetting NODE_NETWORK on prune moden");
        nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK);
        if (!fReindex) {
            uiInterface.InitMessage(_("Pruning blockstore...").translated);
            ::ChainstateActive().PruneAndFlush();
        }
    }

    if (chainparams.GetConsensus().SegwitHeight != std::numeric_limits<int>::max()) {
        // Advertise witness capabilities.
        // The option to not set NODE_WITNESS is only used in the tests and should be removed.
        nLocalServices = ServiceFlags(nLocalServices | NODE_WITNESS);
    }

    // ********************************************************* Step 11: import blocks


> Checks if given data directory is in a partition with enough space for the block data.

    if (!CheckDiskSpace(GetDataDir())) {
        InitError(strprintf(_("Error: Disk space is low for %s").translated, GetDataDir()));
        return false;
    }
    if (!CheckDiskSpace(GetBlocksDir())) {
        InitError(strprintf(_("Error: Disk space is low for %s").translated, GetBlocksDir()));
        return false;
    }

    // Either install a handler to notify us when genesis activates, or set fHaveGenesis directly.
    // No locking, as this happens before any background thread is started.
    boost::signals2::connection block_notify_genesis_wait_connection;
    if (::ChainActive().Tip() == nullptr) {
        block_notify_genesis_wait_connection = uiInterface.NotifyBlockTip_connect(BlockNotifyGenesisWait);
    } else {
        fHaveGenesis = true;
    }

#if HAVE_SYSTEM
    if (gArgs.IsArgSet("-blocknotify"))
        uiInterface.NotifyBlockTip_connect(BlockNotifyCallback);
#endif


> Bootstrap block data.
> This is a long running process which runs in its own thread.

    std::vector<fs::path> vImportFiles;
    for (const std::string& strFile : gArgs.GetArgs("-loadblock")) {
        vImportFiles.push_back(strFile);
    }

    threadGroup.create_thread(std::bind(&ThreadImport, vImportFiles));

    // Wait for genesis block to be processed
    {
        WAIT_LOCK(g_genesis_wait_mutex, lock);
        // We previously could hang here if StartShutdown() is called prior to
        // ThreadImport getting started, so instead we just wait on a timer to
        // check ShutdownRequested() regularly.
        while (!fHaveGenesis && !ShutdownRequested()) {
            g_genesis_wait_cv.wait_for(lock, std::chrono::milliseconds(500));
        }
        block_notify_genesis_wait_connection.disconnect();
    }

    if (ShutdownRequested()) {
        return false;
    }

    // ********************************************************* Step 12: start node

    int chain_active_height;

    //// debug print
    {
        LOCK(cs_main);
        LogPrintf("block tree size = %un", ::BlockIndex().size());
        chain_active_height = ::ChainActive().Height();
    }
    LogPrintf("nBestHeight = %dn", chain_active_height);

    if (gArgs.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION))
        StartTorControl();


> Discover() learns local host addresses and calls AddLocal() to push
> them onto mapLocalHost in net.cpp

    Discover();


> Attempts to use UPnP to map inbound ports to local host on routing device.

    // Map ports with UPnP
    if (gArgs.GetBoolArg("-upnp", DEFAULT_UPNP)) {
        StartMapPort();
    }



> Sets up a CConnman::Options instanced called connOptions which will be 
> passed onto the global CConnman object g_connman's Start() method.
> CConnman::Start resets all statistics such as bytes sent, received and 
> cycle statistics.


    CConnman::Options connOptions;
    connOptions.nLocalServices = nLocalServices;
    connOptions.nMaxConnections = nMaxConnections;
    connOptions.nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections);
    connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS;
    connOptions.nMaxFeeler = 1;
    connOptions.nBestHeight = chain_active_height;
    connOptions.uiInterface = &uiInterface;
    connOptions.m_banman = g_banman.get();
    connOptions.m_msgproc = peerLogic.get();
    connOptions.nSendBufferMaxSize = 1000*gArgs.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER);
    connOptions.nReceiveFloodSize = 1000*gArgs.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER);
    connOptions.m_added_nodes = gArgs.GetArgs("-addnode");

    connOptions.nMaxOutboundTimeframe = nMaxOutboundTimeframe;
    connOptions.nMaxOutboundLimit = nMaxOutboundLimit;
    connOptions.m_peer_connect_timeout = peer_connect_timeout;

    for (const std::string& strBind : gArgs.GetArgs("-bind")) {
        CService addrBind;
        if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false)) {
            return InitError(ResolveErrMsg("bind", strBind));
        }
        connOptions.vBinds.push_back(addrBind);
    }
    for (const std::string& strBind : gArgs.GetArgs("-whitebind")) {
        NetWhitebindPermissions whitebind;
        std::string error;
        if (!NetWhitebindPermissions::TryParse(strBind, whitebind, error)) return InitError(error);
        connOptions.vWhiteBinds.push_back(whitebind);
    }

    for (const auto& net : gArgs.GetArgs("-whitelist")) {
        NetWhitelistPermissions subnet;
        std::string error;
        if (!NetWhitelistPermissions::TryParse(net, subnet, error)) return InitError(error);
        connOptions.vWhitelistedRange.push_back(subnet);
    }

    connOptions.vSeedNodes = gArgs.GetArgs("-seednode");

    // Initiate outbound connections unless connect=0
    connOptions.m_use_addrman_outgoing = !gArgs.IsArgSet("-connect");
    if (!connOptions.m_use_addrman_outgoing) {
        const auto connect = gArgs.GetArgs("-connect");
        if (connect.size() != 1 || connect[0] != "0") {
            connOptions.m_specified_outgoing = connect;
        }
    }



> CConnman::Start on net.cpp binds local listening ports, then adds
> one shot addresses to the list of P2P hosts to try to connect to,
> then loads addresses already on peers.dat and then creates separate
> threads threadSocketHandler, threadDNSAddressSeed, threadOpenConnections
> and threadMessageHandler to handle all network work (file: net.cpp)


    if (!g_connman->Start(scheduler, connOptions)) {
        return false;
    }

    // ********************************************************* Step 13: finished

> Sets fRPCInWarmup flag to false, setting the RPC system up.

    SetRPCWarmupFinished();
    uiInterface.InitMessage(_("Done loading").translated);


> Start all wallets. 
> Calls StartWallets on wallet/load.cpp
> For each wallet it'll call CWallet::postInitProcess() which
> then sends the wallet's pending TX's to the blockchain and
> updates the wallet with TX's on the blockchain.
> Back in StartWallets() it schedules wallet compaction to run every
> half second and resent wallet TX's every second (1000 millis).

    for (const auto& client : interfaces.chain_clients) {
        client->start(scheduler);
    }


> Updates the banned host list (banlist.dat) every 15 minutes
> (DUMP_BANS_INTERVAL = 900s x 1000ms)
> DumpBanlist() first cleans up expired bans, updates the banmap,
> dumps it to file and marks it clean so if it's not changed after
> 15 minutes, the next time it runs it'll return immediately.

    scheduler.scheduleEvery([]{
        g_banman->DumpBanlist();
    }, DUMP_BANS_INTERVAL * 1000);

    return true;
}

When AppInitMain returns the underlying Bitcoin subsystem is fully functional and awaiting for events.

Links

Bitcoin Core 0.11 (ch 3): Initialization and Startup

init.cpp at Github

How to understand Bitcoin source code

About the Author
Published by @rektbuildr - Software developer and technical writer. In charge of fixing the coffee machine at crypto.bi. Interest include Java, Avalanche AVAX, C and C++, Spark, Hadoop, Scala language, golang, RedHat OpenShift and kubernetes, container tech, DevOps and related techs. Learn More About Us