Js-libp2p "peer and content routing" / kad-dht example

So, as far as I understand, you are not running your own star signal server, is that correct? If that is the case, you should spin up your own: https://github.com/libp2p/js-libp2p-webrtc-star/blob/master/DEPLOYMENT.md . The dns ones you pointed out are these signal servers hosted by the Libp2p Project for people to play with.

You can also do something like Spinning up a free IPFS webrtc-star discovery server with Heroku

Next year we should add the capability for distributed signalling via libp2p relays. When we support that, you should be able to leverage a set of libp2p nodes for this. Meanwhile, this is the feasible approach.

Hm, OK, I think I was confused. I was trying to create a star signal server. I thought I could turn the bounce.js node into a star signal server by just telling it to act as a wss node and passing it wrtc. I guess that’s not how it works.

I find these instructions a little confusing. If I need to use docker then I need to use docker!, but there seems to be a “sig-server” directory in js-libp2p-webrtc star. Is the docker image just a container for this “sig-server” node script? It seems weird to bring up docker when I could just run the sig-server directly in node. What does the docker image in the DEPLOYMENT.md provide that just running node sig-server/bin.js does not? Can I use my local SSL certificates with bin.js?

One more question about the -star server— earlier you said

Is there a way when running the -star signaling server to set up the server to volunteer for STUN peer connection, but not volunteer to relay actual content from peer to peer?

Thanks again for taking the time to answer my questions.

Where did got this confusion? Any docs hinted you that?

You can test it locally just running the bin. But for production, you will need to setup the SSL certificates, which you cannot do at this point in libp2p. You also do not need to use docker, but you would need to setup your nginx reverse proxy and use the bin.js with it. I think the docker setup is easier and faster, but you can move on from it after, if you like. Heroku is a nice temporary solution for experimenting .

After the connection is established, the peers will directly communicate over webrtc. So, the star server will not relay the actual content.

Thanks again for the help. I think I now have connection between two NATed nodes using a custom bootstrap + signaling server.

Two more weird questions…

  • Is there a way for a node to know what name another node knows it by? For example, if I have an app that in the bootstrap list is told to connect to /dns4/sub1.mydomain.com/tcp/9111/ws/p2p/SOMEKEY, does the bootstrap node know that the other node thinks of it as sub1.mydomain.com? (I am thinking of doing something hacky involving multiple subdomains pointing to the same IP.)

  • Is there a way within libp2p, with either a node or the signaling server, to track libp2p’s data usage? That is, how much data up/down libp2p has used. I would like to measure how much bandwidth the bounce/signaling server are using. (I may be able to track this on a per-process basis without assistance from libp2p, I haven’t looked into this yet.)

I’m trying to figure out where I got this wrong. I think I probably just made a bad assumption that there was a symmetry between the “signaling server” and a normal node, an assumption that was easier to make because there is symmetry between a websockets bootstrap server and a normal node. Maybe it would help to add an additional sentence to the “Rendezvous server (aka Signaling server)” section of the js-libp2p-webrtc-star README such as “This signaling server is different from a normal peer, and is required for libp2p-webrtc-star to work”. (This is already suggested by the existing text but since learning to use libp2p involves introduction to many new concepts at once, maybe it helps to be extra explicit.)

It would also probably help to have an explicit document explaining how to replace the public servers used by the samples with your own servers. Right now for example the libp2p-in-the-browser example links only to the js-libp2p-webrtc-star/DEPLOYMENT.md, which is vague on a lot of points and does not cover matters such as how to make a websockets bootstrap node. Once I get everything I have learned in this thread up and working I am intending to attempt to write a guide explaining what I did and how, so I can submit that upstream.

Following up on this.

So this is my current logic in the bounce server.

  // Create node with peer
  node1 = await createNode(peerId)
  console.log("Self is", node1.peerId.toB58String())

  if (commander.protocol) { // See note at top
    const tempBuffer = new Uint8Array([0]) // Single zero byte, indicating a 0-length message, indicating ACK
    const protocolString = `/{commander.protocol}`

    node1.handle([protocolString], ({ protocol, stream }) => {
      (async () => {
        await stream.write(tempBuffer, 1)
      })()
    })

    node1.connectionManager.on('peer:connect', async (connection) => {
console.log("TRACE-1")
      try {
        await node1.dialProtocol(connection.remotePeer, protocolString)
console.log("TRACE-2")
      } catch (e) {
        console.log("Connection failed:", e)
        node1.hangUp(connection.remotePeer)
        node1.peerStore.addressBook.delete(connection.remotePeer)
      }
console.log("TRACE-3")
    })
  }

My goal is that every peer that tries to talk to the bounce server, before the bounce server adds that peer to the gossip list, it should screen it to ensure a dial with the protocol is successful.

