主页 > token.im钱包下载 > 从零开始学习比特币(六):建立P2P网络过程中查询DNS节点

从零开始学习比特币(六):建立P2P网络过程中查询DNS节点

token.im钱包下载 2023-04-21 05:21:40

上一节开始,我们已经开始讲解比特币系统中的P2P网络是如何建立起来的。 还记得在比特币系统启动第12步的讲解中,我们提到过几个线程相关的处理很重要吗? 以下内容以此为基础进行详细讲解。 由于文章篇幅,我们将依次分成几篇。

P2P网络的建立是在比特币系统启动的第十二步开始的比特币p2p算法,最后时刻调用了CConnman::Start方法。

这部分内容在net.cpp、net_processing.cpp等文件中。

下面开始讲解各个线程的具体处理。 1、ThreadSocketHandler的详细介绍见上一篇:

从零开始学习比特币(五)——建立P2P网络过程中socket的读取和发送 2. ThreadDNSAddressSeed这个线程的目标是通过查询DNS节点找到足够多的比特币节点。 找到后,就可以连接到比特币网络进行同步了。

DNS种子只在需要地址的时候查询,不需要DNS种子的时候就避免查询DNS种子。 这通过创建更少的识别 DNS​​ 请求来提高用户隐私。

该线程在 net.cpp 文件的第 1603 行定义。 让我们从详细的解释开始。 如果peer节点数大于0,并且没有指定-forcednsseed,或者指定了但是值为false,则进行如下处理:遍历所有节点,如果该节点已连接成功且不是引导节点,而fOneShot属性为false,且不是手动连接,也不是入站节点,那么变量nRelevant加1。 如果变量nRelevant大于2,即P2P网络可用,则退出函数。

if ((addrman.size() > 0) &&
    (!gArgs.GetBoolArg("-forcednsseed", DEFAULT_FORCEDNSSEED))) {
    if (!interruptNet.sleep_for(std::chrono::seconds(11)))
        return;

LOCK(cs_vNodes); int nRelevant = 0; for (auto pnode : vNodes) { nRelevant += pnode->fSuccessfullyConnected && !pnode->fFeeler && !pnode->fOneShot && !pnode->m_manual_connection && !pnode->fInbound; } if (nRelevant >= 2) { LogPrintf("P2P peers available. Skipped DNS seeding.\n"); return; } }

