The rollup node has an optional peer-to-peer (P2P) network service to improve the latency between the view of sequencers and the rest of the network by bypassing the L1 in the happy case, without relying on a single centralized endpoint.
This also enables faster historical sync to be bootstrapped by providing block headers to sync towards, and only having to compare the L2 chain inputs to the L1 data as compared to processing everything one block at a time.
The rollup node will always prioritize L1 and reorganize to match the canonical chain. The L2 data retrieved via the P2P interface is strictly a speculative extension, also known as the "unsafe" chain, to improve the happy case performance.
This also means that P2P behavior is a soft-rule: nodes keep each other in check with scoring and eventual banning of malicious peers by identity or IP. Any behavior on the P2P layer does not affect the rollup security, at worst nodes rely on higher-latency data from L1 to serve.
In summary, the P2P stack looks like:
- Discovery to find peers: Discv5
- Connections, peering, transport security, multiplexing, gossip: LibP2P
- Application-layer publishing and validation of gossiped messages like L2 blocks.
This document only specifies the composition and configuration of these network libraries. These components have their own standards, implementations in Go/Rust/Java/Nim/JS/more, and are adopted by several other blockchains, most notably the L1 consensus layer (Eth2).
Table of Contents
Nodes have a separate network- and consensus-identity.
The network identity is a secp256k1
key, used for both discovery and active LibP2P connections.
Common representations of network identity:
PeerID
: a LibP2P specific ID derived from the pubkey (through protobuf encoding, typing and hashing)NodeID
: a Discv5 specific ID derived from the pubkey (through hashing, used in the DHT)Multi-address
: an unsigned address, containing: IP, TCP port, PeerIDENR
: a signed record used for discovery, containing: IP, TCP port, UDP port, signature (pubkey can be derived) and L2 network identification. Generally encoded in base64.
The Ethereum Node Record (ENR) for an Optimism rollup node must contain the following values, identified by unique keys:
- An IPv4 address (
ip
field) and/or IPv6 address (ip6
field). - A TCP port (
tcp
field) representing the local libp2p listening port. - A UDP port (
udp
field) representing the local discv5 listening port. - An OpStack (
opstack
field) L2 network identifier
The opstack
value is encoded as a single RLP bytes
value, the concatenation of:
- chain ID (
unsigned varint
) - fork ID (
unsigned varint
)
Note that DiscV5 is a shared DHT (Distributed Hash Table): the L1 consensus and execution nodes, as well as testnet nodes, and even external IOT nodes, all communicate records in this large common DHT. This makes it more difficult to censor the discovery of node records.
The discovery process in Optimism is a pipeline of node records:
- Fill the table with
FINDNODES
if necessary (Performed by Discv5 library) - Pull additional records with searches to random Node IDs if necessary
(e.g. iterate
RandomNodes()
in Go implementation) - Pull records from the DiscV5 module when looking for peers
- Check if the record contains the
opstack
entry, verify it matches the chain ID and current or future fork number - If not already connected, and not recently disconnected or put on deny-list, attempt to dial.
TCP transport. Additional transports are supported by LibP2P, but not required.
Nodes should be publicly dialable, not rely on relay extensions, and able to dial both IPv4 and IPv6.
The listening endpoint must be publicly facing, but may be configured behind a NAT. LibP2P will use PMP / UPNP based techniques to track the external IP of the node. It is recommended to disable the above if the external IP is static and configured manually.
The default is to maintain a peer count with a tide-system based on active peer count:
- At "low tide" the node starts to actively search for additional peer connections.
- At "high tide" the node starts to prune active connections, except those that are marked as trusted or have a grace period.
Peers will have a grace period for a configurable amount of time after joining. In emergency, when memory runs low, the node should start pruning more aggressively.
Peer records can be persisted to disk to quickly reconnect with known peers after restarting the rollup node.
The discovery process feeds the peerstore with peer records to connect to, tagged with a time-to-live (TTL). The current P2P processes do not require selective topic-specific peer connections, other than filtering for the basic network participation requirement.
Peers may be banned if their performance score is too low, or if an objectively malicious action was detected.
Banned peers will be persisted to the same data-store as the peerstore records.
TODO: the connection gater does currently not gate by IP address on the dial Accept-callback.
Libp2p-noise, XX
handshake, with the the secp256k1
P2P identity, as popularized in Eth2.
The TLS option is available as well, but noise
should be prioritized in negotiation.
Multistream-select 1.0 (/multistream/1.0.0
) is an interactive protocol
used to negotiate sub-protocols supported in LibP2P peers. Multistream-select 2.0 may be used in the future.
LibP2P offers a minimal identification module to share client version and programming language. This is optional and can be disabled for enhanced privacy. It also includes the same protocol negotiation information, which can speed up initial connections.
LibP2P includes a simple ping protocol to track latency between connections. This should be enabled to help provide insight into the network health.
For async communication over different channels over the same connection, multiplexing is used.
mplex (/mplex/6.7.0
) is required, and yamux (/yamux/1.0.0
) is recommended but optional
GossipSub 1.1 (/meshsub/1.1.0
, i.e. with peer-scoring extension) is a pubsub protocol for mesh-networks,
deployed on L1 consensus (Eth2) and other protocols such as Filecoin, offering lots of customization options.
Messages are deduplicated, and filtered through application-layer signature verification.
Thus origin-stamping is disabled and published messages must only contain application data,
enforced through a StrictNoSign
Signature Policy
This provides greater privacy, and allows sequencers (consensus identity) to maintain multiple network identities for redundancy.
The application contents are compressed with snappy single-block-compression (as opposed to frame-compression), and constrained to 10 MiB.
Same as L1, with recognition of compression:
- If
message.data
has a valid snappy decompression, setmessage-id
to the first 20 bytes of theSHA256
hash of the concatenation ofMESSAGE_DOMAIN_VALID_SNAPPY
with the snappy decompressed message data, i.e.SHA256(MESSAGE_DOMAIN_VALID_SNAPPY + snappy_decompress(message.data))[:20]
. - Otherwise, set
message-id
to the first 20 bytes of theSHA256
hash of the concatenation ofMESSAGE_DOMAIN_INVALID_SNAPPY
with the raw message data, i.e.SHA256(MESSAGE_DOMAIN_INVALID_SNAPPY + message.data)[:20]
.
GossipSub parameters:
D
(topic stable mesh target count): 8D_low
(topic stable mesh low watermark): 6D_high
(topic stable mesh high watermark): 12D_lazy
(gossip target): 6heartbeat_interval
(interval of heartbeat, in seconds): 0.5fanout_ttl
(ttl for fanout maps for topics we are not subscribed to but have published to, in seconds): 24mcache_len
(number of windows to retain full messages in cache forIWANT
responses): 12mcache_gossip
(number of windows to gossip about): 3seen_ttl
(number of heartbeat intervals to retain message IDs): 130 (= 65 seconds)
Notable differences from L1 consensus (Eth2):
seen_ttl
does not need to cover a full L1 epoch (6.4 minutes), but rather just a small window covering latest blocksfanout_ttl
: adjusted to lower thanseen_ttl
mcache_len
: a larger number of heartbeats can be retained since the gossip is much less noisy.heartbeat_interval
: faster interval to reduce latency, bandwidth should still be reasonable since there are far fewer messages to gossip about each interval than on L1 which uses an interval of 0.7 seconds.
Topics have string identifiers and are communicated with messages and subscriptions.
/optimism/chain_id/hardfork_version/Name
chain_id
: replace with decimal representation of chain IDhardfork_version
: replace with decimal representation of hardfork, starting at0
Name
: topic application-name
Note that the topic encoding depends on the topic, unlike L1, since there are less topics, and all are snappy-compressed.
To ensure only valid messages are relayed, and malicious peers get scored based on application behavior, an extended validator checks the message before it is relayed or processed. The extended validator emits one of the following validation signals:
ACCEPT
valid, relayed to other peers and passed to local topic subscriberIGNORE
scored like inactivity, message is dropped and not processedREJECT
score penalties, message is dropped
The primary topic of the L2, to distribute blocks to other nodes faster than proxying through L1 would.
A block is structured as the concatenation of:
signature
: Asecp256k1
signature, always 65 bytes,r (uint256), s (uint256), y_parity (uint8)
payload
: A SSZ-encodedExecutionPayload
, always the remaining bytes.
The topic uses Snappy block-compression (i.e. no snappy frames): the above needs to be compressed after encoding, and decompressed before decoding.
The signature
is a secp256k1
signature, and signs over a message:
keccak256(domain ++ chain_id ++ payload_hash)
, where:
domain
is 32 bytes, reserved for message types and versioning info. All zero for this signature.chain_id
is a big-endian encodeduint256
.payload_hash
iskeccak256(payload)
, wherepayload
is the SSZ-encodedExecutionPayload
The secp256k1
signature must have y_parity = 1 or 0
, the chain_id
is already signed over.
An extended-validator checks the incoming messages as follows, in order of operation:
[REJECT]
if the compression is not valid[REJECT]
if the block encoding is not valid[REJECT]
if thepayload.timestamp
is older than 60 seconds in the past (graceful boundary for worst-case propagation and clock skew)[REJECT]
if thepayload.timestamp
is more than 5 seconds into the future[REJECT]
if theblock_hash
in thepayload
is not valid[REJECT]
if more than 5 different blocks have been seen with the same block height[IGNORE]
if the block has already been seen[REJECT]
if the signature by the sequencer is not valid- Mark the block as seen for the given block height
The block is signed by the corresponding sequencer, to filter malicious messages. The sequencer model is singular but may change to multiple sequencers in the future. A default sequencer pubkey is distributed with rollup nodes and should be configurable.
Note that blocks that a block may still be propagated even if the L1 already confirmed a different block. The local L1 view of the node may be wrong, and the time and signature validation will prevent spam. Hence, calling into the execution engine with a block lookup every propagation step is not worth the added delay.
A node may apply the block to their local engine ahead of L1 availability, if it ensures that:
- The application of the block is reversible, in case of a conflict with delayed L1 information
- The subsequent forkchoice-update ensures this block is recognized as "unsafe"
(see
engine_forkchoiceUpdatedV1
)
TODO: GossipSub per-topic scoring to fine-tune incentives for ideal propagation delay and bandwidth usage.