nimbus-eth2/beacon_chain/gossipsub_protocol.nim

92 lines
3.1 KiB
Nim
Raw Normal View History

import
tables, sets, macros, base64,
chronos, nimcrypto/sysrand, chronicles, json_serialization,
2019-02-05 19:21:18 +00:00
eth/[p2p, rlp], eth/p2p/[rlpx, peer_pool],
spec/[datatypes, crypto],
tracing/stacktraces
type
TopicMsgHandler = proc (msg: string)
2018-11-26 13:33:06 +00:00
GossipSubPeer* = ref object
2018-11-29 01:08:34 +00:00
sentMessages: HashSet[string]
subscribedFor: HashSet[string]
2018-11-29 01:08:34 +00:00
GossipSubNetwork* = ref object
topicSubscribers: Table[string, TopicMsgHandler]
handledMessages: HashSet[string]
proc initProtocolState*(network: GossipSubNetwork, _: EthereumNode) =
network.topicSubscribers = initTable[string, TopicMsgHandler]()
network.handledMessages = initSet[string]()
proc initProtocolState*(peer: GossipSubPeer, _: Peer) =
peer.sentMessages = initSet[string]()
peer.subscribedFor = initSet[string]()
2018-11-29 01:08:34 +00:00
p2pProtocol GossipSub(version = 1,
shortName = "gss",
peerState = GossipSubPeer,
2018-11-29 01:08:34 +00:00
networkState = GossipSubNetwork):
2018-11-26 13:33:06 +00:00
# This is a very barebones emulation of the GossipSub protocol
# available in LibP2P:
onPeerConnected do (peer: Peer):
2019-01-25 22:13:41 +00:00
info "GossipSub Peer connected", peer
let gossipNet = peer.networkState
for topic, _ in gossipNet.topicSubscribers:
asyncCheck peer.subscribeFor(topic)
onPeerDisconnected do (peer: Peer, reason: DisconnectionReason):
info "GossipSub Peer disconnected", peer, reason
# TODO, add stacktraces support to nim-chronicles
debug "Debugging stacktrace"
writeStyledStackTrace()
debug "Continuing ..."
proc subscribeFor(peer: Peer, topic: string) =
peer.state.subscribedFor.incl topic
proc emit(peer: Peer, topic: string, msgId: string, msg: string) =
if msgId in peer.networkState.handledMessages:
trace "Ignored previously handled message", msgId
return
peer.networkState.handledMessages.incl msgId
for p in peer.network.peers(GossipSub):
if msgId notin p.state.sentMessages and topic in p.state.subscribedFor:
p.state.sentMessages.incl msgId
asyncCheck p.emit(topic, msgId, msg)
{.gcsafe.}:
let handler = peer.networkState.topicSubscribers.getOrDefault(topic)
if handler != nil:
handler(msg)
proc broadcastImpl(node: EthereumNode, topic: string, msg: string): seq[Future[void]] {.gcsafe.} =
var randBytes: array[10, byte];
if randomBytes(randBytes) != 10:
warn "Failed to generate random message id"
let msgId = base64.encode(randBytes)
trace "Sending GossipSub message", msgId
for peer in node.peers(GossipSub):
if topic in peer.state(GossipSub).subscribedFor:
result.add peer.emit(topic, msgId, msg)
2018-11-26 13:33:06 +00:00
proc subscribe*[MsgType](node: EthereumNode,
topic: string,
userHandler: proc(msg: MsgType)) {.async.}=
var gossipNet = node.protocolState(GossipSub)
gossipNet.topicSubscribers[topic] = proc (msg: string) =
userHandler Json.decode(msg, MsgType)
2018-11-26 13:33:06 +00:00
for peer in node.peers(GossipSub):
discard peer.subscribeFor(topic)
2018-11-26 13:33:06 +00:00
proc broadcast*(node: EthereumNode, topic: string, msg: auto) {.async.} =
await all(node.broadcastImpl(topic, Json.encode(msg)))