Skip to content
Cosmopediaby Unity Nodes
DocumentationCometBFT specificationcometbft/cometbft › spec › mempool › gossipView on CometBFT specification ↗

P2P

P2P

This module specifies a P2P layer as needed for the gossip protocols. It includes the definitions of nodes, peers, network topology, sending messages, nodes joining and leaving the network.

Types

Nodes are identified by a string.

type NodeID = str

Parameters

The set of all possible node IDs, even those that are not initially connected to the network.

const NodeIDs: Set[NodeID]

Initial network topology. A topology is defined by the set of peers each node has.

const InitialPeers: NodeID -> Set[NodeID]

State

To model network communication, each node has a queue (a list) of incoming messages. Node A sends a message to a node B by appending the message to B's queue. We use queues to model that messages arrive in order, as we assume this is guaranteed by the transport layer. Messages have a sender (a node ID).

The type variable msg can be instantiated on the message types of different protocols.

var incomingMsgs: NodeID -> List[(NodeID, msg)]

In the actual implementation, transaction messages are transmitted on the Mempool data channel of the P2P layer. Control messages are usually transmitted on other channels with different priorities. Here we model a single, reliable channel.

The dynamic network topology. Each node has a set of peers that is updated when nodes join or leave the network.

var peers: NodeID -> Set[NodeID]
<details> <summary>Auxiliary definitions</summary>
def IncomingMsgs(node) = incomingMsgs.get(node)
def Peers(node) = peers.get(node)

Function multiSend sends message msg to a set of targetNodes. It updates a list of incoming messages _incomingMsgs. targetNodes can be empty, in which case _incomingMsgs will stay the same.

pure def multiSend(node, _incomingMsgs, targetNodes, msg) =
    _incomingMsgs.updateMultiple(targetNodes, ms => ms.append((node, msg)))
pure def send(node, _incomingMsgs, targetNode, msg) =
    node.multiSend(_incomingMsgs, Set(targetNode), msg)

A node is in the network if it has peers:

val nodesInNetwork = NodeIDs.filter(node => node.Peers().nonEmpty())
val nodesNotInNetwork = NodeIDs.exclude(nodesInNetwork)

A node disconnects from the network when it does not have peers.

pure def disconnect(_peers, node) =
    // TODO: check that the network does not become disconnected; we don't want to model that.
    _peers.put(node, Set())

The set of node's peers that are not themselves connected to node.

def disconnectedPeers(node) = 
    node.Peers().filter(p => not(node.in(p.Peers())))
</details>

Initial state

The initial state of the P2P layer:

action P2P_init = all {
    incomingMsgs' = NodeIDs.mapBy(_ => List()),
    peers' = NodeIDs.mapBy(n => InitialPeers.get(n)),
}

State transitions (actions)

A node receives one of the incoming messages from a peer and handles it according to its type.

action receiveFromPeer(node, handleMessage) = all {
    require(length(node.IncomingMsgs()) > 0),
    // We model receiving of a message as taking the head of the list of
    // incoming messages and leaving the tail.
    val someMsg = node.IncomingMsgs().head()
    val sender = someMsg._1
    val msg = someMsg._2
    val _incomingMsgs = incomingMsgs.update(node, tail)
    handleMessage(node, _incomingMsgs, sender, msg)
}

A node joins the network by connecting to a given set of peers. All those peers add the new node to their list of peers.

action joinNetwork(node, peerSet) = all {
    // The node must not be connected to the network.
    require(node.Peers().isEmpty()),
    peers' = peers
        // Assign to node the set of new peers.
        .put(node, peerSet)
        // Add node as a new peer to the set of connecting peers.
        .updateMultiple(peerSet, ps => ps.join(node)),
    incomingMsgs' = incomingMsgs,
}

Non-deterministically pick a node and its peers to join the network.

action pickNodeAndJoin = all {
    // Pick a node that is not connected to the network.
    require(NodeIDs.exclude(nodesInNetwork).nonEmpty()),
    nondet node = oneOf(NodeIDs.exclude(nodesInNetwork))
    // Pick a non-empty set of nodes in the network to be the node's peers.
    nondet peerSet = oneOf(nodesInNetwork.powerset().exclude(Set()))
    node.joinNetwork(peerSet),
}

Properties

Invariant Peer relationships are bidirectional or symmetrical: if node A has B as peer, then B has A as peer.

val bidirectionalNetwork =
    NodeIDs.forall(nodeA => 
        nodeA.Peers().forall(nodeB => nodeA.in(nodeB.Peers())))

Property Eventually all messages are delivered (there are no incoming messages).

temporal allMsgsDelivered = 
    eventually(NodeIDs.forall(node => length(node.IncomingMsgs()) == 0))
// TODO: Invariant: all nodes in the network are always connected.
<!-- ```bluespec quint/p2p.qnt += // -*- mode: Bluespec; -*- // File generated from markdown using https://github.com/driusan/lmt. DO NOT EDIT. module p2p { import spells.* from "./spells" //-------------------------------------------------------------------------- // Types //-------------------------------------------------------------------------- <<<types>>> //-------------------------------------------------------------------------- // Parameters //-------------------------------------------------------------------------- <<<params>>> //-------------------------------------------------------------------------- // State //-------------------------------------------------------------------------- <<<state>>> // Auxiliary definitions <<<auxstate>>> //-------------------------------------------------------------------------- // Actions //-------------------------------------------------------------------------- <<<actions>>> //-------------------------------------------------------------------------- // Properties //-------------------------------------------------------------------------- <<<properties>>> } ``` -->