📢 Gate广场 #NERO发帖挑战# 秀观点赢大奖活动火热开启!
Gate NERO生态周来袭!发帖秀出NERO项目洞察和活动实用攻略,瓜分30,000NERO!
💰️ 15位优质发帖用户 * 2,000枚NERO每人
如何参与:
1️⃣ 调研NERO项目
对NERO的基本面、社区治理、发展目标、代币经济模型等方面进行研究,分享你对项目的深度研究。
2️⃣ 参与并分享真实体验
参与NERO生态周相关活动,并晒出你的参与截图、收益图或实用教程。可以是收益展示、简明易懂的新手攻略、小窍门,也可以是行情点位分析,内容详实优先。
3️⃣ 鼓励带新互动
如果你的帖子吸引到他人参与活动,或者有好友评论“已参与/已交易”,将大幅提升你的获奖概率!
NERO热门活动(帖文需附以下活动链接):
NERO Chain (NERO) 生态周:Gate 已上线 NERO 现货交易,为回馈平台用户,HODLer Airdrop、Launchpool、CandyDrop、余币宝已上线 NERO,邀您体验。参与攻略见公告:https://www.gate.com/announcements/article/46284
高质量帖子Tips:
教程越详细、图片越直观、互动量越高,获奖几率越大!
市场见解独到、真实参与经历、有带新互动者,评选将优先考虑。
帖子需原创,字数不少于250字,且需获得至少3条有效互动
libp2p在op-stack中的使用
作者:joohhnnn
optimism中的libp2p应用
在本节中,主要用于讲解optimism是如何使用libp2p来完成op-node中的p2p网络建立的。 p2p网络主要是用于在不同的node中传递信息,例如sequencer完成unsafe的区块构建后,通过p2p的gossiphub的pub/sub进行传播。libp2p还处理了其他,例如网络,寻址等在p2p网络中的基础件层。
了解libp2p
libp2p(简称来自“库对等”或“library peer-to-peer”)是一个面向对等(P2P)网络的框架,能够帮助开发P2P应用程序。它包含了一套协议、规范和库,使网络参与者(也称为“对等体”或“peers”)之间的P2P通信变得更为简便 (source[2])。libp2p最初是作为IPFS(InterPlanetary File ,星际文件系统)项目的一部分,后来它演变成了一个独立的项目,成为了分布式网络的模块化网络堆栈 (source[3])。
libp2p是IPFS社区的一个开源项目,欢迎广泛社区的贡献,包括帮助编写规范、编码实现以及创建示例和教程 (source[4])。libp2p是由多个构建模块组成的,每个模块都有非常明确、有文档记录且经过测试的接口,使得它们可组合、可替换,因此可升级 (source[5])。libp2p的模块化特性使得开发人员可以选择并使用仅对他们的应用程序必要的组件,从而在构建P2P网络应用程序时促进了灵活性和效率。
相关资源
libp2p的模块化架构和开源特性为开发强大、可扩展和灵活的P2P应用程序提供了良好的环境,使其成为分布式网络和网络应用程序开发领域的重要参与者。
libp2p实现方式
在使用libp2p时,你会需要实现和配置一些核心组件以构建你的P2P网络。以下是libp2p在应用中的一些主要实现方面:
1. 节点创建与配置:
libp2p.New()
2. 传输协议:
tcpTransport := tcp.NewTCPTransport()
3. 多路复用和流控制:
yamuxTransport := yamux.New()
4. 安全和加密:
tlsTransport := tls.New()
5. 协议和消息处理:
host.SetStreamHandler("/my-protocol/1.0.0", myProtocolHandler)
6. 发现和路由:
dht := kaddht.NewDHT(ctx, host, datastore.NewMapDatastore())
7. 网络行为和策略:
connManager := connmgr.NewConnManager(lowWater, highWater, gracePeriod)
8. 状态管理和存储:
peerstore := pstoremem.NewPeerstore()
9. 测试和调试:
logging.SetLogLevel("libp2p", "DEBUG")
10. 文档和社区支持:
}
以上是使用libp2p时需要考虑和实现的一些主要方面。每个项目的具体实现可能会有所不同,但这些基本方面是构建和运行libp2p应用所必需的。在实现这些功能时,可以参考libp2p的官方文档[9]和GitHub仓库[10]中的示例代码和教程。
在OP-node中libp2p的使用
为了弄清楚op-node和libp2p的关系,我们必须弄清楚几个问题
op-node需要libp2p网络的原因
首先我们要了解为什么optimism需要p2p网络libp2p是一个模块化的网络协议,允许开发人员构建去中心化的点对点应用,适用于多种用例 (source[11])(source[12])。而devp2p主要用于以太坊生态系统,专为以太坊应用定制 (source[13])。libp2p的灵活性和广泛适用性可能使其成为开发人员的首选。
op-node主要使用libp2p的功能点
代码实现
host自定义初始化
host可以理解为是p2p的节点,当开启这个节点的时候,需要针对自己的项目进行一些特殊的初始化配置
现在让我们看一下 op-node/p2p/host.go文件中的Host方法,
该函数主要用于设置 libp2p 主机并进行各种配置。以下是该函数的关键部分以及各部分的简单中文描述:
如果 P2P 被禁用,函数会直接返回。
使用配置中的公钥来生成 Peer ID。
创建一个基础的 Peerstore 存储。
在基础 Peerstore 的基础上,创建一个扩展的 Peerstore。
在 Peerstore 中存储 Peer 的私钥和公钥。
用于控制网络连接。
用于管理网络连接。
设置网络传输协议和主机的监听地址。
使用前面的所有设置来创建一个新的 libp2p 主机。
如果有配置静态 Peer,进行初始化。
最后,函数返回创建好的 libp2p 主机。
这些关键部分负责 libp2p 主机的初始化和设置,每个部分都负责主机配置的一个特定方面。
gossip下的区块传播
gossip在分布式系统中用于确保数据一致性,并修复由多播引起的问题。它是一种通信协议,其中信息从一个或多个节点发送到网络中的其他节点集,当网络中的一组客户端同时需要相同的数据时,这会很有用。当sequencer产生出unsafe状态的区块的时候,就是通过gossip网络传递给其他节点的。
首先让我们来看看节点是在哪里加入gossip网络的,op-node/p2p/node.go中的init方法,在节点初始化的时候,调用JoinGossip方法加入了gossip网络
接下来来到op-node/p2p/gossip.go中
以下是 JoinGossip 函数中执行的主要操作的简单概述:
创建了一个 publisher 实例并返回,该实例配置为使用提供的配置和区块主题。
func JoinGossip(p2pCtx context.Context, self peer.ID, ps *pubsub.PubSub, log log.Logger, cfg rollup.Config, runCfg GossipRuntimeConfig, gossipIn GossipIn) (GossipOut, error) {
val := guardGossipValidator(log, logValidationResult(self, "validated block", log, BuildBlocksValidator(log, cfg, runCfg)))
blocksTopicName := blocksTopicV1(cfg) // return fmt.Sprintf("/optimism/%s/0/blocks", cfg.L2ChainID.String())
err := ps.RegisterTopicValidator(blocksTopicName,
val,
pubsub.WithValidatorTimeout(3time.Second),
pubsub.WithValidatorConcurrency(4))
if err != nil {
return nil, fmt.Errorf("failed to register blocks gossip topic: %w", err)
}
blocksTopic, err := ps.Join(blocksTopicName)
if err != nil {
return nil, fmt.Errorf("failed to join blocks gossip topic: %w", err)
}
blocksTopicEvents, err := blocksTopic.EventHandler()
if err != nil {
return nil, fmt.Errorf("failed to create blocks gossip topic handler: %w", err)
}
go LogTopicEvents(p2pCtx, log.New("topic", "blocks"), blocksTopicEvents)
subion, err := blocksTopic.Subscribe()
if err != nil {
return nil, fmt.Errorf("failed to subscribe to blocks gossip topic: %w", err)
}
subscriber := MakeSubscriber(log, BlocksHandler(gossipIn.OnUnsafeL2Payload))
go subscriber(p2pCtx, subion)
return &publisher{log: log, cfg: cfg, blocksTopic: blocksTopic, runCfg: runCfg}, nil
}
这样,一个非sequencer节点的订阅就已经建立了,接下来让我们把目光移到sequencer模式的节点当中,然后看看他是如果将区块广播出去的。
op-node/rollup/driver/state.go
在eventloop中通过循环来等待sequencer模式中新的payload的产生(unsafe区块),然后将这个payload通过PublishL2Payload传播到gossip网络中
这样,一个新的payload的就进入到gossip网络中了。
在libp2p的pubsub系统中,节点首先从其他节点接收消息,然后检查消息的有效性。如果消息有效并且符合节点的订阅标准,节点会考虑将其转发给其他节点。基于某些策略,如网络拓扑和节点的订阅情况,节点会决定是否将消息转发给其它节点。如果决定转发,节点会将消息发送给与其连接并订阅了相同主题的所有节点。在转发过程中,为防止消息在网络中无限循环,通常会有机制来跟踪已转发的消息,并确保不会多次转发同一消息。同时,消息可能具有“生存时间”(TTL)属性,定义了消息可以在网络中转发的次数或时间,每当消息被转发时,TTL值都会递减,直到消息不再被转发为止。在验证方面,消息通常会通过一些验证过程,例如检查消息的签名和格式,以确保消息的完整性和真实性。在libp2p的pubsub模型中,这个过程确保了消息能够广泛传播到网络中的许多节点,同时避免了无限循环和网络拥塞,实现了有效的消息传递和处理。
当存在缺失区块,通过p2p快速同步
当节点因为特殊情况,比如宕机后重新链接,可能会产生一些没有同步上的区块(gaps),当遇到这种情况时,可以通过p2p网络的反向链的方式快速同步。
我们来看一下op-node/rollup/driver/state.go中的checkForGapInUnsafeQueue函数
该代码段定义了一个名为 checkForGapInUnsafeQueue 的方法,属于 Driver 结构体。它的目的是检查一个名为 "unsafe queue" 的队列中是否存在数据缺口,并尝试通过一个名为 altSync 的备用同步方法来检索缺失的负载。这里的关键点是,该方法是为了确保数据的连续性,并在检测到数据缺失时尝试从其他同步方法中检索缺失的数据。以下是函数的主要步骤:
函数首先从 s.derivation 中获取 UnsafeL2Head 和 UnsafeL2SyncTarget 作为检查范围的起始和结束点。
函数检查在 start 和 end 之间是否存在缺失的数据块,这是通过比较 end 和 start 的 Number 值来完成的。
如果检测到数据缺口,函数会通过调用 s.altSync.RequestL2Range(ctx, start, end) 来请求缺失的数据范围。如果 end 是一个空引用(即 eth.L2BlockRef{}),函数将请求一个开放结束范围的同步,从 start 开始。
在请求数据时,函数会记录一个调试日志,说明它正在请求哪个范围的数据。
函数最终返回一个错误值。如果没有错误,它会返回 nil
// checkForGapInUnsafeQueue checks if there is a gap in the unsafe queue and attempts to retrieve the missing payloads from an alt-sync method.
// WARNING: This is only an outgoing signal, the blocks are not guaranteed to be retrieved.
// Results are received through OnUnsafeL2Payload.
func (s *Driver) checkForGapInUnsafeQueue(ctx context.Context) error {
start := s.derivation.UnsafeL2Head()
end := s.derivation.UnsafeL2SyncTarget()
// Check if we have missing blocks between the start and end Request them if we do.
if end == (eth.L2BlockRef{}) {
s.log.Debug("requesting sync with open-end range", "start", start)
return s.altSync.RequestL2Range(ctx, start, eth.L2BlockRef{})
} else if end.Number > start.Number+1 {
s.log.Debug("requesting missing unsafe L2 block range", "start", start, "end", end, "size", end.Number-start.Number)
return s.altSync.RequestL2Range(ctx, start, end)
}
return nil
}
RequestL2Range函数向requests通道里传递请求区块的开始和结束信号。
然后通过onRangeRequest方法来对请求向peerRequests通道分发,peerRequests通道会被多个peer开启的loop所等待,即每一次分发都只有一个peer会去处理这个request。
接下来我们看看,当peer收到这个request的时候会怎么处理。
首先我们要知道的是,peer和请求节点之间的链接,或者消息传递是通过libp2p的stream来传递的。stream的处理方法由接收peer节点实现,stream的创建由发送节点来开启。
我们可以在之前的init函数中看到这样的代码,这里MakeStreamHandler返回了一个处理函数,SetStreamHandler将协议id和这个处理函数绑定,因此,每当发送节点创建并使用这个stream的时候,都会触发返回的处理函数。
接下来让我们看看处理函数里面是如何处理的 函数首先进行全局和个人的速率限制检查,以控制处理请求的速度。然后,它读取并验证了请求的区块号,确保它在合理的范围内。之后,函数从 L2 层获取请求的区块负载,并将其写入到响应流中。在写入响应数据时,它设置了写入截止时间,以避免在写入过程中被慢速的 peer 连接阻塞。最终,函数返回请求的区块号和可能的错误。
至此,反向链同步请求和处理的大致流程已经讲解完毕
p2p节点中的积分声誉系统
为了防止某些节点进行恶意的请求与响应来破坏整个网络的安全性,optimism还使用了一套积分系统。
例如在op-node/p2p/app_scores.go 中存在一系列函数对peer的分数进行设置
然后在添加新的节点前会检查其积分情况
总结
libp2p的高度可配置性使得整个项目的p2p具有高度的可自定义化和模块话,以上是optimsim对libp2p进行个性化实现的主要逻辑,还有其他细节可以在p2p目录下通过阅读源码的方式来详细学习。