以太坊源码解析:区块同步协议的核心实现

·

核心关键词:以太坊、区块同步、ProtocolManager、StatusMsg、downloader、fetcher、Handshake、NewBlockMsg、白名单区块

引言

区块链之所以称为分布式总账技术,核心在于节点间实时同步区块数据。以太坊使用 go-ethereum 作为主客户端,在大概率部署场景下,节点需要保证本地链视图与网络一致,这就要依赖稳定和可扩展的区块同步协议。本文聚焦protocol 层面:握手、消息路由、广播策略以及同步触发条件,不涉及 P2P 网络拓扑或 libp2p 底层实现。

👉 想要更系统地复盘挖矿、同步和广播的全链路流程?点我直达!


源码目录与分支信息

轻节点切换靠 cmd/utils/flags.go 的配置:

if cfg.SyncMode == downloader.LightSync {
    return les.New(...)
}

网络运行框架总览

以太坊的网络层由 ProtocolManager 统一管控,职责涵盖:

  1. 新节点握手
  2. 区块/交易广播
  3. 同步策略决策
  4. 消息路由(HandleMessage)

1. 节点连接生命周期

握手(Handshake)

与对端建立 TCP 连接后的第一件事是交换 StatusMsg,结构如下:

type statusData struct {
    ProtocolVersion uint32
    NetworkId       uint64
    TD              *big.Int
    CurrentBlock    common.Hash
    GenesisBlock    common.Hash
}

peer.Handshake 采用双 go-routine 发送&接收 + 2s 超时的并行模式,提前建立信任基线。

可信任节点

如果 peers.Len() >= maxPeers 不是 TrustedPeer,直接 DiscTooManyPeers
通过节点控制台 admin.addTrustedPeer("enode://...") 白名单即可绕过连接数限制。

请求白名单区块

握手后立刻向新节点索要配置里的 whitelist 区块(高度+哈希一旦不一致立即断连),防止早期分叉污染。

2. 消息处理主循环

ProtocolManager.handleMsg 采用「大 switch」模式,消息码表 1:1 对应功能:

消息码作用
GetBlockHeadersMsg请求某区间的 block header
BlockHeadersMsg回应 header 列表
NewBlockMsg广播整条新块
NewBlockHashesMsg广播新区块哈希列表(带宽更轻)
TxMsg广播交易
提示:
Eth63 在 Eth62 基础上又加了GetNodeDataMsg / GetReceiptsMsg,用于状态健校验。

fetcher 与 downloader 的职责分野

所以 handleMsg 中先 fetcher.Filter,再 downloader.Deliver——多路复用不打架。


广播:NewBlockMsg vs NewBlockHashesMsg

场景propagate=true(NewBlockMsg)propagate=false(NewBlockHashesMsg)
默认广播半径sqrt(总节点)所有节点
触发条件本地挖出 / fetcher即将入库常规同步完成汇报最新块
网络带宽大量数据一次放闸轻量哈希广播
这样设计可在“尽快传播”和“防止洪泛”之间做权衡,减少同步高峰时段的网络抖动。

👉 自己动手做一次广播实验,可视化节点间的全网洪水模式!


同步触发时机

ProtocolManager.Start() 会拉起 syncer goroutine:

BestPeer 选择逻辑:max(TD) ,“骨架节点”永远是当前难度最高的,确保同步数据“根正苗红”。

一旦 downloader.Synchronise 结束,主动广播 NewBlockHashesMsg 让全网更新本地 head


peer 与 peerSet:内存中的微账本

peer 对象维护三类“已知数据”:

  1. head, td:最新主链指针
  2. knownBlocks:对方已拥有的 block 哈希集合
  3. knownTxs:对方已拥有的交易哈希集合
Lazy Set:基于 BloomFilter 的衰减集合,防抖动 & 防重广播。

peerSet:线程安全的容器,BestPeer() 每次加读锁遍历取 TD 最大。


FAQ

  1. Q:StatusMsg 只交换一次,后续如何知道对方 TD 更新了?
    A:在收到 NewBlockMsg 后,若 trueTD > peer.td 会调用 peer.SetHead 即时刷新。NewBlockHashesMsg 则需先调用 fetcher.Notify 拉取 header 才能更新。
  2. Q:为什么 whitelist 哈希只需 1 个就可以?
    A:高度是单调递增的,只要 whitelist 区块之后的分叉检测即会看到 TD 增长异常,单点即可阻止大多数分叉攻击。
  3. Q:fetcher 相比 downloader 来说“轻”在哪里?
    A:fetcher 仅对最新_tip做增量化,一次最多拉 1~2 个区块;downloader 做全区间重排,要遍历成百上千的区块。
  4. Q:NewBlockMsg 会触发双重广播吗?
    A:不会。peer.knownBlocks 会一直做 Port 过滤,保证一个 peer 不会收到同一区块两次。
  5. Q:最大连接数如何配置比较合理?
    A:50~100 是大多数验证者节点的默认值,可用 sysctls 调大;需要 TrustedPeer 名单以防无效连接占用额度。
  6. Q:LightSync 模式下 les 与 eth 目录的协议差别?
    A:les 是轻节点协议,只做 header+proof 拉取,协议码定义在 les/peer.goLPP_ 前缀消息;逻辑不能复用 downloader,而是用 les/fetcher

总结:一张图读懂区块同步协议

节点启动 → 建立 TCP → Handshake → Whitelist Check → MainLoop(handleMsg)
       ↑     ↓
   peerSet.Add  peerSet.BestPeer ←【定时/事件】Syncer↓
       ↑                               ↓
fetcher ←→ NewBlockHashesMsg   ←→ downloader.Synchronise → 后续骨架回填
       ↑                                                        ↓
   本地Head广播 ← NewBlockMsg ← 挖矿区块 / 缓存入库

整条链路中:

  1. ProtocolManager 像操作系统调度器;
  2. peerSet 相当于内存中“路由表”;
  3. NewBlockMsg / NewBlockHashesMsg 解决快速广播与减少重复数据;
  4. TrustedPeerWhitelist 是防御女巫与分叉的第一道闸。

掌握以上 4 个关键点,即可对整个同步协议有端到端的认知。