在前两篇中,我们从区块结构 → 交易机制 → 共识流程打造了一个功能完整的迷你区块链系统,包含了区块、交易、交易池、签名验证与PoW共识。
然而,这仍非区块链的全貌——它本质上还是一个单机账本。
一个真正的区块链系统必须具备以下特征:
多节点、互联互通、自动同步、去中心化的分布式网络。
本篇,我们将着手构建区块链的核心网络层,具体实现:
- 实现 P2P 节点发现
- 节点之间自动建立连接
- 消息自动传播(区块、交易等)
- 网络容错
- 无中心依赖
自此,我们的区块链将真正拥有“多节点网络”的能力。
本次我们引入libp2p——一个广泛应用于搭建Web3网络的工业级P2P框架(也是IPFS和Filecoin的底层网络库)。
一、为什么区块链必须依赖P2P网络?
区块链节点不依赖于中心服务器,每个节点都需要具备以下能力:
- 接收其他节点发起的交易
- 与网络同步最新的区块
- 向全网广播自己挖出的新区块
- 与其他节点协作,就最长有效链达成共识
这要求网络必须是去中心化的对等网络拓扑结构,即 Peer-to-Peer (P2P)。
为了构建一个实际可用、可扩展且能实现多节点自动联通的区块链网络,libp2p无疑是现阶段的最优选择。
二、整体设计:GossipSub + mDNS + Peer管理
我们将构建一个简化但实用的P2P网络,它包含以下核心模块:
- libp2p Host:节点的网络身份与连接管理核心。
- GossipSub:作为区块和交易广播的消息总线,是去中心化传播的核心协议。
- mDNS:用于局域网内节点的自动发现,简化本地开发和测试。
- Peer管理:管理与网络中其他节点的连接状态。
- 统一消息结构:定义网络中传递的标准化消息格式。
- 异步消息处理:处理从网络接收到的各种消息。
整个系统的核心数据流如下图所示,从libp2p Host经由GossipSub总线,订阅到具体的Topic,最终通过Subscription循环接收消息:
┌──────────────────────────┐
│ libp2p Host │
└───────────┬──────────────┘
│
┌───────────▼─────────────────┐
│ GossipSub │
│ (区块/交易广播总线) │
└───────────┬──────────────────┘
│
┌──────▼──────┐
│ Topic: mini-chain │
└──────┬──────┘
│
┌──────▼────────────┐
│ Subscription │
│ (Sub.Next) │
└──────────────────┘
三、节点数据结构设计
我们定义Node结构体来代表一个P2P网络节点,它封装了libp2p的核心组件,是构建分布式系统网络层的基础。
// Node 表示一个libp2p节点,包含主机、发布订阅和主题相关信息
type Node struct {
Host host.Host // libp2p主机实例
PubSub *pubsub.PubSub // 发布订阅实例
Topic *pubsub.Topic // 主题实例
Sub *pubsub.Subscription // 订阅实例
}
每个节点需要维护以下关键实例:
Host:本节点的libp2p主机实例,负责所有网络连接和通信。
PubSub:GossipSub发布订阅协议的实例,是消息广播的引擎。
Topic:本地区块链网络所使用的主题实例,所有节点都订阅同一主题以交换信息。
Sub:对该主题的订阅实例,用于持续接收网络消息。
四、创建节点:libp2p Host与GossipSub初始化
创建libp2p Host(节点身份)
首先,我们创建一个libp2p主机,它将作为节点在网络中的身份标识。
// 创建libp2p主机实例
h, err := libp2p.New(
libp2p.ListenAddrStrings(
// 监听所有IPv4地址,并使用指定端口
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", listenPort),
),
)
初始化GossipSub协议
接下来,将创建的主机加入到GossipSub协议中,使其具备消息发布与订阅的能力。
// 创建GossipSub实例
ps, err := pubsub.NewGossipSub(ctx, h)
if err != nil {
return nil, err
}
GossipSub是一个专为去中心化环境设计的消息广播协议,具有抗女巫攻击、高效传播等特性,是ETH2、Filecoin、Cosmos等知名项目的网络层选择,完美契合区块链对消息广播的需求。
加入主题
所有节点需要加入一个共同的主题(例如“mini-chain”),才能相互通信。
// 加入"mini-chain"主题
topic, err := ps.Join("mini-chain")
if err != nil {
return nil, err
}
// 订阅该主题
sub, err := topic.Subscribe()
if err != nil {
return nil, err
}
这相当于为整个区块链网络建立了一个公共“频道”,所有关于区块和交易的消息都在这个频道内广播和接收。
五、节点发现:mDNS自动发现局域网节点
为了在开发或测试环境中方便地自动发现节点,我们启用mDNS服务。
if err := SetupMdns(ctx, h); err != nil {
log.Println("mDNS warning:", err)
}
mDNS(多播DNS)的作用是自动发现同一局域网内的其他libp2p节点,无需手动配置IP地址。这意味着,当你运行两个节点时:
go run main.go -port 3001
go run main.go -port 3002
它们能够自动发现并建立连接,极大简化了多节点环境的搭建。
六、消息广播(核心):实现区块与交易的实时全网同步
广播功能是网络层的核心,通过以下简单的Broadcast函数实现:
func (n *Node) Broadcast(msg *Message) {
data, _ := msg.Encode()
n.Topic.Publish(context.Background(), data)
}
这是最关键的一行代码。它使得每一笔新交易、每一个新挖出的区块,都能自动、可靠地传播到网络中的每一个节点。 相比于简单的TCP广播,GossipSub提供了消息去重、传播限流、网络自愈和路径优化等高级特性,并能更好地穿透NAT,是专为区块链设计的广播层。
七、接收消息:统一处理区块、交易与同步请求
节点需要持续监听网络消息,并进行处理。我们通过一个异步循环来实现:
// handleMessages 循环接收gossipsub消息
func (n *Node) handleMessages(ctx context.Context) {
for {
// 获取下一条消息
msg, err := n.Sub.Next(ctx)
if err != nil {
return
}
// 忽略自己发送的消息
if msg.ReceivedFrom == n.Host.ID() {
continue
}
// 解码消息
m, err := Decode(msg.Data)
if err != nil {
log.Println("invalid message:", err)
continue
}
// 根据消息类型,调用区块链或交易池的相应处理函数
log.Println("Received msg from", msg.ReceivedFrom, "type:", m.Type)
// TODO: 例如,处理新区块、新交易等
}
}
消息处理循环持续运行,负责解码网络原始数据为业务消息(Message),并根据其类型(如BLOCK, TX)分发给后端的区块链逻辑或交易池进行处理。
八、手动连接节点(用于跨网段或远程部署)
除了自动发现,libp2p也支持通过已知地址手动连接节点,这对于构建跨互联网的分布式网络至关重要。
func (n *Node) ConnectPeer(addr string) error {
pi, err := peer.AddrInfoFromString(addr)
return n.Host.Connect(context.Background(), *pi)
}
此功能使得我们的P2P网络不再局限于局域网,可以用于:
- 连接位于不同数据中心的云服务器节点
- 构建跨地域的测试网络
- 完全模拟公链的真实网络拓扑结构
九、最终效果:你的区块链正式进入多节点时代
通过集成libp2p,我们为区块链构建了一个健壮、实用的P2P网络层,它具备以下特性:
- ✔ 自动节点发现 (mDNS):局域网内节点可自动互联。
- ✔ 高效消息广播 (GossipSub):区块与交易能自动、可靠地全网扩散。
- ✔ 安全加密通道:节点间通信默认使用Noise等加密协议,无需额外配置TLS。
- ✔ 完全去中心化:无主节点、无中心服务器,符合区块链精神。
- ✔ 强大的可扩展性:支持远程节点手动连接,易于在云端部署真实的多节点网络。
至此,你的迷你区块链已经具备了真实公链所必需的“网络层”能力。
(本项目主要用于学习案例,不构成生产环境使用建议)