🎉 The #CandyDrop Futures Challenge is live — join now to share a 6 BTC prize pool!
📢 Post your futures trading experience on Gate Square with the event hashtag — $25 × 20 rewards are waiting!
🎁 $500 in futures trial vouchers up for grabs — 20 standout posts will win!
📅 Event Period: August 1, 2025, 15:00 – August 15, 2025, 19:00 (UTC+8)
👉 Event Link: https://www.gate.com/candy-drop/detail/BTC-98
Dare to trade. Dare to win.
The use of libp2p in op-stack
Written by Joohhnnn
Libp2P applications in Optimism
In this section, the main purpose is to explain how Optimism uses Libb2P to complete the establishment of P2P networks in Op-Node. P2P networks are mainly used to pass information in different nodes, such as sequencers after completing the block construction of Unsafe, and propagating through the pub/sub of P2P's gossiphub. Libb2P also deals with other infrastructures such as networking, addressing, etc. in P2P networks.
Learn about libp2p
libp2p (abbreviated from "library peer-to-peer" or "library peer-to-peer") is a peer-to-peer (P2P) networking framework that helps develop P2P applications. It contains a set of protocols, specifications, and libraries that make P2P communication between network participants (also known as "peers" or "peers") easier (source[2] [3])。 Originally part of the IPFS (InterPlanetary File) project, libp2p evolved into a stand-alone project that became a modular network stack (source) for distributed networks )。
libp2p is an open source project of the IPFS community that welcomes contributions from a wide range of communities, including helping to write specifications, code implementations, and create examples and tutorials[4] [5])。 libp2p is made up of multiple building blocks, each with a very clear, documented, and tested interface that makes them composable, replaceable, and therefore upgradeable )。 The modular nature of libp2p allows developers to select and use only the components necessary for their applications, promoting flexibility and efficiency when building P2P web applications.
Related resources
The modular architecture and open source nature of libp2p provide a good environment for developing powerful, scalable, and flexible P2P applications, making it an important player in the field of distributed networks and network application development.
libp2p implementation
When using libp2p, you will need to implement and configure some core components to build your P2P network. Here are some of the main implementation aspects of libp2p in the application:
1. Node Creation and Configuration:
libp2p. New()
2. Transport Protocol:
Choose and configure your transport protocols (e.g. TCP, WebSockets, etc.) to ensure communication between nodes. Key usage codes:
tcpTransport := tcp. NewTCPTransport()
3. Multiplexing and Flow Control:
yamuxTransport := yamux. New()
4. Security and encryption:
tlsTransport := tls. New()
5. Protocols and Message Handling:
Define and implement custom protocols to handle specific network operations and message exchanges.
host. SetStreamHandler("/my-protocol/1.0.0", myProtocolHandler)
6. Discovery and Routing:
dht := kaddht. NewDHT(ctx, host, datastore. NewMapDatastore())
7. Network behavior and policies:
Define and implement network behaviors and policies, such as connection management, error handling, and retry logic. Key usage codes:
connManager := connmgr. NewConnManager(lowWater, highWater, gracePeriod)
8. State Management and Storage:
Manage the status of nodes and networks, including connection status, node list, and data storage. Key usage codes:
peerstore := pstoremem. NewPeerstore()
9. Test and debug:
logging. SetLogLevel("libp2p", "DEBUG")
10. Documentation and Community Support:
}
The above are some of the main aspects to consider and implement when using libp2p. The specific implementation of each project may vary, but these basic aspects are necessary to build and run libp2p applications. When implementing these functions, you can refer to the official documentation of libp2p[9] [10]and GitHub repositories Sample code and tutorials.
Use of libp2p in OP-node
In order to figure out the relationship between op-node and libp2p, we have to figure out several questions
Why op-nodes need a libp2p network
First we need to understand why Optimism requires a P2P network ** LibbP2P is a modular network protocol that allows developers to build decentralized peer-to-peer applications for multiple use cases (sources[11] [12])(source [13])。 DevP2P, on the other hand, is mainly used in the Ethereum ecosystem and is tailored for Ethereum applications (Source )。 The flexibility and wide applicability of libp2p may make it a top choice for developers.
Op-Node mainly uses libp2p function points
Code implementation
host custom initialization
Host can be understood as a P2P node, when opening this node, you need to make some special initialization configuration for your own project
Now let's look at the Host method in the op-node/p2p/host.go file.
This function is mainly used to set up the libp2p host and make various configurations. Here are the key parts of the function and a simple Chinese description of each:
If P2P is disabled, the function returns directly.
Use the public key in the configuration to generate the Peer ID.
Create a base Peerstore store.
On top of the base Peerstore, create an extended Peerstore.
Store the peer's private and public keys in the peerstore.
Used to control network connections.
Used to manage network connections.
Set the network transport protocol and the listening address of the host.
Use all the previous settings to create a new libp2p host.
If you have a static peer configured, initialize it.
Finally, the function returns the created libp2p host.
These key sections are responsible for initializing and setting up the libp2p host, and each part is responsible for a specific aspect of the host configuration.
Generate blocksTopicName using the blocksTopicV1 function, which formats a string based on the L2ChainID in the configuration (cfg). Formatted strings follow a specific structure: /optimism/{L2ChainID}/0/blocks.
function by calling ps. Join(blocksTopicName) attempts to add a block gossip theme. If an error occurs, it returns an error message indicating that the topic could not be joined.
Generated a new goroutine to log topic events using the LogTopicEvents function.
Created a subscriber using the MakeSubscriber function, which encapsulates a BlocksHandler that handles OnUnsafeL2Payload events from gossipIn. A new goroutine was generated to run the provided subion.
func JoinGossip(p2pCtx context. Context, self peer.ID, ps *pubsub. PubSub, log log. Logger, cfg *rollup. Config, runCfg GossipRuntimeConfig, gossipIn GossipIn) (GossipOut, error) {
blocksTopicName := blocksTopicV1(cfg) // return fmt. Sprintf("/optimism/%s/0/blocks", cfg. L2ChainID.String())
return nil, fmt. Errorf("failed to register blocks gossip topic: %w", err)
}
blocksTopic, err := ps. Join(blocksTopicName)
if err != nil {
}
if err != nil {
}
go LogTopicEvents(p2pCtx, log. New("topic", "blocks"), blocksTopicEvents)
if err != nil {
}
return &publisher{log: log, cfg: cfg, blocksTopic: blocksTopic, runCfg: runCfg}, nil
op-node/rollup/driver/state.go
When there are missing blocks, fast synchronization via P2P
When nodes are relinked after downtime due to special circumstances, such as relinking after downtime, some blocks (gaps) may not be synchronized, and when encountering this situation, they can quickly synchronize through the reverse chain of the P2P network.
Let's take a look at the checkForGapInUnsafeQueue function in op-node/rollup/driver/state.go
The code snippet defines a method called checkForGapInUnsafeQueue, which belongs to the Driver structure. Its purpose is to check for data gaps in a queue called "unsafe queue" and attempt to retrieve missing payloads through an alternate synchronization method called altSync. The key point here is that the method is to ensure data continuity and try to retrieve missing data from other synchronization methods when missing data is detected. Here are the main steps of the function:
The function first gets UnsafeL2Head and UnsafeL2SyncTarget from s.derivation as the start and end points of the check range.
The function checks for missing blocks between start and end, which is done by comparing the Number values of end and start.
If a data gap is detected, the function will request the missing data range by calling s.altSync.RequestL2Range(ctx, start, end). If end is a null reference (i.e. eth. L2BlockRef{}), the function will request an open-end range synchronization, starting with start.
When requesting data, the function logs a debug log indicating which range of data it is requesting.
The function eventually returns an error value. If there are no errors, it returns 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
}
The RequestL2Range function signals the start and end of the request block to the requests channel.
The request is then distributed to the peerRequests channel via the onRangeRequest method, and the peerRequests channel is waited for by the loop opened by multiple peers, i.e. only one peer will process the request for each distribution.
Let's see what happens when the peer receives this request.
The first thing we need to know is that the link between the peer and the requesting node, or the message passing, is passed through the libp2p stream. The processing method of the stream is implemented by the receiving peer node, and the creation of the stream is opened by the sending node.
We can see this code in the previous init function, where MakeStreamHandler returns a handler, and SetStreamHandler binds the protocol ID to this handler, so whenever the sending node creates and uses this stream, the returned handler is triggered.
Let's look at how it is handled inside the handler The function first performs global and individual rate limit checks to control how quickly requests are processed. It then reads and verifies the requested block number, ensuring that it is within a reasonable range. The function then gets the chunk payload of the request from the L2 layer and writes it to the response stream. When writing response data, it sets a write deadline to avoid being blocked by slow peer connections during the write process. Finally, the function returns the requested block number and possible errors.
At this point, the general process of reverse chain synchronization request and processing has been explained
Points reputation system in p2p nodes
In order to prevent certain nodes from making malicious requests and responses that undermine the security of the entire network, Optimism also uses a points system.
For example, in op-node/p2p/app_scores.go, there are a series of functions to set the score of a peer
The integration status of the new node is then checked before it is added
Summary
The high configurability of libp2p makes the entire project p2p highly customizable and modular, the above is the main logic of optimsim's personalized implementation of libp2p, and other details can be learned in detail by reading the source code in the p2p directory.