crypto.bi – ELI5 Cryptography, cryptocurrency and programming

qt/bitcoin.cpp – Commented Bitcoin source code

qt/bitcoin.cpp is where Bitcoin Core initialization leads us next.

main.cpp, the Bitcoin Qt application entry point, is basically just a stub that calls GuiMain() at qt/bitcoin.cpp

So, by sticking to our strategy, I’ll step into the call stack which now has GuiMain() at the top.

GuiMain

GuiMain gets the GUI set up, connects Qt signals/slots, starts up a Node (wrapper around bitcoind functionality) and gets the Qt event loop running via the exec() call. When exec() returns, the application begins its shutdown procedure.

Returning from GuiMain means exiting the program, since it’s the second call in our stack, right after main()

Note: My comments start with a > while original source code comments start with // . I left most original comments unchanged, unless when I could provide additional information or edit for clarity.

int GuiMain(int argc, char* argv[])
{

> Compile-time conditional checks for Windows32 API:
> If being compiled on Windows, uses the WinCmdLineArgs from util/system.h
> to return a std::pair<int, char**> via the get() method.
> While std::tie(argc, argv) may look intuitive to python programmers,
> in C++ you need a function to turn a rvalue tuple (a,b) into 
> lvalues that you can assign to. That's what std:tie() does here.

#ifdef WIN32
    util::WinCmdLineArgs winArgs;
    std::tie(argc, argv) = winArgs.get();
#endif

> SetupEnvironment() sets up memory allocation and locale.
> It starts by configuring the memory allocator so that 32 bit systems only 
> use one memory arena per core. It then sets the locale for POSIX systems.
> On Windows it sets the charset to UTF8. 

    SetupEnvironment();

> ThreadRename(std::string name) calls into util/threadnames.cpp and sets
> the current thread name to name. On linux it uses prctl, on *BSD it
> calls pthread_set_name_np and on MacOSX it calls pthread_setname_np

    util::ThreadRename("main");

> Instantiates interfaces::Node using the interfaces::MakeNode() factory
> method. A Node is an interface offered by the Bitcoin node. Same thing
> that bitcoind does as a daemon offering an entry point to several services.

    std::unique_ptr<interfaces::Node> node = interfaces::MakeNode();

> Here several pointers to event handlers are instantiated to handle 
> user interface signals. The boost::signals2 library is used to handle these.
> interfaces::Handler is just a thin wrapper around boost::signals2::connection

    std::unique_ptr<interfaces::Handler> handler_message_box = node->handleMessageBox(noui_ThreadSafeMessageBox);
    std::unique_ptr<interfaces::Handler> handler_question = node->handleQuestion(noui_ThreadSafeQuestion);
    std::unique_ptr<interfaces::Handler> handler_init_message = node->handleInitMessage(noui_InitMessage);


> Qt-specific GUI settings are applied. Check the Qt documentation.

    // Do not refer to data directory yet, this can be overridden by Intro::pickDataDirectory

    /// 1. Basic Qt initialization (not dependent on parameters or configuration)
    Q_INIT_RESOURCE(bitcoin);
    Q_INIT_RESOURCE(bitcoin_locale);

    // Generate high-dpi pixmaps
    QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#if QT_VERSION >= 0x050600
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
#ifdef Q_OS_MAC
    QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
#endif


> The main BitcoinApplication is created. The node pointer that was instantiated earlier
> is passed as a parameter. BitcoinApplication class is discussed in qt/bitcoin.h.
> BitcoinApplication is a Qt QApplication derived class. The constructor receiving a Node is
> customized.


    BitcoinApplication app(*node);


> Here several object types are registered with the Qt system so they can 
> receive asynchronous calls. Messages from the Bitcoin subsystem can 
> arrive at any time. So the notifications and GUI updates are made async.


    // Register meta types used for QMetaObject::invokeMethod
    qRegisterMetaType< bool* >();
#ifdef ENABLE_WALLET
    qRegisterMetaType<WalletModel*>();
#endif
    //   Need to pass name here as CAmount is a typedef (see http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType)
    //   IMPORTANT if it is no longer a typedef use the normal variant above
    qRegisterMetaType< CAmount >("CAmount");
    qRegisterMetaType< std::function<void()> >("std::function<void()>");
    qRegisterMetaType<QMessageBox::Icon>("QMessageBox::Icon");


> setupServerArgs() calls into SetupServerArgs() at init.cpp
> This helper function parses the command line and updates the global gArgs object
> Not much to comment on here, check out init.cpp for the specific options it processes.
> SetupUIArgs() does something similar, except for user interface command line options.
> SetupUIArgs() is defined in this same source file, check the implementation for details.

    /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these
    // Command-line options take precedence:
    node->setupServerArgs();
    SetupUIArgs();
    std::string error;

    if (!node->parseParameters(argc, argv, error)) {
        node->initError(strprintf("Error parsing command line arguments: %sn", error));
        // Create a message box, because the gui has neither been created nor has subscribed to core signals
        QMessageBox::critical(nullptr, PACKAGE_NAME,
            // message can not be translated because translations have not been initialized
            QString::fromStdString("Error parsing command line arguments: %1.").arg(QString::fromStdString(error)));
        return EXIT_FAILURE;
    }


> Platform style has been discussed in the qt/bitcoin.h header post.
> It basically sets some GUI specifics for MAC, WIN and "other" (Linux, etc).
> e.g. Icons on buttons and other visual details are set via the platform style.

    // Now that the QApplication is setup and we have parsed our parameters, we can set the platform style
    app.setupPlatformStyle();


> Qt saves lots of global state.
> In the following calls the Qt "options model" is configured with settings
> which are later read by QSettings when performing several tasks.
> QSettings is an abstraction of Windows Registry and similar local configs.
> See https://doc.qt.io/qt-5/qsettings.html


    /// 3. Application identification
    // must be set before OptionsModel is initialized or translations are loaded,
    // as it is used to locate QSettings
    QApplication::setOrganizationName(QAPP_ORG_NAME);
    QApplication::setOrganizationDomain(QAPP_ORG_DOMAIN);
    QApplication::setApplicationName(QAPP_APP_NAME_DEFAULT);


> Qt's internal language translation mechanism is set up
> Not much to comment here, check Qt docs for details.


    /// 4. Initialization of translations, so that intro dialog is in user's language
    // Now that QSettings are accessible, initialize translations
    QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator;
    initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator);


