mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-07-08 13:21:34 +00:00
Compare commits
13 Commits
fix/return
...
feat/more-
Author | SHA1 | Date | |
---|---|---|---|
5e5cf2d31a | |||
a3ea2f4550 | |||
a71fe96aaf | |||
a331b84f13 | |||
78d152dd68 | |||
7e14aa19b5 | |||
2440c872df | |||
6c7e5e5eef | |||
388df6b6e6 | |||
7dbfe6ab1a | |||
cea59a1fe4 | |||
7da9ad44ab | |||
3896941128 |
20
CHANGELOG.md
20
CHANGELOG.md
@ -1,3 +1,23 @@
|
||||
<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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* create dial target for peer with no known addrs ([#715](https://github.com/libp2p/js-libp2p/issues/715)) ([7da9ad4](https://github.com/libp2p/js-libp2p/commit/7da9ad4))
|
||||
|
||||
|
||||
|
||||
<a name="0.28.7"></a>
|
||||
## [0.28.7](https://github.com/libp2p/js-libp2p/compare/v0.28.6...v0.28.7) (2020-07-14)
|
||||
|
||||
|
10
doc/API.md
10
doc/API.md
@ -418,7 +418,7 @@ const latency = await libp2p.ping(otherPeerId)
|
||||
|
||||
## multiaddrs
|
||||
|
||||
Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs
|
||||
Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs
|
||||
of the peer by joining the multiaddrs that libp2p transports are listening on with the announce multiaddrs
|
||||
provided in the libp2p config. Configured no announce multiaddrs will be filtered out of the advertised addresses.
|
||||
|
||||
@ -760,7 +760,7 @@ Get the known [`Addresses`][address] of a provided peer.
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `Array<Address>` | Array of peer's [`Addresses`][address] containing the multiaddr and its metadata |
|
||||
| `Array<Address>|undefined` | Array of peer's [`Addresses`][address] containing the multiaddr and its metadata if available, otherwise undefined |
|
||||
|
||||
#### Example
|
||||
|
||||
@ -797,7 +797,7 @@ Get the known `Multiaddr` of a provided peer. All returned multiaddrs will inclu
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `Array<Multiaddr>` | Array of peer's multiaddr |
|
||||
| `Array<Multiaddr>|undefined` | Array of peer's multiaddr if available, otherwise undefined |
|
||||
|
||||
#### Example
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1801,7 +1801,7 @@ console.log(peerStats.toJSON())
|
||||
|
||||
## Events
|
||||
|
||||
Once you have a libp2p instance, you can listen to several events it emits, so that you can be notified of relevant network events.
|
||||
Once you have a libp2p instance, you can listen to several events it emits, so that you can be notified of relevant network events.
|
||||
|
||||
### libp2p
|
||||
|
||||
|
@ -304,7 +304,7 @@ const node = await Libp2p.create({
|
||||
},
|
||||
config: {
|
||||
peerDiscovery: {
|
||||
webRTCStar: {
|
||||
[WebRTCStar.tag]: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
82
examples/pubsub/message-filtering/1.js
Normal file
82
examples/pubsub/message-filtering/1.js
Normal 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)
|
||||
})()
|
108
examples/pubsub/message-filtering/README.md
Normal file
108
examples/pubsub/message-filtering/README.md
Normal 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
|
||||
```
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "libp2p",
|
||||
"version": "0.28.7",
|
||||
"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.6",
|
||||
"libp2p-crypto": "^0.17.9",
|
||||
"libp2p-interfaces": "^0.3.1",
|
||||
"libp2p-utils": "^0.1.2",
|
||||
"mafmt": "^7.0.0",
|
||||
|
@ -112,7 +112,7 @@ class Dialer {
|
||||
this.peerStore.addressBook.add(id, multiaddrs)
|
||||
}
|
||||
|
||||
let addrs = this.peerStore.addressBook.getMultiaddrsForPeer(id)
|
||||
let addrs = this.peerStore.addressBook.getMultiaddrsForPeer(id) || []
|
||||
|
||||
// If received a multiaddr to dial, it should be the first to use
|
||||
// But, if we know other multiaddrs for the peer, we should try them too.
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,8 +174,8 @@ 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.
|
||||
* @returns {KeyInfo}
|
||||
* @param {int} [size] - The key size in bits. Used for rsa keys only.
|
||||
* @returns {KeyInfo}
|
||||
*/
|
||||
async createKey (name, type, size) {
|
||||
const self = this
|
||||
@ -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
|
||||
|
@ -168,8 +168,9 @@ class AddressBook extends Book {
|
||||
/**
|
||||
* Get the known multiaddrs for a given peer. All returned multiaddrs
|
||||
* will include the encapsulated `PeerId` of the peer.
|
||||
* Returns `undefined` if there are no known multiaddrs for the given peer.
|
||||
* @param {PeerId} peerId
|
||||
* @returns {Array<Multiaddr>}
|
||||
* @returns {Array<Multiaddr>|undefined}
|
||||
*/
|
||||
getMultiaddrsForPeer (peerId) {
|
||||
if (!PeerId.isPeerId(peerId)) {
|
||||
@ -179,7 +180,7 @@ class AddressBook extends Book {
|
||||
const record = this.data.get(peerId.toB58String())
|
||||
|
||||
if (!record) {
|
||||
return []
|
||||
return undefined
|
||||
}
|
||||
|
||||
return record.map((address) => {
|
||||
|
@ -77,8 +77,9 @@ class Book {
|
||||
|
||||
/**
|
||||
* Get the known data of a provided peer.
|
||||
* Returns `undefined` if there is no available data for the given peer.
|
||||
* @param {PeerId} peerId
|
||||
* @returns {Array<Data>}
|
||||
* @returns {Array<Data>|undefined}
|
||||
*/
|
||||
get (peerId) {
|
||||
if (!PeerId.isPeerId(peerId)) {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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')
|
||||
|
@ -96,6 +96,15 @@ describe('Dialing (direct, TCP)', () => {
|
||||
.and.to.have.nested.property('._errors[0].code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE)
|
||||
})
|
||||
|
||||
it('should fail to connect if peer has no known addresses', async () => {
|
||||
const dialer = new Dialer({ transportManager: localTM, peerStore })
|
||||
const peerId = await PeerId.createFromJSON(Peers[1])
|
||||
|
||||
await expect(dialer.connectToPeer(peerId))
|
||||
.to.eventually.be.rejectedWith(Error)
|
||||
.and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES)
|
||||
})
|
||||
|
||||
it('should be able to connect to a given peer id', async () => {
|
||||
const peerStore = new PeerStore()
|
||||
const dialer = new Dialer({
|
||||
|
@ -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()
|
||||
|
@ -323,10 +323,10 @@ describe('addressBook', () => {
|
||||
throw new Error('invalid peerId should throw error')
|
||||
})
|
||||
|
||||
it('returns empty array if no multiaddrs are known for the provided peer', () => {
|
||||
it('returns undefined if no multiaddrs are known for the provided peer', () => {
|
||||
const addresses = ab.getMultiaddrsForPeer(peerId)
|
||||
|
||||
expect(addresses).to.be.empty()
|
||||
expect(addresses).to.not.exist()
|
||||
})
|
||||
|
||||
it('returns the multiaddrs stored', () => {
|
||||
|
Reference in New Issue
Block a user