2018-11-23 23:58:49 +00:00
|
|
|
import
|
2018-12-10 12:06:45 +00:00
|
|
|
tables, sets, macros, base64,
|
2019-02-06 17:56:04 +00:00
|
|
|
chronos, nimcrypto/sysrand, chronicles, json_serialization,
|
2019-03-28 11:01:28 +00:00
|
|
|
eth/[p2p, rlp, async_utils], eth/p2p/[rlpx, peer_pool],
|
2019-03-26 09:56:23 +00:00
|
|
|
spec/[datatypes, crypto],
|
|
|
|
tracing/stacktraces
|
2018-11-23 23:58:49 +00:00
|
|
|
|
|
|
|
type
|
2019-03-05 22:54:08 +00:00
|
|
|
TopicMsgHandler = proc (msg: string)
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-12-28 16:51:40 +00:00
|
|
|
GossipSubPeer* = ref object
|
2018-11-29 01:08:34 +00:00
|
|
|
sentMessages: HashSet[string]
|
2018-12-10 12:06:45 +00:00
|
|
|
subscribedFor: HashSet[string]
|
2018-11-29 01:08:34 +00:00
|
|
|
|
2018-12-28 16:51:40 +00:00
|
|
|
GossipSubNetwork* = ref object
|
2018-12-10 12:06:45 +00:00
|
|
|
topicSubscribers: Table[string, TopicMsgHandler]
|
2018-12-28 16:51:40 +00:00
|
|
|
handledMessages: HashSet[string]
|
2018-12-10 12:06:45 +00:00
|
|
|
|
2018-12-28 16:51:40 +00:00
|
|
|
proc initProtocolState*(network: GossipSubNetwork, _: EthereumNode) =
|
2018-12-10 12:06:45 +00:00
|
|
|
network.topicSubscribers = initTable[string, TopicMsgHandler]()
|
2018-12-28 16:51:40 +00:00
|
|
|
network.handledMessages = initSet[string]()
|
2018-12-10 12:06:45 +00:00
|
|
|
|
2018-12-28 16:51:40 +00:00
|
|
|
proc initProtocolState*(peer: GossipSubPeer, _: Peer) =
|
2018-12-10 12:06:45 +00:00
|
|
|
peer.sentMessages = initSet[string]()
|
|
|
|
peer.subscribedFor = initSet[string]()
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2019-03-26 10:01:13 +00:00
|
|
|
proc trySubscribing(peer: Peer, topic: string) {.gcsafe.}
|
|
|
|
proc tryEmitting(peer: Peer, topic: string,
|
|
|
|
msgId: string, msg: string): Future[void] {.gcsafe.}
|
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
p2pProtocol GossipSub(version = 1,
|
2019-08-15 16:00:12 +00:00
|
|
|
rlpxName = "gss",
|
2018-12-28 16:51:40 +00:00
|
|
|
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:
|
|
|
|
|
2018-12-28 16:51:40 +00:00
|
|
|
onPeerConnected do (peer: Peer):
|
2019-01-25 22:13:41 +00:00
|
|
|
info "GossipSub Peer connected", peer
|
2018-12-28 16:51:40 +00:00
|
|
|
let gossipNet = peer.networkState
|
|
|
|
for topic, _ in gossipNet.topicSubscribers:
|
2019-03-26 10:01:13 +00:00
|
|
|
peer.trySubscribing(topic)
|
2018-12-28 16:51:40 +00:00
|
|
|
|
|
|
|
onPeerDisconnected do (peer: Peer, reason: DisconnectionReason):
|
2019-01-05 21:01:26 +00:00
|
|
|
info "GossipSub Peer disconnected", peer, reason
|
2018-12-28 16:51:40 +00:00
|
|
|
|
2018-12-10 12:06:45 +00:00
|
|
|
proc subscribeFor(peer: Peer, topic: string) =
|
|
|
|
peer.state.subscribedFor.incl topic
|
|
|
|
|
2018-12-28 16:51:40 +00:00
|
|
|
proc emit(peer: Peer, topic: string, msgId: string, msg: string) =
|
|
|
|
if msgId in peer.networkState.handledMessages:
|
2019-01-05 21:01:26 +00:00
|
|
|
trace "Ignored previously handled message", msgId
|
2018-12-28 16:51:40 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
peer.networkState.handledMessages.incl msgId
|
|
|
|
|
2018-12-10 12:06:45 +00:00
|
|
|
for p in peer.network.peers(GossipSub):
|
|
|
|
if msgId notin p.state.sentMessages and topic in p.state.subscribedFor:
|
2018-12-28 16:51:40 +00:00
|
|
|
p.state.sentMessages.incl msgId
|
2019-03-28 11:01:28 +00:00
|
|
|
traceAsyncErrors p.tryEmitting(topic, msgId, msg)
|
2018-12-10 12:06:45 +00:00
|
|
|
|
2019-02-05 17:48:34 +00:00
|
|
|
{.gcsafe.}:
|
|
|
|
let handler = peer.networkState.topicSubscribers.getOrDefault(topic)
|
|
|
|
if handler != nil:
|
2019-03-05 22:54:08 +00:00
|
|
|
handler(msg)
|
2018-12-28 16:51:40 +00:00
|
|
|
|
2019-03-26 10:01:13 +00:00
|
|
|
proc trySubscribing(peer: Peer, topic: string) =
|
|
|
|
var fut = peer.subscribeFor(topic)
|
|
|
|
fut.addCallback do (arg: pointer):
|
|
|
|
if fut.failed:
|
2019-03-28 11:08:56 +00:00
|
|
|
debug "Failed to subscribe to topic with GossipSub peer", topic, peer
|
2019-03-26 10:01:13 +00:00
|
|
|
|
|
|
|
proc tryEmitting(peer: Peer, topic: string,
|
|
|
|
msgId: string, msg: string): Future[void] =
|
|
|
|
var fut = peer.emit(topic, msgId, msg)
|
|
|
|
fut.addCallback do (arg: pointer):
|
|
|
|
if fut.failed:
|
2019-03-28 11:08:56 +00:00
|
|
|
debug "GossipSub message not delivered to Peer", peer
|
2019-03-26 10:01:13 +00:00
|
|
|
return fut
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2019-03-05 22:54:08 +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) =
|
2018-12-28 16:51:40 +00:00
|
|
|
userHandler Json.decode(msg, MsgType)
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2019-03-05 22:54:08 +00:00
|
|
|
for peer in node.peers(GossipSub):
|
2019-03-26 10:01:13 +00:00
|
|
|
peer.trySubscribing(topic)
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2019-03-28 11:01:28 +00:00
|
|
|
proc broadcast*(node: EthereumNode, topic: string, msg: auto) =
|
2019-03-28 11:08:56 +00:00
|
|
|
var randBytes: array[10, byte];
|
|
|
|
if randomBytes(randBytes) != 10:
|
|
|
|
warn "Failed to generate random message id"
|
|
|
|
|
|
|
|
let msg = Json.encode(msg)
|
|
|
|
let msgId = base64.encode(randBytes)
|
|
|
|
trace "Sending GossipSub message", msgId
|
|
|
|
|
|
|
|
for peer in node.peers(GossipSub):
|
|
|
|
if topic in peer.state(GossipSub).subscribedFor:
|
|
|
|
traceAsyncErrors peer.tryEmitting(topic, msgId, msg)
|
2018-11-23 23:58:49 +00:00
|
|
|
|