(You are correct that if the bounce server is specific to the application then probably no one will connect to the bounce server unless they are running the application, but, I have seen strange things happen on P2P networks.)

In my implementation, however, what I really did was wait for a connection to be opened, and when it opens I dial the protocol and see if that raises an exception or not. If an exception is raised I close the connection and remove the peer from the peerStore.

In my testing, what happens if I run a non-compliant node is the node connects, the bounce server prints TRACE-1, there is a pause of several seconds, and then the bounce server prints the error. Unless node is flushing messages strangely, it seems that an unsuccessful dial does not fail immediately but times out. When the noncompliant node is disconnected, it seems to immediately try to reconnect, it tries several times but it seems to back off exponentially.

autoDial is false on the bounce server (autoDial defaults to false, right? this is unclear to me in the documentation) but this doesn’t matter because the point of the bounce server is nodes connect to it.

So this is probably “good enough” but it does not quite meet my goal because I think I am still potentially gossiping about noncompliant nodes. I think (?) that in the several seconds between a noncompliant node connecting and me realizing it is noncompliant and disconnecting it, I will probably still gossip about that node to anyone I’m talking to in the meanwhile. That’s mostly harmless but I am curious.

What determines whether a node is gossiped about? Is it just anything in the peerStore? What controls when things are added to the peerStore?

Looking at the peerStore config and peerStore methods I don’t find any good hints. It appears something automatic is happening but I cannot find where it is documented. What should I look at here?

Nice, happy to hear!

We have this notion of observedAddresses, which I think is what you are looking for: https://github.com/libp2p/js-libp2p/blob/v0.29.4/src/identify/index.js#L217 However, this is not yet fully implemented. The blocker is that we need to have multiaddr confidence in place, in order to properly track what multiaddrs are more likely to work.

For now, you probably will have to do your own protocol:

You would basically have a protocol (let’s say a one message each chat), where once peers connect they would send each other the known multiaddrs of the other one (getting them from peerStore.addressBook.get)

Yes! You can look into:

Thanks, that would be super nice! :slight_smile: I agree with you, I started production guides https://github.com/libp2p/js-libp2p/tree/0.30.x/doc/production but they still have a long way to go. It is important for us to have users that try it out and help with this type of documentation.

autoDial is true by default at the moment. That will change in a couple of releases.

I think you can simplify this to:

if (commander.protocol) { // See note at top
    const tempBuffer = new Uint8Array([0]) // Single zero byte, indicating a 0-length message, indicating ACK
    const protocolString = `/{commander.protocol}`

    node1.handle([protocolString], ({ protocol, stream }) => {
      (async () => {
        await stream.write(tempBuffer, 1)
      })()
    })

    node1.peerStore.on('change:protocols', ({ peerId, protocols}) => {
      if (!protocols.include(protocolString)) {
        node1.hangUp(peerId)
        node1.peerStore.addressBook.delete(peerId)
      }
    })
  }

When two peers connect, they will run identify and exchange protocols. This will always be triggered and you kick it out.

I need to double check what might be happening, as I believe it should not take too long to fail the dialProtocol.

Yes, that currently happens as the other peer will likely have autoDial set to true. We used to have a deny list, but we do not support it since we removed libp2p-switch. There are two things that we need to do in this context:

It depends on the subsystems enabled. In gossipsub peer exchange, this will not likely happen unless you exchange pubsub messages with the peer. In the DHT side of things, it will happen if DHT queries get to that ID on the algortihm.
It would probably be nice if libp2p could offer a customFilterFunction that would filter out peers that could be shared. With this, users could just add on peer:connect metadata to the PeerStore stating that the peer is being “inspected”. The customFilterFunction would remove these peers. Once the protocol for the peer is validated, the metadata would be removed. We will also work on connection tags, which could probably be also used in this context if we provide such function to the user.

Nothing controls things being added to the peerStore, and I think that is correct. We could just have the filter I described above. Once we know something new from a peer (addresses, protocols) it is added by libp2p to the PeerStore. You can see these flows in https://github.com/libp2p/js-libp2p/tree/master/src/peer-store

1 Like

Hello again. @vasco-santos thank you for the help, I am working on this again and I have one more question.

I can now create a peer network exclusively consisting of “my app” (or apps claiming to support my app’s protocol). I now need clients of that app to find each other using the DHT. I see the example uses the DHT to let peers find each other by peer id, but what I want is for them to look each other up using a specific 256-bit key.

Looking at the summary doc I find a provide(key). This is exactly what I want. But there is a problem: The doc does not explain what type key should be, and I can’t figure out what object provide() is called on. After a bunch of fiddling, I came up with:

import { encode as encodeMb } from 'multibase' // NPM module
const CID = require('cids') // Not sure where 'cids' came from but webpack allows it!

