mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-07-24 13:01:58 +00:00
Compare commits
7 Commits
feat/more-
...
fix/subseq
Author | SHA1 | Date | |
---|---|---|---|
|
71f9af6f87 | ||
|
0e18735b8c | ||
|
f68ff35625 | ||
|
8c56ec0d23 | ||
|
fa5ee873e3 | ||
|
51d7ca44c1 | ||
|
726a746479 |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,3 +1,19 @@
|
||||
<a name="0.28.10"></a>
|
||||
## [0.28.10](https://github.com/libp2p/js-libp2p/compare/v0.28.9...v0.28.10) (2020-08-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow certain keychain operations without a password ([#726](https://github.com/libp2p/js-libp2p/issues/726)) ([8c56ec0](https://github.com/libp2p/js-libp2p/commit/8c56ec0))
|
||||
* **identify:** make agentversion dynamic and add it to the peerstore ([#724](https://github.com/libp2p/js-libp2p/issues/724)) ([726a746](https://github.com/libp2p/js-libp2p/commit/726a746))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **keychain:** add support for ed25519 and secp keys ([#725](https://github.com/libp2p/js-libp2p/issues/725)) ([51d7ca4](https://github.com/libp2p/js-libp2p/commit/51d7ca4))
|
||||
|
||||
|
||||
|
||||
<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)
|
||||
|
||||
|
@@ -23,8 +23,8 @@
|
||||
<a href="https://david-dm.org/libp2p/js-libp2p"><img src="https://david-dm.org/libp2p/js-libp2p.svg?style=flat-square" /></a>
|
||||
<a href="https://github.com/feross/standard"><img src="https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square"></a>
|
||||
<a href="https://github.com/RichardLitt/standard-readme"><img src="https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square" /></a>
|
||||
<a href=""><img src="https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square" /></a>
|
||||
<a href=""><img src="https://img.shields.io/badge/Node.js-%3E%3D6.0.0-orange.svg?style=flat-square" /></a>
|
||||
<a href=""><img src="https://img.shields.io/badge/npm-%3E%3D6.0.0-orange.svg?style=flat-square" /></a>
|
||||
<a href=""><img src="https://img.shields.io/badge/Node.js-%3E%3D10.0.0-orange.svg?style=flat-square" /></a>
|
||||
<br>
|
||||
</p>
|
||||
|
||||
|
51
package.json
51
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "libp2p",
|
||||
"version": "0.28.9",
|
||||
"version": "0.28.10",
|
||||
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
|
||||
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
|
||||
"main": "src/index.js",
|
||||
@@ -126,43 +126,44 @@
|
||||
"Vasco Santos <vasco.santos@moxy.studio>",
|
||||
"Alan Shaw <alan@tableflip.io>",
|
||||
"Cayman <caymannava@gmail.com>",
|
||||
"Alex Potsides <alex@achingbrain.net>",
|
||||
"Pedro Teixeira <i@pgte.me>",
|
||||
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
||||
"Alex Potsides <alex@achingbrain.net>",
|
||||
"Maciej Krüger <mkg20001@gmail.com>",
|
||||
"Hugo Dias <mail@hugodias.me>",
|
||||
"Volker Mische <volker.mische@gmail.com>",
|
||||
"dirkmc <dirkmdev@gmail.com>",
|
||||
"Volker Mische <volker.mische@gmail.com>",
|
||||
"Richard Littauer <richard.littauer@gmail.com>",
|
||||
"Thomas Eizinger <thomas@eizinger.io>",
|
||||
"Ryan Bell <ryan@piing.net>",
|
||||
"Giovanni T. Parra <fiatjaf@gmail.com>",
|
||||
"Andrew Nesbitt <andrewnez@gmail.com>",
|
||||
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
|
||||
"Andrew Nesbitt <andrewnez@gmail.com>",
|
||||
"Elven <mon.samuel@qq.com>",
|
||||
"Didrik Nordström <didrik.nordstrom@gmail.com>",
|
||||
"Tiago Alves <alvesjtiago@gmail.com>",
|
||||
"Yusef Napora <yusef@napora.org>",
|
||||
"Zane Starr <zcstarr@gmail.com>",
|
||||
"ebinks <elizabethjbinks@gmail.com>",
|
||||
"isan_rivkin <isanrivkin@gmail.com>",
|
||||
"robertkiel <robert.kiel@validitylabs.org>",
|
||||
"RasmusErik Voel Jensen <github@solsort.com>",
|
||||
"Bernd Strehl <bernd.strehl@gmail.com>",
|
||||
"Chris Bratlien <chrisbratlien@gmail.com>",
|
||||
"Daijiro Wachi <daijiro.wachi@gmail.com>",
|
||||
"Diogo Silva <fsdiogo@gmail.com>",
|
||||
"Dmitriy Ryajov <dryajov@gmail.com>",
|
||||
"Fei Liu <liu.feiwood@gmail.com>",
|
||||
"Florian-Merle <florian.david.merle@gmail.com>",
|
||||
"Giovanni T. Parra <fiatjaf@gmail.com>",
|
||||
"Ryan Bell <ryan@piing.net>",
|
||||
"Thomas Eizinger <thomas@eizinger.io>",
|
||||
"Didrik Nordström <didrik@betamos.se>",
|
||||
"Francis Gulotta <wizard@roborooter.com>",
|
||||
"Henrique Dias <hacdias@gmail.com>",
|
||||
"Irakli Gozalishvili <rfobic@gmail.com>",
|
||||
"Florian-Merle <florian.david.merle@gmail.com>",
|
||||
"Joel Gustafson <joelg@mit.edu>",
|
||||
"Julien Bouquillon <contact@revolunet.com>",
|
||||
"Kevin Kwok <antimatter15@gmail.com>",
|
||||
"Felipe Martins <felipebrasil93@gmail.com>",
|
||||
"Nuno Nogueira <nunofmn@gmail.com>",
|
||||
"Fei Liu <liu.feiwood@gmail.com>",
|
||||
"RasmusErik Voel Jensen <github@solsort.com>",
|
||||
"Dmitriy Ryajov <dryajov@gmail.com>",
|
||||
"Soeren <nikorpoulsen@gmail.com>",
|
||||
"Sönke Hahn <soenkehahn@gmail.com>"
|
||||
"Sönke Hahn <soenkehahn@gmail.com>",
|
||||
"Tiago Alves <alvesjtiago@gmail.com>",
|
||||
"Diogo Silva <fsdiogo@gmail.com>",
|
||||
"Yusef Napora <yusef@napora.org>",
|
||||
"Zane Starr <zcstarr@gmail.com>",
|
||||
"Daijiro Wachi <daijiro.wachi@gmail.com>",
|
||||
"Chris Bratlien <chrisbratlien@gmail.com>",
|
||||
"ebinks <elizabethjbinks@gmail.com>",
|
||||
"Bernd Strehl <bernd.strehl@gmail.com>",
|
||||
"isan_rivkin <isanrivkin@gmail.com>",
|
||||
"Henrique Dias <hacdias@gmail.com>",
|
||||
"robertkiel <robert.kiel@validitylabs.org>",
|
||||
"Irakli Gozalishvili <rfobic@gmail.com>"
|
||||
]
|
||||
}
|
||||
|
@@ -73,7 +73,21 @@ class Dialer {
|
||||
if (!dialTarget.addrs.length) {
|
||||
throw errCode(new Error('The dial request has no addresses'), codes.ERR_NO_VALID_ADDRESSES)
|
||||
}
|
||||
const pendingDial = this._pendingDials.get(dialTarget.id) || this._createPendingDial(dialTarget, options)
|
||||
|
||||
// Used for subsequent dials pending
|
||||
let subsequentDialAborted = false
|
||||
const onAbort = () => {
|
||||
subsequentDialAborted = true
|
||||
pendingDial.controller.abort()
|
||||
}
|
||||
|
||||
let pendingDial = this._pendingDials.get(dialTarget.id)
|
||||
if (!pendingDial) {
|
||||
pendingDial = this._createPendingDial(dialTarget, options)
|
||||
} else {
|
||||
// track subsequent dial abort
|
||||
options.signal && options.signal.addEventListener('abort', onAbort)
|
||||
}
|
||||
|
||||
try {
|
||||
const connection = await pendingDial.promise
|
||||
@@ -81,12 +95,16 @@ class Dialer {
|
||||
return connection
|
||||
} catch (err) {
|
||||
// Error is a timeout
|
||||
if (pendingDial.controller.signal.aborted) {
|
||||
if (pendingDial.controller.signal.aborted && !subsequentDialAborted) {
|
||||
err.code = codes.ERR_TIMEOUT
|
||||
// Error is a subsequent dial abort
|
||||
} else if (subsequentDialAborted) {
|
||||
err.code = codes.ERR_SUBSEQUENT_DIAL_ABORT
|
||||
}
|
||||
log.error(err)
|
||||
throw err
|
||||
} finally {
|
||||
options.signal && options.signal.removeEventListener('abort', onAbort)
|
||||
pendingDial.destroy()
|
||||
}
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ exports.codes = {
|
||||
ERR_INVALID_PEER: 'ERR_INVALID_PEER',
|
||||
ERR_MUXER_UNAVAILABLE: 'ERR_MUXER_UNAVAILABLE',
|
||||
ERR_TIMEOUT: 'ERR_TIMEOUT',
|
||||
ERR_SUBSEQUENT_DIAL_ABORT: 'ERR_SUBSEQUENT_DIAL_ABORT',
|
||||
ERR_TRANSPORT_UNAVAILABLE: 'ERR_TRANSPORT_UNAVAILABLE',
|
||||
ERR_TRANSPORT_DIAL_FAILED: 'ERR_TRANSPORT_DIAL_FAILED',
|
||||
ERR_UNSUPPORTED_PROTOCOL: 'ERR_UNSUPPORTED_PROTOCOL',
|
||||
|
@@ -1,6 +1,8 @@
|
||||
'use strict'
|
||||
|
||||
const libp2pVersion = require('../../package.json').version
|
||||
|
||||
module.exports.PROTOCOL_VERSION = 'ipfs/0.1.0'
|
||||
module.exports.AGENT_VERSION = 'js-libp2p/0.1.0'
|
||||
module.exports.AGENT_VERSION = `js-libp2p/${libp2pVersion}`
|
||||
module.exports.MULTICODEC_IDENTIFY = '/ipfs/id/1.0.0'
|
||||
module.exports.MULTICODEC_IDENTIFY_PUSH = '/ipfs/id/push/1.0.0'
|
||||
|
@@ -175,6 +175,7 @@ class IdentifyService {
|
||||
// Update peers data in PeerStore
|
||||
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
|
||||
this.peerStore.protoBook.set(id, protocols)
|
||||
this.peerStore.metadataBook.set(id, 'AgentVersion', Buffer.from(message.agentVersion))
|
||||
|
||||
// TODO: Track our observed address so that we can score it
|
||||
log('received observed address of %s', observedAddr)
|
||||
|
@@ -82,7 +82,7 @@ class Libp2p extends EventEmitter {
|
||||
}
|
||||
|
||||
// Create keychain
|
||||
if (this._options.keychain && this._options.keychain.pass && this._options.keychain.datastore) {
|
||||
if (this._options.keychain && this._options.keychain.datastore) {
|
||||
log('creating keychain')
|
||||
|
||||
const keychainOpts = Keychain.generateOptions()
|
||||
|
@@ -110,7 +110,7 @@ class Keychain {
|
||||
this.opts = mergeOptions(defaultOptions, options)
|
||||
|
||||
// Enforce NIST SP 800-132
|
||||
if (!this.opts.passPhrase || this.opts.passPhrase.length < 20) {
|
||||
if (this.opts.passPhrase && this.opts.passPhrase.length < 20) {
|
||||
throw new Error('passPhrase must be least 20 characters')
|
||||
}
|
||||
if (this.opts.dek.keyLength < NIST.minKeyLength) {
|
||||
@@ -123,13 +123,13 @@ class Keychain {
|
||||
throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
|
||||
}
|
||||
|
||||
// Create the derived encrypting key
|
||||
const dek = crypto.pbkdf2(
|
||||
const dek = this.opts.passPhrase ? crypto.pbkdf2(
|
||||
this.opts.passPhrase,
|
||||
this.opts.dek.salt,
|
||||
this.opts.dek.iterationCount,
|
||||
this.opts.dek.keyLength,
|
||||
this.opts.dek.hash)
|
||||
this.opts.dek.hash) : ''
|
||||
|
||||
Object.defineProperty(this, '_', { value: () => dek })
|
||||
}
|
||||
|
||||
|
@@ -15,6 +15,7 @@ const { NOISE: Crypto } = require('libp2p-noise')
|
||||
const multiaddr = require('multiaddr')
|
||||
const PeerId = require('peer-id')
|
||||
const AggregateError = require('aggregate-error')
|
||||
const AbortController = require('abort-controller')
|
||||
const { AbortError } = require('libp2p-interfaces/src/transport/errors')
|
||||
|
||||
const { codes: ErrorCodes } = require('../../src/errors')
|
||||
@@ -35,8 +36,10 @@ const remoteAddr = MULTIADDRS_WEBSOCKETS[0]
|
||||
describe('Dialing (direct, WebSockets)', () => {
|
||||
let localTM
|
||||
let peerStore
|
||||
let peerId
|
||||
|
||||
before(() => {
|
||||
before(async () => {
|
||||
[peerId] = await createPeerId()
|
||||
peerStore = new PeerStore()
|
||||
localTM = new TransportManager({
|
||||
libp2p: {},
|
||||
@@ -181,6 +184,47 @@ describe('Dialing (direct, WebSockets)', () => {
|
||||
.and.to.have.property('code', ErrorCodes.ERR_TIMEOUT)
|
||||
})
|
||||
|
||||
it('should abort subsequent dials', async () => {
|
||||
const dialer = new Dialer({
|
||||
transportManager: localTM,
|
||||
timeout: 1000,
|
||||
peerStore: {
|
||||
addressBook: {
|
||||
add: () => { },
|
||||
getMultiaddrsForPeer: () => [remoteAddr]
|
||||
}
|
||||
}
|
||||
})
|
||||
const controller = new AbortController()
|
||||
|
||||
const deferredDial = pDefer()
|
||||
const deferredAbort = pDefer()
|
||||
|
||||
sinon.stub(localTM, 'dial').callsFake(async (_, options) => {
|
||||
deferredDial.resolve()
|
||||
expect(options.signal).to.exist()
|
||||
expect(options.signal.aborted).to.equal(false)
|
||||
await deferredAbort.promise
|
||||
expect(options.signal.aborted).to.equal(true)
|
||||
throw new AbortError()
|
||||
})
|
||||
|
||||
const dialPromise1 = dialer.connectToPeer(peerId)
|
||||
const dialPromise2 = dialer.connectToPeer(peerId, { signal: controller.signal })
|
||||
|
||||
await deferredDial.promise
|
||||
controller.abort()
|
||||
|
||||
deferredAbort.resolve()
|
||||
|
||||
await expect(dialPromise1)
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', ErrorCodes.ERR_SUBSEQUENT_DIAL_ABORT)
|
||||
await expect(dialPromise2)
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', ErrorCodes.ERR_SUBSEQUENT_DIAL_ABORT)
|
||||
})
|
||||
|
||||
it('should dial to the max concurrency', async () => {
|
||||
const dialer = new Dialer({
|
||||
transportManager: localTM,
|
||||
|
@@ -19,6 +19,7 @@ const { IdentifyService, multicodecs } = require('../../src/identify')
|
||||
const Peers = require('../fixtures/peers')
|
||||
const Libp2p = require('../../src')
|
||||
const baseOptions = require('../utils/base-options.browser')
|
||||
const pkg = require('../../package.json')
|
||||
|
||||
const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser')
|
||||
const remoteAddr = MULTIADDRS_WEBSOCKETS[0]
|
||||
@@ -53,6 +54,9 @@ describe('Identify', () => {
|
||||
},
|
||||
protoBook: {
|
||||
set: () => { }
|
||||
},
|
||||
metadataBook: {
|
||||
set: () => { }
|
||||
}
|
||||
},
|
||||
multiaddrs: []
|
||||
@@ -77,6 +81,7 @@ describe('Identify', () => {
|
||||
|
||||
sinon.spy(localIdentify.peerStore.addressBook, 'set')
|
||||
sinon.spy(localIdentify.peerStore.protoBook, 'set')
|
||||
sinon.spy(localIdentify.peerStore.metadataBook, 'set')
|
||||
|
||||
// Run identify
|
||||
await Promise.all([
|
||||
@@ -90,6 +95,12 @@ describe('Identify', () => {
|
||||
|
||||
expect(localIdentify.peerStore.addressBook.set.callCount).to.equal(1)
|
||||
expect(localIdentify.peerStore.protoBook.set.callCount).to.equal(1)
|
||||
|
||||
const metadataArgs = localIdentify.peerStore.metadataBook.set.firstCall.args
|
||||
expect(metadataArgs[0].id.bytes).to.equal(remotePeer.bytes)
|
||||
expect(metadataArgs[1]).to.equal('AgentVersion')
|
||||
expect(metadataArgs[2].toString()).to.equal(`js-libp2p/${pkg.version}`)
|
||||
|
||||
// Validate the remote peer gets updated in the peer store
|
||||
const call = localIdentify.peerStore.addressBook.set.firstCall
|
||||
expect(call.args[0].id.bytes).to.equal(remotePeer.bytes)
|
||||
|
@@ -2,10 +2,8 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const { expect } = chai
|
||||
const { chai, expect } = require('aegir/utils/chai')
|
||||
const fail = expect.fail
|
||||
chai.use(require('dirty-chai'))
|
||||
chai.use(require('chai-string'))
|
||||
|
||||
const peerUtils = require('../utils/creators/peer')
|
||||
@@ -40,8 +38,8 @@ describe('keychain', () => {
|
||||
emptyKeystore = new Keychain(datastore1, { passPhrase: passPhrase })
|
||||
})
|
||||
|
||||
it('needs a pass phrase to encrypt a key', () => {
|
||||
expect(() => new Keychain(datastore2)).to.throw()
|
||||
it('can start without a password', () => {
|
||||
expect(() => new Keychain(datastore2)).to.not.throw()
|
||||
})
|
||||
|
||||
it('needs a NIST SP 800-132 non-weak pass phrase', () => {
|
||||
@@ -56,12 +54,48 @@ describe('keychain', () => {
|
||||
expect(Keychain.options).to.exist()
|
||||
})
|
||||
|
||||
it('needs a supported hashing alorithm', () => {
|
||||
it('supports supported hashing alorithms', () => {
|
||||
const ok = new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'sha2-256' } })
|
||||
expect(ok).to.exist()
|
||||
})
|
||||
|
||||
it('does not support unsupported hashing alorithms', () => {
|
||||
expect(() => new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'my-hash' } })).to.throw()
|
||||
})
|
||||
|
||||
it('can list keys without a password', async () => {
|
||||
const keychain = new Keychain(datastore2)
|
||||
|
||||
expect(await keychain.listKeys()).to.have.lengthOf(0)
|
||||
})
|
||||
|
||||
it('can find a key without a password', async () => {
|
||||
const keychain = new Keychain(datastore2)
|
||||
const keychainWithPassword = new Keychain(datastore2, { passPhrase: `hello-${Date.now()}-${Date.now()}` })
|
||||
const id = `key-${Math.random()}`
|
||||
|
||||
await keychainWithPassword.createKey(id, 'rsa', 2048)
|
||||
|
||||
await expect(keychain.findKeyById(id)).to.eventually.be.ok()
|
||||
})
|
||||
|
||||
it('can remove a key without a password', async () => {
|
||||
const keychainWithoutPassword = new Keychain(datastore2)
|
||||
const keychainWithPassword = new Keychain(datastore2, { passPhrase: `hello-${Date.now()}-${Date.now()}` })
|
||||
const name = `key-${Math.random()}`
|
||||
|
||||
expect(await keychainWithPassword.createKey(name, 'rsa', 2048)).to.have.property('name', name)
|
||||
expect(await keychainWithoutPassword.findKeyByName(name)).to.have.property('name', name)
|
||||
await keychainWithoutPassword.removeKey(name)
|
||||
await expect(keychainWithoutPassword.findKeyByName(name)).to.be.rejectedWith(/does not exist/)
|
||||
})
|
||||
|
||||
it('requires a key to create a password', async () => {
|
||||
const keychain = new Keychain(datastore2)
|
||||
|
||||
await expect(keychain.createKey('derp')).to.be.rejected()
|
||||
})
|
||||
|
||||
it('can generate options', () => {
|
||||
const options = Keychain.generateOptions()
|
||||
options.passPhrase = passPhrase
|
||||
@@ -475,7 +509,7 @@ describe('libp2p.keychain', () => {
|
||||
throw new Error('should throw an error using the keychain if no passphrase provided')
|
||||
})
|
||||
|
||||
it('can be used if a passphrase is provided', async () => {
|
||||
it('can be used when a passphrase is provided', async () => {
|
||||
const [libp2p] = await peerUtils.createPeer({
|
||||
started: false,
|
||||
config: {
|
||||
@@ -492,6 +526,22 @@ describe('libp2p.keychain', () => {
|
||||
expect(kInfo).to.exist()
|
||||
})
|
||||
|
||||
it('does not require a keychain passphrase', async () => {
|
||||
const [libp2p] = await peerUtils.createPeer({
|
||||
started: false,
|
||||
config: {
|
||||
keychain: {
|
||||
datastore: new MemoryDatastore()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await libp2p.loadKeychain()
|
||||
|
||||
const kInfo = await libp2p.keychain.createKey('keyName', 'ed25519')
|
||||
expect(kInfo).to.exist()
|
||||
})
|
||||
|
||||
it('can reload keys', async () => {
|
||||
const datastore = new MemoryDatastore()
|
||||
const [libp2p] = await peerUtils.createPeer({
|
||||
|
Reference in New Issue
Block a user