diff --git a/README.md b/README.md index 6cb2ba9..4fe2191 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # @libp2p/pubsub-peer-discovery [![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) -[![IRC](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p) [![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) [![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-pubsub-peer-discovery.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-pubsub-peer-discovery) -[![CI](https://img.shields.io/github/workflow/status/libp2p/js-libp2p-interfaces/test%20&%20maybe%20release/master?style=flat-square)](https://github.com/libp2p/js-libp2p-pubsub-peer-discovery/actions/workflows/js-test-and-release.yml) +[![CI](https://img.shields.io/github/workflow/status/libp2p/js-libp2p-pubsub-peer-discovery/test%20&%20maybe%20release/master?style=flat-square)](https://github.com/libp2p/js-libp2p-pubsub-peer-discovery/actions/workflows/js-test-and-release.yml) > A libp2p module that uses pubsub for mdns like peer discovery @@ -22,7 +21,7 @@ - [Default Topic](#default-topic) - [Contribute](#contribute) - [License](#license) -- [Contribution](#contribution) +- [Contribute](#contribute-1) ## Install @@ -55,25 +54,25 @@ If you are only interested in listening to the global pubsub topic the minimal c ```js import { createLibp2p } from 'libp2p' -import { Websockets } from '@libp2p/websockets' -import { Mplex } from '@libp2p/mplex' -import { Noise } from '@libp2p/noise' -import GossipSub from 'libp2p-gossipsub' -import { PubSubPeerDiscovery } from '@libp2p/pubsub-peer-discovery' +import { websockets } from '@libp2p/websockets' +import { mplex } from '@libp2p/mplex' +import { noise } from '@chainsafe/libp2p-noise' +import { gossipsub } from '@chainsafe/libp2p-gossipsub' +import { pubsubPeerDiscovery } from '@libp2p/pubsub-peer-discovery' const node = await createLibp2p({ transports: [ - new Websockets() + websockets() ], // Any libp2p transport(s) can be used streamMuxers: [ - new Mplex() + mplex() ], connectionEncryption: [ - new Noise() + noise() ], pubsub: new GossipSub(), // Can also be `libp2p-floodsub` if desired peerDiscovery: [ - new PubSubPeerDiscovery() + pubsubPeerDiscovery() ] }) ``` @@ -95,7 +94,7 @@ const topics = [ const node = await createLibp2p({ // ... peerDiscovery: [ - new PubSubPeerDiscovery({ + pubsubPeerDiscovery({ interval: 10000, topics: topics, // defaults to ['_peer-discovery._p2p._pubsub'] listenOnly: false @@ -131,6 +130,6 @@ Licensed under either of - Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) - MIT ([LICENSE-MIT](LICENSE-MIT) / ) -## Contribution +## Contribute Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/package.json b/package.json index 0745a28..59925ac 100644 --- a/package.json +++ b/package.json @@ -143,24 +143,24 @@ "release": "semantic-release" }, "dependencies": { - "@libp2p/components": "^2.0.4", "@libp2p/interface-peer-discovery": "^1.0.1", + "@libp2p/interface-peer-id": "^1.0.5", "@libp2p/interface-peer-info": "^1.0.2", - "@libp2p/interface-pubsub": "^2.0.1", + "@libp2p/interface-pubsub": "^3.0.0", "@libp2p/interfaces": "^3.0.3", "@libp2p/logger": "^2.0.1", "@libp2p/peer-id": "^1.1.15", - "@multiformats/multiaddr": "^10.4.0", - "protons-runtime": "^3.1.0" + "@multiformats/multiaddr": "^11.0.5", + "protons-runtime": "^4.0.1" }, "devDependencies": { - "@libp2p/interface-address-manager": "^1.0.2", - "@libp2p/interface-peer-discovery-compliance-tests": "^1.0.1", + "@libp2p/interface-address-manager": "^2.0.0", + "@libp2p/interface-peer-discovery-compliance-tests": "^2.0.0", "@libp2p/peer-id-factory": "^1.0.18", "aegir": "^37.2.0", "p-defer": "^4.0.0", "p-wait-for": "^5.0.0", - "protons": "^5.1.0", + "protons": "^6.0.0", "sinon": "^14.0.0", "ts-sinon": "^2.0.2" } diff --git a/src/index.ts b/src/index.ts index a6729e9..9e69098 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,15 @@ import { logger } from '@libp2p/logger' import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events' import type { Startable } from '@libp2p/interfaces/startable' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { Peer as PBPeer } from './peer.js' import { peerIdFromKeys } from '@libp2p/peer-id' import type { PeerDiscovery, PeerDiscoveryEvents } from '@libp2p/interface-peer-discovery' -import { Components, Initializable } from '@libp2p/components' -import type { Message } from '@libp2p/interface-pubsub' +import type { Message, PubSub } from '@libp2p/interface-pubsub' import type { PeerInfo } from '@libp2p/interface-peer-info' import { symbol } from '@libp2p/interface-peer-discovery' +import type { AddressManager } from '@libp2p/interface-address-manager' +import type { PeerId } from '@libp2p/interface-peer-id' const log = logger('libp2p:discovery:pubsub') export const TOPIC = '_peer-discovery._p2p._pubsub' @@ -30,17 +31,23 @@ export interface PubsubPeerDiscoveryInit { listenOnly?: boolean } +export interface PubSubPeerDiscoveryComponents { + peerId: PeerId + pubsub: PubSub + addressManager: AddressManager +} + /** * A Peer Discovery Service that leverages libp2p Pubsub to find peers. */ -export class PubSubPeerDiscovery extends EventEmitter implements PeerDiscovery, Startable, Initializable { +export class PubSubPeerDiscovery extends EventEmitter implements PeerDiscovery, Startable { private readonly interval: number private readonly listenOnly: boolean private readonly topics: string[] private intervalId?: ReturnType - private components: Components = new Components() + private readonly components: PubSubPeerDiscoveryComponents - constructor (init: PubsubPeerDiscoveryInit = {}) { + constructor (components: PubSubPeerDiscoveryComponents, init: PubsubPeerDiscoveryInit = {}) { super() const { @@ -49,6 +56,7 @@ export class PubSubPeerDiscovery extends EventEmitter imple listenOnly } = init + this.components = components this.interval = interval ?? 10000 this.listenOnly = listenOnly ?? false @@ -70,10 +78,6 @@ export class PubSubPeerDiscovery extends EventEmitter imple return '@libp2p/pubsub-peer-discovery' } - init (components: Components) { - this.components = components - } - isStarted () { return this.intervalId != null } @@ -91,7 +95,7 @@ export class PubSubPeerDiscovery extends EventEmitter imple return } - const pubsub = this.components.getPubSub() + const pubsub = this.components.pubsub if (pubsub == null) { throw new Error('PubSub not configured') @@ -118,7 +122,7 @@ export class PubSubPeerDiscovery extends EventEmitter imple } beforeStop () { - const pubsub = this.components.getPubSub() + const pubsub = this.components.pubsub if (pubsub == null) { throw new Error('PubSub not configured') @@ -144,7 +148,7 @@ export class PubSubPeerDiscovery extends EventEmitter imple * Performs a broadcast via Pubsub publish */ _broadcast () { - const peerId = this.components.getPeerId() + const peerId = this.components.peerId if (peerId.publicKey == null) { throw new Error('PeerId was missing public key') @@ -152,11 +156,11 @@ export class PubSubPeerDiscovery extends EventEmitter imple const peer = { publicKey: peerId.publicKey, - addrs: this.components.getAddressManager().getAddresses().map(ma => ma.bytes) + addrs: this.components.addressManager.getAddresses().map(ma => ma.bytes) } const encodedPeer = PBPeer.encode(peer) - const pubsub = this.components.getPubSub() + const pubsub = this.components.pubsub if (pubsub == null) { throw new Error('PubSub not configured') @@ -186,7 +190,7 @@ export class PubSubPeerDiscovery extends EventEmitter imple void peerIdFromKeys(peer.publicKey).then(peerId => { // Ignore if we received our own response - if (peerId.equals(this.components.getPeerId())) { + if (peerId.equals(this.components.peerId)) { return } @@ -195,7 +199,7 @@ export class PubSubPeerDiscovery extends EventEmitter imple this.dispatchEvent(new CustomEvent('peer', { detail: { id: peerId, - multiaddrs: peer.addrs.map(b => new Multiaddr(b)), + multiaddrs: peer.addrs.map(b => multiaddr(b)), protocols: [] } })) @@ -204,3 +208,7 @@ export class PubSubPeerDiscovery extends EventEmitter imple }) } } + +export function pubsubPeerDiscovery (init: PubsubPeerDiscoveryInit = {}): (components: PubSubPeerDiscoveryComponents) => PeerDiscovery { + return (components: PubSubPeerDiscoveryComponents) => new PubSubPeerDiscovery(components, init) +} diff --git a/src/peer.proto b/src/peer.proto index cb3ca22..cbe4fb3 100644 --- a/src/peer.proto +++ b/src/peer.proto @@ -1,6 +1,6 @@ syntax = "proto3"; message Peer { - required bytes publicKey = 0; - repeated bytes addrs = 1; -} \ No newline at end of file + bytes publicKey = 1; + repeated bytes addrs = 2; +} diff --git a/src/peer.ts b/src/peer.ts index eb039e7..2a9041f 100644 --- a/src/peer.ts +++ b/src/peer.ts @@ -1,5 +1,7 @@ /* eslint-disable import/export */ +/* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ import { encodeMessage, decodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -15,29 +17,25 @@ export namespace Peer { export const codec = (): Codec => { if (_codec == null) { - _codec = message((obj, writer, opts = {}) => { + _codec = message((obj, w, opts = {}) => { if (opts.lengthDelimited !== false) { - writer.fork() + w.fork() } - if (obj.publicKey != null) { - writer.uint32(2) - writer.bytes(obj.publicKey) - } else { - throw new Error('Protocol error: required field "publicKey" was not found in object') + if (opts.writeDefaults === true || (obj.publicKey != null && obj.publicKey.byteLength > 0)) { + w.uint32(10) + w.bytes(obj.publicKey) } if (obj.addrs != null) { for (const value of obj.addrs) { - writer.uint32(10) - writer.bytes(value) + w.uint32(18) + w.bytes(value) } - } else { - throw new Error('Protocol error: required field "addrs" was not found in object') } if (opts.lengthDelimited !== false) { - writer.ldelim() + w.ldelim() } }, (reader, length) => { const obj: any = { @@ -51,10 +49,10 @@ export namespace Peer { const tag = reader.uint32() switch (tag >>> 3) { - case 0: + case 1: obj.publicKey = reader.bytes() break - case 1: + case 2: obj.addrs.push(reader.bytes()) break default: @@ -63,10 +61,6 @@ export namespace Peer { } } - if (obj.publicKey == null) { - throw new Error('Protocol error: value for required field "publicKey" was not found in protobuf') - } - return obj }) } diff --git a/test/compliance.spec.ts b/test/compliance.spec.ts index e8995bb..066535b 100644 --- a/test/compliance.spec.ts +++ b/test/compliance.spec.ts @@ -1,14 +1,13 @@ /* eslint-env mocha */ import tests from '@libp2p/interface-peer-discovery-compliance-tests' -import { PubSubPeerDiscovery, TOPIC } from '../src/index.js' +import { pubsubPeerDiscovery, TOPIC } from '../src/index.js' import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import { Components } from '@libp2p/components' import { stubInterface } from 'ts-sinon' import type { PubSub } from '@libp2p/interface-pubsub' import { CustomEvent } from '@libp2p/interfaces/events' import type { AddressManager } from '@libp2p/interface-address-manager' -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { Peer as PBPeer } from '../src/peer.js' describe('compliance tests', () => { @@ -21,26 +20,24 @@ describe('compliance tests', () => { const addressManager = stubInterface() addressManager.getAddresses.returns([ - new Multiaddr(`/ip4/43.10.1.2/tcp/39832/p2p/${peerId.toString()}`) + multiaddr(`/ip4/43.10.1.2/tcp/39832/p2p/${peerId.toString()}`) ]) - const pubsubDiscovery = new PubSubPeerDiscovery() - - const components = new Components() - components.setPubSub(stubInterface()) - components.setPeerId(await createEd25519PeerId()) - components.setAddressManager(addressManager) - - pubsubDiscovery.init(components) + const pubsubDiscovery = pubsubPeerDiscovery()({ + pubsub: stubInterface(), + peerId: await createEd25519PeerId(), + addressManager + }) intervalId = setInterval(() => { const peer = PBPeer.encode({ publicKey: peerId.publicKey, addrs: [ - new Multiaddr('/ip4/166.10.1.2/tcp/80').bytes + multiaddr('/ip4/166.10.1.2/tcp/80').bytes ] }).subarray() + // @ts-expect-error private field pubsubDiscovery._onMessage(new CustomEvent('message', { detail: { type: 'unsigned', diff --git a/test/index.spec.ts b/test/index.spec.ts index d751671..2dadccc 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -4,25 +4,25 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import defer from 'p-defer' import pWaitFor from 'p-wait-for' -import { Multiaddr } from '@multiformats/multiaddr' -import { PubSubPeerDiscovery, TOPIC } from '../src/index.js' +import { multiaddr } from '@multiformats/multiaddr' +import { pubsubPeerDiscovery, PubSubPeerDiscoveryComponents, TOPIC } from '../src/index.js' import * as PB from '../src/peer.js' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { StubbedInstance, stubInterface } from 'ts-sinon' import type { PubSub } from '@libp2p/interface-pubsub' -import { Components } from '@libp2p/components' import { peerIdFromKeys } from '@libp2p/peer-id' import type { PeerInfo } from '@libp2p/interface-peer-info' import { CustomEvent } from '@libp2p/interfaces/events' import type { AddressManager } from '@libp2p/interface-address-manager' import { start, stop } from '@libp2p/interfaces/startable' +import type { PeerDiscovery } from '@libp2p/interface-peer-discovery' -const listeningMultiaddr = new Multiaddr('/ip4/127.0.0.1/tcp/9000/ws') +const listeningMultiaddr = multiaddr('/ip4/127.0.0.1/tcp/9000/ws') describe('PubSub Peer Discovery', () => { let mockPubsub: StubbedInstance - let discovery: PubSubPeerDiscovery - let components: Components + let discovery: PeerDiscovery + let components: PubSubPeerDiscoveryComponents beforeEach(async () => { const peerId = await createEd25519PeerId() @@ -34,10 +34,11 @@ describe('PubSub Peer Discovery', () => { listeningMultiaddr ]) - components = new Components() - components.setPeerId(peerId) - components.setPubSub(mockPubsub) - components.setAddressManager(addressManager) + components = { + peerId, + pubsub: mockPubsub, + addressManager + } }) afterEach(async () => { @@ -49,12 +50,12 @@ describe('PubSub Peer Discovery', () => { }) it('should not discover self', async () => { - discovery = new PubSubPeerDiscovery() - discovery.init(components) - await discovery.start() - await discovery.afterStart() + discovery = pubsubPeerDiscovery()(components) + await start(discovery) expect(mockPubsub.publish.callCount).to.equal(1) + + // @ts-expect-error private field discovery._broadcast() expect(mockPubsub.publish.callCount).to.equal(2) @@ -66,7 +67,7 @@ describe('PubSub Peer Discovery', () => { const peer = PB.Peer.decode(eventData) const peerId = await peerIdFromKeys(peer.publicKey) - expect(peerId.equals(components.getPeerId())).to.equal(true) + expect(peerId.equals(components.peerId)).to.equal(true) expect(peer.addrs).to.have.length(1) peer.addrs.forEach((addr) => { expect(addr).to.equalBytes(listeningMultiaddr.bytes) @@ -74,6 +75,8 @@ describe('PubSub Peer Discovery', () => { const spy = sinon.spy() discovery.addEventListener('peer', spy) + + // @ts-expect-error private field await discovery._onMessage(new CustomEvent('message', { detail: { type: 'unsigned', @@ -85,30 +88,30 @@ describe('PubSub Peer Discovery', () => { }) it('should be able to encode/decode a message', async () => { - discovery = new PubSubPeerDiscovery() - discovery.init(components) + discovery = pubsubPeerDiscovery()(components) await start(discovery) const peerId = await createEd25519PeerId() const expectedPeerData: PeerInfo = { id: peerId, multiaddrs: [ - new Multiaddr('/ip4/0.0.0.0/tcp/8080/ws'), - new Multiaddr('/ip4/0.0.0.0/tcp/8081/ws') + multiaddr('/ip4/0.0.0.0/tcp/8080/ws'), + multiaddr('/ip4/0.0.0.0/tcp/8081/ws') ], protocols: [] } const peer = { publicKey: peerId.publicKey, - addrs: expectedPeerData.multiaddrs.map(ma => new Multiaddr(ma).bytes) + addrs: expectedPeerData.multiaddrs.map(ma => multiaddr(ma).bytes) } const deferred = defer() const encodedPeer = PB.Peer.encode(peer).subarray() - discovery.addEventListener('peer', (evt) => { + discovery.addEventListener('peer', (evt: CustomEvent) => { deferred.resolve(evt.detail) }) + // @ts-expect-error private field await discovery._onMessage(new CustomEvent('message', { detail: { type: 'unsigned', @@ -126,24 +129,21 @@ describe('PubSub Peer Discovery', () => { }) it('should not broadcast if only listening', async () => { - discovery = new PubSubPeerDiscovery({ listenOnly: true }) - discovery.init(components) + discovery = pubsubPeerDiscovery({ listenOnly: true })(components) await start(discovery) expect(mockPubsub.dispatchEvent.callCount).to.equal(0) }) it('should broadcast after start and on interval', async () => { - discovery = new PubSubPeerDiscovery({ interval: 100 }) - discovery.init(components) + discovery = pubsubPeerDiscovery({ interval: 100 })(components) await start(discovery) await pWaitFor(() => mockPubsub.publish.callCount >= 2) }) it('should be able to add and remove peer listeners', async () => { - discovery = new PubSubPeerDiscovery() - discovery.init(components) + discovery = pubsubPeerDiscovery()(components) await start(discovery) const handler = () => {} @@ -163,10 +163,7 @@ describe('PubSub Peer Discovery', () => { // Listen to the global topic and the namespace of `myApp` const topics = [`myApp.${TOPIC}`, TOPIC] - discovery = new PubSubPeerDiscovery({ - topics - }) - discovery.init(components) + discovery = pubsubPeerDiscovery({ topics })(components) await start(discovery) expect(mockPubsub.addEventListener.callCount).to.equal(2)