比特幣源碼學習0.13(二)

源碼0.13.2版的,在sublime中打開的



默認文件在src文件夾下,ide改爲sublime,在跳轉到定義的體驗好很多

6.2選擇比特幣網絡

比特幣網絡分爲主網、測試網以及私有網三種網絡:
主網:Main network
測試網:Testnet (v3)
私有網:Regression test
接上面的代碼

 // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
        try {
            SelectParams(ChainNameFromCommandLine());
        } catch (const std::exception& e) {
            fprintf(stderr, "Error: %s\n", e.what());
            return false;
        }

代碼註釋的含義爲檢查testnet或regtest參數(Params()僅在該子句之後生效),try子句中調用了SelectParams()函數,是以ChainNameFromCommandLine()的返回值作爲參數,先來看ChainNameFromCommandLine()函數,位於chainparamsbase.cpp

std::string ChainNameFromCommandLine()
{
    bool fRegTest = GetBoolArg("-regtest", false);
    bool fTestNet = GetBoolArg("-testnet", false);

    if (fTestNet && fRegTest)
        throw std::runtime_error("Invalid combination of -regtest and -testnet.");
    if (fRegTest)
        return CBaseChainParams::REGTEST;
    if (fTestNet)
        return CBaseChainParams::TESTNET;
    return CBaseChainParams::MAIN;

首先獲取-testnet-regtest的參數設置,兩個都設置的話扔出錯誤,根據設置返回,都沒設置的話返回主網。

class CBaseChainParams
{
public:
    /** BIP70 chain name strings (main, test or regtest) */
    static const std::string MAIN;
    static const std::string TESTNET;
    static const std::string REGTEST;

返回值爲字符串常量,在chainparamsbase.h的定義如上,在chainparamsbase.cpp定義如下

const std::string CBaseChainParams::MAIN = "main";
const std::string CBaseChainParams::TESTNET = "test";
const std::string CBaseChainParams::REGTEST = "regtest";

在獲得鏈的網絡名稱後,我們來看SelectParams()這個函數,位於chainparams最後幾行

void SelectParams(const std::string& network)
{
    SelectBaseParams(network);
    pCurrentParams = &Params(network);
}

這個函數中首先調用SelectBaseParams函數,位於chainpatamsbase.cpp

void SelectBaseParams(const std::string& chain)
{
    pCurrentBaseParams = &BaseParams(chain);
}

這個函數是實現對pCurrentBaseParams的賦值,pCurrentBaseParamsCBaseChainParams類型的

static CBaseChainParams* pCurrentBaseParams = 0;

CBaseChainParams是三種網絡類型參數的基類,三種網絡參數類位於chainpatamsbase.cpp

class CBaseMainParams : public CBaseChainParams
{
public:
    CBaseMainParams()
    {
        nRPCPort = 8332;
    }
};
static CBaseMainParams mainParams;

/**
 * Testnet (v3)
 */
class CBaseTestNetParams : public CBaseChainParams
{
public:
    CBaseTestNetParams()
    {
        nRPCPort = 18332;
        strDataDir = "testnet3";
    }
};
static CBaseTestNetParams testNetParams;

/*
 * Regression test
 */
class CBaseRegTestParams : public CBaseChainParams
{
public:
    CBaseRegTestParams()
    {
        nRPCPort = 18332;
        strDataDir = "regtest";
    }
};
static CBaseRegTestParams regTestParams;

在每個類的構造函數中定義了對應使用的端口以及數據保存目錄。再來看BaseParams()的函數實現,位於chainpatamsbase.cpp

CBaseChainParams& BaseParams(const std::string& chain)
{
    if (chain == CBaseChainParams::MAIN)
        return mainParams;
    else if (chain == CBaseChainParams::TESTNET)
        return testNetParams;
    else if (chain == CBaseChainParams::REGTEST)
        return regTestParams;
    else
        throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain));
}

就是根據傳入的參數返回對應的參數類對象。
最後再來看SelectParams中的pCurrentParams = &Params(network);,可以發現Params()函數與BaseParams()函數實現是一樣的,都是根據傳入參數獲取對應的類對象,區別在於返回類對象的不同,Params()返回的是CChainParams&類型的,這個類型是鏈的三個不同網絡參數的基類,截取部分代碼可以看到,這個類相比帶base的類的配置信息更多,可以理解爲base是基本參數,這一步是設置相應鏈的主要參數

