P2P Networking

Ethereum 2.0 is a peer-to-peer, distributed network. To accomplish this, we use Protocol Labs' awesome libp2p project. It is a next generation, well-supported library that allows us our application to not have to deal with low-level p2p issues and focus instead on the logic we care about. That being said, we have a simple wrapper in Prysm's p2p server that defines a common interface for broadcasting and subscribing to objects via peers in the network. Protocol buffers are the default transport data structures used by libp2p and in Prysm.

We use two basic interfaces throughout Prysm related to p2p:

// Sender represents a struct that is able to relay information via p2p.
// Server implements this interface.
type Sender interface {
Send(ctx context.Context, msg proto.Message, peer peer.ID) error
}
// Broadcaster represents a subset of the p2p.Server. This interface is useful
// for testing or when the calling code only needs access to the broadcast
// method.
type Broadcaster interface {
Broadcast(context.Context, proto.Message)
}
// Subscribe returns a subscription to a feed of msg's Type and adds the channels to the feed.
func (s *Server) Subscribe(msg proto.Message, channel chan Message) event.Subscription {
return s.Feed(msg).Subscribe(channel)
}

Nodes subscribe to a list of p2p topic objects which correspond to protobuf type definitions, and can then subscribe to announcements from peers via event feeds. One simple example is in our sync service, which subscribes to block announcement events from the network via a channel in an event feed:

// run handles incoming block sync.
func (rs *RegularSync) run() {
announceBlockSub := rs.p2p.Subscribe(&pb.BeaconBlockAnnounce{}, rs.announceBlockBuf)
defer announceBlockSub.Unsubscribe()
for {
select {
case <-rs.ctx.Done():
log.Debug("Exiting goroutine")
return
case msg := <-rs.announceBlockBuf:
safelyHandleMessage(rs.receiveBlockAnnounce, msg)
}
}
}

If a node instead wants to send to a single peer rather than broadcasting to the network as a whole, the Sender interface has a Send function which requires a peer ID and makes this possible. We keep p2p across the repo simple and only expose the basic functions our nodes need to accomplish their tasks.