Compare commits

...

7 Commits

Author SHA1 Message Date
Jacob Heun
5e5cf2d31a refactor: cleanup keychain usage 2020-08-05 18:13:13 +02:00
Jacob Heun
a3ea2f4550 chore: bump crypto 2020-08-05 17:21:03 +02:00
Jacob Heun
a71fe96aaf feat(keychain): add support for ed25519 and secp keys 2020-08-04 16:32:06 +02:00
Felipe Martins
a331b84f13 docs(example): add pubssub message filter example
moved the filter folder

rename folder

docs(refactor): clean up pubsub validator example
2020-07-31 12:09:34 +02:00
Jacob Heun
78d152dd68 chore: release version v0.28.9 2020-07-27 11:58:53 +02:00
Jacob Heun
7e14aa19b5 chore: update contributors 2020-07-27 11:58:52 +02:00
Vasco Santos
2440c872df fix: ping multiaddr from peer not previously stored in peerstore (#719) 2020-07-27 10:53:23 +02:00
10 changed files with 293 additions and 18 deletions

View File

@@ -1,3 +1,13 @@
<a name="0.28.9"></a>
## [0.28.9](https://github.com/libp2p/js-libp2p/compare/v0.28.8...v0.28.9) (2020-07-27)
### Bug Fixes
* ping multiaddr from peer not previously stored in peerstore ([#719](https://github.com/libp2p/js-libp2p/issues/719)) ([2440c87](https://github.com/libp2p/js-libp2p/commit/2440c87))
<a name="0.28.8"></a>
## [0.28.8](https://github.com/libp2p/js-libp2p/compare/v0.28.7...v0.28.8) (2020-07-20)

View File

@@ -1454,7 +1454,7 @@ Create a key in the keychain.
|------|------|-------------|
| name | `string` | The local key name. It cannot already exist. |
| type | `string` | One of the key types; 'rsa' |
| size | `number` | The key size in bits. |
| [size] | `number` | The key size in bits. Must be provided for rsa keys. |
#### Returns

View File

@@ -0,0 +1,82 @@
/* eslint-disable no-console */
'use strict'
const { Buffer } = require('buffer')
const Libp2p = require('../../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const Gossipsub = require('libp2p-gossipsub')
const createNode = async () => {
const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO],
pubsub: Gossipsub
}
})
await node.start()
return node
}
(async () => {
const topic = 'fruit'
const [node1, node2, node3] = await Promise.all([
createNode(),
createNode(),
createNode(),
])
// node1 conect to node2 and node2 conect to node3
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.dial(node2.peerId)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await node2.dial(node3.peerId)
//subscribe
await node1.pubsub.subscribe(topic, (msg) => {
console.log(`node1 received: ${msg.data.toString()}`)
})
await node2.pubsub.subscribe(topic, (msg) => {
console.log(`node2 received: ${msg.data.toString()}`)
})
await node3.pubsub.subscribe(topic, (msg) => {
console.log(`node3 received: ${msg.data.toString()}`)
})
const validateFruit = (msgTopic, peer, msg) => {
const fruit = msg.data.toString();
const validFruit = ['banana', 'apple', 'orange']
const valid = validFruit.includes(fruit);
return valid;
}
//validate fruit
node1.pubsub._pubsub.topicValidators.set(topic, validateFruit);
node2.pubsub._pubsub.topicValidators.set(topic, validateFruit);
node3.pubsub._pubsub.topicValidators.set(topic, validateFruit);
// node1 publishes "fruits" every five seconds
var count = 0;
const myFruits = ['banana', 'apple', 'car', 'orange'];
// car is not a fruit !
setInterval(() => {
console.log('############## fruit ' + myFruits[count] + ' ##############')
node1.pubsub.publish(topic, Buffer.from(myFruits[count]))
count++
if (count == myFruits.length) {
count = 0
}
}, 5000)
})()

View File