//chainparams.cpp
class CMainParams : public CChainParams {
public:
    CMainParams() {
        strNetworkID = "main";
        consensus.nSubsidyHalvingInterval = 210000;//區塊獎勵減半間隔
        consensus.nMajorityEnforceBlockUpgrade = 750;
        consensus.nMajorityRejectBlockOutdated = 950;
        consensus.nMajorityWindow = 1000;
        consensus.BIP34Height = 227931;
        consensus.BIP34Hash = uint256S("0x000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8");
        consensus.powLimit = uint256S("00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff");//算力極限值
        consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
        consensus.nPowTargetSpacing = 10 * 60;//算力修改間隔
        // The best chain should have at least this much work.
        consensus.nMinimumChainWork = uint256S("0x0000000000000000000000000000000000000000003418b3ccbe5e93bcb39b43");//最低工作量
        //創世塊,第一個塊獎勵50個比特幣
        genesis = CreateGenesisBlock(1231006505, 2083236893, 0x1d00ffff, 1, 50 * COIN);
        ···

這段參數設置代碼的參數中,我們主要關注下共識參數與創世區塊信息的參數

consensus::Params consensus;//chainparams.h
CBlock genesis//chainparams.cpp

主要來看主網中對這些參數的相關設置,⬆️看上面一段,添加備註的參數是經常聽到的名次,如果要創建自己的數字貨幣,修改這些參數就可以了。所以創建一個新的貨幣並不難,關鍵在於是否有價值。


6.3RPC命令行判斷

回到bitcoin.cpp繼續參數解析部分

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

        if (fCommandLine)
        {
            fprintf(stderr, "Error: There is no RPC client functionality in bitcoind anymore. Use the bitcoin-cli utility instead.\n");
            exit(EXIT_FAILURE);
        }

上述代碼對輸入的參數逐個判斷,首先通過IsSwitchCahr函數

//util.h
inline bool IsSwitchChar(char c)
{
#ifdef WIN32
    return c == '-' || c == '/';
#else
    return c == '-';
#endif
}

判斷參數是否有’-‘或’/’,並且不包含’bitcoin:’,帶有不包含”-“的參數會報錯,提示使用bitconi-cli
這裏寫圖片描述


6.4 服務參數設置

默認bitcoind是打開服務器端

//bitcoind.cpp
SoftSetBoolArg("-server", true);

函數SoftSetBoolArg()的實現在util.cpp

bool SoftSetBoolArg(const std::string& strArg, bool fValue)
{
    if (fValue)
        return SoftSetArg(strArg, std::string("1"));
    else
        return SoftSetArg(strArg, std::string("0"));
}
bool SoftSetArg(const std::string& strArg, const std::string& strValue)
{
    if (mapArgs.count(strArg))
        return false;
    mapArgs[strArg] = strValue;
    return true;
}

調用了SoftSetArg函數,strArg被賦值爲server,那麼首先在mapArgs(之前有介紹)中查找是否存在server,如果存在就不做處理,否則按傳入的值設置。


7.初始化日誌

回到bitcoind.cpp繼續後面的代碼

//init.cpp
void InitLogging()
{
    //-printtoconsole 日誌信息發送到控制檯,默認不發送到控制檯
    fPrintToConsole = GetBoolArg("-printtoconsole", false);
    //-logtimestamps 在日誌中打印時間戳,默認打印
    fLogTimestamps = GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS);
    //-logtimemicros 在日誌中按微妙格式打印,默認不按
    fLogTimeMicros = GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS);
    //-logips 在日誌中打印ip,默認不包含
    fLogIPs = GetBoolArg("-logips", DEFAULT_LOGIPS);

    LogPrintf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
    LogPrintf("Bitcoin version %s\n", FormatFullVersion());
}

其中三個默認參數

//util.h
static const bool DEFAULT_LOGTIMEMICROS = false;//按微秒格式打印
static const bool DEFAULT_LOGIPS        = false;//包含ip地址
static const bool DEFAULT_LOGTIMESTAMPS = true;//打印時間戳

看一下日誌打印內容
這裏寫圖片描述


8.InitParameterInteraction

初始化參數交互處理,設置後都會打印對應的語句
1)綁定並監聽地址

