1. 程式人生 > >從零開始學習區塊鏈技術(二)--如何接入比特幣網路以及其原理分析

從零開始學習區塊鏈技術(二)--如何接入比特幣網路以及其原理分析

如何接入比特幣網路以及原理分析

1、如何接入比特幣網路?

其實接入比特幣網路是非常簡單的,我說了你一定不信,啟動比特幣客戶端即可:

在命令列終端輸入啟動命令:./src/bitcoind -testnet

輸入之後會有一個和網路同步資料的過程,你會看到:

資料同步

資料同步

這個過程需要一點時間,同步資料完成後,即接入了比特幣網路。

2、啟動流程鳥瞰

雖然說一句命令即搞定,但是,這個背後程式碼執行的邏輯可就不簡單咯~

來,我給大家分析一下

當在命令列終端輸入啟動命令:./src/bitcoind -testnet 後,作業系統就會找到這個檔案中的 main 函式,開始比特幣客戶端的啟動。

對於所有的c++程式碼,整個程式都是從main函式開始執行的,bitcoind 的main函式位於 src/bitcoind.cpp,程式碼拉到最後就找到了我們的 main 函式。

main 函式本身沒有太多東西,主要是呼叫3個函式來執行,它們的主要作用是設定環境變數、設定訊號處理和啟動系統。

具體程式碼如下:

int main(int argc, char* argv[])
{
    SetupEnvironment();

    // Connect bitcoind signal handlers
    noui_connect();

    return (AppInit(argc, argv)
? EXIT_SUCCESS : EXIT_FAILURE); }

這段程式碼簡單說明如下:

  1. SetupEnvironment 函式,主要用來設定系統的環境變數,包括:malloc 分配記憶體的行為、Locale、檔案路徑的本地化設定等。
  2. noui_connect 函式,設定連線到 bitcoind 的訊號的處理。
  3. AppInit 函式,進行系統啟動。