获取并遍历所有 DNS 种子节点。

 for (const std::string &seed : vSeeds) {
     if (interruptNet) {
         return;
     }

比特币算法源代码_比特币挖矿算法_比特币p2p算法

if (HaveNameProxy()) { AddOneShot(seed); } else { std::vector vIPs; std::vector vAdd; ServiceFlags requiredServiceBits = GetDesirableServiceFlags(NODE_NONE); std::string host = strprintf("x%x.%s", requiredServiceBits, seed); CNetAddr resolveSource; if (!resolveSource.SetInternal(host)) { continue; } unsigned int nMaxIPs = 256; // Limits number of IPs learned from a DNS seed if (LookupHost(host.c_str(), vIPs, nMaxIPs, true)) { for (const CNetAddr& ip : vIPs) { int nOneDay = 24*3600; CAddress addr = CAddress(CService(ip, Params().GetDefaultPort()), requiredServiceBits); addr.nTime = GetTime() - 3*nOneDay - GetRand(4*nOneDay); // use a random age between 3 and 7 days old vAdd.push_back(addr); found++; } addrman.Add(vAdd, resolveSource); } else { // We now avoid directly using results from DNS Seeds which do not support service bit filtering,

比特币算法源代码_比特币挖矿算法_比特币p2p算法

// instead using them as a oneshot to get nodes with our desired service bits. AddOneShot(seed); } } }

接下来解释一下上面的代码。

如果指定代理,则调用AddOneShot方法将当前DNS种子节点保存到vOneShots集合中。 否则进行如下处理: 如果LookupHost方法返回的结果为真,即根据当前DNS种子节点至少找到一个对端节点,则进行以下处理。 遍历vIPs集合,根据当前IP地址生成一个CAddress地址对象,保存在vAdd集合中比特币p2p算法,并给代表找到节点的变量found加1。 调用地址管理器的Add方法保存多个地址。

具体代码如下:

for (const CNetAddr& ip : vIPs)
{
    int nOneDay = 24*3600;
    CAddress addr = CAddress(CService(ip, Params().GetDefaultPort()), requiredServiceBits);
    addr.nTime = GetTime() - 3*nOneDay - GetRand(4*nOneDay); // use a random age between 3 and 7 days old
    vAdd.push_back(addr);
    found++;
}
addrman.Add(vAdd, resolveSource);

如果 LookupHost 方法返回结果为假,即根据当前 DNS 种子节点没找到一个对等节点,则调用 AddOneShot 方法进行处理。

在内部,AddOneShot 方法只是将当前 DNS 种子添加到 vOneShots 集合中。 2.1. CAddrMan::Add方法 接下来介绍地址管理器的Add方法。 此方法位于 addrman.h 文件的第 540 行。

该方法的主体是一个for循环,遍历CAddress集合,对每个CAddress对象调用Add_方法进行处理。 并返回添加是否成功。 代码如下:

bool Add(const std::vector &vAddr, const CNetAddr& source, int64_t nTimePenalty = 0)
{
    LOCK(cs);
    int nAdd = 0;
    Check();

比特币挖矿算法_比特币p2p算法_比特币算法源代码

for (std::vector::const_iterator it = vAddr.begin(); it != vAddr.end(); it++) nAdd += Add_(*it, source, nTimePenalty) ? 1 : 0; Check(); if (nAdd) { LogPrint(BCLog::ADDRMAN, "Added %i addresses from %s: %i tried, %i new\n", nAdd, source.ToString(), nTried, nNew); } return nAdd > 0; }

接下来,让我们看一下 Add_ 方法。 此方法位于 addrman.cpp 文件的第 254 行。 如果当前地址不可路由,则简单地返回 false。

 if (!addr.IsRoutable())
     return false;

调用Find方法根据地址对象查找对应的地址信息。

 std::map::iterator it = mapAddr.find(addr);
 if (it == mapAddr.end())
     return nullptr;
 if (pnId)
     *pnId = (*it).second;
 std::map::iterator it2 = mapInfo.find((*it).second);
 if (it2 != mapInfo.end())
     return &(*it2).second;
 return nullptr;

如果地址对象是源对象,则设置变量nTimePenalty等于0。如果找到对应的地址信息,则设置地址信息的相关属性

 bool fCurrentlyOnline = (GetAdjustedTime() - addr.nTime < 24 * 60 * 60);
 int64_t nUpdateInterval = (fCurrentlyOnline ? 60 * 60 : 24 * 60 * 60);
 if (addr.nTime && (!pinfo->nTime || pinfo->nTime < addr.nTime - nUpdateInterval - nTimePenalty))

比特币p2p算法_比特币算法源代码_比特币挖矿算法

pinfo->nTime = std::max((int64_t)0, addr.nTime - nTimePenalty);

// add services pinfo->nServices = ServiceFlags(pinfo->nServices | addr.nServices);

// do not update if no new information is present if (!addr.nTime || (pinfo->nTime && addr.nTime <= pinfo->nTime)) return false;

// do not update if the entry was already in the "tried" table if (pinfo->fInTried) return false;

// do not update if the max reference count is reached if (pinfo->nRefCount == ADDRMAN_NEW_BUCKETS_PER_ADDRESS) return false;

// stochastic test: previous nRefCount == N: 2^N times harder to increase it int nFactor = 1; for (int n = 0; n < pinfo->nRefCount; n++) nFactor *= 2; if (nFactor > 1 && (RandomInt(nFactor) != 0)) return false;

如果没有找到对应的地址信息,则生成新的地址信息。

 pinfo = Create(addr, source, &nId);
 pinfo->nTime = std::max((int64_t)0, (int64_t)pinfo->nTime - nTimePenalty);
 nNew++;
 fNew = true;

在Create方法中,生成一个新的CAddrInfo对象放入mapInfo集合中,并在mapAddr集合中添加相应的条目。 具体代码如下:

 int nId = nIdCount++;
 mapInfo[nId] = CAddrInfo(addr, addrSource);
 mapAddr[addr] = nId;
 mapInfo[nId].nRandomPos = vRandom.size();
 vRandom.push_back(nId);
 if (pnId)

比特币挖矿算法_比特币算法源代码_比特币p2p算法

*pnId = nId; return &mapInfo[nId];

接下来,将处理一些其他信息。 代码比较简单,就不详细介绍了。

 int nUBucket = pinfo->GetNewBucket(nKey, source);
 int nUBucketPos = pinfo->GetBucketPosition(nKey, true, nUBucket);
 if (vvNew[nUBucket][nUBucketPos] != nId) {
     bool fInsert = vvNew[nUBucket][nUBucketPos] == -1;
     if (!fInsert) {
         CAddrInfo& infoExisting = mapInfo[vvNew[nUBucket][nUBucketPos]];
         if (infoExisting.IsTerrible() || (infoExisting.nRefCount > 1 && pinfo->nRefCount == 0)) {
             // Overwrite the existing new table entry.
             fInsert = true;
         }
     }
     if (fInsert) {
         ClearNew(nUBucket, nUBucketPos);
         pinfo->nRefCount++;
         vvNew[nUBucket][nUBucketPos] = nId;
     } else {
         if (pinfo->nRefCount == 0) {
             Delete(nId);
         }
     }
 }

返回真。 我是欧小白,Ulord全球社区联盟(优德社区)区块链核心技术开发者。 我研究过比特币、以太坊、EOS Dash、Rsk、Java、Nodejs、PHP、Python 和 C++。 多位区块链开发者,共同学习,共同进步。 为了更高效的交流和讨论区块链开发过程中遇到的问题,欢迎在帖子下方留言。