ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Bitcoin Core 硬编码种子节点生成流程:从 contrib/seeds 工具到 chainparamsseeds.h 的深度剖析

Bitcoin Core 硬编码种子节点生成流程:从 contrib/seeds 工具到 chainparamsseeds.h 的深度剖析 Bitcoin Core 硬编码种子节点生成流程从 contrib/seeds 工具到 chainparamsseeds.h 的深度剖析【免费下载链接】bitcoinBitcoin Core integration/staging tree项目地址: https://gitcode.com/GitHub_Trending/bi/bitcoinBitcoin Core 客户端在 P2P 层内嵌了一份编译期固定的种子节点列表fixed seeds用于新节点启动时的地址引导。本文以 contrib/seeds/README.md 为主线完整梳理“拉取 DNS seed 数据 → makeseeds.py 过滤筛选 → generate-seeds.py 序列化为 C 常量数组”的全套生成流程并结合 makeseeds.py、generate-seeds.py 的源码实现讲解每一道过滤规则的设计意图与关键参数MIN_BLOCKS、PATTERN_AGENT、ASN 上限等的维护要点帮助读者理解种子节点列表从数据源到编译产物的完整链路。硬编码种子节点在客户端中的位置在展开工具链之前先明确这些种子最终“长”在哪里。生成产物是头文件 src/chainparamsseeds.h文件头部注释标明它由contrib/seeds/generate-seeds.py自动生成AUTOGENERATED by contrib/seeds/generate-seeds.py其中每行是一个BIP155 序列化的 (networkID, addr, port) 元组按网络拆分为四个常量数组chainparams_seed_main、chainparams_seed_signet、chainparams_seed_test、chainparams_seed_testnet4。这些数组在 src/kernel/chainparams.cpp#L184 被载入ChainParams::vFixedSeedsvFixedSeeds std::vectoruint8_t(std::begin(chainparams_seed_main), std::end(chainparams_seed_main));节点连接 P2P 网络时这些固定种子是 addrman 的初始地址来源之一。从 src/net.cpp#L2394-L2414 的 DNS 种子加载逻辑可以看到配套机制客户端查询 DNS 种子时会请求x{servicebits}.{seed}子域名servicebits 取SeedsServiceFlags()且单个 DNS 种子最多取回 32 个 IP防止其结果主导 AddrMan。SeedsServiceFlags()定义在 src/protocol.h#L366constexpr ServiceFlags SeedsServiceFlags() { return ServiceFlags(NODE_NETWORK | NODE_WITNESS); }也就是说硬编码种子默认只要求节点同时支持完整区块服务NODE_NETWORK与见证数据服务NODE_WITNESS。这正是 README 中“维护时机”一段的关键前提见下文“维护时机”一节。工具链总览目录里各文件分别做什么contrib/seeds/ 目录内容及各文件角色如下文件角色README.md生成流程的操作说明本文主线makeseeds.py解析原始 DNS seed 导出文件按多道规则过滤输出nodes_*.txt明文ip:port列表generate-seeds.py读取nodes_*.txt做 BIP155 二进制序列化生成 src/chainparamsseeds.hnodes_main.txt、nodes_signet.txt、nodes_test.txt、nodes_testnet4.txt四个链的种子明文清单随仓库提交。以 mainnet 为例当前共 2059 行signet/test/testnet4 分别为 170、181、230 行流程一句话概括外部数据源 → seeds_.txt原始导出→ makeseeds.py 过滤 → nodes_.txt入选清单→ generate-seeds.py → chainparamsseeds.h编译进客户端。数据源与拉取命令README 明确说明编译进发布版本的种子列表来自sipa 与 achow101 的 DNS seed、virtu 的爬虫crawler以及 asmap 社区的 AS map 数据。README 给出的一组完整命令如下需在contrib/seeds目录下执行curl https://21.ninja/seeds.txt.gz | gzip -dc seeds_main.txt curl https://mainnet.achownodes.xyz/seeds.txt.gz | gzip -dc seeds_main.txt curl https://signet.achownodes.xyz/seeds.txt.gz | gzip -dc seeds_signet.txt curl https://testnet.achownodes.xyz/seeds.txt.gz | gzip -dc seeds_test.txt curl https://testnet4.achownodes.xyz/seeds.txt.gz | gzip -dc seeds_testnet4.txt curl https://raw.githubusercontent.com/asmap/asmap-data/main/latest_asmap.dat asmap-filled.dat python3 makeseeds.py -a asmap-filled.dat -s seeds_main.txt nodes_main.txt python3 makeseeds.py -a asmap-filled.dat -s seeds_signet.txt -m 266000 nodes_signet.txt python3 makeseeds.py -a asmap-filled.dat -s seeds_test.txt -m 4650000 nodes_test.txt python3 makeseeds.py -a asmap-filled.dat -s seeds_testnet4.txt -m 100000 nodes_testnet4.txt python3 generate-seeds.py . ../../src/chainparamsseeds.h几个值得注意的细节mainnet 的原始数据是两条 curl 追加后接合并的即 sipa 与 achow101 两个 DNS seed 的导出拼成一个文件。这也解释了makeseeds.py中dedup()的去重逻辑注释——“in case multiple seeds files were concatenated”多个种子文件拼接时可能重复。每条链的-m/--minblocks最少区块高度不同mainnet 使用脚本内置默认值而 signet 显式传 266000、testnet 传 4650000、testnet4 传 100000。这些阈值必须与各链当前出块高度相匹配README 因此要求“按需更新makeseeds.py中的MIN_BLOCKS以及下面的-m/--minblocks参数”。asmap-filled.dat是 ASN自治系统号映射数据库供筛选时做按 ASN 去冗余防单一 AS 运营商垄断种子。各链 DNS seed 的运维需满足社区的最低信任与行为约定参考 doc/dnsseed-policy.md例如结果必须来自公平选出的正常节点、TTL 不得小于 1 分钟等。makeseeds.py过滤流水线的每一道关卡makeseeds.py 的核心常量与规则决定了“什么样的节点有资格进入硬编码列表”。关键参数一览从 makeseeds.py#L22-L29 可以看到NSEEDS512 MAX_SEEDS_PER_ASN { ipv4: 2, ipv6: 10, } MIN_BLOCKS 910000NSEEDS512每类网络net最多入选的种子数量上限由filterbyasn()以max_per_net参数生效MAX_SEEDS_PER_ASN同一 ASN 在 IPv4 中最多 2 个、IPv6 中最多 10 个种子用于限制单个网络运营商对种子池的话语权该函数注释注明其思路参考自 Greg Maxwell 的 seed_filter.pyMIN_BLOCKS910000节点必须具备的最少区块数低于主网当前链尖高度的节点会被剔除可通过-m覆盖各测试链即如此使用。行解析支持的五类地址parseline()用四条正则识别地址族makeseeds.py#L31-L34IPv4含 0–255 合法性检查IPv6排除 localhost::以fc开头的 fc00::/8 地址被识别为CJDNS而非普通 IPv6;.onionTor v3 地址;.b32.i2pI2P 地址。每条记录提取的字段包括网络类型、IP、端口、30 天在线率uptime %、最近成功连接的 Unix 时间戳、协议版本、user agent/Satoshi:x.y.z、服务标志位16 进制、区块高度。注释行#开头与过短/无效的字段会被直接跳过。过滤顺序main 函数流水线main()中的处理顺序makeseeds.py#L208-L267每一步都会打印各网络数量统计便于核对筛选效果丢弃无效地址正则不匹配或字段非法的行去重同一(ip, port)保留lastsuccess最新的一条强制最少区块数blocks minblocks要求服务位 1(service 1) 1即必须声明NODE_NETWORK能提供完整区块在线率门槛makeseeds.py#L237-L245req_uptime { ipv4: 50, ipv6: 50, onion: 50, i2p: 50, cjdns: 10, }即 clearnetIPv4/IPv6、Tor、I2P 均要求 30 天在线率超过 50%CJDNS 因节点规模小放宽到 10%要求已知且较新的 user agent节点版本必须匹配PATTERN_AGENT正则makeseeds.py#L35-L54该正则逐条枚举0.14到30.x各发行版的版本串用于淘汰长期未升级、行为未知的老节点——这也是 README 第一条维护提示“务必更新PATTERN_AGENT加入当前版本”的原因按可用性排序以(uptime, lastsuccess, ip)降序排列让更稳定的节点优先入选多端口过滤filtermultiport()同一 IP 上存在多个不同端口的比特币端口时全部剔除源码注释认为“these are likely abusive”大概率是滥用行为如扫描器或多实例滥挂ASN 与网络配额filterbyasn()IPv4/IPv6 按 ASN 限额上表的MAX_SEEDS_PER_ASN且每网络总数不超过NSEEDSonion、I2P、CJDNS 各最多取NSEEDS个不做 ASN 限制确定性排序输出最后按(net, sortkey)排序后逐行输出ip:portIPv6/CJDNS 带方括号可选附# ASasn注释保证多次运行结果一致、diff 干净。输出样例即仓库中已提交的 contrib/seeds/nodes_main.txt[fc11:f769:16e6:3611:58ae:1d4a:fcf7:57a4]:8333 22pis7zmm4r466tciqekpwjwzf2qi3a536bow7k5tu5kxgmbvrkq.b32.i2p:0可以看到 CJDNSfc…前缀、I2P.b32.i2p与 clearnet 节点并存I2P 端口记为 0 表示使用网络默认端口。generate-seeds.pyBIP155 序列化与 C 头文件生成gateway 第二步 只依赖标准库职责是把nodes_*.txt转成头文件脚本头部注释声明其输入为四个nodes_*.txt行格式支持ip:port、[ipv6]:port、onion:port、i2p:port网络类型枚举遵循 BIP155generate-seeds.py#L38-L44IPV41、IPV62、TORV23已不再支持遇到即丢弃、TORV34、I2P5、CJDNS6name_to_bip155()负责地址解析.onion做 base32 解码并校验 v3 标志位fc00::/8前缀的地址按注释说明归入 CJDNS参照 RFC4193 关于 ULA 的讨论bip155_serialize()的字节布局为1 字节 networkID compact_size 长度前缀 地址字节 2 字节大端端口process_nodes()逐行序列化并写出十六进制常量main()依次处理 main、signet、test、testnet4 四份文件生成带BITCOIN_CHAINPARAMSSEEDS_H头文件守卫的完整头文件最终重定向到 src/chainparamsseeds.h。因此 src/chainparamsseeds.h 中那些看似不可读的十六进制如0x05,0x20,…开头、networkID5 的 I2P 条目0x06,0x10,0xfc,…开头、networkID6 的 CJDNS 条目都能按上述布局逐字节解读这与nodes_main.txt的明文条目一一对应可作为人工核对生成的正确性。维护时机README 中两条警示的底层原因README 在命令之前给出的两条维护要求恰好对应客户端的运行时语义“更新makeseeds.py中的PATTERN_AGENT加入当前版本、按需移除旧版本至少在SeedsServiceFlags()默认返回值变化时”。PATTERN_AGENT决定了哪些版本的节点能入选而SeedsServiceFlags()src/protocol.h#L366决定客户端从种子含 DNS 种子的x{bits}子域名查询期望获得哪些服务。两者变化不同步时硬编码种子与服务位假设就会脱节——例如默认服务集扩展后若未按新标志重新生成种子客户端拿到的引导节点可能不满足新的服务要求。“按需更新MIN_BLOCKS与-m/--minblocks”。 区块高度门槛是相对各链“当前链尖”的经验值随出块持续增长README 中 signet/test/testnet4 的显式-m值266000、4650000、100000正是当前各链对应的阈值主网默认值 910000 亦同。小结contrib/seeds提供了一条可复现、可审计的硬编码种子生成流水线数据来自多个独立 DNS seed 与爬虫经makeseeds.py的十道规则地址合法性、去重、区块数、服务位、在线率、版本白名单、多端口滥用过滤、ASN 配额、网络配额、确定性排序收敛出每网络至多 512 个高可用节点再由generate-seeds.py序列化为 BIP155 二进制常量写回 src/chainparamsseeds.h最终由 src/kernel/chainparams.cpp#L184 注入vFixedSeeds编译进客户端。维护者需要随版本演进同步PATTERN_AGENT、MIN_BLOCKS及各链-m参数并注意SeedsServiceFlags()语义变化时的重新生成时机。对希望理解“新节点如何获得第一手地址”或参与种子体系维护的开发者这条从 Python 脚本到 C 头文件的路径是完整的参考实现。【免费下载链接】bitcoinBitcoin Core integration/staging tree项目地址: https://gitcode.com/GitHub_Trending/bi/bitcoin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进