Skip to content

P2P Protocol

quan8 edited this page May 18, 2023 · 1 revision

p2p protocol

The section describes a p2p protocol used in the current implementation to aim for:

  • low-latency event propagation
  • fast initial fetching of events
  • tolerance to fork-events

Although this p2p protocol works in the same manner like existing p2p protocols for exchanging blocks in other blockchains, it is engineered to handle the graph structure of Lachesis.

The protocol's algorithm includes 2 mechanisms which complement each other:

  • DAG streams: Peers request ordered chunks of events from each other by specifying a lexicographic selector. DAG streams are primarily active during initial events fetching.
  • Event broadcasts: just after event connection, the node broadcasts the connected event to the interested peers. Broadcasting events to peers is primarily active when node has already synced up.

P2P protocol is a “transport” level in which events are delivered and exchanged by peers. This P2P layer doesn’t depend on a consensus algorithm. It is not necessary for a node to know which peer delivers which event, as long as these events are valid and signed.

Nodes don't exchange the blocks

In Lachesis consensus, blocks are not exchanged but calculated independently on each node from events.

In other words, blocks are not consensus messages but rather only a result of ordering events by the consensus algorithm.

Hence the P2P protocol exchanges DAGs of events, but not the blocks.

DAG streams

Lamport timestamp example

To understand how DAG streams work, it's useful to first understand the Lamport timestamps.

By definition:
If parents list is not empty, then Lamport timestamp of an event v, denoted by LS(v) is max{LS(u) | u is a parent of v} + 1. Otherwise, LS(v) is 1.

Events form a partial ordering by the Lamport timestamps as parents are always sorted before their children. That is, LS(u) is smaller than LS(v) if u is a parent of v.

This property is especially useful for fetching events, as it allows a downloader to request events from a peer in an order in which the events may be connected, knowing only event IDs.

DAG fetching and streaming

The DAG streams protocol is very simple. It is similar to fetching files:

Key-value databases (such as LevelDB) can iterate over data in a lexicographic order, which allows to quickly serve the requests from the peers. Lexicographic order is relevant because event ID is a concatenation of {epoch, Lamport time, hash}.

Relay/broadcasting events

Sequence diagram

Is peer interested in an event?

input: event ID, peer

The function returns false if peer probably knows about the event, so there's no need to broadcast the event ID (or full event, if propagation is aggressive) to this peer.

if peer sent me this event ID?
	return false
if I sent event ID to this peer?
	return false
return (event's epoch == peer.progress.Epoch OR
	    event's epoch == peer.progress.Epoch+1)

Raw protocol messages

HandshakeMsg

Sent once in the beginning of each connection (i.e. used identically to the Status message from the eth62 protocol).

Code = 0
type handshakeData struct {
	ProtocolVersion uint32
	NetworkID       uint64
	Genesis         common.Hash
}

ProgressMsg

Signals about the current synchronization status.

Sent:

  • Second message for each connection (after Handshake message)
  • Periodically (every ~10 second) to every peer
  • After each epoch sealing to every peer
Code = 1
type PeerProgress struct {
	Epoch            idx.Epoch
	LastBlockIdx     idx.Block
	LastBlockAtropos hash.Event
	HighestLamport   idx.Lamport
}

EvmTxsMsg

Contains a batch of txpool transactions.

Might be either an answer to GetEvmTxsMsg, or be sent during an aggressive transactions propagation.

Code = 2
[]types.Transaction

NewEvmTxHashesMsg

Non-aggressive transactions propagation.

Signals about a new batch of txpool transactions, sending only their hashes.

Code = 3
[]common.Hash

GetEvmTxsMsg

Request a batch of txpool transactions by hashes.

Code = 4
[]common.Hash

NewEventIDsMsg

Non-aggressive events propagation.

Signals about newly-connected batch of events, sending only their IDs.

Code = 5
[]hash.Event

GetEventsMsg

Request a batch of events by IDs.

Code = 6
[]hash.Event

EventsMsg

Contains a batch of events.

Might be either an answer to GetEventsMsg, or be sent during an aggressive events propagation.

Code = 7
[]inter.EventPayload

RequestEventsStream

Request a series of chunks (batches) of events or event IDs by a lexicographic selector.

Limit indicates maximum size for each chunk. Last read event will be appended to the chunk even if it exceeds the limit. The size of the chunk is calculated as {number of events, total size of events}, even if RequestType is RequestIDs.

MaxChunks specifies how many chunks should be sent as an answer to this request.

Might be used to both initiate a new session and request additional chunks from an existing or newly opened session.

Client shouldn't open more than 2 parallel sessions.

Code = 8
type dag.Metric struct {
	Num  idx.Event
	Size uint64
}
type Session struct {
	ID    uint32
	Start []byte
	Stop  []byte
}
type RequestType uint8
type dagstream.Request struct {
	Session   Session
	Limit     dag.Metric
	Type      RequestType
	MaxChunks uint32
}

EventsStreamResponse

Sends events or event IDs as a response to the RequestEventsStream.

Flag Done indicates that the session is finished (i.e. all known events were read for the session's lexicographic selector).

Code = 9
type dagstream.Response struct {
	SessionID uint32
	Done      bool
	IDs       []hash.Event
	Events    []inter.EventPayload
}

Txpool transactions exchanging

Txpool transactions exchanging consists of messages EvmTxsMsg, NewEvmTxHashesMsg, GetEvmTxsMsg.

New txpool transactions are broadcasted with either EvmTxsMsg or NewEvmTxHashesMsg. Unknown transactions hashes are requested with GetEvmTxsMsg.

By default, every peer will periodically send a random subset of his txpool transaction hashes to one random peer, to ensure that every peer will eventually receive all the transactions even if they missed the broadcast.

Node will send all the tx hashes (chunk-by-chunk) to every newly connected peer.

Messages serialization

RLP is used for messages serialization.

Transport protocol

RLPx is used as a transport protocol (the same protocol as in Ethereum).

Discovery protocols

Opera supports the Ethereum discovery protocols - which are used to find connections in the network.