> If user requested -help on the command line then allow HelpMessageDialog class to decide whether to
> show a GUI dialog box or print out help data on the console. HelpMessageDialog is implemented
> in qt/utilitydialog.h

    // Show help message immediately after parsing command-line options (for "-lang") and setting locale,
    // but before showing splash screen.
    if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
        HelpMessageDialog help(*node, nullptr, gArgs.IsArgSet("-version"));
        help.showOrPrint();
        return EXIT_SUCCESS;
    }

> The Intro class is defined in qt/intro.h and implemented in qt/intro.cpp
> pickDataDirectory checks for a -datadir option passed on the command line
> It then tries to read a default datadir via GUIUtil::getDefaultDataDirectory()
> If the data dir is not found, it enters a loop that exists only when cancelled
> or a valid data directory is chosen.
> After the data dir is set, it updates global settings strDataDir and fReset and moves on
> reading config and data files in the configured directory.


    /// 5. Now that settings and translations are available, ask user for data directory
    // User language is set up: pick a data directory
    if (!Intro::pickDataDirectory(*node))
        return EXIT_SUCCESS;

    /// 6. Determine availability of data directory and parse bitcoin.conf
    /// - Do not call GetDataDir(true) before this step finishes
    if (!CheckDataDirOption()) {
        node->initError(strprintf("Specified data directory "%s" does not exist.n", gArgs.GetArg("-datadir", "")));
        QMessageBox::critical(nullptr, PACKAGE_NAME,
            QObject::tr("Error: Specified data directory "%1" does not exist.").arg(QString::fromStdString(gArgs.GetArg("-datadir", ""))));
        return EXIT_FAILURE;
    }
    if (!node->readConfigFiles(error)) {
        node->initError(strprintf("Error reading configuration file: %sn", error));
        QMessageBox::critical(nullptr, PACKAGE_NAME,
            QObject::tr("Error: Cannot parse configuration file: %1.").arg(QString::fromStdString(error)));
        return EXIT_FAILURE;
    }

    /// 7. Determine network (and switch to network specific options)
    // - Do not call Params() before this step
    // - Do this after parsing the configuration file, as the network can be switched there
    // - QSettings() will use the new application name after this, resulting in network-specific settings
    // - Needs to be done before createOptionsModel