@@ -0,0 +1,108 @@
# Filter Messages
To prevent undesired data from being propagated on the network, we can apply a filter to Gossipsub. Messages that fail validation in the filter will not be re-shared.
## 1. Setting up a PubSub network with three nodes
First, let's update our libp2p configuration with a pubsub implementation.
```JavaScript
const Libp2p = require('libp2p')
const Gossipsub = require('libp2p-gossipsub')
const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ],
pubsub: Gossipsub
}
})
```
Then, create three nodes and connect them together. In this example, we will connect the nodes in series. Node 1 connected with node 2 and node 2 connected with node 3.
```JavaScript
const [node1, node2, node3] = await Promise.all([
createNode(),
createNode(),
createNode(),
])
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.dial(node2.peerId)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await node2.dial(node3.peerId)
```
Now we' can subscribe to the fruit topic and log incoming messages.
```JavaScript
const topic = 'fruit'
await node1.pubsub.subscribe(topic, (msg) => {
console.log(`node1 received: ${msg.data.toString()}`)
})
await node2.pubsub.subscribe(topic, (msg) => {
console.log(`node2 received: ${msg.data.toString()}`)
})
await node3.pubsub.subscribe(topic, (msg) => {
console.log(`node3 received: ${msg.data.toString()}`)
})
```
Finally, let's define the additional filter in the fruit topic.
```JavaScript
const validateFruit = (msgTopic, peer, msg) => {
const fruit = msg.data.toString();
const validFruit = ['banana', 'apple', 'orange']
const valid = validFruit.includes(fruit);
return valid;
}
node1.pubsub._pubsub.topicValidators.set(topic, validateFruit);
node2.pubsub._pubsub.topicValidators.set(topic, validateFruit);
node3.pubsub._pubsub.topicValidators.set(topic, validateFruit);
```
In this example, node one has an outdated version of the system, or is a malicious node. When it tries to publish fruit, the messages are re-shared and all the nodes share the message. However, when it tries to publish a vehicle the message is not re-shared.
```JavaScript
var count = 0;
const myFruits = ['banana', 'apple', 'car', 'orange'];
setInterval(() => {
console.log('############## fruit ' + myFruits[count] + ' ##############')
node1.pubsub.publish(topic, Buffer.from(myFruits[count]))
count++
if (count == myFruits.length) {
count = 0
}
}, 5000)
```
Result
```
> node 1.js
############## fruit banana ##############
node1 received: banana
node2 received: banana
node3 received: banana
############## fruit apple ##############
node1 received: apple
node2 received: apple
node3 received: apple
############## fruit car ##############
node1 received: car
############## fruit orange ##############
node1 received: orange
node2 received: orange
node3 received: orange
```

View File

