bitcoin原始碼分析

尹成發表於2018-05-13

1 必讀–如何閱讀org文件

如何開啟org文件
可以直接在github上開啟org文件,但是為了方便的跳轉到原始碼,請使用Emacs編輯器開啟org文件!Windows環境的下載連結,Linux下直接使用apt-get或者yum直接安裝emacs即可;
如何使用TAB按鍵
在*和**、***以及更多星號開頭的標題上敲擊鍵盤上的Tab按鍵,可以展開和隱藏這個標題裡的內容;
如何理解行首的冒號
行首的冒號(:)是方便org文件輸出到HTML文件重點標註程式碼和命令,除了在#+BEGINEXAMPLE和#+ENDEXAMPLE裡原樣輸出;
如何生成HTML文件
選單欄裡選擇Org->Export/Publish,會呼叫匯出HTML的選項;
如何連結到原始碼
比如file:~/.bitcoin ,將游標移動到file開頭的連結上,滑鼠點選,就會自動跳轉到原始碼了,如果是目錄,就會開啟目錄。
下載bitcoin原始碼
github網站上直接下載或者使用命令列工具:
git clone --branch v0.8.2 https://github.com/bitcoin/bitcoin.git

注意將bitcoin原始碼目錄放在~目錄下,目錄名為bitcoin,以便迅速在Emacs編輯器中開啟bitcoin原始碼,Windows下的目錄一般為:

C:\Documents and Settings\Administrator\Applicatin Data

如果沒有這個目錄,可以用如下命令檢視目錄路徑

set appdata

2 準備工作

假設你已經對STL及gdb有了一些基本認識,熟悉C++程式設計。

2.1 1.產生除錯資訊

在configure.ac檔案裡增加2行程式碼(注意:行首有冒號):

