源碼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的賦值,pCurrentBaseParams是CBaseChainParams類型的
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時,也要監聽它。綁定地址有兩種參數,分別是bind和whitebind,從代碼看對這兩種參數的處理方式是相同的。
這裏看一下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。可以看前面的截圖第二行就是對應的打印內容。