@@ -1,6 +1,6 @@
{
"name": "libp2p",
"version": "0.28.8",
"version": "0.28.9",
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
"main": "src/index.js",
@@ -58,7 +58,7 @@
"it-length-prefixed": "^3.0.1",
"it-pipe": "^1.1.0",
"it-protocol-buffers": "^0.2.0",
"libp2p-crypto": "^0.17.8",
"libp2p-crypto": "^0.17.9",
"libp2p-interfaces": "^0.3.1",
"libp2p-utils": "^0.1.2",
"mafmt": "^7.0.0",

View File

@@ -324,7 +324,7 @@ class Libp2p extends EventEmitter {
* @returns {Promise<Connection|*>}
*/
async dialProtocol (peer, protocols, options) {
const { id, multiaddrs } = getPeer(peer, this.peerStore)
const { id, multiaddrs } = getPeer(peer)
let connection = this.connectionManager.get(id)
if (!connection) {
@@ -396,7 +396,12 @@ class Libp2p extends EventEmitter {
* @returns {Promise<number>}
*/
ping (peer) {
const { id } = getPeer(peer)
const { id, multiaddrs } = getPeer(peer)
// If received multiaddr, ping it
if (multiaddrs) {
return ping(this, multiaddrs[0])
}
return ping(this, id)
}

View File

@@ -7,6 +7,9 @@ const crypto = require('libp2p-crypto')
const DS = require('interface-datastore')
const CMS = require('./cms')
const errcode = require('err-code')
const { Number } = require('ipfs-utils/src/globalthis')
require('node-forge/lib/sha512')
const keyPrefix = '/pkcs8/'
const infoPrefix = '/info/'
@@ -171,7 +174,7 @@ class Keychain {
*
* @param {string} name - The local key name; cannot already exist.
* @param {string} type - One of the key types; 'rsa'.
* @param {int} size - The key size in bits.
* @param {int} [size] - The key size in bits. Used for rsa keys only.
* @returns {KeyInfo}
*/
async createKey (name, type, size) {
@@ -185,17 +188,13 @@ class Keychain {
return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE'))
}
if (!Number.isSafeInteger(size)) {
return throwDelayed(errcode(new Error(`Invalid key size '${size}'`), 'ERR_INVALID_KEY_SIZE'))
}
const dsname = DsName(name)
const exists = await self.store.has(dsname)
if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
switch (type.toLowerCase()) {
case 'rsa':
if (size < 2048) {
if (!Number.isSafeInteger(size) || size < 2048) {
return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE'))
}
break

View File

@@ -15,11 +15,11 @@ const { PROTOCOL, PING_LENGTH } = require('./constants')
/**
* Ping a given peer and wait for its response, getting the operation latency.
* @param {Libp2p} node
* @param {PeerId} peer
* @param {PeerId|multiaddr} peer
* @returns {Promise<Number>}
*/
async function ping (node, peer) {
log('dialing %s to %s', PROTOCOL, peer.toB58String())
log('dialing %s to %s', PROTOCOL, peer.toB58String ? peer.toB58String() : peer)
const { stream } = await node.dialProtocol(peer, PROTOCOL)

View File

@@ -17,7 +17,7 @@ describe('ping', () => {
beforeEach(async () => {
nodes = await peerUtils.createPeer({
number: 2,
number: 3,
config: baseOptions
})
@@ -25,7 +25,14 @@ describe('ping', () => {
nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
})
it('ping once from peer0 to peer1', async () => {
it('ping once from peer0 to peer1 using a multiaddr', async () => {
const ma = `${nodes[2].multiaddrs[0]}/p2p/${nodes[2].peerId.toB58String()}`
const latency = await nodes[0].ping(ma)
expect(latency).to.be.a('Number')
})
it('ping once from peer0 to peer1 using a peerId', async () => {
const latency = await nodes[0].ping(nodes[1].peerId)
expect(latency).to.be.a('Number')

View File

@@ -149,6 +149,70 @@ describe('keychain', () => {
})
})
describe('ed25519 keys', () => {
const keyName = 'my custom key'
it('can be an ed25519 key', async () => {
const keyInfo = await ks.createKey(keyName, 'ed25519')
expect(keyInfo).to.exist()
expect(keyInfo).to.have.property('name', keyName)
expect(keyInfo).to.have.property('id')
})
it('does not overwrite existing key', async () => {
const err = await ks.createKey(keyName, 'ed25519').then(fail, err => err)
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
})
it('can export/import a key', async () => {
const keyName = 'a new key'
const password = 'my sneaky password'
const keyInfo = await ks.createKey(keyName, 'ed25519')
const exportedKey = await ks.exportKey(keyName, password)
// remove it so we can import it
await ks.removeKey(keyName)
const importedKey = await ks.importKey(keyName, exportedKey, password)
expect(importedKey.id).to.eql(keyInfo.id)
})
it('cannot create the "self" key', async () => {
const err = await ks.createKey('self', 'ed25519').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
})
describe('secp256k1 keys', () => {
const keyName = 'my secp256k1 key'
it('can be an secp256k1 key', async () => {
const keyInfo = await ks.createKey(keyName, 'secp256k1')
expect(keyInfo).to.exist()
expect(keyInfo).to.have.property('name', keyName)
expect(keyInfo).to.have.property('id')
})
it('does not overwrite existing key', async () => {
const err = await ks.createKey(keyName, 'secp256k1').then(fail, err => err)
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
})
it('can export/import a key', async () => {
const keyName = 'a new secp256k1 key'
const password = 'my sneaky password'
const keyInfo = await ks.createKey(keyName, 'secp256k1')
const exportedKey = await ks.exportKey(keyName, password)
// remove it so we can import it
await ks.removeKey(keyName)
const importedKey = await ks.importKey(keyName, exportedKey, password)
expect(importedKey.id).to.eql(keyInfo.id)
})
it('cannot create the "self" key', async () => {
const err = await ks.createKey('self', 'secp256k1').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
})
describe('query', () => {
it('finds all existing keys', async () => {
const keys = await ks.listKeys()