> Select Bitcoin network.
> Check Bitcoin wiki for info on Bitcoin network specifics.

    // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
    try {
        node->selectParams(gArgs.GetChainName());
    } catch(std::exception &e) {
        node->initError(strprintf("%sn", e.what()));
        QMessageBox::critical(nullptr, PACKAGE_NAME, QObject::tr("Error: %1").arg(e.what()));
        return EXIT_FAILURE;
    }

> The PaymentServer handles Bitcoin urls clicked on or passed via command line.
> During initialization it checks if any Bitcoin URLs were passed on the command line.
> It then starts the PaymentServer to handle Bitcoin links as the program runs.
> The whole following section deals with PaymentServer and command line URL parsing.

#ifdef ENABLE_WALLET
    // Parse URIs on command line -- this can affect Params()
    PaymentServer::ipcParseCommandLine(*node, argc, argv);
#endif

    QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString())));
    assert(!networkStyle.isNull());
    // Allow for separate UI settings for testnets
    QApplication::setApplicationName(networkStyle->getAppName());
    // Re-initialize translations after changing application name (language in network-specific settings can be different)
    initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator);

#ifdef ENABLE_WALLET
    /// 8. URI IPC sending
    // - Do this early as we don't want to bother initializing if we are just calling IPC
    // - Do this *after* setting up the data directory, as the data directory hash is used in the name
    // of the server.
    // - Do this after creating app and setting up translations, so errors are
    // translated properly.

    if (PaymentServer::ipcSendCommandLine())
        exit(EXIT_SUCCESS);

    // Start up the payment server early, too, so impatient users that click on
    // bitcoin: links repeatedly have their payment requests routed to this process:
    if (WalletModel::isWalletEnabled()) {
        app.createPaymentServer();
    }
#endif // ENABLE_WALLET


> Set up Qt GUI. No bitcoin specifics to comment on here.

    /// 9. Main GUI initialization
    // Install global event filter that makes sure that long tooltips can be word-wrapped
    app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app));
#if defined(Q_OS_WIN)
    // Install global event filter for processing Windows session related Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION)
    qApp->installNativeEventFilter(new WinShutdownMonitor());
#endif
    // Install qDebug() message handler to route to debug.log
    qInstallMessageHandler(DebugMessageHandler);
    // Allow parameter interaction before we create the options model
    app.parameterSetup();
    // Load GUI settings from QSettings
    app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false));

    if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false))
        app.createSplashScreen(networkStyle.data());

    int rv = EXIT_SUCCESS;
    try
    {
        app.createWindow(networkStyle.data());
        // Perform base initialization before spinning up initialization/shutdown thread
        // This is acceptable because this function only contains steps that are quick to execute,
        // so the GUI thread won't be held up.
        if (app.baseInitialize()) {
            app.requestInitialize();
#if defined(Q_OS_WIN)
            WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely...").arg(PACKAGE_NAME), (HWND)app.getMainWinId());
#endif

> The exec() method of QApplication blocks until exit() is called in the application
> After exec() returns, the requestShutdown() method executes, which resets the
> shutdownWindow, hides the main window, disconnects signals, shuts the Node down
> and stops pollShutdownTimer (discussed in qt/bitcoin.h) and finally fires
> the requestedShutdown() signal.


            app.exec();
            app.requestShutdown();

> Here the main event loop is restarted to allow the splash screen to show and for any remaining signals to be handled
> including the requestedShutdown() which will trigger QWidget::close()


            app.exec();

> Finally the return value is captured into rv to be returned at the end of GuiMain
> which in turn returns to main(). Therefore the rv return value will end up as 
> the operating system's return value.


            rv = app.getReturnValue();

        } else {
            // A dialog with detailed error will have been shown by InitError()
            rv = EXIT_FAILURE;
        }
    } catch (const std::exception& e) {
        PrintExceptionContinue(&e, "Runaway exception");
        app.handleRunawayException(QString::fromStdString(node->getWarnings("gui")));
    } catch (...) {
        PrintExceptionContinue(nullptr, "Runaway exception");
        app.handleRunawayException(QString::fromStdString(node->getWarnings("gui")));
    }
    return rv;
}

Once GuiMain() reaches app.exec() we have a Qt event loop running and all further routines are asynchronous.

The software now takes on a non deterministic path, listening to user and Bitcoin network events, both of which are random.

GuiMain() only returns if the software exits. It gives back control to main().

We still haven’t seen how, exactly, the subsystem got initialized inside GuiMain(), so there’s more to look at in bitcoin.cpp, which we’ll do next when we study the BitcoinCore class which binds together the Qt system and the underlying Bitcoin-specific services.