//init.cpp
// Parameter interaction based on rules
void InitParameterInteraction()
{
    // when specifying an explicit binding address, you want to listen on it
    // even when -connect or -proxy is specified
    if (mapArgs.count("-bind")) {
        if (SoftSetBoolArg("-listen", true))
            LogPrintf("%s: parameter interaction: -bind set -> setting -listen=1\n", __func__);
    }
    if (mapArgs.count("-whitebind")) {
        if (SoftSetBoolArg("-listen", true))
            LogPrintf("%s: parameter interaction: -whitebind set -> setting -listen=1\n", __func__);
    }

在指定顯式綁定地址時,即使在指定-connect或-proxy時,也要監聽它。綁定地址有兩種參數,分別是bindwhitebind,從代碼看對這兩種參數的處理方式是相同的。
這裏看一下LogPrintf函數,通過調用LogPrint實現

//util.h
/** Return true if log accepts specified category */
bool LogAcceptCategory(const char* category);
/** Send a string to the log output */
int LogPrintStr(const std::string &str);
#define LogPrintf(...) LogPrint(NULL, __VA_ARGS__)

template<typename T1, typename... Args>
static inline int LogPrint(const char* category, const char* fmt, const T1& v1, const Args&... args)
{
    if(!LogAcceptCategory(category)) return 0;                            \
    return LogPrintStr(tfm::format(fmt, v1, args...));
}

LogPrint對日誌是否接收制定目錄做判斷,然後調用的是LogPrintStr

//util.cpp
int LogPrintStr(const std::string &str)
{
    int ret = 0; // Returns total number of characters written
    static bool fStartedNewLine = true;
    //添加時間戳
    string strTimestamped = LogTimestampStr(str, &fStartedNewLine);
    if (fPrintToConsole)//在7.初始化日誌有設置
    {// print to console
        ret = fwrite(strTimestamped.data(), 1, strTimestamped.size(), stdout);
        fflush(stdout);
    }
    else if (fPrintToDebugLog)
    {
        boost::call_once(&DebugPrintInit, debugPrintInitFlag);
        boost::mutex::scoped_lock scoped_lock(*mutexDebugLog);

        // buffer if we haven't opened the log yet
        if (fileout == NULL) {
            assert(vMsgsBeforeOpenLog);
            ret = strTimestamped.length();
            vMsgsBeforeOpenLog->push_back(strTimestamped);
        }
        else
        {
            // reopen the log file, if requested
            if (fReopenDebugLog) {
                fReopenDebugLog = false;
                //日誌的默認地址對應路徑下的debug.log
                boost::filesystem::path pathDebug = GetDataDir() / "debug.log";
                if (freopen(pathDebug.string().c_str(),"a",fileout) != NULL)
                    setbuf(fileout, NULL); // unbuffered
            }

            ret = FileWriteStr(strTimestamped, fileout);
        }
    }
    return ret;
}

&fStartedNewLine是由調用上下文保存的狀態變量,當不進行換行的多個調用時,將抑制時間戳的打印。將其初始化爲true,並將其保存在調用上下文中。
LogPrintStr首先根據fStartedNewLine調用LogTimestampStr添加時間戳,其中根據fLogTimeMicros決定是否添加微秒的時間。根據條件輸出到對應的設備中。
2)連接可信節點
查找mapArgs是否包含-connect參數,包括則將-dnsseed(使用dns查找)和-listen(接受外部的連接,並對其進行監聽)設置爲false,並打印。
3)代理模式
查找mapArgs參數中是否包含-proxy參數,如果包含則將-listen、-upnp、-discover都設置爲false。
4)監聽設置處理

//net.h
/** -listen default */
static const bool DEFAULT_LISTEN = true;

如果-listen參數不爲DEFAULT_LISTEN,即-listen爲false則-upnp、-discover(自動默認發現地址)、-listenonion(匿名地址監聽)都設置爲false。
5)外部ip參數處理
查找mapArgs參數中是否包含-externalip參數,存在則將-discover設置爲false,即不用查找其他的ip。
6)重新掃描錢包參數設置
如果-salvagewallet(嘗試在啓動時從毀壞的錢包恢復私鑰)或-zapwallettxes(刪除所有錢包交易,只恢復部分)設置爲true,則將-rescan設置爲true,重新掃描錢包。
7)區塊模式參數設置

//net.h
/** Default for blocks only*/
static const bool DEFAULT_BLOCKSONLY = false;

如果-blocksonly設置爲true,則將-whitelistrelay、-walletbroadcast設置爲false。
8)強制白名單連接參數

//main.h
/** Default for DEFAULT_WHITELISTFORCERELAY. */
static const bool DEFAULT_WHITELISTFORCERELAY = true;

來自白名單主機的強制中繼意味着我們將首先接受來自它們的中繼。默認有白名單,將-whitelistrelay設置爲true。可以看前面的截圖第二行就是對應的打印內容。


比特幣源碼學習(三)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章