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_2_customproto.nim")
|
||||||
tutorialToMd("examples/tutorial_3_protobuf.nim")
|
tutorialToMd("examples/tutorial_3_protobuf.nim")
|
||||||
tutorialToMd("examples/tutorial_4_gossipsub.nim")
|
tutorialToMd("examples/tutorial_4_gossipsub.nim")
|
||||||
|
tutorialToMd("examples/tutorial_5_discovery.nim")
|
||||||
tutorialToMd("examples/circuitrelay.nim")
|
tutorialToMd("examples/circuitrelay.nim")
|
||||||
exec "mkdocs build"
|
exec "mkdocs build"
|
||||||
|
|
||||||
|
@ -104,6 +105,7 @@ task examples_build, "Build the samples":
|
||||||
# These tutorials relies on post 1.4 exception tracking
|
# These tutorials relies on post 1.4 exception tracking
|
||||||
buildSample("tutorial_3_protobuf", true)
|
buildSample("tutorial_3_protobuf", true)
|
||||||
buildSample("tutorial_4_gossipsub", true)
|
buildSample("tutorial_4_gossipsub", true)
|
||||||
|
buildSample("tutorial_5_discovery", true)
|
||||||
|
|
||||||
# pin system
|
# pin system
|
||||||
# while nimble lockfile
|
# while nimble lockfile
|
||||||
|
|
|
@ -559,7 +559,9 @@ proc request*(rdv: RendezVous,
|
||||||
for (_, r) in s.values():
|
for (_, r) in s.values():
|
||||||
rdv.save(ns, peer, r, false)
|
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 limit == 0: break
|
||||||
if RendezVousCodec notin rdv.switch.peerStore[ProtoBook][peer]: continue
|
if RendezVousCodec notin rdv.switch.peerStore[ProtoBook][peer]: continue
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -46,5 +46,6 @@ nav:
|
||||||
- 'Create a custom protocol': tutorial_2_customproto.md
|
- 'Create a custom protocol': tutorial_2_customproto.md
|
||||||
- 'Protobuf': tutorial_3_protobuf.md
|
- 'Protobuf': tutorial_3_protobuf.md
|
||||||
- 'GossipSub': tutorial_4_gossipsub.md
|
- 'GossipSub': tutorial_4_gossipsub.md
|
||||||
|
- 'Discovery Manager': tutorial_5_discovery.md
|
||||||
- 'Circuit Relay': circuitrelay.md
|
- 'Circuit Relay': circuitrelay.md
|
||||||
- Reference: '/nim-libp2p/master/libp2p.html'
|
- Reference: '/nim-libp2p/master/libp2p.html'
|
||||||
|
|
Loading…
Reference in New Issue