從零開始學習區塊鏈技術(二):如何接入比特幣網路以及其原理分析
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); }
這段程式碼簡單說明如下:
-
SetupEnvironment
函式,主要用來設定系統的環境變數,包括:malloc
分配記憶體的行為、Locale、檔案路徑的本地化設定等。 -
noui_connect
函式,設定連線到 bitcoind 的訊號的處理。 -
AppInit
函式,進行系統啟動。
下面我們重點講下 AppInit
函式的執行
- 呼叫
SetupServerArgs
函式,設定系統可接受的所有命令列引數。然後開始解析命令列傳遞的各種引數。系統執行的重要一步就是設定可以接收的引數並解析使用者啟動時傳遞的各種引數,SetupServerArgs
函式就是完成這個目的。下面來看這個函式的執行流程。
- 首先,呼叫
CreateBaseChainParams
函式,生成預設的基本引數,包括:使用的資料目錄和監聽的埠。根據不同的網路型別,主網路使用 8332 埠和指定目錄下的當前目錄,測試網路使用 18332 埠和指定目錄下的 testnet3 子目錄,迴歸測試網路 使用 18443 埠和指定目錄下的 regtest 子目錄。 - 然後,呼叫
CreateChainParams
函式,生成預設的區塊鏈引數。這個方法也會區分不同的網路。如果是主網路,則生成CMainParams
物件進行初始化。在建構函式中,進行如下的設定:
- 設定網路ID 為
main
; - 設定共識引數(
Consensus::Params
)的各個值: - 每隔多少個塊(
nSubsidyHalvingInterval
)後續比特幣的獎勵會減半,值為 210000。根據創世區塊獎勵的數量(50),根據等比數列求和公式:
50∗(1/(1−0.5))∗210000″ role=”presentation” style=”box-sizing: border-box; outline: 0px; display: inline; line-height: normal; word-spacing: normal; overflow-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; padding: 0px; margin: 0px; word-break: break-all; position: relative;”>50∗(1/(1−0.5))∗21000050∗(1/(1−0.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.be
,dnsseed.bluematt.me
,dnsseed.bitcoin.dashjr.org
,seed.bitcoinstats.com
,seed.bitcoin.jonasschnelli.ch
,seed.btc.petertodd.org
,seed.bitcoin.sprovoost.nl
等,通過解析 DNS 種子節點,比特幣節點啟動時可以找到更多的對等節點來進行連線。 - 接下來,設定相關的檢查點資料。
如果是測試網路,則生成
CTestNetParams
物件進行初始化。(供開發完成後測試使用。)如果是迴歸測試網路,則生成
CRegTestParams
物件進行初始化。(供開發時連線使用。)對於這兩種測試網路,處理基本和主網路相同,只是某些引數不一樣。
上面提到的3個物件
CMainParams
CTestNetParams
CRegTestParams
的定義都在chainparams.cpp
檔案中。感興趣同學的可以對照原始碼進一步探究。 - 設定網路ID 為
- 接下來,設定系統可接收的所有引數。部分引數解釋如下:
- ?,顯示幫助資訊;
- -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 介面,我們呼叫比特幣核心提供的多種服務。這些命令通常會在配置檔案中進行設定,不用在命令列指定。
- 首先,呼叫
- 接下來,檢查使用者指定命令引數是否正確。
if (!gArgs.ParseParameters(argc, argv, error)) { fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); return false; }
- 如果傳遞的是幫助和版本引數,則顯示幫助或版本資訊,然後退出。
- 檢查資料目錄(可指定或預設)是否是存在。如果不存在,則列印錯誤資訊,然後退出。
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
引數來確定使用預設的資料目錄還是使用者指定的資料目錄。 - 讀取並解析配置檔案,同時檢查指定資料目錄是否存在。如果任何一個步驟出錯,都列印錯誤資訊,然後退出。
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
集合中。
- 首先,呼叫
- 呼叫
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
物件包含了區塊鏈物件的所有重要資訊,比如:共識規則、部署狀態、檢查點、創世區塊等。 - 檢查所有命令列引數,如果有錯誤,則列印錯誤,並退出。
- 設定引數
-server
預設為真。bitcoind 守護程序預設server
為真。 - 呼叫
InitLogging
函式,初始化系統所用日誌,並列印系統的版本資訊。具體程式碼如下,根據是否指定debuglogfile
、printtoconsole
等確定日誌列印到檔案或是控制檯。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
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 呼叫
InitParameterInteraction
函式,根據引數間的關係,檢查所有的互動引數。 - 呼叫
AppInitBasicSetup
函式,進行基本的設定。如果有錯誤,則列印錯誤,然後退出。經過前面漫長的檢查與設定,終於開始了應用基本的設定。具體解讀見第二部分。 - 呼叫
AppInitSanityChecks
函式,處理底層加密函式相關內容。具體解讀見第二部分。 - 呼叫
AppInitLockDataDirectory
函式,檢查並鎖定資料目錄。具體解讀見第二部分。 - 呼叫
AppInitMain
函式,比特幣主要的啟動過程。具體解讀見第二部分。 - 如果應用初始化主函數出錯,則呼叫
Interrupt
函式進行中止,否則呼叫WaitForShutdown
函式等待系統結束。WaitForShutdown
函式是一個無限迴圈函式。
2、系統啟動詳述
以下為系統啟動過程中重要的步驟。
第1步,應用初始化基本設定( src/bitcoind.cpp
)
AppInitBasicSetup
函式進行基本的設定。
- 呼叫
SetupNetworking
函式,進行網路設定。主要是針對 Win32 系統處理套接字,別的系統直接返回真。 - 如果不是 WIN32 系統,進行下面的處理:
- 如果設定
sysperms
引數為真,呼叫umask
函式,設定位碼為 077。 - 呼叫
registerSignalHandler
函式,設定SIGTERM
資訊處理器為HandleSIGTERM
;SIGINT
為HandleSIGTERM
;SIGHUP
為HandleSIGHUP
。
- 如果設定
第2步,應用初始引數互動設定( src/bitcoind.cpp
)
AppInitParameterInteraction
函式前半部分。
- 首先,呼叫
Params
方法,獲取前面初始化的globalChainParams
區塊鏈物件。 - 檢查指定的區塊目錄是否存。如果不存在,則返回初始化錯誤。
- 如果同時指定了
prune
、txindex
,則丟擲初始化錯誤。如果指定了區塊修剪prune
,就要禁止交易索引txindex
,兩者不相容,只能其一。 - 如果同時指定了
bind
或whitebind
就不能同時指定listen
。 - 確保有足夠的檔案符可用。因為在類 Unix 系統中,每個套接字都是一個檔案,都需要一個檔案描述符。所以要檢查指定的最大連線數
maxconnections
是否超過系統可用限制。
第3步,引數到內部標誌的處理( src/bitcoind.cpp
)
AppInitParameterInteraction
函式後半部分。
- 處理
debug
、debugexclude
、debugnet
等引數。 - 如果指定了
socks
,則提示使用 SOCKS5 - 如果指定了
tor
,則提示使用onion
。 - 如果指定了
benchmark
,則提示使用-debug=bench
。 - 如果指定了
whitelistalwaysrelay
,則提示使用whitelistrelay
,或whitelistforcerelay
。 - 如果指定了
blockminsize
,則提示使用blockminsize
。 - 在迴歸模式
regtest
下,Checkmempool
和checkblockindex
預設為真。 - 處理
assumevalid
引數。 - 根據是否指定
minimumchainwork
,計算最小區塊鏈工作量。 - 計算記憶體池限制,包括處理
maxmempool
、limitdescendantsize
- 如果指定了
incrementalrelayfee
,則進行相關處理。 - 處理
par
引數。 - 處理區塊修剪引數
prune
。 - 處理連線超時時間
timeout
。 - 處理
minrelaytxfee
引數。 - 處理
blockmintxfee
引數。 - 處理
dustrelayfee
引數。 - 處理
acceptnonstdtxn
引數。 - 處理
bytespersigop
引數。 - 呼叫錢包初始介面物件的
ParameterInteraction
方法,初始錢包相關的引數。本方法在wallet/init.cpp
檔案中。- 檢查是否禁止錢包
disablewallet
。 - 設定
wallet
在沒有指定情況預設為空字串。 - 處理
blocksonly
、walletbroadcast
引數。 - 處理
salvagewallet
、rescan
引數。 - 處理
zapwallettxes
、persistmempool
引數。 - 處理
upgradewallet
引數。 - 處理
maxtxfee
引數。
- 檢查是否禁止錢包
- 獲取
permitbaremultisig
、datacarrier
、datacarriersize
等引數的值。 - 呼叫
SetMockTime
方法,設定模擬時間。 - 根據
peerbloomfilters
引數,設定本地支援的服務。 - 檢測
rpcserialversion
引數是否小於0,是否大於1。 - 獲取
maxtipage
引數值,表示區塊鏈頂端值存活時間。 - 處理
mempoolreplacement
引數。 - 處理
vbparams
引數。
第4步,檢查相關的加密函式( src/bitcoind.cpp
)
AppInitSanityChecks
函式初始相關的加密曲線與函式。
同時,呼叫 LockDataDirectory
函式,鎖定資料目錄,確保只有 Bitcoind 在執行。
第4a 步,應用程式初始化( src/init.cpp::AppInitMain()
)
AppInitMain
函式是應用初始化的主體,包括本步驟在內的以下步驟的主體都是在這個函式內部執行。
- 呼叫
Params
函式,獲取chainparams
。方法定義在src/chainparams.cpp
檔案中。這個變數主要是包含一些共識的引數,自身是根據選擇不同的網路main
、testnet
或者regtest
來生成不同的引數。 - 如果是非 Windows 系統,則呼叫
CreatePidFile
函式,建立程序的PID檔案。pid 檔案簡介如下:- pid檔案的內容pid檔案為文字檔案,內容只有一行, 記錄了該程序的ID。 用cat命令可以看到。
- pid檔案的作用防止程序啟動多個副本。只有獲得pid檔案(固定路徑固定檔名)寫入許可權(F_WRLCK)的程序才能正常啟動並把自身的PID寫入該檔案中。其它同一個程式的多餘程序則自動退出。
- 如果命令列指定了
shrinkdebugfile
引數或預設的除錯檔案,則呼叫日誌物件的ShrinkDebugFile
方法,處理debug.log
檔案。如果日誌長度小於11MB,那麼就不做處理;否則讀取檔案的最後RECENT_DEBUG_HISTORY_SIZE
10M 內容,重新儲存到debug.log檔案中。 - 呼叫日誌物件的
OpenDebugLog
方法,開啟日誌檔案。如果不能開啟則丟擲異常。 - 呼叫
InitSignatureCache
函式,設定簽名緩衝區大小。 - 呼叫
InitScriptExecutionCache
函式,設定指令碼執行快取區大小。 - 根據
nScriptCheckThreads
變數的值,迴圈呼叫threadGroup.create_thread
方法,建立指定數量的執行緒,並放入執行緒組。nScriptCheckThreads
變數在前面根據命令列引數par
進行設定。執行緒內部呼叫ThreadScriptCheck
函式進行執行。ThreadScriptCheck
函式過程如下:- 首先呼叫
RenameThread
函式(內部呼叫pthread_setname_np
函式)將當前執行緒重新命名為bitcoin-scriptch
。 - 然後呼叫
CCheckQueue
佇列物件的Thread
方法,開啟內部迴圈。Thread
方法又呼叫內部私有方法Loop
方法,生成一個指令碼驗證工作者,然後進行無限迴圈,在迴圈內部呼叫工作者的wait(lock)
方法,從而執行緒進入阻塞,直到有新的任務被加到佇列中中時,才會被喚醒執行任務。
- 首先呼叫
- 呼叫
boost::bind
方法,生成CScheduler
物件serviceQueue
方法的替代方法。然後呼叫threadGroup.create_thread
方法,建立一個執行緒。執行緒執行的方法是boost::bind
返回的替代方法,bind
方法的第一個引數為TraceThread
函式,第二個引數為執行緒的名字,第三個引數為serviceQueue
方法的替代方法。TraceThread
函式內部呼叫RenameThread
方法修改執行緒名字,此處執行緒名字修改為bitcoin-scheduler
;然後執行傳入的可呼叫物件,此處為前面的替代方法,即CScheduler
物件serviceQueue
方法。serviceQueue
方法主體是一個無限迴圈方法,如果佇列為空,則程序進入阻塞,直到佇列有任務,則醒來執行任務,並把任務從佇列中移除。 - 呼叫
GetMainSignals().RegisterBackgroundSignalScheduler
方法,註冊後臺訊號排程器。 - 呼叫
GetMainSignals().RegisterWithMempoolSignals
方法,註冊記憶體池訊號處理器。 - 呼叫行內函數
RegisterAllCoreRPCCommands
,註冊所有核心的 RPC 命令。第一步,呼叫RegisterBlockchainRPCCommands
方法,註冊所有關於區塊鏈的 RPC 命令。第二步,呼叫RegisterNetRPCCommands
方法,註冊所有關於網路相關的 RPC 命令。第三步,呼叫RegisterMiscRPCCommands
方法,註冊所有的雜項 RPC 命令。第四步,呼叫RegisterMiningRPCCommands
方法,註冊所有關於挖礦相關的 RPC 命令。第五步,呼叫RegisterRawTransactionRPCCommands
方法,註冊所有關於原始交易的 RPC 命令。 - 呼叫錢包介面的
RegisterRPC
方法,註冊錢包介面的 RPC 命令。實現類為wallet/init.cpp
,方法內部呼叫RegisterWalletRPCCommands
進行註冊,後者又呼叫wallet/rpcwallet.cpp
檔案中的RegisterWalletRPCCommands
方法,完成註冊錢包的 RPC 命令。 - 如果命令引數指定
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()
)
- 根據命令列引數
uacomment
,處理使用者代理。 - 檢查版本是否大於
version
訊息指定的訊息最大長度。 - 如果指定了
onlynet
引數,則設定僅可以連線的節點。 - 如果指定了代理
proxy
,且不等於 0,則:根據代理引數、dns
查詢等,呼叫Lookup
方法,查詢/設定代理伺服器;呼叫SetProxy
方法,設定 IPv4、IPv6、Tor 網路的代理;呼叫SetNameProxy
方法,設定命名(域名)代理;呼叫SetLimited
方法,設定不自動連線到 Tor 網路。 - 如果指定了
onion
引數,則處理洋蔥網路的相關設定。 - 處理通過
externalip
引數設定的外部 IP,呼叫Lookup
方法查詢外部地址,如果成功則呼叫AddLocal
方法,儲存新的地址。 - 如果設定了
maxuploadtarget
引數,則設定最大出站限制。
第7步,載入區塊鏈( src/init.cpp::AppInitMain()
)
首先,計算快取的大小。包括:區塊索引資料庫、區塊狀態資料庫、記憶體中 UTXO 集。
然後,以下開始迴圈處理。
UnloadBlockIndex LoadBlockIndex LookupBlockIndex LoadGenesisBlock
第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()
)
- 呼叫
CheckDiskSpace
函式,檢查硬碟空間是否足夠。如果沒有足夠的硬碟空間,則退出。 - 檢查最佳區塊鏈頂端指示指標。如果頂端打針為空,UI介面進行通知。如果不空,則設定有創世區塊,即
fHaveGenesis
設為真。 - 如果指定了
blocknotify
引數,設定介面通知為BlockNotifyCallback
。 - 遍歷引數
loadblock
指定要載入的區塊檔案,放進向量變數vImportFiles
中。然後呼叫threadGroup.create_thread
方法,建立一個執行緒。執行緒執行的函式為ThreadImport
,引數為要載入的區塊檔案。 - 獲取
cs_GenesisWait
鎖,等待創世區塊被處理完成。
第12步,啟動節點( src/init.cpp::AppInitMain()
)
- 如果指定了監聽洋蔥網路
listenonion
,呼叫StartTorControl
函式,開始 Tor 控制。 - 呼叫
Discover
函式,開始發現外部節點。 - 如果指定了
upnp
引數,則呼叫StartMapPort
函式,開始進行埠對映。 - 生成選項物件,並進行初始化。
- 如果指定了
bind
引數,則對所有的繫結地址,呼叫Lookup
方法,查詢並進行繫結,然後放入選項物件的vBinds
屬性中。 - 如果指定了
whitebind
引數,則對所有的繫結地址,呼叫Lookup
方法,查詢並進行繫結,然後放入選項物件的vWhiteBinds
屬性中。whitebind
引數指定的地址需要帶有埠號。 - 如果指定了
whitelist
引數,遍歷列表,呼叫LookupSubNet
方法,生成對應的子網,然後放入選項物件的vWhitelistedRange
屬性中。 - 取得引數
seednode
指定的值,放入選項物件的vSeedNodes
屬性中。 - 呼叫
CConnman
物件的Start
方法,初始所有的出站連線。本方法非常非常重要,因為它啟動了一個重要的流程,即底層的 P2P 網路建立和訊息處理流動。
第13步,結束啟動( src/init.cpp::AppInitMain()
)
- 呼叫錢包介面物件的
Start
方法,開始進行錢包相關的處理,並定時重新整理錢包資料到資料庫中。
我是區小白,區塊鏈開發者,區塊鏈技術愛好者,深入研究比特幣,以太坊,EOS Dash,Rsk,Java, Nodejs,PHP,Python,C++ 現為Ulord全球社群聯盟(優得社群)核心開發者。
我希望能聚集更多區塊鏈開發者,一起學習共同進步。
敬請期待下一篇文章:如何啟動比特幣系統並加入比特幣網路
版權宣告: ofollow,noindex">
作者保留權利。文章為作者獨立觀點,不代表巴位元立場。