比特币源码学习0.13 (五)

源码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
可以看到,这段代码是有两种角色的,MasterWorker,其中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类型的全局变量,该函数的作用就是把新的参数值赋给rpcWarmupStatuscs_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."));
    }

bindwhitebind都是在listentrue情况下才能绑定本地端口和网卡,绑定参数中的端口,任何一个绑定失败都会导致最后报错。

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参数来设置最大上传速度。


···

猜你喜欢

转载自blog.csdn.net/m0_37847176/article/details/81509873