下面我們重點講下 AppInit 函式的執行

  1. 呼叫 SetupServerArgs 函式,設定系統可接受的所有命令列引數。然後開始解析命令列傳遞的各種引數。

    系統執行的重要一步就是設定可以接收的引數並解析使用者啟動時傳遞的各種引數,SetupServerArgs

    函式就是完成這個目的。下面來看這個函式的執行流程。

    • 首先,呼叫 CreateBaseChainParams 函式,生成預設的基本引數,包括:使用的資料目錄和監聽的埠。根據不同的網路型別,主網路使用 8332 埠和指定目錄下的當前目錄,測試網路使用 18332 埠和指定目錄下的 testnet3 子目錄,迴歸測試網路 使用 18443 埠和指定目錄下的 regtest 子目錄。

    • 然後,呼叫 CreateChainParams 函式,生成預設的區塊鏈引數。這個方法也會區分不同的網路。

      如果是主網路,則生成 CMainParams 物件進行初始化。在建構函式中,進行如下的設定:

      • 設定網路ID 為 main
      • 設定共識引數(Consensus::Params)的各個值:
      • 每隔多少個塊(nSubsidyHalvingInterval)後續比特幣的獎勵會減半,值為 210000。根據創世區塊獎勵的數量(50),根據等比數列求和公式:50(1/(10.5))210000,可計算貨幣總量為 2100W個比特幣。
      • BIP34 啟用高度(BIP34Height)為 227931。
      • BIP34 啟用雜湊(BIP34Hash)為 0x000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8
      • BIP65 啟用高度(BIP65Height)為 388381。
      • BIP66 啟用高度(BIP66Height)為 363725。
      • 工作量限制(powLimit)為一個大整數。
      • 難度改變的週期(nPowTargetTimespan)為 2周。
      • 平均出塊時間(nPowTargetSpacing)為10分鐘。
      • 改變共識需要的區塊數(nRuleChangeActivationThreshold)為 1916,即 2016 的 95%。
      • 礦工確認視窗(nMinerConfirmationWindow)為 2016,等於難度改變週期除以平均出塊時間。
      • 接下來設定區塊鏈相關的部署狀態,包括:測試相關的(DEPLOYMENT_TESTDUMMY)、CSV 軟分叉相關的(涉及到 BIP68、BIP112、BIP113)和隔離見證相關的(涉及到 BIP141、BIP143、BIP147)。
      • 最佳區塊鏈的最小工作量。
      • 設定預設埠(nDefaultPort)為 8333。
      • 達到多少個區塊之後進行區塊修剪(nPruneAfterHeight),當前值為 100000。
      • 接下來,呼叫 CreateGenesisBlock 方法,生成創世區塊。這個方法的引數是固定的,指定了創世區塊的時間、隨機數、難度值、版本號、獎勵等。在方法內部,生成創世區塊的輸出指令碼和輸入指令碼,中本聰那句著名的評論就出現在創世區塊的第一個交易的簽名中,他寫道:The Times 03/Jan/2009 Chancellor on brink of second bailout for banks。
      • 設定創世區塊的雜湊為剛生成的創業區塊的雜湊。
      • 設定 DNS 種子節點 vSeeds 集合包含的 DNS 種子有:seed.bitcoin.sipa.bednsseed.bluematt.mednsseed.bitcoin.dashjr.orgseed.bitcoinstats.comseed.bitcoin.jonasschnelli.chseed.btc.petertodd.orgseed.bitcoin.sprovoost.nl 等,通過解析 DNS 種子節點,比特幣節點啟動時可以找到更多的對等節點來進行連線。
      • 接下來,設定相關的檢查點資料。

      如果是測試網路,則生成 CTestNetParams 物件進行初始化。(供開發完成後測試使用。)

      如果是迴歸測試網路,則生成 CRegTestParams 物件進行初始化。(供開發時連線使用。)

      對於這兩種測試網路,處理基本和主網路相同,只是某些引數不一樣。

      上面提到的3個物件 CMainParams CTestNetParams CRegTestParams的定義都在 chainparams.cpp 檔案中。感興趣同學的可以對照原始碼進一步探究。

    • 接下來,設定系統可接收的所有引數。

      部分引數解釋如下:

      • ?,顯示幫助資訊;
      • -version,列印版本資訊,並退出系統。
      • -assumevalid=hex,如果指定的區塊存在區塊鏈中,假定它及其祖先有效並可能跳過其指令碼驗證。
      • -blocksdir=dir,指定區塊鏈存放的目錄。
      • -blocknotify=cmd,指定當主鏈上的區塊改變時執行的命令。
      • -conf=file,指定配置檔案的目錄,相對於下面指定的資料目錄。
      • -datadir=dir,指定資料目錄。
      • -dbcache=n,設定資料庫快取大小。
      • -debuglogfile=file,設定除錯檔案的位置。
      • -feefilter,告訴其他節點通過最小交易費用過濾傳送給我們的庫存訊息。
      • -loadblock=file,在啟動時,從外部 blk000??.dat 檔案匯入區塊。
      • -maxmempool=n,指定交易池的最大記憶體數,單位為兆位元組。
      • -maxorphantx=n,指定記憶體中最大的孤兒交易數量。
      • -mempoolexpiry=n,指定交易池中不跟蹤超過指定時間(小時)的交易。
      • -par=n,指定指令碼簽名的執行緒數量。
      • -persistmempool,指定是否持久化交易池中的交易,啟動時恢復載入。
      • -pid=file,指定程序檔案。
      • -prune=n,通過啟用舊區塊的修剪(刪除)來降低儲存要求。 這允許呼叫 pruneblockchain RPC 來刪除特定塊,並且如果提供目標大小,則啟用對舊塊的自動修剪。 此模式與 -txindex-rescan 不相容。
      • -reindex,根據硬碟上的 blk*.dat 檔案重建區塊鏈狀態和區塊的索引。
      • -reindex-chainstate,根據當前區塊的索引重建區塊鏈的狀態。
      • -txindex,維護所有交易的索引,被 getrawtransaction RPC 命令呼叫。
      • -addnode=ip,新增一個節點,並連線它,並保持連線。
      • -banscore=n,斷開行為不端的同伴的門檻。
      • -bantime=n,不誠實節點重新連線需要的秒數。
      • -bind=addr,繫結到指定的IP,並總是連線到這個地址。
      • -connect=ip,僅僅只連線到指定的節點,如果不是ip而是0,則表示禁止自動連線。
      • -discover,是否發現自己的IP地址。
      • -dns,對於 -addnode-seednode-connect 總是使用 DNS 查詢。
      • -dnsseed,指定如果已有地址比較少,則進行 DNS 查詢來獲取對等節點。
      • -enablebip61,允許傳送 BIP61 定義的拒絕訊息。
      • -externalip=ip,指定自身的外部 IP 地址。
      • -forcednsseed,總是通過 DNS 查詢來獲取對等節點的地址。
      • -listen,接收外部對等節點的連線。
      • -listenonion,自動建立 Tor 隱藏服務。
      • -maxconnections=n,維護到別的節點的最大連線數。
      • -maxreceivebuffer=n,每個對等節點的最大接收快取。
      • -maxsendbuffer=n,每個對等節點的最大發送快取。
      • -onion=ip:port,設定 SOCKS5 代理。
      • -peerbloomfilters,支援布隆過濾器過濾區塊和交易。
      • -permitbaremultisig,中繼非 P2SH 多重簽名。
      • -port=port,指定預設的監聽埠。
      • -proxy=ip:port,通過 SOCKS5 代理進行連線。
      • -proxyrandomize,隨機化每個代理連線的憑據。 從而使Tor流進行隔離。
      • -seednode=ip,指定一個節點來檢索其他的節點,隨後就從這個接點進行斷開。
      • -torcontrol=ip:port,在 onion 啟用的情況下,指定 Tor 控制器使用的埠。
      • -torpassword=pass,Tor 控制器的密碼。
      • -checkblocks=n,在啟動時要檢查多少個區塊。
      • -checklevel=n,checkblocks 驗證區塊的程度。
      • -checkblockindex,進行完整的一致性檢查,包括:mapBlockIndex、setBlockIndexCandidates、chainActive、mapBlocksUnlinked 等。
      • -checkmempool=n,每多少個交易進行檢驗。
      • -checkpoints,提供檢查點,對已知鏈的歷史不進行檢驗。
      • -deprecatedrpc=method,不贊成使用的 RPC 方法。
      • -limitancestorcount=n,如果交易池中的祖先交易達到或超過指定的值時,不再接收交易。
      • -limitancestorsize=n,如果交易池中的祖先交易大小達到或超過指定的值時,不再接收交易。
      • -limitdescendantcount=n,如果交易池中祖先交易的後代已經達到或超過指定的值時,不再接收交易。
      • -blockmaxweight=n,設定 BIP141 區塊的最大 weight。
      • -blockmintxfee=amt,設定包含在建立區塊的交易最小費用。
      • -rpcuser=user,進行 RPC 呼叫的使用者名稱。
      • -rpcpassword=pw,進行 RPC 呼叫的使用者密碼。
      • -rpcport=port,進行 RPC 呼叫的埠

    上面是一些常用的引數,通過這些引數可以影響比特幣核心的命令。應用開發者比較關注的是 RPC 相關的設定,通過 RPC 介面,我們呼叫比特幣核心提供的多種服務。這些命令通常會在配置檔案中進行設定,不用在命令列指定。

  2. 接下來,檢查使用者指定命令引數是否正確。

     if (!gArgs.ParseParameters(argc, argv, error)) {
         fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str());
         return false;
     }
  3. 如果傳遞的是幫助和版本引數,則顯示幫助或版本資訊,然後退出。

  4. 檢查資料目錄(可指定或預設)是否是存在。如果不存在,則列印錯誤資訊,然後退出。

     if (!fs::is_directory(GetDataDir(false)))
     {
         fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str());
         return false;
     }

    GetDataDir 方法中,根據使用者是否在命令列提供 datadir 引數來確定使用預設的資料目錄還是使用者指定的資料目錄。

  5. 讀取並解析配置檔案,同時檢查指定資料目錄是否存在。如果任何一個步驟出錯,都列印錯誤資訊,然後退出。

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

    其中 ReadConfigFiles 方法具體處理如下:

    • 首先,呼叫 GetArg 方法,獲取配置檔名稱,預設為 bitcoin.conf
    • 然後,通過 GetConfigFile 方法獲取配置檔案的絕對路徑(方法內部會委託 AbsPathForConfigVal 方法進行處理,後者決定根據使用者指定的路徑或使用預設路徑來生成配置檔案的絕對路徑)。在得到配置檔案的絕對路徑之後,構造檔案輸入流,從而讀取配置檔案 fs::ifstream stream(GetConfigFile(confPath))
    • 在成功構造輸入流之後,呼叫 ReadConfigStream 方法開始讀取配置檔案的內容。方法內部按行讀取配置檔案,並以鍵值對的形式儲存在 m_config_args 集合中。
  6. 呼叫 SelectParams(gArgs.GetChainName()) 函式,生成全域性的區塊鏈引數,並設定系統的網路型別。如果有錯誤,則列印錯誤,然後退出。

    gArgs.GetChainName() 方法會返回當前使用的網路。針對主網路,返回字串 main;測試網路,返回字串 test;迴歸測試網路,返回字串 regtest

    SelectParams 方法的實現如下所示:

    void SelectParams(const std::string& network)
    {
       SelectBaseParams(network);
       globalChainParams = CreateChainParams(network);
    }

    SelectBaseParams 方法會根據指定的網路引數生成 CBaseChainParams 物件,並儲存在 globalChainBaseParams 變數中,並在指定 gArgs 物件中儲存網路型別(m_network 屬性)。CBaseChainParams 物件中僅儲存系統的資料目錄和執行的埠,所以稱之為基本區塊鏈引數物件。

    CreateChainParams 方法會根據不同的網路引數生成 CChainParams 類的子物件,可能為以下三種:CMainParams、CTestNetParams、CRegTestParams。CChainParams 物件包含了區塊鏈物件的所有重要資訊,比如:共識規則、部署狀態、檢查點、創世區塊等。

  7. 檢查所有命令列引數,如果有錯誤,則列印錯誤,並退出。

  8. 設定引數 -server 預設為真。

    bitcoind 守護程序預設 server 為真。

  9. 呼叫 InitLogging 函式,初始化系統所用日誌,並列印系統的版本資訊。

    具體程式碼如下,根據是否指定 debuglogfileprinttoconsole 等確定日誌列印到檔案或是控制檯。

    void InitLogging()
    {
       g_logger->m_print_to_file = !gArgs.IsArgNegated("-debuglogfile");
       g_logger->m_file_path = AbsPathForConfigVal(gArgs.GetArg("-debuglogfile", DEFAULT_DEBUGLOGFILE));
    
       LogPrintf("\n\n\n\n\n");
    
       g_logger->m_print_to_console = gArgs.GetBoolArg("-printtoconsole", !gArgs.GetBoolArg("-daemon", false));
       g_logger->m_log_timestamps = gArgs.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS);
       g_logger->m_log_time_micros = gArgs.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS);
    
       fLogIPs = gArgs.GetBoolArg("-logips", DEFAULT_LOGIPS);
    
       std::string version_string = FormatFullVersion();
    
       LogPrintf(PACKAGE_NAME " version %s\n", version_string);
    }
  10. 呼叫 InitParameterInteraction 函式,根據引數間的關係,檢查所有的互動引數。

  11. 呼叫 AppInitBasicSetup 函式,進行基本的設定。如果有錯誤,則列印錯誤,然後退出。

    經過前面漫長的檢查與設定,終於開始了應用基本的設定。具體解讀見第二部分。

  12. 呼叫 AppInitSanityChecks 函式,處理底層加密函式相關內容。

    具體解讀見第二部分。

  13. 呼叫 AppInitLockDataDirectory 函式,檢查並鎖定資料目錄。

    具體解讀見第二部分。

  14. 呼叫 AppInitMain 函式,比特幣主要的啟動過程。

    具體解讀見第二部分。

  15. 如果應用初始化主函數出錯,則呼叫 Interrupt 函式進行中止,否則呼叫 WaitForShutdown 函式等待系統結束。

    WaitForShutdown 函式是一個無限迴圈函式。