AC_INIT([Bitcoin]...
: ${CFLAGS="-g -ggdb"}
: ${CXXFLAGS="-g -ggdb"}

按照doc/build-unix.md檔案裡的的要求重新配置並編譯:

./autogen.sh
./configure
make -B //如果是第一次編譯,不需要-B

這樣在輸出的.o檔案及elf檔案裡就會包含有除錯資訊,否則預設會使用-O2優化選項。

2.2 2.gdb裡增加對stl的支援

bitcoin裡大量使用了stl,方便在Linux、Windows、Mac間移植。 7.0以後的gdb已經增加了對Python的支援,通過Python,增加gdb對STL的支援: http://sourceware.org/gdb/wiki/STLSupport

3 開始分析

3.1 靜態分析

3.1.1 配置檔案、快鏈資料檔案、索引檔案及相關目錄:

在root下有一個目錄.bitcoin,首先介紹下幾個檔案: file:~/.bitcoin

bitcoin.conf
配置檔案,bitcoind啟動的時候會讀取這個檔案
debug.log
除錯資訊輸出到這個檔案裡
peers.dat
儲存的其他peer的資訊
wallet.dat
錢包檔案
blocks
快鏈(block chain)儲存的地方
chainstate
快鏈的狀態
testnet3
用於測試的快鏈,有一個不同的起始塊(genesis block),testnet1中的起始塊,就是目前大家在交易的塊鏈。

3.1.2 原始碼檔案說明:

.h檔案及.cpp檔案中類的定義及說明: Doxygen自動產生的說明

3.2 動態分析

3.2.1 配置bitcoin.conf

testnet=1

使用測試網路,詳見bitcoin.conf的詳細配置

3.2.2 開始除錯

gdb bitcoind

好了,從bitcoind裡讀取symbol的時間可能會稍微長點:

Reading symbols from bitcoin/src/bitcoind...done.

開始除錯:

b main
run
int main(int argc, char* argv[])
{
    bool fRet = false;

    // Connect bitcoind signal handlers
    noui_connect();

    fRet = AppInit(argc, argv);

    if (fRet && fDaemon)
        return 0;

    return (fRet ? 0 : 1);
 }

main()函式的重點是AppInit(),nouiconnect()是用來連結前端Qt的初始化程式,圖形介面我們就不分析了,直接進入到AppInit()

1. bool AppInit(int argc, char* argv[])
2. {
3.     boost::thread_group threadGroup;//使用boost的多執行緒,使得前端Qt程式執行流暢
4.     boost::thread* detectShutdownThread = NULL;//在程式啟動期間,如果按了Ctrl-C鍵退出,則讓多執行緒處理完後退出

5.     bool fRet = false;
6.     try
7.     {
8.         //
9.         // Parameters
10.         //
11.         // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main()
12.         ParseParameters(argc, argv);
13.         if (!boost::filesystem::is_directory(GetDataDir(false)))
14.         {
15.             fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", mapArgs["-datadir"].c_str());
16.             return false;
17.         }
18.         ReadConfigFile(mapArgs, mapMultiArgs);
19.         // Check for -testnet or -regtest parameter (TestNet() calls are only valid after this clause)
20.         if (!SelectParamsFromCommandLine()) {
21.             fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n");
22.             return false;
23.         }

24.         if (mapArgs.count("-?") || mapArgs.count("--help"))
25.         {
26.             // First part of help message is specific to bitcoind / RPC client
27.             std::string strUsage = _("Bitcoin Core Daemon") + " " + _("version") + " " + FormatFullVersion() + "\n\n" +
28.                 _("Usage:") + "\n" +
29.                   "  bitcoind [options]                     " + _("Start Bitcoin server") + "\n" +
30.                 _("Usage (deprecated, use bitcoin-cli):") + "\n" +
31.                   "  bitcoind [options] <command> [params]  " + _("Send command to Bitcoin server") + "\n" +
32.                   "  bitcoind [options] help                " + _("List commands") + "\n" +
33.                   "  bitcoind [options] help <command>      " + _("Get help for a command") + "\n";

34.             strUsage += "\n" + HelpMessage(HMM_BITCOIND);
35.             strUsage += "\n" + HelpMessageCli(false);

36.             fprintf(stdout, "%s", strUsage.c_str());
37.             return false;
38.         }

39.         // Command-line RPC
40.         bool fCommandLine = false;
41.         for (int i = 1; i < argc; i++)
42.             if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "bitcoin:"))
43.                 fCommandLine = true;

44.         if (fCommandLine)
45.         {
46.             int ret = CommandLineRPC(argc, argv);
47.             exit(ret);
48.         }
49. #ifndef WIN32
50.         fDaemon = GetBoolArg("-daemon", false);
51.         if (fDaemon)
52.         {
53.             fprintf(stdout, "Bitcoin server starting\n");

54.             // Daemonize
55.             pid_t pid = fork();
56.             if (pid < 0)
57.             {
58.                 fprintf(stderr, "Error: fork() returned %d errno %d\n", pid, errno);
59.                 return false;
60.             }
61.             if (pid > 0) // Parent process, pid is child process id
62.             {
63.                 CreatePidFile(GetPidFile(), pid);
64.                 return true;
65.             }
66.             // Child process falls through to rest of initialization

67.             pid_t sid = setsid();
68.             if (sid < 0)
69.                 fprintf(stderr, "Error: setsid() returned %d errno %d\n", sid, errno);
70.         }
71. #endif
72.         SoftSetBoolArg("-server", true);

73.         detectShutdownThread = new boost::thread(boost::bind(&DetectShutdownThread, &threadGroup));
74.         fRet = AppInit2(threadGroup);
75.     }
76.     catch (std::exception& e) {
77.         PrintExceptionContinue(&e, "AppInit()");
78.     } catch (...) {
79.         PrintExceptionContinue(NULL, "AppInit()");
80.     }

