nim-libp2p/libp2p/protocols/pubsub/floodsub.nim

179 lines
6.3 KiB
Nim
Raw Normal View History

2019-09-10 02:14:24 +00:00
## Nim-LibP2P
2019-09-24 17:48:23 +00:00
## Copyright (c) 2019 Status Research & Development GmbH
2019-09-10 02:14:24 +00:00
## Licensed under either of
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
## at your option.
## This file may not be copied, modified, or distributed except according to
## those terms.
import sequtils, tables, options, sets, strutils
2019-09-10 02:14:24 +00:00
import chronos, chronicles
2019-09-10 02:15:52 +00:00
import pubsub,
pubsubpeer,
rpcmsg,
2019-09-28 19:55:35 +00:00
../../crypto/crypto,
2019-09-10 02:15:52 +00:00
../../connection,
../../peerinfo,
../../peer
2019-09-10 02:14:24 +00:00
logScope:
2019-09-10 02:15:52 +00:00
topic = "FloodSub"
2019-09-10 02:14:24 +00:00
const FloodSubCodec* = "/floodsub/1.0.0"
type
FloodSub* = ref object of PubSub
2019-10-03 19:14:04 +00:00
peers*: Table[string, PubSubPeer] # peerid to peer map
peerTopics*: Table[string, HashSet[string]] # topic to remote peer map
2019-09-10 02:14:24 +00:00
2019-09-10 02:15:52 +00:00
proc sendSubs(f: FloodSub,
peer: PubSubPeer,
topics: seq[string],
subscribe: bool) {.async, gcsafe.} =
2019-09-10 02:15:52 +00:00
## send subscriptions to remote peer
trace "sending subscriptions", peer = peer.id, subscribe = subscribe
2019-09-10 02:15:52 +00:00
var msg: RPCMsg
for t in topics:
trace "sending topic", peer = peer.id, subscribe = subscribe, topicName = t
2019-09-10 02:15:52 +00:00
msg.subscriptions.add(SubOpts(topic: t, subscribe: subscribe))
2019-09-10 02:14:24 +00:00
2019-09-10 02:15:52 +00:00
await peer.send(@[msg])
2019-09-10 02:14:24 +00:00
2019-10-03 19:14:04 +00:00
proc subscribeTopic(f: FloodSub, topic: string, subscribe: bool, peerId: string) {.gcsafe.} =
if not f.peerTopics.contains(topic):
f.peerTopics[topic] = initSet[string]()
if subscribe:
trace "adding subscription for topic", peer = peerId, name = topic
# subscribe the peer to the topic
f.peerTopics[topic].incl(peerId)
else:
trace "removing subscription for topic", peer = peerId, name = topic
# unsubscribe the peer from the topic
f.peerTopics[topic].excl(peerId)
2019-09-10 02:15:52 +00:00
proc rpcHandler(f: FloodSub,
peer: PubSubPeer,
rpcMsgs: seq[RPCMsg]) {.async, gcsafe.} =
## method called by a PubSubPeer every
2019-09-10 02:15:52 +00:00
## time it receives an RPC message
##
## The RPC message might contain subscriptions
2019-09-10 02:15:52 +00:00
## or messages forwarded to this peer
##
2019-09-10 02:14:24 +00:00
trace "processing RPC message", peer = peer.id, msg = rpcMsgs
2019-09-10 02:15:52 +00:00
for m in rpcMsgs: # for all RPC messages
trace "processing message", msg = rpcMsgs
2019-09-10 02:15:52 +00:00
if m.subscriptions.len > 0: # if there are any subscriptions
for s in m.subscriptions: # subscribe/unsubscribe the peer for each topic
2019-09-12 02:10:38 +00:00
let id = peer.id
2019-09-12 05:46:08 +00:00
2019-10-03 19:14:04 +00:00
f.subscribeTopic(s.topic, s.subscribe, id)
2019-09-10 02:15:52 +00:00
# send subscriptions to every peer
for p in f.peers.values:
2019-10-03 19:14:04 +00:00
if p.id != peer.id:
await p.send(@[RPCMsg(subscriptions: m.subscriptions)])
2019-09-10 02:15:52 +00:00
2019-09-12 10:08:11 +00:00
var toSendPeers: HashSet[string] = initSet[string]()
2019-09-10 02:15:52 +00:00
if m.messages.len > 0: # if there are any messages
for msg in m.messages: # for every message
for t in msg.topicIDs: # for every topic in the message
toSendPeers.incl(f.peerTopics[t]) # get all the peers interested in this topic
if f.topics.contains(t): # check that we're subscribed to it
for h in f.topics[t].handler:
await h(t, msg.data) # trigger user provided handler
2019-09-10 02:15:52 +00:00
2019-09-14 13:56:02 +00:00
# forward the message to all peers interested in it
2019-09-10 02:15:52 +00:00
for p in toSendPeers:
2019-10-03 19:29:28 +00:00
if p in f.peers and f.peers[p].id != peer.id:
2019-10-02 21:43:00 +00:00
await f.peers[p].send(@[RPCMsg(messages: m.messages)])
2019-09-10 02:15:52 +00:00
2019-09-12 10:08:11 +00:00
proc handleConn(f: FloodSub,
conn: Connection) {.async, gcsafe.} =
2019-09-10 02:15:52 +00:00
## handle incoming/outgoing connections
##
## this proc will:
## 1) register a new PubSubPeer for the connection
## 2) register a handler with the peer;
## this handler gets called on every rpc message
## that the peer receives
2019-10-03 19:14:04 +00:00
## 3) ask the peer to subscribe us to every topic
2019-09-10 02:15:52 +00:00
## that we're interested in
##
2019-10-03 19:14:04 +00:00
if conn.peerInfo.peerId.isNone:
trace "no valid PeerId for peer"
2019-09-12 02:10:38 +00:00
return
2019-10-03 19:14:04 +00:00
# create new pubsub peer
var peer = newPubSubPeer(conn, proc (peer: PubSubPeer,
msgs: seq[RPCMsg]) {.async, gcsafe.} =
# call floodsub rpc handler
await f.rpcHandler(peer, msgs))
trace "created new pubsub peer", id = peer.id
2019-09-12 02:10:38 +00:00
f.peers[peer.id] = peer
2019-09-10 02:15:52 +00:00
let topics = toSeq(f.topics.keys)
await f.sendSubs(peer, topics, true)
2019-10-03 19:14:04 +00:00
let handlerFut = peer.handle() # spawn peer read loop
handlerFut.addCallback(
proc(udata: pointer = nil) {.gcsafe.} =
trace "pubsub peer handler ended, cleaning up",
peer = conn.peerInfo.peerId.get().pretty
f.peers.del(peer.id)
)
2019-09-10 02:15:52 +00:00
method init(f: FloodSub) =
2019-09-12 10:08:11 +00:00
proc handler(conn: Connection, proto: string) {.async, gcsafe.} =
## main protocol handler that gets triggered on every
2019-09-10 02:15:52 +00:00
## connection for a protocol string
## e.g. ``/floodsub/1.0.0``, etc...
##
await f.handleConn(conn)
2019-09-10 02:14:24 +00:00
f.handler = handler
2019-09-10 02:15:52 +00:00
f.codec = FloodSubCodec
2019-09-12 02:10:38 +00:00
method subscribeToPeer*(f: FloodSub, conn: Connection) {.async, gcsafe.} =
2019-09-10 02:15:52 +00:00
await f.handleConn(conn)
method publish*(f: FloodSub,
topic: string,
data: seq[byte]) {.async, gcsafe.} =
await procCall PubSub(f).publish(topic, data)
2019-09-28 19:55:35 +00:00
trace "about to publish message on topic", name = topic, data = data.toHex()
if data.len > 0 and topic.len > 0:
let msg = makeMessage(f.peerInfo.peerId.get(), data, topic)
if topic in f.peerTopics:
2019-10-03 19:14:04 +00:00
trace "publishing on topic", name = topic
2019-09-28 19:55:35 +00:00
for p in f.peerTopics[topic]:
2019-10-03 19:14:04 +00:00
trace "publishing message", name = topic, peer = p, data = data
2019-09-28 19:55:35 +00:00
await f.peers[p].send(@[RPCMsg(messages: @[msg])])
2019-09-10 02:14:24 +00:00
2019-09-12 10:08:11 +00:00
method subscribe*(f: FloodSub,
topic: string,
handler: TopicHandler) {.async, gcsafe.} =
2019-09-10 02:15:52 +00:00
await procCall PubSub(f).subscribe(topic, handler)
2019-10-03 19:14:04 +00:00
f.subscribeTopic(topic, true, f.peerInfo.peerId.get().pretty)
2019-09-10 02:15:52 +00:00
for p in f.peers.values:
await f.sendSubs(p, @[topic], true)
2019-09-10 02:14:24 +00:00
method unsubscribe*(f: FloodSub,
topics: seq[TopicPair]) {.async, gcsafe.} =
2019-09-10 02:15:52 +00:00
await procCall PubSub(f).unsubscribe(topics)
for p in f.peers.values:
await f.sendSubs(p, topics.mapIt(it.topic).deduplicate(), false)
2019-09-10 02:14:24 +00:00
method initPubSub*(f: FloodSub) =
f.peers = initTable[string, PubSubPeer]()
f.topics = initTable[string, Topic]()
f.peerTopics = initTable[string, HashSet[string]]()
f.init()