2、系統啟動詳述

以下為系統啟動過程中重要的步驟。

第1步,應用初始化基本設定(src/bitcoind.cpp

AppInitBasicSetup 函式進行基本的設定。

  1. 呼叫 SetupNetworking 函式,進行網路設定。

    主要是針對 Win32 系統處理套接字,別的系統直接返回真。

  2. 如果不是 WIN32 系統,進行下面的處理:

    • 如果設定 sysperms 引數為真,呼叫 umask 函式,設定位碼為 077。
    • 呼叫 registerSignalHandler 函式,設定 SIGTERM 資訊處理器為 HandleSIGTERMSIGINTHandleSIGTERMSIGHUPHandleSIGHUP

第2步,應用初始引數互動設定(src/bitcoind.cpp

AppInitParameterInteraction 函式前半部分。

  1. 首先,呼叫 Params 方法,獲取前面初始化的 globalChainParams 區塊鏈物件。

  2. 檢查指定的區塊目錄是否存。如果不存在,則返回初始化錯誤。

  3. 如果同時指定了 prunetxindex,則丟擲初始化錯誤。

    如果指定了區塊修剪 prune,就要禁止交易索引 txindex,兩者不相容,只能其一。

  4. 如果同時指定了 bindwhitebind 就不能同時指定 listen

  5. 確保有足夠的檔案符可用。因為在類 Unix 系統中,每個套接字都是一個檔案,都需要一個檔案描述符。所以要檢查指定的最大連線數 maxconnections 是否超過系統可用限制。

第3步,引數到內部標誌的處理(src/bitcoind.cpp

AppInitParameterInteraction 函式後半部分。

  1. 處理 debugdebugexcludedebugnet 等引數。
  2. 如果指定了 socks,則提示使用 SOCKS5
  3. 如果指定了 tor,則提示使用 onion
  4. 如果指定了 benchmark,則提示使用 -debug=bench
  5. 如果指定了 whitelistalwaysrelay,則提示使用 whitelistrelay,或whitelistforcerelay
  6. 如果指定了 blockminsize,則提示使用 blockminsize
  7. 在迴歸模式regtest下,Checkmempoolcheckblockindex 預設為真。
  8. 處理 assumevalid 引數。
  9. 根據是否指定 minimumchainwork,計算最小區塊鏈工作量。
  10. 計算記憶體池限制,包括處理 maxmempoollimitdescendantsize
  11. 如果指定了 incrementalrelayfee,則進行相關處理。
  12. 處理 par 引數。
  13. 處理區塊修剪引數 prune
  14. 處理連線超時時間 timeout
  15. 處理 minrelaytxfee 引數。
  16. 處理 blockmintxfee 引數。
  17. 處理 dustrelayfee 引數。
  18. 處理 acceptnonstdtxn 引數。
  19. 處理 bytespersigop 引數。
  20. 呼叫錢包初始介面物件的 ParameterInteraction 方法,初始錢包相關的引數。本方法在 wallet/init.cpp 檔案中。
    • 檢查是否禁止錢包 disablewallet
    • 設定 wallet 在沒有指定情況預設為空字串。
    • 處理 blocksonlywalletbroadcast 引數。
    • 處理 salvagewalletrescan 引數。
    • 處理 zapwallettxespersistmempool 引數。
    • 處理 upgradewallet 引數。
    • 處理 maxtxfee 引數。
  21. 獲取 permitbaremultisigdatacarrierdatacarriersize等引數的值。
  22. 呼叫 SetMockTime 方法,設定模擬時間。
  23. 根據 peerbloomfilters 引數,設定本地支援的服務。
  24. 檢測 rpcserialversion 引數是否小於0,是否大於1。
  25. 獲取 maxtipage 引數值,表示區塊鏈頂端值存活時間。
  26. 處理 mempoolreplacement 引數。
  27. 處理 vbparams 引數。

第4步,檢查相關的加密函式(src/bitcoind.cpp

AppInitSanityChecks 函式初始相關的加密曲線與函式。

同時,呼叫 LockDataDirectory 函式,鎖定資料目錄,確保只有 Bitcoind 在執行。

第4a 步,應用程式初始化(src/init.cpp::AppInitMain()

AppInitMain 函式是應用初始化的主體,包括本步驟在內的以下步驟的主體都是在這個函式內部執行。

  1. 呼叫 Params 函式,獲取 chainparams

    方法定義在 src/chainparams.cpp 檔案中。這個變數主要是包含一些共識的引數,自身是根據選擇不同的網路 maintestnet 或者 regtest 來生成不同的引數。

  2. 如果是非 Windows 系統,則呼叫 CreatePidFile 函式,建立程序的PID檔案。

    pid 檔案簡介如下:

    • pid檔案的內容

      pid檔案為文字檔案,內容只有一行, 記錄了該程序的ID。 用cat命令可以看到。

    • pid檔案的作用

      防止程序啟動多個副本。只有獲得pid檔案(固定路徑固定檔名)寫入許可權(F_WRLCK)的程序才能正常啟動並把自身的PID寫入該檔案中。其它同一個程式的多餘程序則自動退出。

  3. 如果命令列指定了 shrinkdebugfile 引數或預設的除錯檔案,則呼叫日誌物件的 ShrinkDebugFile 方法,處理 debug.log 檔案。

    如果日誌長度小於11MB,那麼就不做處理;否則讀取檔案的最後 RECENT_DEBUG_HISTORY_SIZE 10M 內容,重新儲存到debug.log檔案中。

  4. 呼叫日誌物件的 OpenDebugLog 方法,開啟日誌檔案。如果不能開啟則丟擲異常。

  5. 呼叫 InitSignatureCache 函式,設定簽名緩衝區大小。

  6. 呼叫 InitScriptExecutionCache 函式,設定指令碼執行快取區大小。

  7. 根據 nScriptCheckThreads 變數的值,迴圈呼叫 threadGroup.create_thread 方法,建立指定數量的執行緒,並放入執行緒組。

    nScriptCheckThreads 變數在前面根據命令列引數 par 進行設定。

    執行緒內部呼叫 ThreadScriptCheck 函式進行執行。 ThreadScriptCheck 函式過程如下:

    • 首先呼叫 RenameThread 函式(內部呼叫 pthread_setname_np 函式)將當前執行緒重新命名為 bitcoin-scriptch

    • 然後呼叫 CCheckQueue 佇列物件的 Thread 方法,開啟內部迴圈。

      Thread 方法又呼叫內部私有方法 Loop 方法,生成一個指令碼驗證工作者,然後進行無限迴圈,在迴圈內部呼叫工作者的 wait(lock) 方法,從而執行緒進入阻塞,直到有新的任務被加到佇列中中時,才會被喚醒執行任務。

  8. 呼叫 boost::bind 方法,生成 CScheduler 物件 serviceQueue 方法的替代方法。然後呼叫 threadGroup.create_thread 方法,建立一個執行緒。

    執行緒執行的方法是 boost::bind 返回的替代方法,bind 方法的第一個引數為 TraceThread 函式,第二個引數為執行緒的名字,第三個引數為serviceQueue 方法的替代方法。

    TraceThread 函式內部呼叫 RenameThread 方法修改執行緒名字,此處執行緒名字修改為 bitcoin-scheduler;然後執行傳入的可呼叫物件,此處為前面的替代方法,即 CScheduler 物件 serviceQueue 方法。

    serviceQueue 方法主體是一個無限迴圈方法,如果佇列為空,則程序進入阻塞,直到佇列有任務,則醒來執行任務,並把任務從佇列中移除。

  9. 呼叫 GetMainSignals().RegisterBackgroundSignalScheduler 方法,註冊後臺訊號排程器。

  10. 呼叫 GetMainSignals().RegisterWithMempoolSignals 方法,註冊記憶體池訊號處理器。

  11. 呼叫行內函數 RegisterAllCoreRPCCommands ,註冊所有核心的 RPC 命令。

    第一步,呼叫 RegisterBlockchainRPCCommands 方法,註冊所有關於區塊鏈的 RPC 命令。

    第二步,呼叫 RegisterNetRPCCommands 方法,註冊所有關於網路相關的 RPC 命令。

    第三步,呼叫 RegisterMiscRPCCommands 方法,註冊所有的雜項 RPC 命令。

    第四步,呼叫 RegisterMiningRPCCommands 方法,註冊所有關於挖礦相關的 RPC 命令。

    第五步,呼叫 RegisterRawTransactionRPCCommands 方法,註冊所有關於原始交易的 RPC 命令。

  12. 呼叫錢包介面的 RegisterRPC 方法,註冊錢包介面的 RPC 命令。

    實現類為 wallet/init.cpp ,方法內部呼叫 RegisterWalletRPCCommands 進行註冊,後者又呼叫 wallet/rpcwallet.cpp 檔案中的 RegisterWalletRPCCommands 方法,完成註冊錢包的 RPC 命令。

  13. 如果命令引數指定 server ,則呼叫 AppInitServers 方法,註冊伺服器。

    方法內處理流程如下:

    • 呼叫 RPCServer::OnStarted 方法,設定 RPC 伺服器啟動時的處理方法。

    • 呼叫 RPCServer::OnStopped 方法,設定 RPC 伺服器關閉時的處理方法。

    • 呼叫 InitHTTPServer 方法,初始化 HTTP 伺服器。

    • 呼叫 StartRPC 方法,啟動 RPC 訊號監聽。

    • 呼叫 StartHTTPRPC 方法,啟動 HTTP RPC 伺服器。

      方法內部呼叫 RegisterHTTPHandler 方法,註冊 / 請求處理方法為 HTTPReq_JSONRPC 。呼叫 RegisterHTTPHandler 方法,註冊 /wallet/ 請求處理方法為 HTTPReq_JSONRPC

    • 如果命令引數指定 rest,呼叫 StartREST 方法,設定 /rest/xxx 一系列 HTTP 請求的處理器。

    • 呼叫 StartHTTPServer 方法,啟動 HTTP 伺服器。

第5步,驗證錢包資料庫完整性(src/init.cpp::AppInitMain()

呼叫錢包介面的 Verify 方法,驗證錢包資料庫。實現類為 wallet/init.cpp ,內部處理流程如下:

  • 檢查命令列指定了禁止錢包 disablewallet,如果禁止,則直接返回。
  • 如果設定了錢包 walletdir,則檢查錢包資料庫目錄是否存在,是否為目錄、且是否為常規的的路徑。
  • 檢查所有的錢包檔案:不存在名稱相同的,沒有問題(呼叫 CWallet::Verify 方法進行驗證)。

第6步,網路初始化(src/init.cpp::AppInitMain()

  1. 根據命令列引數 uacomment,處理使用者代理。
  2. 檢查版本是否大於 version 訊息指定的訊息最大長度。
  3. 如果指定了 onlynet 引數,則設定僅可以連線的節點。
  4. 如果指定了代理 proxy,且不等於 0,則:根據代理引數、dns查詢等,呼叫 Lookup 方法,查詢/設定代理伺服器;呼叫 SetProxy 方法,設定 IPv4、IPv6、Tor 網路的代理;呼叫 SetNameProxy 方法,設定命名(域名)代理;呼叫 SetLimited 方法,設定不自動連線到 Tor 網路。
  5. 如果指定了 onion 引數,則處理洋蔥網路的相關設定。
  6. 處理通過 externalip 引數設定的外部 IP,呼叫 Lookup 方法查詢外部地址,如果成功則呼叫 AddLocal 方法,儲存新的地址。
  7. 如果設定了 maxuploadtarget 引數,則設定最大出站限制。

第7步,載入區塊鏈(src/init.cpp::AppInitMain()

首先,計算快取的大小。包括:區塊索引資料庫、區塊狀態資料庫、記憶體中 UTXO 集。

然後,以下開始迴圈處理。

  1. 呼叫 UnloadBlockIndex 方法,解除安裝區塊相關的索引。
  2. 重置 Coin 相關的結構。
  3. 呼叫 LoadBlockIndex 方法,載入區塊索引。
  4. 呼叫 LookupBlockIndex 方法,載入區塊索引,並檢查是否包含創世區塊。如果出錯,則返回異常。
  5. 如果指定有修剪,但又沒有處於修剪模式,則退出迴圈。
  6. 如果不重建索引,呼叫 LoadGenesisBlock 載入創世區塊失敗,則退出迴圈。
  7. 檢查是否需要升級資料庫格式。

第8步,開始索引(src/init.cpp::AppInitMain()

如果指定了 txindex 引數,則呼叫 MakeUnique 函式,生成交易索引物件,然後呼叫其 Start 方法,開始建立索引。

第9步,載入錢包(src/init.cpp::AppInitMain()

呼叫錢包介面物件的 Open 方法,開始載入錢包。

第10,資料目錄維護(src/init.cpp::AppInitMain()

如果當前為修剪模式,本地服務去掉 NODE_NETWORK,然後如果不需要索引則呼叫 PruneAndFlush 函式,修剪並重新整理。

第11步,匯入區塊(src/init.cpp::AppInitMain()

  1. 呼叫 CheckDiskSpace 函式,檢查硬碟空間是否足夠。

    如果沒有足夠的硬碟空間,則退出。

  2. 檢查最佳區塊鏈頂端指示指標。

    如果頂端打針為空,UI介面進行通知。如果不空,則設定有創世區塊,即 fHaveGenesis 設為真。

  3. 如果指定了 blocknotify 引數,設定介面通知為 BlockNotifyCallback

  4. 遍歷引數 loadblock 指定要載入的區塊檔案,放進向量變數 vImportFiles中。然後呼叫 threadGroup.create_thread 方法,建立一個執行緒。執行緒執行的函式為 ThreadImport,引數為要載入的區塊檔案。

  5. 獲取 cs_GenesisWait 鎖,等待創世區塊被處理完成。

第12步,啟動節點(src/init.cpp::AppInitMain()

  1. 如果指定了監聽洋蔥網路 listenonion,呼叫 StartTorControl 函式,開始 Tor 控制。

  2. 呼叫 Discover 函式,開始發現外部節點。

  3. 如果指定了 upnp 引數,則呼叫 StartMapPort 函式,開始進行埠對映。

  4. 生成選項物件,並進行初始化。

  5. 如果指定了 bind 引數,則對所有的繫結地址,呼叫 Lookup 方法,查詢並進行繫結,然後放入選項物件的 vBinds 屬性中。

  6. 如果指定了 whitebind 引數,則對所有的繫結地址,呼叫 Lookup 方法,查詢並進行繫結,然後放入選項物件的 vWhiteBinds 屬性中。

    whitebind 引數指定的地址需要帶有埠號。

  7. 如果指定了 whitelist 引數,遍歷列表,呼叫 LookupSubNet 方法,生成對應的子網,然後放入選項物件的 vWhitelistedRange 屬性中。

  8. 取得引數 seednode 指定的值,放入選項物件的 vSeedNodes 屬性中。

  9. 呼叫 CConnman 物件的 Start 方法,初始所有的出站連線

    本方法非常非常重要,因為它啟動了一個重要的流程,即底層的 P2P 網路建立和訊息處理流動。

第13步,結束啟動(src/init.cpp::AppInitMain()

  1. 呼叫錢包介面物件的 Start 方法,開始進行錢包相關的處理,並定時重新整理錢包資料到資料庫中。

我是區小白,區塊鏈開發者,區塊鏈技術愛好者,深入研究比特幣,以太坊,EOS Dash,Rsk,Java, Nodejs,PHP,Python,C++ 現為Ulord全球社群聯盟(優得社群)核心開發者。

我希望能聚集更多區塊鏈開發者,一起學習共同進步。

敬請期待下一篇文章:如何啟動比特幣系統並加入比特幣網路