Add a tutorial for the discovery manager (#787)
Co-authored-by: Tanguy <tanguy@status.im>
This commit is contained in:
parent
4bce8f38c9
commit
b4f96721af
|
@ -0,0 +1,132 @@
|
|||
## # Discovery Manager
|
||||
##
|
||||
## In the [previous tutorial](tutorial_4_gossipsub.md), we built a custom protocol using [protobuf](https://developers.google.com/protocol-buffers) and
|
||||
## spread informations (some metrics) on the network using gossipsub.
|
||||
## For this tutorial, on the other hand, we'll go back on a simple example
|
||||
## we'll try to discover a specific peers to greet on the network.
|
||||
##
|
||||
## First, as usual, we import the dependencies:
|
||||
import sequtils
|
||||
import chronos
|
||||
import stew/byteutils
|
||||
|
||||
import libp2p
|
||||
import libp2p/protocols/rendezvous
|
||||
import libp2p/discovery/rendezvousinterface
|
||||
import libp2p/discovery/discoverymngr
|
||||
|
||||
## We'll not use newStandardSwitch this time as we need the discovery protocol
|
||||
## [RendezVous](https://github.com/libp2p/specs/blob/master/rendezvous/README.md) to be mounted on the switch using withRendezVous.
|
||||
##
|
||||
## Note that other discovery methods such as [Kademlia](https://github.com/libp2p/specs/blob/master/kad-dht/README.md) or [discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md) exist.
|
||||
proc createSwitch(rdv: RendezVous = RendezVous.new()): Switch =
|
||||
SwitchBuilder.new()
|
||||
.withRng(newRng())
|
||||
.withAddresses(@[ MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet() ])
|
||||
.withTcpTransport()
|
||||
.withYamux()
|
||||
.withNoise()
|
||||
.withRendezVous(rdv)
|
||||
.build()
|
||||
|
||||
# Create a really simple protocol to log one message received then close the stream
|
||||
const DumbCodec = "/dumb/proto/1.0.0"
|
||||
type DumbProto = ref object of LPProtocol
|
||||
proc new(T: typedesc[DumbProto], nodeNumber: int): T =
|
||||
proc handle(conn: Connection, proto: string) {.async, gcsafe.} =
|
||||
echo "Node", nodeNumber, " received: ", string.fromBytes(await conn.readLp(1024))
|
||||
await conn.close()
|
||||
return T(codecs: @[DumbCodec], handler: handle)
|
||||
|
||||
## ## Bootnodes
|
||||
## The first time a p2p program is ran, he needs to know how to join
|
||||
## its network. This is generally done by hard-coding a list of stable
|
||||
## nodes in the binary, called "bootnodes". These bootnodes are a
|
||||
## critical part of a p2p network, since they are used by every new
|
||||
## user to onboard the network.
|
||||
##
|
||||
## By using libp2p, we can use any node supporting our discovery protocol
|
||||
## (rendezvous in this case) as a bootnode. For this example, we'll
|
||||
## create a bootnode, and then every peer will advertise itself on the
|
||||
## bootnode, and use it to find other peers
|
||||
proc main() {.async, gcsafe.} =
|
||||
let bootNode = createSwitch()
|
||||
await bootNode.start()
|
||||
|
||||
# Create 5 nodes in the network
|
||||
var
|
||||
switches: seq[Switch] = @[]
|
||||
discManagers: seq[DiscoveryManager] = @[]
|
||||
|
||||
for i in 0..5:
|
||||
let rdv = RendezVous.new()
|
||||
# Create a remote future to await at the end of the program
|
||||
let switch = createSwitch(rdv)
|
||||
switch.mount(DumbProto.new(i))
|
||||
switches.add(switch)
|
||||
|
||||
# A discovery manager is a simple tool, you can set it up by adding discovery
|
||||
# interfaces (such as RendezVousInterface) then you can use it to advertise
|
||||
# something on the network or to request something from it.
|
||||
let dm = DiscoveryManager()
|
||||
# A RendezVousInterface is a RendezVous protocol wrapped to be usable by the
|
||||
# DiscoveryManager.
|
||||
dm.add(RendezVousInterface.new(rdv))
|
||||
discManagers.add(dm)
|
||||
|
||||
# We can now start the switch and connect to the bootnode
|
||||
await switch.start()
|
||||
await switch.connect(bootNode.peerInfo.peerId, bootNode.peerInfo.addrs)
|
||||
|
||||
# Each nodes of the network will advertise on some topics (EvenGang or OddClub)
|
||||
dm.advertise(RdvNamespace(if i mod 2 == 0: "EvenGang" else: "OddClub"))
|
||||
|
||||
## We can now create the newcomer. This peer will connect to the boot node, and use
|
||||
## it to discover peers & greet them.
|
||||
let
|
||||
rdv = RendezVous.new()
|
||||
newcomer = createSwitch(rdv)
|
||||
dm = DiscoveryManager()
|
||||
await newcomer.start()
|
||||
await newcomer.connect(bootNode.peerInfo.peerId, bootNode.peerInfo.addrs)
|
||||
dm.add(RendezVousInterface.new(rdv, ttr = 250.milliseconds))
|
||||
|
||||
# Use the discovery manager to find peers on the OddClub topic to greet them
|
||||
let queryOddClub = dm.request(RdvNamespace("OddClub"))
|
||||
for _ in 0..2:
|
||||
let
|
||||
# getPeer give you a PeerAttribute containing informations about the peer.
|
||||
res = await queryOddClub.getPeer()
|
||||
# Here we will use the PeerId and the MultiAddress to greet him
|
||||
conn = await newcomer.dial(res[PeerId], res.getAll(MultiAddress), DumbCodec)
|
||||
await conn.writeLp("Odd Club suuuucks! Even Gang is better!")
|
||||
# Uh-oh!
|
||||
await conn.close()
|
||||
# Wait for the peer to close the stream
|
||||
await conn.join()
|
||||
# Queries will run in a loop, so we must stop them when we are done
|
||||
queryOddClub.stop()
|
||||
|
||||
# Maybe it was because he wanted to join the EvenGang
|
||||
let queryEvenGang = dm.request(RdvNamespace("EvenGang"))
|
||||
for _ in 0..2:
|
||||
let
|
||||
res = await queryEvenGang.getPeer()
|
||||
conn = await newcomer.dial(res[PeerId], res.getAll(MultiAddress), DumbCodec)
|
||||
await conn.writeLp("Even Gang is sooo laaame! Odd Club rocks!")
|
||||
# Or maybe not...
|
||||
await conn.close()
|
||||
await conn.join()
|
||||
queryEvenGang.stop()
|
||||
# What can I say, some people just want to watch the world burn... Anyway
|
||||
|
||||
# Stop all the discovery managers
|
||||
for d in discManagers:
|
||||
d.stop()
|
||||
dm.stop()
|
||||
|
||||
# Stop all the switches
|
||||
await allFutures(switches.mapIt(it.stop()))
|
||||
await allFutures(bootNode.stop(), newcomer.stop())
|
||||
|
||||
waitFor(main())
|
|
@ -91,6 +91,7 @@ task website, "Build the website":
|
|||
tutorialToMd("examples/tutorial_2_customproto.nim")
|
||||
tutorialToMd("examples/tutorial_3_protobuf.nim")
|
||||
tutorialToMd("examples/tutorial_4_gossipsub.nim")
|
||||
tutorialToMd("examples/tutorial_5_discovery.nim")
|
||||
tutorialToMd("examples/circuitrelay.nim")
|
||||
exec "mkdocs build"
|
||||
|
||||
|
@ -104,6 +105,7 @@ task examples_build, "Build the samples":
|
|||
# These tutorials relies on post 1.4 exception tracking
|
||||
buildSample("tutorial_3_protobuf", true)
|
||||
buildSample("tutorial_4_gossipsub", true)
|
||||
buildSample("tutorial_5_discovery", true)
|
||||
|
||||
# pin system
|
||||
# while nimble lockfile
|
||||
|
|
|
@ -559,7 +559,9 @@ proc request*(rdv: RendezVous,
|
|||
for (_, r) in s.values():
|
||||
rdv.save(ns, peer, r, false)
|
||||
|
||||
for peer in rdv.peers:
|
||||
# copy to avoid resizes during the loop
|
||||
let peers = rdv.peers
|
||||
for peer in peers:
|
||||
if limit == 0: break
|
||||
if RendezVousCodec notin rdv.switch.peerStore[ProtoBook][peer]: continue
|
||||
try:
|
||||
|
|
|
@ -46,5 +46,6 @@ nav:
|
|||
- 'Create a custom protocol': tutorial_2_customproto.md
|
||||
- 'Protobuf': tutorial_3_protobuf.md
|
||||
- 'GossipSub': tutorial_4_gossipsub.md
|
||||
- 'Discovery Manager': tutorial_5_discovery.md
|
||||
- 'Circuit Relay': circuitrelay.md
|
||||
- Reference: '/nim-libp2p/master/libp2p.html'
|
||||
|
|
Loading…
Reference in New Issue