const key = login.value.get('signKey') // This is a 32-byte Uint8Array
const signKeyMultihash = encodeMb('base58btc', signKey.publicKey)
// Note 0x12 for SHA-256
node._dht.provide(new CID(1, 0x12, signKeyMultihash))

When I run this, it throws the exception:

Error: multihash unknown function code: 0x7a
    at Function.validateCID (index.js:312)
    at new CID (index.js:121)

How do I create the key/“CID”? and is “node._dht” correct or is there some more appropriate way to get the dht associated with the node (the node was created with modules: {dht: KadDHT})?

Update on this, this appears to work:

      // We want to use the 256-bit public key as a 256-bit Kademlia key.
      // libp2p won't let us do that. We have to convert it to a "multihash" and then to a "CID".
      // This appears to mutate the key along the way, but I'm not sure how.
      // This is the best I can find for how to make a "CID" https://github.com/multiformats/js-cid#usage
      const multihashing = require("multihashing-async")
      const signKey = login.value.get('signKey')
      const signKeyMultihash = await multihashing(signKey.publicKey, 'sha2-256') // Does this *create* a SHA256 or simply *tag* as SHA256?
      const CID = require('cids') // FIXME: This isn't in package.json. Where did it come from?
      console.log("Node has", Object.keys(node._dht))
      // I don't know how I'm supposed to access the "._dht" object. The _dht key appears to work at least.
      // Note 0x12 for SHA-256
      node._dht.provide(new CID(1, 0x12, signKeyMultihash)) 

However, as noted in the comments, there are some things I don’t understand about why it works :slight_smile:

Another update, I now have this fully working… i can provide() in one tab and findProviders() in another tab.

However, the findProviders() fails to find the provide()-d content… :frowning:

Probably I will file an issue tomorrow.

Here’s the bug I filed:

If I connect with node._dht.provide(), the find() call fails silently— well, that’s not a surprise, the underscore object probably just doesn’t work. If I call contentProvider.provide(), however, I get an error message. Because the code in this app is so similar to the js-libp2p “peer and content routing” demo, I think this indicates a bug in libp2p somewhere. A minimal repro is attached. Any help appreciated.

Answered in the github issue

Hello, I’m sorry to keep coming back with weird questions but…

I thought that I had this example fully working because dialProtocol had connected. However, I had not yet tried to send data over the stream. I find that when I send data over the stream, extra and seemingly random bytes I did not send are inserted into the stream. I made a minimal example and filed a github issue here.

More detail (also in the github issue):

In my test, a client browser-tab looks up a provider browser-tab using libp2p-kad-dht, then dials it up on a custom protocol using libp2p-mplex. The client tab sends a short query on the stream using it-pipe, and the provider tab reads the query then sends a response on the stream using it-pipe. What I find is that in my tests, one side or the other receives a couple of bytes of garbage before seeing the data the other tab sent.

The test is the dht-stream-garbage-bytes branch here. It is a little complicated to run but if you want to run it yourself the instructions are in run.txt. You have to run a webrtc server and a bootstrap node on a server, then put the IP address and multihash of that server in the file BOUNCE-SERVER.js. Open the web app in 2 tabs and open the javascript console in each. After logging in, each tab will print “Encoded:” in the console.log followed by three lines of unicode. This is an encoded DHT key. Copy one of the three lines (they mean the same thing) out of one tab and into the other tab’s “connect” box.

At this point:

  • The client tab will print “SEARCHING” and the byte array it sent.
  • The provider tab will print “KEY QUERY” the byte array it received.
  • The provider tab will print “Sending this many bytes of data” and a one or two byte array (it will be longer if the message you typed in was longer).
  • The client tab will print “Uleb debug” and the first byte it received.

The “expected behavior” is that “SEARCHING” should equal “KEY QUERY” and “Sending this many bytes of data” should equal “Uleb debug”.

But the “Observed behavior”:

In my tests, for a brief period the “KEY QUERY” array [received] was the “SEARCHING” array [sent] BUT it was prefixed by two bytes of garbage. The two bytes of garbage were different every time. Eventually, after one recompile, this symptom went away completely and never came back.

Since then, I see a different symptom: “KEY QUERY” is correctly equal to “SEARCHING”, but when the provider tab sends its one or two byte array, it is not the value that is seen by the client tab. Instead the client tab sees a garbage byte (this seems to almost always be either 17 or 41— I don’t know the significance). Because the app does not understand this byte, it does not look for a second byte.

Within the code of the demo, here is the code path of the “client” (dht search, dial, query) and here is the code path for the “provider”.

Any help appreciated, I’m not sure how to debug something like extra garbage bytes :frowning:

The process describe here does not seem to be the case. I am running two instances of a node with bootstrapp and DHT and no extra peers are being found. :frowning: