Skip to content

Commit

Permalink
chore: add basic usage example
Browse files Browse the repository at this point in the history
Add a basic usage example whereby the client code runs on top of each
libp2p DHT node. The idea is that each user has its own deaddrop,
identified by her UserId (on the DHT identifiers namespace), where other
users can leave messages to.

The demo instantiates three application (and thus DHT) nodes. Bootstrap
is handled by having an "always-on" node where others can connect to.
In real world examples, this would not suffice, as a discovery mechanism
needs to be in place (either by using the RandomWalk peer discovery or
by switching any implementations of libp2p/interface-peer-discovery).

Once the nodes are instantiated and connected, thus forming a single
DHT, one deaddrop application (userA) writes a message for userB. Then,
userB reads her messages (after having been informed about the message
through off-channel means).

For this example to make sense, any sent message is encrypted using the
recipient's public key, so that only she can read them, by decrypting
them with her private key.

Resolves libp2p#36.

Signed-off-by: Chris Aslanoglou <[email protected]>
  • Loading branch information
chris-asl committed Jan 3, 2019
1 parent 59d1a94 commit fb0817f
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 0 deletions.
58 changes: 58 additions & 0 deletions examples/deaddrops/deaddrop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict'

const waterfall = require('async').waterfall

const DHTNode = require('./dht').DHTNode

class DeadDrop {
constructor(userId, dht) {
this.userId = userId
this.userIdKey = Buffer.from(userId)
this.dht = dht
}

init(cb) {
this.dht.start(cb)
}

dial(target, cb) {
this.dht.dial(target, cb)
}

shutdown(cb) {
this.dht.stop(cb)
}

leaveMessageToUser(receiver, message, cb) {
const key = Buffer.from(receiver)
const value = Buffer.from(message)
this.dht.put(key, value, cb)
}

readMyMessages(cb) {
this.dht.get(this.userIdKey, cb)
}
}

function instantiateDeaddrop(userId, callback) {
let deaddrop
waterfall(
[
(cb) => DHTNode.createInstance(cb),
(dht, cb) => {
deaddrop = new DeadDrop(userId, dht)
deaddrop.init(cb)
}
],
(err) => {
if (err) callback(err, null);

console.log(`Deaddrop[${userId}] was successfuly initialized...`);
callback(null, deaddrop)
}
)
}

module.exports = {
instantiateDeaddrop
}
114 changes: 114 additions & 0 deletions examples/deaddrops/dht.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
'use strict'

const {
series
} = require('async')

const TCP = require('libp2p-tcp')
const Switch = require('libp2p-switch')
const Mplex = require('libp2p-mplex')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const PeerBook = require('peer-book')

const KadDHT = require('../../src');

function createPeerInfo(callback) {
// Creation of PeerId should be done only once, persisted and then read upon initialization.
PeerId.create({bits: 512}, (err, id) => {
if (err) callback(err, null)

callback(null, new PeerInfo(id))
})
}

function createTCPSwitch(peerInfo) {
// Required for the nodes to be able to communicate
// N.B. You cannot add solely a transport, a stream multiplexer is needed as well so that it
// allows for a TCP stream to be multiplexed over a connection (even though you have only one transport), IIUC.
const sw = new Switch(peerInfo, new PeerBook())
sw.transport.add('tcp', new TCP())
sw.connection.addStreamMuxer(Mplex)
sw.connection.reuse()
return sw
}

class DHTNode {
/**
* Initializes a DHT node.
* @param {Switch} sw A libp2p-switch implementation. More info https://github.com/libp2p/js-libp2p-switch
*/
constructor(sw) {
const options = { kBucketSize: 1 }
this._sw = sw
// Discovery is enabled by default since KadDHT implements also
// https://github.com/libp2p/interface-peer-discovery through its RandomWalk module.
this._dht = new KadDHT(sw, options)
}

/**
* Creates a new DHT Node.
* The node listens on localhost, using TCP, at any port, using the js-libp2p-switch implementation.
*/
static createInstance(callback) {
createPeerInfo((err, peerInfo) => {
if (err) callback(err, null)

// This allows the node (its switch actually) to act as a "listener" (server).
peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/0')

const sw = createTCPSwitch(peerInfo)
const dht = new DHTNode(sw)
callback(null, dht)
})
}

start(callback) {
series(
[
(cb) => this._sw.start(cb),
(cb) => this._dht.start(cb),
],
(err) => {
if (err) callback(err, null)

console.log(`DHTNode[${this._dht.peerInfo.id.toB58String()}] initialized and listening on:`)
this._dht.peerInfo.multiaddrs.forEach(ma => console.log(`\tMultiAddress[${ma.toString()}]`))

callback()
}
)
}

stop(callback) {
series(
[
(cb) => this._dht.stop(cb),
(cb) => this._sw.stop(cb),
],
(err) => {
if (err) callback(err, null)

callback()
}
)
}

dial(target, cb) {
this._sw.dial(target, cb)
}

put(key, value, cb) {
this._dht.put(key, value, cb)
}

get(key, cb) {
this._dht.get(key, null, cb)
}

getPeerInfo() {
return this._dht.peerInfo
}
}

module.exports.DHTNode = DHTNode
92 changes: 92 additions & 0 deletions examples/deaddrops/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use strict'

const _ = require('lodash')
const {
each,
series,
parallel,
waterfall,
} = require('async')

const instantiateDeaddrop = require('./deaddrop').instantiateDeaddrop

// Dummy example:
// Create 3 deaddrops. One that's always on (for bootstrapping) and two others.
// Once the bootstrapping process completes, userA leaves a message in
// user's B deaddrop, who then later reads it.
const userAlwaysOnId = 'userAlwaysOn'
const userAId = 'userA'
const userBId = 'userB'
let deaddropAlwaysOn, deaddropA, deaddropB

// Utility method for instantiating the three deaddrops.
function instantiateAll(callback) {
parallel(
[
(cb) => instantiateDeaddrop(userAlwaysOnId, cb),
(cb) => instantiateDeaddrop(userAId, cb),
(cb) => instantiateDeaddrop(userBId, cb),
],
(err, deaddrops) => {
if (err) callback(err)

deaddropAlwaysOn = deaddrops[0]
deaddropA = deaddrops[1]
deaddropB = deaddrops[2]

console.log('Deaddrops were instantiated.\n')
callback()
}
)
}

// Adapted from https://github.com/libp2p/js-libp2p-kad-dht/blob/master/test/kad-dht.spec.js#L34-L39
function connectNoSync(a, b, callback) {
const target = _.cloneDeep(b.dht.getPeerInfo())
target.id._pubKey = target.id.pubKey
target.id._privKey = null
a.dht.dial(target, callback)
}

// Utility method for connecting the two deaddrops with the always-on one.
function bootstrap(callback) {
series(
[
(cb) => connectNoSync(deaddropA, deaddropAlwaysOn, cb),
(cb) => connectNoSync(deaddropB, deaddropAlwaysOn, cb),
],
(err) => {
if (err) callback(err)

console.log('Deaddrops connected to the always-on one.')
callback()
}
)
}

waterfall(
[
(cb) => instantiateAll(cb),
(cb) => bootstrap(cb),
(cb) => deaddropA.leaveMessageToUser(userBId, `Greetings from ${userAId}`, cb),
(cb) => deaddropB.readMyMessages(cb),
(message, cb) => {
console.log(`${userBId}: received message[${message}]`)
cb()
},
(cb) => {
each(
[deaddropAlwaysOn, deaddropA, deaddropB],
(deaddrop, callback) => {
deaddrop.shutdown(callback)
},
(err) => {
if (err) cb(err)

console.log('\nAll deaddrops were shutdown.')
cb()
}
)
}
]
)

0 comments on commit fb0817f

Please sign in to comment.