Bitcoind Source 분석 (1)

in #understanding7 years ago (edited)

<bitcoind.cpp> 에서 시작.

2018년 5월 15일 master branch 기준으로 분석 시작.

현재 bitcoin 소스는 c++11 표준이하 코드를 사용하고 있는 것 같다. linux 환경 하에서 동작하는 코드를 기준으로 분석 시작!

int main(int argc, char* argv[])
{
    // 32bit 시스템에서 glibc 2.10 이상 버전을 사용하는 경우, 강제로 1 arena 로 세팅.
    // fallback locale 설정 및 path locale 설정.
    SetupEnvironment();

    // Connect bitcoind signal handlers
    // 함수 이름처럼 ui 를 사용하지 않는 경우의 로깅 callback 등록.
    // 이 곳에서는 ThreadSafeMessageBox, ThreadSafeQuestion, InitMessage 에 대한 callback 등록.
    // 각각은 ui_interface.h 를 참고.
    noui_connect();

    // command line argument 들을 가지고 AppInit() 호출.
    return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE);
}

AppInit() 의 내용은 아래와 같다.

//////////////////////////////////////////////////////////////////////////////
//
// Start
//
static bool AppInit(int argc, char* argv[])
{
    bool fRet = false;

    //
    // Parameters
    //
    // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main()
    // 이 소스 분석에서는 Qt 를 사용하지 않는 버전의 소스를 따라가기로 한다.

    // ArgsManager (util.h 참고) 의 m_available_args 에 현재 가용한 옵션들과 그에 대한 도움말을 세팅
    // 컴파일 환경 또는 옵션에 따라서 win32 용, wallet, zmq, UPnP 용 옵션들에 대한 세팅도 진행.
    // 실제 SetupServerArgs() 의 내용은 init.cpp 를 참고.
    SetupServerArgs();

#if HAVE_DECL_DAEMON    // daemon() 을 사용할 수 있다면...
    // ArgsManager 에 이에 대한 옵션과 도움말도 추가한다.
    gArgs.AddArg("-daemon", _("Run in the background as a daemon and accept commands"), false, OptionsCategory::OPTIONS);
#endif

    // command line argument 들을 가지고 ArgsManager 의 ParseParameters() 메소드를 수행.
    // (해당 메소드의 코드는 util.cpp 에서 찾을 수 있다.)
    // 전달된 command line argument 들을 각각 모두 검사하는데 우선 '=' 문자가 있는지 찾아서 key=val 포멧으로 구분하려고 시도.
    // --foo 형태의 key 를 -foo 형태의 key로 치환 작업도 진행.
    // -nofoo 형태의 argument 는 -foo=bool(true|false) 형태로 치환 후 bool 이 true 면 ArgsManager의 m_override_args[key].clear() 호출
    // 그 외에의 경우는 ArgsManager 의 m_override_args[key].push_back() 호출하여 저장.
    // 여기서 m_override_args 는 std::map<std::string, std::vector<std::string>> 타입.
    // 만약 command line argument 에 -includeconf 가 존재하고 값이 존재한다면, 해당 argument 는 제거.
    gArgs.ParseParameters(argc, argv);

    // Process help and version before taking care about datadir
    // command line argument 에 "-?", "-h", "-help" 또는 "-version" 이  존재한다면...
    if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
        // _() 도 함수. (소스는 util.h 참고) - 사용자의 언어에 맞춘 메세지가 등록되어 있다면 해당 메세지로 치환해주는 역할 
        std::string strUsage = strprintf(_("%s Daemon"), _(PACKAGE_NAME)) + " " + _("version") + " " + FormatFullVersion() + "\n";

        // command line argument 에 "-version"이 존재한다면
        if (gArgs.IsArgSet("-version"))
        {
            // LicenseInfo() 소스는 init.cpp 를 참고, FormatParagraph() 는 utilstrencodings.cpp 참고
            // 결국 특정 포멧으로 출력되도록 문자열을 formatting.
            strUsage += FormatParagraph(LicenseInfo());
        }
        else
        {
            strUsage += "\n" + _("Usage:") + "\n" +
                  "  bitcoind [options]                     " + strprintf(_("Start %s Daemon"), _(PACKAGE_NAME)) + "\n";

            strUsage += "\n" + gArgs.GetHelpMessage();
        }

        // 표준 출력(실행 화면)에 결과 출력
        fprintf(stdout, "%s", strUsage.c_str());
        return true;
    }

    try
    {
        // GetDataDir() 은 util.cpp 참고. 
        // cache 된 path 가 존재한다면 그것을 리턴, 
        // command line arguments 에 "-datadir" 로 주어진 패스를 절대 경로로 변환 뒤 디렉토리 만들고 하위에 "wallets" 디렉토리도 생성
        // 또는 각 os 환경의 default 위치에 datadir 을 만들고 하위에 wallets 디렉토리까지 생성
        // - util.cpp 의 GetDefaultDataDir() 참고.
        if (!fs::is_directory(GetDataDir(false)))
        {
            fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str());
            return false;
        }
        try
        {
            // 상위의 ParseParameters() 와 유사한 작업을 하는데 command line arguments 가 아니라 이제 config file 을 읽어들인다.
            // "-conf" 로 별도의 설정파일 경로를 지정하지 않았다면 "bitcoin.conf"
            // 단, 해당 결과는 ArgsManager 의 m_config_args 에 저장.
            // 사용자가 "-noincludeconf" 라고 명시하지 않았다면, 설정 파일에서 "includeconf" 에 명시된 파일에서도 설정들을 읽어들인다.
            // 역시 m_config_args 에 저장!
            gArgs.ReadConfigFiles();
        } catch (const std::exception& e) {
            fprintf(stderr,"Error reading configuration file: %s\n", e.what());
            return false;
        }
        // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
        try {
            // gArgs.GetChainName() 에서는
            // command line argument 들 또는 설정 파일들 상에서 "-regtest" 또는 "-testnet" 관련 설정이 있는지 살펴보고
            // 적절하게 CBaseChainParams::REGTEST, CBaseChainParams::TESTNET, CBaseChainParams::MAIN 을 
            // (chainparamsbase.cpp 참고) 리턴하고, 이 리턴 값을 바탕으로 CBaseChainParams 인스턴스를 생성하여
            // static::unique_ptr<CBaseChainParams> globalChainBaseParams 에 할당.
            // gArgs.SelectConfigNetwork() 도 호출하여 ArgsManager 의 m_network 에 CBaseChainParams::REGTEST, 
            // CBaseChainParams::TESTNET, CBaseChainParams::MAIN 값 중 하나를 세팅.
            // static std::unique_ptr<CChainParams> globalChainParams 에도 CMainParams(), CTestNetParams(), CRegTestParams() 중 하나를 생성하여 세팅.
            SelectParams(gArgs.GetChainName());
        } catch (const std::exception& e) {
            fprintf(stderr, "Error: %s\n", e.what());
            return false;
        }

        // Error out when loose non-argument tokens are encountered on command line
        for (int i = 1; i < argc; i++) {
            // command line argument 들의 시작 character 가 '-' 이 아니라면
            if (!IsSwitchChar(argv[i][0])) {
                fprintf(stderr, "Error: Command line contains unexpected token '%s', see bitcoind -h for a list of options.\n", argv[i]);
                return false;
            }
        }

        // -server defaults to true for bitcoind but not for the GUI so do this here
        // "-server" 설정 값이 기존에 설정되어 있다면 그대로 리턴, 그게 아니라면 강제로 m_override_args["-server"] = {1} 로 설정.
        gArgs.SoftSetBoolArg("-server", true);
        
        // Set this early so that parameter interactions go to console
        // 광역 logger 초기화. (g_logger, logging.cpp 참고)
        // g_logger 관련 필드인 m_print_to_file, m_file_path, m_print_to_console, m_log_timestamps, m_log_time_micros  값을 세팅.
        // "-logips" 설정에 따라 global 변수인 fLogIPs 에  boolean 값 세팅.
        // "Bitcoin Core" + version_string 출력 
        InitLogging();

        // 서로 연관을 줄 수 있는 파라미터들을 rule base 로 조정. (자세한 소스는 init.cpp 참고)
        InitParameterInteraction();

        // "-sysperms" 설정이 없다면 file mode create mask 를 umask(022) 로 설정. 파일이나 디렉토리 생성시 022 가 default turn off 될 것. user: rwx, group:r-x, other:r-x
        // SIGTERM, SIGINT 에 대한 signal handler 를 HandleSIGTERM, 단순히 fRequestShutdown 전역 변수의 값을 true 로 세팅.
        // SIGHUP 에 대한 signal handler 를 HandleSIGHUP, g_logger->m_reopen_file = true 로 세팅.
        // SIGPIPE 는 무시. 패킷 전송 중 프로그램이 죽을 수 있는 가능성 방지.
        // memory allocation 에 실패했을 경우, "Error: Out of memory. Terminating.\n" 이란 로그를 찍고 깨끗한 메모리 상태에서
        // 종료될 수 있도록(SIGABRT 이용) std::set_new_handler() 에 새로운 핸들러 등록.
        if (!AppInitBasicSetup())
        {
            // InitError will have been called with detailed error, which ends up on console
            return false;
        }

        // 이 부분은 분량이 많은 관계로 다음 포스팅에서 별도로 쪼개서 설명.
        if (!AppInitParameterInteraction())
        {
            // InitError will have been called with detailed error, which ends up on console
            return false;
        }

        // Elliptic curve code 에 대한 초기화를 수행
        // "sse4" instruction 을 지원하는지 여부에 따라 SHA256 변환 알고리즘 로직 선택
        // 동작하기 위한 ECC, glibc, Random 관련 sanity check.
        // Data directory 에 ".lock" 이 존재하는지 체크하고, 없다면 생성 후 해당 파일에 locking 을 시도해 봄.
        if (!AppInitSanityChecks())
        {
            // InitError will have been called with detailed error, which ends up on console
            return false;
        }

        // "-daemon" 설정이 존재한다면
        if (gArgs.GetBoolArg("-daemon", false))
        {
#if HAVE_DECL_DAEMON
#if defined(MAC_OSX)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
            fprintf(stdout, "Bitcoin server starting\n");

            // Daemonize 
            // bitcoind 를 실행시켰던 터미널이 종료되더라도 bitcoind 가 계속 background 에서 실행되도록 하려면
            // daemonize  는 필수.
            // (daemonize 시에 해당 프로세스의 현재 working directory 를 변경하지 않고
            // standard in, standard out, standard err 를 모두 /dev/null 로 리다렉트 시킴.)
            if (daemon(1, 0)) { // don't chdir (1), do close FDs (0)
                fprintf(stderr, "Error: daemon() failed: %s\n", strerror(errno));
                return false;
            }
#if defined(MAC_OSX)
#pragma GCC diagnostic pop
#endif
#else
            fprintf(stderr, "Error: -daemon is not supported on this operating system\n");
            return false;
#endif // HAVE_DECL_DAEMON
        }

        // Lock data directory after daemonization
        // 위에서는 단순히 data directory 에 locking 이 가능한지 체크만 해봤다면, 여기서는 daemonize() 직후
        // data directory 에 lock 을 걸고 다른 프로세스가 접근할 수 없도록 함(advisory locking)
        // 이 lock 은 이 프로세세가 종료될 때까지 이 lock 을 소유하고 있음.
        if (!AppInitLockDataDirectory())
        {
            // If locking the data directory failed, exit immediately
            return false;
        }

        // 이것도 분량이 너무 많기 때문에 다음 번에 계속해서 관련 내용 포스팅 예정.
        fRet = AppInitMain();
    }
    catch (const std::exception& e) {
        PrintExceptionContinue(&e, "AppInit()");
    } catch (...) {
        PrintExceptionContinue(nullptr, "AppInit()");
    }

    // 이하 함수들은 모두 init.cpp 를 참고.
    if (!fRet)
    {
        // fRet 이 false 라면(bitcoind 실행에 실패했다면), 생성되어 있던 쓰레드들이 더 이상 작업을 처리하지 않도록 표시.
        Interrupt();
    } else {
        // fRet 이 true 라면(bitcoind 실행에 성공했다면), 위에서 signal handler 에서 값을 세팅했던 fRequestShutdown 의
        // 값을 200 ms 마다 체크하여 false 라면 계속 동일 체크 수행.
        // 그렇지 않다면, Interrupt() 를 호출하여 생성되어 있던 쓰레드들이 더 이상 작업을 처리하지 않도록 표시.
        WaitForShutdown();
    }

    // 실제 clean up 수행.
    Shutdown();

    return fRet;
}
Sort:  

@jayson.jeong, congratulations on making your first post! I gave you an upvote!

Please give me a follow and take a moment to read this post regarding commenting and spam.
(tl;dr - if you spam, you will be flagged!)