This is our last Qt layer before delving into Bitcoin-specific code.

BitcoinCore

BitcoinCore is another important class defined in qt/bitcoin.cpp

As we saw above, GuiMain called app.requestInitialize() but we didn’t step into this routine. Now we’ll follow this branch of execution to see what happens there.

The method is so short, we can paste it verbatim:

void BitcoinApplication::requestInitialize()
{
    qDebug() << __func__ << ": Requesting initialize";
    startThread();
    Q_EMIT requestedInitialize();
}

startThread() and Q_EMIT requestedInitialize()are the 2 interesting lines here.

startThread()

This subroutine is important enough that it warrants its own section. Let’s have a look at the code.

void BitcoinApplication::startThread()
{

> If coreThread has been defined, do not start it again. Return immediately.

    if(coreThread)
        return;



> OK so we don't have a coreThread yet.
> We'll create one and then instantiate a BitcoinCore object called executor.
> The executor is then moved to the newly created coreThread.
> It's necessary to move the executor to a new thread so the GUI and underlying
> Bitcoin messages are processed in different threads. The network messages from Bitcoin and user
> actions on the GUI may happen at random times. It's necessary to run both in parallel.


    coreThread = new QThread(this);
    BitcoinCore *executor = new BitcoinCore(m_node);
    executor->moveToThread(coreThread);


> Next we connect the BitcoinApplication to the BitcoinCore object.
> The three first signals go from BitcoinCore to BitcoinApplication. 
> The next two from BitcoinApplication to BitcoinCore
> Lastly a QThread::finished signal is hooked up to one of Qt's default cleanup handlers.
> coreThread->start() gets the new thread running and off goes the BitcoinCore object on its own.
> Special attention for the BitcoinCore::initialize handler (4th connect() statement below), as
> we'll discuss it next.

    /*  communication to and from thread */
    connect(executor, &BitcoinCore::initializeResult, this, &BitcoinApplication::initializeResult);
    connect(executor, &BitcoinCore::shutdownResult, this, &BitcoinApplication::shutdownResult);
    connect(executor, &BitcoinCore::runawayException, this, &BitcoinApplication::handleRunawayException);
    connect(this, &BitcoinApplication::requestedInitialize, executor, &BitcoinCore::initialize);
    connect(this, &BitcoinApplication::requestedShutdown, executor, &BitcoinCore::shutdown);
    /*  make sure executor object is deleted in its own thread */
    connect(coreThread, &QThread::finished, executor, &QObject::deleteLater);

    coreThread->start();
}

Q_EMIT requestedInitialize()

The other relevant line in BitcoinApplication::requestInitialize()sends arequestedInitialize()signal which is hooked up to BitcoinCore::initialize()

Let’s take a look at this signal handler.

void BitcoinCore::initialize()
{
    try
    {
        qDebug() << __func__ << ": Running initialization in thread";

> Rename the current thread to qt-init
        util::ThreadRename("qt-init");



> Run our Node instance's appInitMain() method.
> appInitMain() is a stub that calls into Bitcoin Core init.cpp and performs
> the first core Bitcoin-specific code we've encountered so far. Until now
> we set up the whole Qt framework, signals, threads but ran no Bitcoin code.
> So this method is very important and will lead us to the next source file (init.cpp).

> Note that m_node was created by GuiMain (program entry point after main()) 
> then passed to BitcoinCore constructor by BitcoinApplication
> So the same Node instance is used by all components, including BitcoinCore and BitcoinApplication
> Here we're running in a different thread, though. So Node's appInitMain will
> run on qt-init thread while the rest of the application remains on the main thread.
> It's important to note that it's a single Node instance passed to multiple threads.

> The Node (NodeImpl to be precise) instance runs appInitMain(). Keep this in mind.


        bool rv = m_node.appInitMain();


> After completing appInitMain() we send a initializeResult() signal passing
> a boolean as parameter. This signal will be handled by BitcoinApplication::initializeResult.


        Q_EMIT initializeResult(rv);
    } catch (const std::exception& e) {
        handleRunawayException(&e);
    } catch (...) {
        handleRunawayException(nullptr);
    }
}

More details on AppInitMain(), which is also our first Bitcoin-specific code, will follow up on the init.cpp post.

Links

bitcoin.cpp Source on Github

Qt Programming Tutorial

Exit mobile version