程式碼裡增加了一些中文註釋,作為對英文註釋的補充。

  • try..catch{}方式處理這段程式碼,因為這裡涉及大量磁碟讀取,有可能碰到無法開啟檔案,磁碟空間滿等其他問題。
  • ParseParameters()及ReadConfigFile()都是在讀取一些引數,需要注意的是,命令列引數的優先順序高於配置檔案,比如執行了如下語句
    bitcoind -testnet=0
    

則在bitcoin.conf中配置的testnet則無效了。

引數最終儲存在mapArgs及mapMultiArgs中,定義如下:

map<string, string> mapArgs;
map<string, vector<string> > mapMultiArgs;

使用了STL的map定義的,後面會經常使用這幾個函式GetBoolArg()、GetArg()來檢視引數設定,如:

bool GetBoolArg(const std::string& strArg, bool fDefault)
{
    if (mapArgs.count(strArg))
    {
        if (mapArgs[strArg].empty())
            return true;
        return (atoi(mapArgs[strArg]) != 0);
    }
    return fDefault;
    }

經常會看到這樣呼叫GetBoolArg():

GetBoolArg("-daemon", false)

如果沒有找到相關設定,則返回假。

  • 24行到38行是列印幫助提示。
  • 介紹下面的內容前,先介紹下RPC(遠端過程呼叫),命令列方式下RPC的使用方法:
    bitcoind -daemon //後臺執行
    bitcoind getinfo //獲取狀態資訊
    bitcoind -stop//停止daemon程式
    
  • 39行到48行判斷是否是上面第二行(getinfo)的語句:首先判斷引數是否有'-'或'/',並且不包含'bitcoin:',則執行RPC(使用的是JSON-RPC呼叫協議)

bitcoin:URI是用於請款的,所以應該排除這種情況: bitcoin://1F2EUzKR1XsLRCtEnsnpDQZ13XJgS6P3ZK?amount=0.001&message=donation

接著執行CommandLineRPC(),具體執行RPC(遠端過程呼叫).

  • 49行到71行是處理-daemon引數,假設這樣執行bitcond
    bitcoind -daemon
    

通過fork()建立一個子程式,建立成功,父程式則在64行的時候返回,子程式接著執行下面的初始化,包括AppInit2()。 在.bitcoin目錄下建立了一個bitcoind.pid的檔案,記錄了子程式的PID。

file:~/bitcoin/src/init.cpp::bool AppInit2 boost threadgroup threadGroup

/** Initialize bitcoin.
 *  @pre Parameters should be parsed and config file should be read.
 */
bool AppInit2(boost::thread_group& threadGroup)
{
    // ****************** Step 1: setup   設定
    ...
    // ****************** Step 2: parameter interactions   引數互動(主要是一些引數設定)
    ...
    // ****************** Step 3: parameter-to-internal-flags   引數傳入內部標記(bool型變數)
    ...
    // ****************** Step 4: application initialization: dir lock, daemonize, pidfile, debug log   應用初始化:鎖定目錄,後臺執行,除錯資訊
    ...
    // ****************** Step 5: verify wallet database integrity   確認錢包資料庫的完整性
    ...
    // ****************** Step 6: network initialization   網路初始化
    ...
    // ****************** Step 7: load block chain   載入塊鏈
    ...
    // ****************** Step 8: load wallet   載入錢包
    ...
    // ****************** Step 9: import blocks   匯入塊資料
    ...
    // ****************** Step 10: load peers   匯入peers
    ...
    // ****************** Step 11: start node   開始節點(挖礦程式在這裡)
    ...
    // ****************** Step 12: finished  完成
    ...
}
  • AppInit2裡包含了bitcoin的大部分初始程式,包括讀取'塊索引'、載入塊鏈、載入100個預產生的keys,匯入peers.dat中的資訊,以及初始化其他執行緒。






網址:http://www.qukuailianxueyuan.io/



欲領取造幣技術與全套虛擬機器資料

區塊鏈技術交流QQ群:756146052  備註:CSDN

尹成學院微信:備註:CSDN


相關文章