源码0.13.2版的,在vscode中打开的
排版好累,不写目录了,源码看到现在,需要恶补点boost库的知识点,这个库在源码中使用还是挺频繁的
继续之前的代码
创建脚本检查线程
LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads);
if (nScriptCheckThreads) {
for (int i=0; i<nScriptCheckThreads-1; i++)
threadGroup.create_thread(&ThreadScriptCheck);
}
nScriptCheckThreads
是由参数中-par
设定的,在比特币源码学习(三)的9.3的4)脚本验证线程中,这段代码是根据参数通过线程组threadGroup(boost::thread_group)
来创建线程,其中ThreadScriptCheck
函数的实现如下
//main.cpp
void ThreadScriptCheck() {
RenameThread("bitcoin-scriptch");
scriptcheckqueue.Thread();
}
首先重命名当前线程为"bitcoin-scriptch"
,然后启动线程,线程启动流程如下
//main.cpp
static CCheckQueue<CScriptCheck> scriptcheckqueue(128);
//checkqueue.h class CCheckQueue
//! Worker thread
void Thread()
{
Loop();
}
//checkqueue.h class CCheckQueue
/** Internal function that does bulk of the verification work. */
bool Loop(bool fMaster = false) {···}
线程启动是通过CCheckQueue
类型的静态变量scriptcheckqueue
执行类中的成员函数Thread()
,而Thread()
函数又调用类中的私有函数Loop()
,这个函数就是整个script check
的线程控制中心,看Loop
的具体实现
bool Loop(bool fMaster = false)
{
boost::condition_variable& cond = fMaster ? condMaster : condWorker;
std::vector<T> vChecks;
vChecks.reserve(nBatchSize);
unsigned int nNow = 0;
bool fOk = true;
do {
{
boost::unique_lock<boost::mutex> lock(mutex);
// first do the clean-up of the previous loop run (allowing us to do it in the same critsect)
if (nNow) {
fAllOk &= fOk;
nTodo -= nNow;
if (nTodo == 0 && !fMaster)
// We processed the last element; inform the master it can exit and return the result
condMaster.notify_one();
} else {
// first iteration
nTotal++;
}
// logically, the do loop starts here
while (queue.empty()) {
if ((fMaster || fQuit) && nTodo == 0) {
nTotal--;
bool fRet = fAllOk;
// reset the status for new work later
if (fMaster)
fAllOk = true;
// return the current status
return fRet;
}
nIdle++;
cond.wait(lock); // wait
nIdle--;
}
// Decide how many work units to process now.
// * Do not try to do everything at once, but aim for increasingly smaller batches so
// all workers finish approximately simultaneously.
// * Try to account for idle jobs which will instantly start helping.
// * Don't do batches smaller than 1 (duh), or larger than nBatchSize.
nNow = std::max(1U, std::min(nBatchSize, (unsigned int)queue.size() / (nTotal + nIdle + 1)));
vChecks.resize(nNow);
for (unsigned int i = 0; i < nNow; i++) {
// We want the lock on the mutex to be as short as possible, so swap jobs from the global
// queue to the local batch vector instead of copying.
vChecks[i].swap(queue.back());
queue.pop_back();
}
// Check whether we need to do work at all
fOk = fAllOk;
}
// execute work
BOOST_FOREACH (T& check, vChecks)
if (fOk)
fOk = check();
vChecks.clear();
} while (true);
}
首先注意两个变量,第一个是std::vector<T> vChecks
中的T
,从static CCheckQueue<CScriptCheck> scriptcheckqueue(128);
中来看T
代表的是CScriptCheck
,该类位于main.h
,主要的函数是重载()操作符,可以解释上面这段代码的fOk = check();
这句
//main.cpp
bool CScriptCheck::operator()() {
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
const CScriptWitness *witness = (nIn < ptxTo->wit.vtxinwit.size()) ? &ptxTo->wit.vtxinwit[nIn].scriptWitness : NULL;
if (!VerifyScript(scriptSig, scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, amount, cacheStore, *txdata), &error)) {
return false;
}
return true;
}
先来解释这个重载()
操作符,其中scriptSig
就是用户的签名也就是解锁脚本,witness
是隔离见证中从scriptSig
中分离出来的,scriptPubKey
就是锁定脚本,调用VerifyScript
来验证脚本和锁定脚本是否正确运行。关于脚本的验证和执行及脚本的结构等具体内容,可以参考书籍,看过就会有大致的概念的。
再返回上一段代码,注意boost::condition_variable& cond
这个变量,这个变量是是并发编程中的条件变量,查阅参考https://segmentfault.com/a/1190000006679917
可以看到,这段代码是有两种角色的,Master
和Worker
,其中Master
负责统计结果,Worker
负责执行具体的脚本。从这段源码的执行顺序来看,首先执行CCheckQueue
类的Thread
创建Worker
,此时任务队列queue
为空(不为空也会先清空),所有的的Worker
创建后都在cond.wait(lock);
处阻塞,等所有新的任务被加到queue
中时,才会唤醒批量执行任务;处理完最后一个任务后,通知Master
统计计算结果并退出。
CScheduler实现
// Start the lightweight task scheduler thread
CScheduler::Function serviceLoop = boost::bind(&CScheduler::serviceQueue, &scheduler);
threadGroup.create_thread(boost::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop));
首先通过boost::bind将函数绑定到对象,等价于serviceLoop=scheduler.serviceQueue()
,绑定成员函数serviceQueue到函数对象serviceLoop,然后通过线程组threadGroup创建新的线程,线程的执行函数为bind返回的函数对象,TraceThread的定义如下
//util.h
/**
* .. and a wrapper that just calls func once
*/
template <typename Callable> void TraceThread(const char* name, Callable func)
{
std::string s = strprintf("bitcoin-%s", name);
RenameThread(s.c_str());
try
{
LogPrintf("%s thread start\n", name);
func();
LogPrintf("%s thread exit\n", name);
}
catch (const boost::thread_interrupted&)
{
LogPrintf("%s thread interrupt\n", name);
throw;
}
catch (const std::exception& e) {
PrintExceptionContinue(&e, name);
throw;
}
catch (...) {
PrintExceptionContinue(NULL, name);
throw;
}
}
这个函数通过template<typename Callable>
来定义传入函数的类型,因此在实际调用时传入的参数类型为CScheduler::Function
,实例对象为serviceLoop
,第一个参数为"scheduler"
。这个函数的作用时重命名线程并将调用函数执行一次,也就是serviceLoop
,实际上是scheduler.serviceQueue
函数,这里来看serviceQueue
这个函数的实现
//scheduler.cpp
void CScheduler::serviceQueue()
{
boost::unique_lock<boost::mutex> lock(newTaskMutex);
++nThreadsServicingQueue;
// newTaskMutex is locked throughout this loop EXCEPT
// when the thread is waiting or when the user's function
// is called.
while (!shouldStop()) {
try {
while (!shouldStop() && taskQueue.empty()) {
// Wait until there is something to do.
newTaskScheduled.wait(lock);
}
// Wait until either there is a new task, or until
// the time of the first item on the queue:
// wait_until needs boost 1.50 or later; older versions have timed_wait:
#if BOOST_VERSION < 105000
while (!shouldStop() && !taskQueue.empty() &&
newTaskScheduled.timed_wait(lock, toPosixTime(taskQueue.begin()->first))) {
// Keep waiting until timeout
}
#else
// Some boost versions have a conflicting overload of wait_until that returns void.
// Explicitly use a template here to avoid hitting that overload.
while (!shouldStop() && !taskQueue.empty()) {
boost::chrono::system_clock::time_point timeToWaitFor = taskQueue.begin()->first;
if (newTaskScheduled.wait_until<>(lock, timeToWaitFor) == boost::cv_status::timeout)
break; // Exit loop after timeout, it means we reached the time of the event
}
#endif
// If there are multiple threads, the queue can empty while we're waiting (another
// thread may service the task we were waiting on).
if (shouldStop() || taskQueue.empty())
continue;
Function f = taskQueue.begin()->second;
taskQueue.erase(taskQueue.begin());
{
// Unlock before calling f, so it can reschedule itself or another task
// without deadlocking:
reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock);
f();
}
} catch (...) {
--nThreadsServicingQueue;
throw;
}
}
--nThreadsServicingQueue;
newTaskScheduled.notify_one();
}
首先定义一个unique_lock
来保证线程安全,除了线程正在等待或当用户的函数被调用,NeXTaskMutEX
在整个循环中被锁定。开启一个while
循环,判断条件是shouldStop()
//scheduler.h class CSecheduler
bool shouldStop() { return stopRequested || (stopWhenEmpty && taskQueue.empty()); }
如果stopRequested
被设置为true或者队列为空且stopWhenEmpty被设置为true,那么shouldStop()
就返回true
表示循环停止。回到serviceQueue()
函数,如果队列为空且未设置停止标志,sleep
一段时间直到有任务。等到有任务了,也就是taskQueue
不为空了,针对不同的boost
版本,有不同的时间等待方式,等待之后,获取第二个参数要执行的任务Function f = taskQueue.begin()->second;
,从任务队列中删除该任务,此时所有的共享变量(taskQueue
)已经访问完了,所以在执行调用函数(f()
)前,可以先解锁,因为这个函数已经从队列中删除了,也就是只会执行一次。
启动RPCServer、HTTPServer
/* Start the RPC server already. It will be started in "warmup" mode
* and not really process calls already (but it will signify connections
* that the server is there and will be ready later). Warmup mode will
* be disabled when initialisation is finished.
*/
if (fServer)
{
uiInterface.InitMessage.connect(SetRPCWarmupStatus);
if (!AppInitServers(threadGroup))
return InitError(_("Unable to start HTTP server. See debug log for details."));
}
从注释来看,这一步是已开启RPC server
,它将在"warmup"
模式中启动但不是完成真正的程序调用(它会通知这个连接该服务已经到位并即将开启)。"warmup"
模式会在初始化结束后取消。
回到代码,fServer
这个参数是在step3
中处理的,如果该参数为true
,需要启动相关server
服务来处理client
发送过来的命令,启动前先给InitMessage
信号添加一个新的执行函数
//src/rpc/server.cpp
void SetRPCWarmupStatus(const std::string& newStatus)
{
LOCK(cs_rpcWarmup);
rpcWarmupStatus = newStatus;
}
static std::string rpcWarmupStatus("RPC server started");
rpcWarmupStatus
是一个静态string
类型的全局变量,该函数的作用就是把新的参数值赋给rpcWarmupStatus
,cs_rpcWarmup
是实例化类CCriticalSection
,该类从注释可以知道这个类继承AnnotatedMixin
类,而且有boost::recursive_mutex
的类模板。因此CCriticalSection
类定义的对象都是boost::recursive_mutex
的类,而这是个互斥的类。所以cs_rpcWarmup
为一个互斥类对象。
9.5 验证钱包数据库完整性
#ifdef ENABLE_WALLET
if (!fDisableWallet) {
if (!CWallet::Verify())
return false;
} // (!fDisableWallet)
#endif // ENABLE_WALLET
钱包的启用是通过一个宏定义来进行实现的,如果启用了这个宏并且fDisableWallet
(配置读入,前面的代码已处理)为false
就会进行钱包数据的完整性校验,重点查看CWallet::Verify()
这个函数
//src/wallet/wallet.cpp
bool CWallet::Verify()
{
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
std::string walletFile = GetArg("-wallet", DEFAULT_WALLET_DAT);
LogPrintf("Using wallet %s\n", walletFile);
uiInterface.InitMessage(_("Verifying wallet..."));
// Wallet file must be a plain filename without a directory
if (walletFile != boost::filesystem::basename(walletFile) + boost::filesystem::extension(walletFile))
return InitError(strprintf(_("Wallet %s resides outside data directory %s"), walletFile, GetDataDir().string()));
if (!bitdb.Open(GetDataDir()))
{
// try moving the database env out of the way
boost::filesystem::path pathDatabase = GetDataDir() / "database";
boost::filesystem::path pathDatabaseBak = GetDataDir() / strprintf("database.%d.bak", GetTime());
try {
boost::filesystem::rename(pathDatabase, pathDatabaseBak);
LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string());
} catch (const boost::filesystem::filesystem_error&) {
// failure is ok (well, not really, but it's not worse than what we started with)
}
// try again
if (!bitdb.Open(GetDataDir())) {
// if it still fails, it probably means we can't even create the database env
return InitError(strprintf(_("Error initializing wallet database environment %s!"), GetDataDir()));
}
}
首先打印一些钱包相关的日志,验证钱包文件必须是没有目录的纯文件名,之后打开(或者创建一个)数据库,移动数据库环境尝试重命名,这里可以失败,若再次打开(或者创建)数据库失败,则表示钱包环境创建失败,报错误信息。
来看其中的bitdb.Open()
函数
//src/wallet/db.cpp
bool CDBEnv::Open(const boost::filesystem::path& pathIn)
{
if (fDbEnvInit)
return true;
boost::this_thread::interruption_point();
strPath = pathIn.string();
boost::filesystem::path pathLogDir = pathIn / "database";
TryCreateDirectory(pathLogDir);
boost::filesystem::path pathErrorFile = pathIn / "db.log";
LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string());
unsigned int nEnvFlags = 0;
if (GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB))
nEnvFlags |= DB_PRIVATE;
dbenv->set_lg_dir(pathLogDir.string().c_str());
dbenv->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet
dbenv->set_lg_bsize(0x10000);
dbenv->set_lg_max(1048576);
dbenv->set_lk_max_locks(40000);
dbenv->set_lk_max_objects(40000);
dbenv->set_errfile(fopen(pathErrorFile.string().c_str(), "a")); /// debug
dbenv->set_flags(DB_AUTO_COMMIT, 1);
dbenv->set_flags(DB_TXN_WRITE_NOSYNC, 1);
dbenv->log_set_config(DB_LOG_AUTO_REMOVE, 1);
int ret = dbenv->open(strPath.c_str(),
DB_CREATE |
DB_INIT_LOCK |
DB_INIT_LOG |
DB_INIT_MPOOL |
DB_INIT_TXN |
DB_THREAD |
DB_RECOVER |
nEnvFlags,
S_IRUSR | S_IWUSR);
if (ret != 0)
return error("CDBEnv::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret));
fDbEnvInit = true;
fMockDb = false;
return true;
}
这个函数首先检查数据库文件是否存在(fDbEnvInit
),不存在则根据传入的参数创建,然后设置日志文件,并且通过dbenv
指针设置一系列数据库运行相关的参数,参数参考http://web.mit.edu/jhawk/mnt/spo/subversion/docs/api_cxx/c_index.html。都设置完成后,将fDbEnvInit
设置为true
,然后返回true
.
9.6 初始化网络
注册节点信号
// ********************************************************* Step 6: network initialization
RegisterNodeSignals(GetNodeSignals());
首先是调用函数RegisterNodeSignals()
注册节点信号
//main.cpp
void RegisterNodeSignals(CNodeSignals& nodeSignals)
{
nodeSignals.GetHeight.connect(&GetHeight);
nodeSignals.ProcessMessages.connect(&ProcessMessages);
nodeSignals.SendMessages.connect(&SendMessages);
nodeSignals.InitializeNode.connect(&InitializeNode);
nodeSignals.FinalizeNode.connect(&FinalizeNode);
}
注册节点的获取高度,消息处理,初始化等的信号。
添加用户代理注释
// sanitize comments per BIP-0014, format user agent and check total size
std::vector<string> uacomments;
BOOST_FOREACH(string cmt, mapMultiArgs["-uacomment"])
{
if (cmt != SanitizeString(cmt, SAFE_CHARS_UA_COMMENT))
return InitError(strprintf(_("User Agent comment (%s) contains unsafe characters."), cmt));
uacomments.push_back(SanitizeString(cmt, SAFE_CHARS_UA_COMMENT));
}
strSubVersion = FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments);
if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) {
return InitError(strprintf(_("Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments."),
strSubVersion.size(), MAX_SUBVERSION_LENGTH));
}
-uacomment:给用户代理字符串添加注释
首先将用户对代理的注释信息保存到uacomments
中,将CLIENT_NAME, CLIENT_VERSION, uacomments
按照BIP 14 spec的要求(/CLIENT_NAME:CLIENT_VERSION(comments1;comments2;···)/
)连接起来,连接方式由FormatSubVersion(clientversion.cpp)
函数定义,最后需要判断格式化后的字符串是否超过最大长度限制
//net.h
/** Maximum length of strSubVer in `version` message */
static const unsigned int MAX_SUBVERSION_LENGTH = 256;
在版本信息中的最大字符串长度为256。
设定网络范围
if (mapArgs.count("-onlynet")) {
std::set<enum Network> nets;
BOOST_FOREACH(const std::string& snet, mapMultiArgs["-onlynet"]) {
enum Network net = ParseNetwork(snet);
if (net == NET_UNROUTABLE)
return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'"), snet));
nets.insert(net);
}
for (int n = 0; n < NET_MAX; n++) {
enum Network net = (enum Network)n;
if (!nets.count(net))
SetLimited(net);
}
}
-onlynet:Only connect to nodes in network (ipv4, ipv6 or onion)
首先来看Network的定义
//netbase.h
enum Network
{
NET_UNROUTABLE = 0,
NET_IPV4,
NET_IPV6,
NET_TOR,
NET_MAX,
};
这里定义了几种网络,-onlynet则将连接范围限定在某一种或几种网络内。
白名单设置
if (mapArgs.count("-whitelist")) {
BOOST_FOREACH(const std::string& net, mapMultiArgs["-whitelist"]) {
CSubNet subnet(net);
if (!subnet.IsValid())
return InitError(strprintf(_("Invalid netmask specified in -whitelist: '%s'"), net));
CNode::AddWhitelistedRange(subnet);
}
}
-whitelist:Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6”
若是设置过-whitelist
,首先检查地址是否正确,正确则加入CNode
的白名单范围。
代理设置
bool proxyRandomize = GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE);
// -proxy sets a proxy for all outgoing network traffic
// -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default
std::string proxyArg = GetArg("-proxy", "");
SetLimited(NET_TOR);
if (proxyArg != "" && proxyArg != "0") {
proxyType addrProxy = proxyType(CService(proxyArg, 9050), proxyRandomize);
if (!addrProxy.IsValid())
return InitError(strprintf(_("Invalid -proxy address: '%s'"), proxyArg));
SetProxy(NET_IPV4, addrProxy);
SetProxy(NET_IPV6, addrProxy);
SetProxy(NET_TOR, addrProxy);
SetNameProxy(addrProxy);
SetLimited(NET_TOR, false); // by default, -proxy sets onion as reachable, unless -noonion later
}
-proxy=:Connect through SOCKS5 proxy
-proxyrandomize:Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)
首先检查两个参数,然后通过SetLimited(NET_TOR)
禁用洋葱路由。之后检查代理proxyArg
不为空,则根据代理域名进行dns
查询,查到相应的ip
并检查代理的合法性之后,再为IPV4、IPV6、TOR
设置代理。最后解禁洋葱路由,因为之前禁止了,所以这里启用,这里的设置对洋葱路由的启用无关,后面会根据-onion
参数再进行相应的设置。
设置洋葱路由
// -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses
// -noonion (or -onion=0) disables connecting to .onion entirely
// An empty string is used to not override the onion proxy (in which case it defaults to -proxy set above, or none)
std::string onionArg = GetArg("-onion", "");
if (onionArg != "") {
if (onionArg == "0") { // Handle -noonion/-onion=0
SetLimited(NET_TOR); // set onions as unreachable
} else {
proxyType addrOnion = proxyType(CService(onionArg, 9050), proxyRandomize);
if (!addrOnion.IsValid())
return InitError(strprintf(_("Invalid -onion address: '%s'"), onionArg));
SetProxy(NET_TOR, addrOnion);
SetLimited(NET_TOR, false);
}
}
-onion
=:Use separate SOCKS5 proxy to reach peers via Tor hidden services (default: %s)
和设置代理类似的方式设置洋葱路由,先判断,再解析域名,启用洋葱路由。
设置各类参数
// see Step 2: parameter interactions for more information about these
fListen = GetBoolArg("-listen", DEFAULT_LISTEN);
fDiscover = GetBoolArg("-discover", true);
fNameLookup = GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);
fRelayTxes = !GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY);
-listen
:Accept connections from outside (default: 1 if no -proxy or -connect)
-discover
:Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)
-dns
:Allow DNS lookups for -addnode, -seednode and -connect
-blocksonly
:Whether to operate in a blocks only mode (default: %u)
-blocksonly
的解释有些不明,这些在step1其实已经介绍过了,blocksonly
只接受矿工打包成功的区块,不接受未确认的交易,这是当前节点网络资源有限的情况下减少负载的一种方式。
bool fBound = false;
if (fListen) {
if (mapArgs.count("-bind") || mapArgs.count("-whitebind")) {
BOOST_FOREACH(const std::string& strBind, mapMultiArgs["-bind"]) {
CService addrBind;
if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false))
return InitError(ResolveErrMsg("bind", strBind));
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR));
}
BOOST_FOREACH(const std::string& strBind, mapMultiArgs["-whitebind"]) {
CService addrBind;
if (!Lookup(strBind.c_str(), addrBind, 0, false))
return InitError(ResolveErrMsg("whitebind", strBind));
if (addrBind.GetPort() == 0)
return InitError(strprintf(_("Need to specify a port with -whitebind: '%s'"), strBind));
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST));
}
}
else {
struct in_addr inaddr_any;
inaddr_any.s_addr = INADDR_ANY;
fBound |= Bind(CService(in6addr_any, GetListenPort()), BF_NONE);
fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE);
}
if (!fBound)
return InitError(_("Failed to listen on any port. Use -listen=0 if you want this."));
}
bind
和whitebind
都是在listen
为true
情况下才能绑定本地端口和网卡,绑定参数中的端口,任何一个绑定失败都会导致最后报错。
if (mapArgs.count("-externalip")) {
BOOST_FOREACH(const std::string& strAddr, mapMultiArgs["-externalip"]) {
CService addrLocal;
if (Lookup(strAddr.c_str(), addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid())
AddLocal(addrLocal, LOCAL_MANUAL);
else
return InitError(ResolveErrMsg("externalip", strAddr));
}
}
-externalip
:Specify your own public address
对于指定的-external ip
首先查询对应的ip(指定的可以是域名,或者将字符串ip转换成CService
),然后通过AddLocal将指定的ip添加到LOCAL_MANUAL
//net.h
enum
{
LOCAL_NONE, // unknown
LOCAL_IF, // address a local interface listens on
LOCAL_BIND, // address explicit bound to
LOCAL_UPNP, // address reported by UPnP
LOCAL_MANUAL, // address explicitly specified (-externalip=)
LOCAL_MAX
};
这个枚举结构维护所有的本地ip
BOOST_FOREACH(const std::string& strDest, mapMultiArgs["-seednode"])
AddOneShot(strDest);
seednode:Connect to a node to retrieve peer addresses, and disconnect
通过连接到一个(或多个)节点将其作为种子, 在使用初始种子节点形成介绍后,客户端将断开连接并使用新发现的对等体。
ZMQ
#if ENABLE_ZMQ
pzmqNotificationInterface = CZMQNotificationInterface::CreateWithArguments(mapArgs);
if (pzmqNotificationInterface) {
RegisterValidationInterface(pzmqNotificationInterface);
}
#endif
if (mapArgs.count("-maxuploadtarget")) {
CNode::SetMaxOutboundTarget(GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET)*1024*1024);
}
-maxuploadtarget
:Tries to keep outbound traffic under the given target (in MiB per 24h), 0 = no limit (default: %d)
首先通过一个宏定义来表示是否启用ZMQ
,创建成功的话就调用RegisterValidationInterface
注册这个接口,然后通过-maxuploadtarget
参数来设置最大上传速度。
···