diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f7ea4b3f79..e14c0cb1d2 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,13 +1,13 @@ { - "packages/utils": "0.0.13", - "packages/proto": "0.0.5", - "packages/interfaces": "0.0.20", - "packages/message-hash": "0.1.9", - "packages/enr": "0.0.19", - "packages/peer-exchange": "0.0.18", - "packages/core": "0.0.25", - "packages/dns-discovery": "0.0.19", - "packages/message-encryption": "0.0.23", - "packages/relay": "0.0.8", - "packages/sdk": "0.0.21" + "packages/utils": "0.0.14", + "packages/proto": "0.0.6", + "packages/interfaces": "0.0.21", + "packages/message-hash": "0.1.10", + "packages/enr": "0.0.20", + "packages/peer-exchange": "0.0.19", + "packages/core": "0.0.26", + "packages/dns-discovery": "0.0.20", + "packages/message-encryption": "0.0.24", + "packages/relay": "0.0.9", + "packages/sdk": "0.0.22" } diff --git a/package-lock.json b/package-lock.json index 5c8e357541..b653967d08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10424,9 +10424,9 @@ } }, "node_modules/fast-check": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.14.0.tgz", - "integrity": "sha512-9Z0zqASzDNjXBox/ileV/fd+4P+V/f3o4shM6QawvcdLFh8yjPG4h5BrHUZ8yzY6amKGDTAmRMyb/JZqe+dCgw==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.15.0.tgz", + "integrity": "sha512-iBz6c+EXL6+nI931x/sbZs1JYTZtLG6Cko0ouS8LRTikhDR7+wZk4TYzdRavlnByBs2G6+nuuJ7NYL9QplNt8Q==", "funding": [ { "type": "individual", @@ -26602,6 +26602,7 @@ "chai-as-promised": "^7.1.1", "debug": "^4.3.4", "dockerode": "^3.3.5", + "fast-check": "^3.15.0", "p-retry": "^6.1.0", "p-timeout": "^6.1.0", "portfinder": "^1.0.32", @@ -30072,6 +30073,7 @@ "datastore-core": "^9.2.6", "debug": "^4.3.4", "dockerode": "^3.3.5", + "fast-check": "^3.15.0", "interface-datastore": "^8.2.5", "libp2p": "^0.46.14", "mocha": "^10.2.0", @@ -33454,9 +33456,9 @@ "version": "0.1.8" }, "fast-check": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.14.0.tgz", - "integrity": "sha512-9Z0zqASzDNjXBox/ileV/fd+4P+V/f3o4shM6QawvcdLFh8yjPG4h5BrHUZ8yzY6amKGDTAmRMyb/JZqe+dCgw==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.15.0.tgz", + "integrity": "sha512-iBz6c+EXL6+nI931x/sbZs1JYTZtLG6Cko0ouS8LRTikhDR7+wZk4TYzdRavlnByBs2G6+nuuJ7NYL9QplNt8Q==", "requires": { "pure-rand": "^6.0.0" } diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 0b2c396fc6..150a00de52 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The file is maintained by [Release Please](https://github.com/googleapis/release-please) based on [Conventional Commits](https://www.conventionalcommits.org) specification, and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.0.26](https://github.com/waku-org/js-waku/compare/core-v0.0.25...core-v0.0.26) (2024-01-10) + + +### ⚠ BREAKING CHANGES + +* add support for sharded pubsub topics & remove support for named pubsub topics ([#1697](https://github.com/waku-org/js-waku/issues/1697)) +* change all instances of `PubSubTopic` to `PubsubTopic` ([#1703](https://github.com/waku-org/js-waku/issues/1703)) + +### Features + +* Add support for autosharded pubsub topics ([2bc3735](https://github.com/waku-org/js-waku/commit/2bc3735e4dcf85f06b3dee542024d7f20a40fac2)) +* Add support for sharded pubsub topics & remove support for named pubsub topics ([#1697](https://github.com/waku-org/js-waku/issues/1697)) ([4cf2ffe](https://github.com/waku-org/js-waku/commit/4cf2ffefa75e0571805036b71644d2cdd4fe3192)) +* Metadata protocol ([#1732](https://github.com/waku-org/js-waku/issues/1732)) ([9ac2a3f](https://github.com/waku-org/js-waku/commit/9ac2a3f36352523b79fcd8f8a94bd6e0e109fc30)) +* Track node connection state ([#1719](https://github.com/waku-org/js-waku/issues/1719)) ([1d0e2ac](https://github.com/waku-org/js-waku/commit/1d0e2ace7fa5b44ab192505c7ebce01a7ce343e0)) + + +### Miscellaneous Chores + +* Change all instances of `PubSubTopic` to `PubsubTopic` ([#1703](https://github.com/waku-org/js-waku/issues/1703)) ([3166a51](https://github.com/waku-org/js-waku/commit/3166a5135e77583da4fa722ee2aa47c785854a38)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @waku/enr bumped from ^0.0.19 to ^0.0.20 + * @waku/interfaces bumped from 0.0.20 to 0.0.21 + * @waku/proto bumped from 0.0.5 to 0.0.6 + * @waku/utils bumped from 0.0.13 to 0.0.14 + ## [0.0.25](https://github.com/waku-org/js-waku/compare/core-v0.0.24...core-v0.0.25) (2023-11-01) diff --git a/packages/core/package.json b/packages/core/package.json index 3ad4bbbaf2..428c35d76a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@waku/core", - "version": "0.0.25", + "version": "0.0.26", "description": "TypeScript implementation of the Waku v2 protocol", "types": "./dist/index.d.ts", "module": "./dist/index.js", @@ -73,10 +73,10 @@ }, "dependencies": { "@noble/hashes": "^1.3.2", - "@waku/enr": "^0.0.19", - "@waku/interfaces": "0.0.20", - "@waku/proto": "0.0.5", - "@waku/utils": "0.0.13", + "@waku/enr": "^0.0.20", + "@waku/interfaces": "0.0.21", + "@waku/proto": "0.0.6", + "@waku/utils": "0.0.14", "debug": "^4.3.4", "it-all": "^3.0.4", "it-length-prefixed": "^9.0.1", diff --git a/packages/core/src/lib/base_protocol.ts b/packages/core/src/lib/base_protocol.ts index b5dcb1fd37..b1dfada4eb 100644 --- a/packages/core/src/lib/base_protocol.ts +++ b/packages/core/src/lib/base_protocol.ts @@ -1,6 +1,5 @@ import type { Libp2p } from "@libp2p/interface"; import type { Stream } from "@libp2p/interface/connection"; -import type { PeerId } from "@libp2p/interface/peer-id"; import { Peer, PeerStore } from "@libp2p/interface/peer-store"; import type { IBaseProtocol, @@ -9,14 +8,14 @@ import type { PubsubTopic } from "@waku/interfaces"; import { DefaultPubsubTopic } from "@waku/interfaces"; -import { shardInfoToPubsubTopics } from "@waku/utils"; +import { Logger, shardInfoToPubsubTopics } from "@waku/utils"; import { getConnectedPeersForProtocolAndShard, getPeersForProtocol, - selectPeerForProtocol + sortPeersByLatency } from "@waku/utils/libp2p"; -import { filterPeers } from "./filterPeers.js"; +import { filterPeersByDiscovery } from "./filterPeers.js"; import { StreamManager } from "./stream_manager.js"; /** @@ -32,7 +31,8 @@ export class BaseProtocol implements IBaseProtocol { constructor( public multicodec: string, private components: Libp2pComponents, - public options?: ProtocolCreateOptions + private log: Logger, + private options?: ProtocolCreateOptions ) { this.pubsubTopics = this.initializePubsubTopic(options); @@ -77,22 +77,14 @@ export class BaseProtocol implements IBaseProtocol { }); } - protected async getPeer(peerId?: PeerId): Promise { - const { peer } = await selectPeerForProtocol( - this.peerStore, - [this.multicodec], - peerId - ); - return peer; - } - /** - * Retrieves a list of connected peers based on the specified criteria. + * Retrieves a list of connected peers that support the protocol. The list is sorted by latency. * * @param numPeers - The total number of peers to retrieve. If 0, all peers are returned. * @param maxBootstrapPeers - The maximum number of bootstrap peers to retrieve. - * @returns A Promise that resolves to an array of peers based on the specified criteria. - */ + + * @returns A list of peers that support the protocol sorted by latency. + */ protected async getPeers( { numPeers, @@ -110,15 +102,30 @@ export class BaseProtocol implements IBaseProtocol { await getConnectedPeersForProtocolAndShard( this.components.connectionManager.getConnections(), this.peerStore, - [this.multicodec] + [this.multicodec], + this.options?.shardInfo ); - // Filter the peers based on the specified criteria - return filterPeers( + // Filter the peers based on discovery & number of peers requested + const filteredPeers = filterPeersByDiscovery( connectedPeersForProtocolAndShard, numPeers, maxBootstrapPeers ); + + // Sort the peers by latency + const sortedFilteredPeers = await sortPeersByLatency( + this.peerStore, + filteredPeers + ); + + if (sortedFilteredPeers.length === 0) { + this.log.warn( + "No peers found. Ensure you have a connection to the network." + ); + } + + return sortedFilteredPeers; } private initializePubsubTopic( diff --git a/packages/core/src/lib/filter/index.ts b/packages/core/src/lib/filter/index.ts index fc3b397520..d60f316a8b 100644 --- a/packages/core/src/lib/filter/index.ts +++ b/packages/core/src/lib/filter/index.ts @@ -278,7 +278,7 @@ class Filter extends BaseProtocol implements IReceiver { } constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) { - super(FilterCodecs.SUBSCRIBE, libp2p.components, options); + super(FilterCodecs.SUBSCRIBE, libp2p.components, log, options); libp2p.handle(FilterCodecs.PUSH, this.onRequest.bind(this)).catch((e) => { log.error("Failed to register ", FilterCodecs.PUSH, e); diff --git a/packages/core/src/lib/filterPeers.spec.ts b/packages/core/src/lib/filterPeers.spec.ts index 50cfbfd30e..a2eae25235 100644 --- a/packages/core/src/lib/filterPeers.spec.ts +++ b/packages/core/src/lib/filterPeers.spec.ts @@ -4,9 +4,9 @@ import { createSecp256k1PeerId } from "@libp2p/peer-id-factory"; import { Tags } from "@waku/interfaces"; import { expect } from "chai"; -import { filterPeers } from "./filterPeers.js"; +import { filterPeersByDiscovery } from "./filterPeers.js"; -describe("filterPeers function", function () { +describe("filterPeersByDiscovery function", function () { it("should return all peers when numPeers is 0", async function () { const peer1 = await createSecp256k1PeerId(); const peer2 = await createSecp256k1PeerId(); @@ -27,7 +27,7 @@ describe("filterPeers function", function () { } ] as unknown as Peer[]; - const result = filterPeers(mockPeers, 0, 10); + const result = filterPeersByDiscovery(mockPeers, 0, 10); expect(result.length).to.deep.equal(mockPeers.length); }); @@ -56,7 +56,7 @@ describe("filterPeers function", function () { } ] as unknown as Peer[]; - const result = filterPeers(mockPeers, 0, 0); + const result = filterPeersByDiscovery(mockPeers, 0, 0); // result should have no bootstrap peers, and a total of 2 peers expect(result.length).to.equal(2); @@ -95,7 +95,7 @@ describe("filterPeers function", function () { } ] as unknown as Peer[]; - const result = filterPeers(mockPeers, 0, 1); + const result = filterPeersByDiscovery(mockPeers, 0, 1); // result should have 1 bootstrap peers, and a total of 4 peers expect(result.length).to.equal(4); @@ -134,7 +134,7 @@ describe("filterPeers function", function () { } ] as unknown as Peer[]; - const result = filterPeers(mockPeers, 5, 2); + const result = filterPeersByDiscovery(mockPeers, 5, 2); // check that result has at least 2 bootstrap peers and no more than 5 peers expect(result.length).to.be.at.least(2); diff --git a/packages/core/src/lib/filterPeers.ts b/packages/core/src/lib/filterPeers.ts index 8751aeb4b5..4f51790258 100644 --- a/packages/core/src/lib/filterPeers.ts +++ b/packages/core/src/lib/filterPeers.ts @@ -2,23 +2,31 @@ import { Peer } from "@libp2p/interface/peer-store"; import { Tags } from "@waku/interfaces"; /** - * Retrieves a list of peers based on the specified criteria. + * Retrieves a list of peers based on the specified criteria: + * 1. If numPeers is 0, return all peers + * 2. Bootstrap peers are prioritized + * 3. Non-bootstrap peers are randomly selected to fill up to numPeers * * @param peers - The list of peers to filter from. - * @param numPeers - The total number of peers to retrieve. If 0, all peers are returned. + * @param numPeers - The total number of peers to retrieve. If 0, all peers are returned, irrespective of `maxBootstrapPeers`. * @param maxBootstrapPeers - The maximum number of bootstrap peers to retrieve. - * @returns A Promise that resolves to an array of peers based on the specified criteria. + * @returns An array of peers based on the specified criteria. */ -export function filterPeers( +export function filterPeersByDiscovery( peers: Peer[], numPeers: number, maxBootstrapPeers: number ): Peer[] { // Collect the bootstrap peers up to the specified maximum - const bootstrapPeers = peers + let bootstrapPeers = peers .filter((peer) => peer.tags.has(Tags.BOOTSTRAP)) .slice(0, maxBootstrapPeers); + // If numPeers is less than the number of bootstrap peers, adjust the bootstrapPeers array + if (numPeers > 0 && numPeers < bootstrapPeers.length) { + bootstrapPeers = bootstrapPeers.slice(0, numPeers); + } + // Collect non-bootstrap peers const nonBootstrapPeers = peers.filter( (peer) => !peer.tags.has(Tags.BOOTSTRAP) diff --git a/packages/core/src/lib/light_push/index.ts b/packages/core/src/lib/light_push/index.ts index 7bba940b2f..22ae0a7b37 100644 --- a/packages/core/src/lib/light_push/index.ts +++ b/packages/core/src/lib/light_push/index.ts @@ -46,7 +46,7 @@ class LightPush extends BaseProtocol implements ILightPush { private readonly NUM_PEERS_PROTOCOL = 1; constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) { - super(LightPushCodec, libp2p.components, options); + super(LightPushCodec, libp2p.components, log, options); } private async preparePushMessage( diff --git a/packages/core/src/lib/metadata/index.ts b/packages/core/src/lib/metadata/index.ts index b84e26a530..d457699b1a 100644 --- a/packages/core/src/lib/metadata/index.ts +++ b/packages/core/src/lib/metadata/index.ts @@ -32,7 +32,7 @@ class Metadata extends BaseProtocol implements IMetadata { public shardInfo: ShardingParams, libp2p: Libp2pComponents ) { - super(MetadataCodec, libp2p.components, shardInfo && { shardInfo }); + super(MetadataCodec, libp2p.components, log, shardInfo && { shardInfo }); this.libp2pComponents = libp2p; void libp2p.registrar.handle(MetadataCodec, (streamData) => { void this.onRequest(streamData); @@ -75,7 +75,10 @@ class Metadata extends BaseProtocol implements IMetadata { async query(peerId: PeerId): Promise { const request = proto_metadata.WakuMetadataRequest.encode(this.shardInfo); - const peer = await this.getPeer(peerId); + const peer = await this.peerStore.get(peerId); + if (!peer) { + throw new Error(`Peer ${peerId.toString()} not found`); + } const stream = await this.getStream(peer); diff --git a/packages/core/src/lib/store/index.ts b/packages/core/src/lib/store/index.ts index afed546a9a..6cfb6b73b7 100644 --- a/packages/core/src/lib/store/index.ts +++ b/packages/core/src/lib/store/index.ts @@ -76,7 +76,7 @@ class Store extends BaseProtocol implements IStore { private readonly NUM_PEERS_PROTOCOL = 1; constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) { - super(StoreCodec, libp2p.components, options); + super(StoreCodec, libp2p.components, log, options); } /** diff --git a/packages/dns-discovery/CHANGELOG.md b/packages/dns-discovery/CHANGELOG.md index 04d7fc7f83..1fd993ef83 100644 --- a/packages/dns-discovery/CHANGELOG.md +++ b/packages/dns-discovery/CHANGELOG.md @@ -46,6 +46,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * devDependencies * @waku/interfaces bumped from 0.0.13 to 0.0.14 +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @waku/enr bumped from 0.0.19 to 0.0.20 + * @waku/utils bumped from 0.0.13 to 0.0.14 + * devDependencies + * @waku/interfaces bumped from 0.0.20 to 0.0.21 + ## [0.0.19](https://github.com/waku-org/js-waku/compare/dns-discovery-v0.0.18...dns-discovery-v0.0.19) (2023-11-01) diff --git a/packages/dns-discovery/package.json b/packages/dns-discovery/package.json index 415e5e0447..c0b5593ff9 100644 --- a/packages/dns-discovery/package.json +++ b/packages/dns-discovery/package.json @@ -1,6 +1,6 @@ { "name": "@waku/dns-discovery", - "version": "0.0.19", + "version": "0.0.20", "description": "DNS Peer Discovery (EIP-1459)", "types": "./dist/index.d.ts", "module": "./dist/index.js", @@ -51,8 +51,8 @@ "node": ">=18" }, "dependencies": { - "@waku/enr": "0.0.19", - "@waku/utils": "0.0.13", + "@waku/enr": "0.0.20", + "@waku/utils": "0.0.14", "debug": "^4.3.4", "dns-query": "^0.11.2", "hi-base32": "^0.5.1", @@ -67,7 +67,7 @@ "@rollup/plugin-node-resolve": "^15.2.3", "@types/chai": "^4.3.11", "@waku/build-utils": "*", - "@waku/interfaces": "0.0.20", + "@waku/interfaces": "0.0.21", "chai": "^4.3.10", "cspell": "^7.3.2", "mocha": "^10.2.0", diff --git a/packages/enr/CHANGELOG.md b/packages/enr/CHANGELOG.md index c971b4978e..242dc00977 100644 --- a/packages/enr/CHANGELOG.md +++ b/packages/enr/CHANGELOG.md @@ -59,6 +59,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * devDependencies * @waku/interfaces bumped from 0.0.16 to 0.0.17 +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @waku/utils bumped from 0.0.13 to 0.0.14 + * devDependencies + * @waku/interfaces bumped from 0.0.20 to 0.0.21 + ## [0.0.19](https://github.com/waku-org/js-waku/compare/enr-v0.0.18...enr-v0.0.19) (2023-11-01) diff --git a/packages/enr/package.json b/packages/enr/package.json index a2eb199f77..a8899ba406 100644 --- a/packages/enr/package.json +++ b/packages/enr/package.json @@ -1,6 +1,6 @@ { "name": "@waku/enr", - "version": "0.0.19", + "version": "0.0.20", "description": "ENR (EIP-778) for Waku", "types": "./dist/index.d.ts", "module": "./dist/index.js", @@ -56,7 +56,7 @@ "@libp2p/peer-id": "^3.0.3", "@multiformats/multiaddr": "^12.0.0", "@noble/secp256k1": "^1.7.1", - "@waku/utils": "0.0.13", + "@waku/utils": "0.0.14", "debug": "^4.3.4", "js-sha3": "^0.9.2" }, @@ -68,7 +68,7 @@ "@types/chai": "^4.3.11", "@types/mocha": "^10.0.1", "@waku/build-utils": "*", - "@waku/interfaces": "0.0.20", + "@waku/interfaces": "0.0.21", "chai": "^4.3.10", "cspell": "^7.3.2", "fast-check": "^3.14.0", diff --git a/packages/interfaces/CHANGELOG.md b/packages/interfaces/CHANGELOG.md index 73553c6ee7..80d9da4344 100644 --- a/packages/interfaces/CHANGELOG.md +++ b/packages/interfaces/CHANGELOG.md @@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file. The file is maintained by [Release Please](https://github.com/googleapis/release-please) based on [Conventional Commits](https://www.conventionalcommits.org) specification, and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.0.21](https://github.com/waku-org/js-waku/compare/interfaces-v0.0.20...interfaces-v0.0.21) (2024-01-10) + + +### ⚠ BREAKING CHANGES + +* add support for sharded pubsub topics & remove support for named pubsub topics ([#1697](https://github.com/waku-org/js-waku/issues/1697)) +* change all instances of `PubSubTopic` to `PubsubTopic` ([#1703](https://github.com/waku-org/js-waku/issues/1703)) + +### Features + +* Add support for autosharded pubsub topics ([2bc3735](https://github.com/waku-org/js-waku/commit/2bc3735e4dcf85f06b3dee542024d7f20a40fac2)) +* Add support for sharded pubsub topics & remove support for named pubsub topics ([#1697](https://github.com/waku-org/js-waku/issues/1697)) ([4cf2ffe](https://github.com/waku-org/js-waku/commit/4cf2ffefa75e0571805036b71644d2cdd4fe3192)) +* Metadata protocol ([#1732](https://github.com/waku-org/js-waku/issues/1732)) ([9ac2a3f](https://github.com/waku-org/js-waku/commit/9ac2a3f36352523b79fcd8f8a94bd6e0e109fc30)) +* Track node connection state ([#1719](https://github.com/waku-org/js-waku/issues/1719)) ([1d0e2ac](https://github.com/waku-org/js-waku/commit/1d0e2ace7fa5b44ab192505c7ebce01a7ce343e0)) + + +### Miscellaneous Chores + +* Change all instances of `PubSubTopic` to `PubsubTopic` ([#1703](https://github.com/waku-org/js-waku/issues/1703)) ([3166a51](https://github.com/waku-org/js-waku/commit/3166a5135e77583da4fa722ee2aa47c785854a38)) + ## [0.0.20](https://github.com/waku-org/js-waku/compare/interfaces-v0.0.19...interfaces-v0.0.20) (2023-11-01) diff --git a/packages/interfaces/package.json b/packages/interfaces/package.json index 7998b20eae..ecdb96a9a9 100644 --- a/packages/interfaces/package.json +++ b/packages/interfaces/package.json @@ -1,6 +1,6 @@ { "name": "@waku/interfaces", - "version": "0.0.20", + "version": "0.0.21", "description": "Definition of Waku interfaces", "types": "./dist/index.d.ts", "module": "./dist/index.js", diff --git a/packages/interfaces/src/peer_exchange.ts b/packages/interfaces/src/peer_exchange.ts index 8183d6fe13..b194c9c6a9 100644 --- a/packages/interfaces/src/peer_exchange.ts +++ b/packages/interfaces/src/peer_exchange.ts @@ -11,7 +11,7 @@ export interface IPeerExchange extends IBaseProtocol { export interface PeerExchangeQueryParams { numPeers: number; - peerId?: PeerId; + peerId: PeerId; } export interface PeerExchangeResponse { diff --git a/packages/message-encryption/CHANGELOG.md b/packages/message-encryption/CHANGELOG.md index 82b90abf86..00ef3ae3c1 100644 --- a/packages/message-encryption/CHANGELOG.md +++ b/packages/message-encryption/CHANGELOG.md @@ -68,6 +68,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * @waku/interfaces bumped from 0.0.17 to 0.0.18 * @waku/utils bumped from 0.0.10 to 0.0.11 +## [0.0.24](https://github.com/waku-org/js-waku/compare/message-encryption-v0.0.23...message-encryption-v0.0.24) (2024-01-10) + + +### ⚠ BREAKING CHANGES + +* add support for sharded pubsub topics & remove support for named pubsub topics ([#1697](https://github.com/waku-org/js-waku/issues/1697)) +* export crypto primitives ([#1728](https://github.com/waku-org/js-waku/issues/1728)) +* change all instances of `PubSubTopic` to `PubsubTopic` ([#1703](https://github.com/waku-org/js-waku/issues/1703)) + +### Features + +* Add support for autosharded pubsub topics ([2bc3735](https://github.com/waku-org/js-waku/commit/2bc3735e4dcf85f06b3dee542024d7f20a40fac2)) +* Add support for sharded pubsub topics & remove support for named pubsub topics ([#1697](https://github.com/waku-org/js-waku/issues/1697)) ([4cf2ffe](https://github.com/waku-org/js-waku/commit/4cf2ffefa75e0571805036b71644d2cdd4fe3192)) +* Export crypto primitives ([#1728](https://github.com/waku-org/js-waku/issues/1728)) ([7eb3375](https://github.com/waku-org/js-waku/commit/7eb3375f50265240096d70bd2814549c8156a0eb)) +* New `verifySignature` ([2f67a3b](https://github.com/waku-org/js-waku/commit/2f67a3baffc085d61b057f9fdb5ab404bfd70a1b)) + + +### Miscellaneous Chores + +* Change all instances of `PubSubTopic` to `PubsubTopic` ([#1703](https://github.com/waku-org/js-waku/issues/1703)) ([3166a51](https://github.com/waku-org/js-waku/commit/3166a5135e77583da4fa722ee2aa47c785854a38)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @waku/core bumped from 0.0.25 to 0.0.26 + * @waku/interfaces bumped from 0.0.20 to 0.0.21 + * @waku/proto bumped from 0.0.5 to 0.0.6 + * @waku/utils bumped from 0.0.13 to 0.0.14 + ## [0.0.23](https://github.com/waku-org/js-waku/compare/message-encryption-v0.0.22...message-encryption-v0.0.23) (2023-11-01) diff --git a/packages/message-encryption/package.json b/packages/message-encryption/package.json index 30ff2854b4..17727df6ff 100644 --- a/packages/message-encryption/package.json +++ b/packages/message-encryption/package.json @@ -1,6 +1,6 @@ { "name": "@waku/message-encryption", - "version": "0.0.23", + "version": "0.0.24", "description": "Waku Message Payload Encryption", "types": "./dist/index.d.ts", "module": "./dist/index.js", @@ -76,10 +76,10 @@ }, "dependencies": { "@noble/secp256k1": "^1.7.1", - "@waku/core": "0.0.25", - "@waku/interfaces": "0.0.20", - "@waku/proto": "0.0.5", - "@waku/utils": "0.0.13", + "@waku/core": "0.0.26", + "@waku/interfaces": "0.0.21", + "@waku/proto": "0.0.6", + "@waku/utils": "0.0.14", "debug": "^4.3.4", "js-sha3": "^0.9.2", "uint8arrays": "^5.0.0" diff --git a/packages/message-hash/CHANGELOG.md b/packages/message-hash/CHANGELOG.md index 2f5120b4ae..9fbb005c3e 100644 --- a/packages/message-hash/CHANGELOG.md +++ b/packages/message-hash/CHANGELOG.md @@ -40,6 +40,14 @@ * devDependencies * @waku/interfaces bumped from 0.0.19 to 0.0.20 +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @waku/utils bumped from 0.0.13 to 0.0.14 + * devDependencies + * @waku/interfaces bumped from 0.0.20 to 0.0.21 + ## [0.1.8](https://github.com/waku-org/js-waku/compare/message-hash-v0.1.7...message-hash-v0.1.8) (2023-10-16) diff --git a/packages/message-hash/package.json b/packages/message-hash/package.json index 615579212a..99cd40b1a0 100644 --- a/packages/message-hash/package.json +++ b/packages/message-hash/package.json @@ -1,6 +1,6 @@ { "name": "@waku/message-hash", - "version": "0.1.9", + "version": "0.1.10", "description": "TypeScript implementation of the Deterministic Message Hashing as specified in 14/WAKU2-MESSAGE", "types": "./dist/index.d.ts", "module": "./dist/index.js", @@ -51,7 +51,7 @@ }, "dependencies": { "@noble/hashes": "^1.3.2", - "@waku/utils": "0.0.13" + "@waku/utils": "0.0.14" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", @@ -61,7 +61,7 @@ "@types/debug": "^4.1.12", "@types/mocha": "^10.0.1", "@waku/build-utils": "*", - "@waku/interfaces": "0.0.20", + "@waku/interfaces": "0.0.21", "chai": "^4.3.10", "cspell": "^7.3.2", "fast-check": "^3.14.0", diff --git a/packages/peer-exchange/CHANGELOG.md b/packages/peer-exchange/CHANGELOG.md index 544702b445..1b539db8ee 100644 --- a/packages/peer-exchange/CHANGELOG.md +++ b/packages/peer-exchange/CHANGELOG.md @@ -64,6 +64,16 @@ * devDependencies * @waku/interfaces bumped from 0.0.14 to 0.0.15 +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @waku/core bumped from 0.0.25 to 0.0.26 + * @waku/enr bumped from 0.0.19 to 0.0.20 + * @waku/interfaces bumped from 0.0.20 to 0.0.21 + * @waku/proto bumped from 0.0.5 to 0.0.6 + * @waku/utils bumped from 0.0.13 to 0.0.14 + ## [0.0.18](https://github.com/waku-org/js-waku/compare/peer-exchange-v0.0.17...peer-exchange-v0.0.18) (2023-11-01) diff --git a/packages/peer-exchange/package.json b/packages/peer-exchange/package.json index 3b50db416c..a02dd0e217 100644 --- a/packages/peer-exchange/package.json +++ b/packages/peer-exchange/package.json @@ -1,6 +1,6 @@ { "name": "@waku/peer-exchange", - "version": "0.0.18", + "version": "0.0.19", "description": "Peer Exchange (https://rfc.vac.dev/spec/34/) protocol for Waku", "types": "./dist/index.d.ts", "module": "./dist/index.js", @@ -49,11 +49,11 @@ }, "dependencies": { "@libp2p/interfaces": "^3.3.2", - "@waku/core": "0.0.25", - "@waku/enr": "0.0.19", - "@waku/interfaces": "0.0.20", - "@waku/proto": "0.0.5", - "@waku/utils": "0.0.13", + "@waku/core": "0.0.26", + "@waku/enr": "0.0.20", + "@waku/interfaces": "0.0.21", + "@waku/proto": "0.0.6", + "@waku/utils": "0.0.14", "debug": "^4.3.4", "it-all": "^3.0.4", "it-length-prefixed": "^9.0.1", diff --git a/packages/peer-exchange/src/waku_peer_exchange.ts b/packages/peer-exchange/src/waku_peer_exchange.ts index 21ac4aab4d..473864474a 100644 --- a/packages/peer-exchange/src/waku_peer_exchange.ts +++ b/packages/peer-exchange/src/waku_peer_exchange.ts @@ -27,7 +27,7 @@ export class WakuPeerExchange extends BaseProtocol implements IPeerExchange { * @param components - libp2p components */ constructor(components: Libp2pComponents) { - super(PeerExchangeCodec, components); + super(PeerExchangeCodec, components, log); } /** @@ -42,7 +42,10 @@ export class WakuPeerExchange extends BaseProtocol implements IPeerExchange { numPeers: BigInt(numPeers) }); - const peer = await this.getPeer(params.peerId); + const peer = await this.peerStore.get(params.peerId); + if (!peer) { + throw new Error(`Peer ${params.peerId.toString()} not found`); + } const stream = await this.getStream(peer); diff --git a/packages/proto/CHANGELOG.md b/packages/proto/CHANGELOG.md index b0723243c7..58b71a5378 100644 --- a/packages/proto/CHANGELOG.md +++ b/packages/proto/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.0.6](https://github.com/waku-org/js-waku/compare/proto-v0.0.5...proto-v0.0.6) (2024-01-10) + + +### Features + +* Metadata protocol ([#1732](https://github.com/waku-org/js-waku/issues/1732)) ([9ac2a3f](https://github.com/waku-org/js-waku/commit/9ac2a3f36352523b79fcd8f8a94bd6e0e109fc30)) + ## [0.0.5](https://github.com/waku-org/js-waku/compare/proto-v0.0.4...proto-v0.0.5) (2023-05-26) diff --git a/packages/proto/package.json b/packages/proto/package.json index a7c78972c8..9efdb32986 100644 --- a/packages/proto/package.json +++ b/packages/proto/package.json @@ -1,6 +1,6 @@ { "name": "@waku/proto", - "version": "0.0.5", + "version": "0.0.6", "description": "Protobuf definitions for Waku", "types": "./dist/index.d.ts", "module": "./dist/index.js", diff --git a/packages/relay/CHANGELOG.md b/packages/relay/CHANGELOG.md index 1342721de3..3e3bd70032 100644 --- a/packages/relay/CHANGELOG.md +++ b/packages/relay/CHANGELOG.md @@ -25,6 +25,34 @@ * @waku/interfaces bumped from 0.0.16 to 0.0.17 * @waku/utils bumped from 0.0.9 to 0.0.10 +## [0.0.9](https://github.com/waku-org/js-waku/compare/relay-v0.0.8...relay-v0.0.9) (2024-01-10) + + +### ⚠ BREAKING CHANGES + +* add support for sharded pubsub topics & remove support for named pubsub topics ([#1697](https://github.com/waku-org/js-waku/issues/1697)) +* change all instances of `PubSubTopic` to `PubsubTopic` ([#1703](https://github.com/waku-org/js-waku/issues/1703)) + +### Features + +* Add support for autosharded pubsub topics ([2bc3735](https://github.com/waku-org/js-waku/commit/2bc3735e4dcf85f06b3dee542024d7f20a40fac2)) +* Add support for sharded pubsub topics & remove support for named pubsub topics ([#1697](https://github.com/waku-org/js-waku/issues/1697)) ([4cf2ffe](https://github.com/waku-org/js-waku/commit/4cf2ffefa75e0571805036b71644d2cdd4fe3192)) + + +### Miscellaneous Chores + +* Change all instances of `PubSubTopic` to `PubsubTopic` ([#1703](https://github.com/waku-org/js-waku/issues/1703)) ([3166a51](https://github.com/waku-org/js-waku/commit/3166a5135e77583da4fa722ee2aa47c785854a38)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @waku/core bumped from 0.0.25 to 0.0.26 + * @waku/interfaces bumped from 0.0.20 to 0.0.21 + * @waku/proto bumped from 0.0.5 to 0.0.6 + * @waku/utils bumped from 0.0.13 to 0.0.14 + ## [0.0.8](https://github.com/waku-org/js-waku/compare/relay-v0.0.7...relay-v0.0.8) (2023-11-01) diff --git a/packages/relay/package.json b/packages/relay/package.json index e63c757678..d3eca999e5 100644 --- a/packages/relay/package.json +++ b/packages/relay/package.json @@ -1,6 +1,6 @@ { "name": "@waku/relay", - "version": "0.0.8", + "version": "0.0.9", "description": "Relay Protocol for Waku", "types": "./dist/index.d.ts", "module": "./dist/index.js", @@ -51,10 +51,10 @@ "dependencies": { "@chainsafe/libp2p-gossipsub": "^10.1.1", "@noble/hashes": "^1.3.2", - "@waku/core": "0.0.25", - "@waku/interfaces": "0.0.20", - "@waku/proto": "0.0.5", - "@waku/utils": "0.0.13", + "@waku/core": "0.0.26", + "@waku/interfaces": "0.0.21", + "@waku/proto": "0.0.6", + "@waku/utils": "0.0.14", "chai": "^4.3.10", "debug": "^4.3.4", "fast-check": "^3.14.0" diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index fd3b27e072..2c8fa4f324 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -47,6 +47,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * @waku/interfaces bumped from 0.0.19 to 0.0.20 * @waku/peer-exchange bumped from ^0.0.17 to ^0.0.18 +## [0.0.22](https://github.com/waku-org/js-waku/compare/sdk-v0.0.21...sdk-v0.0.22) (2024-01-10) + + +### ⚠ BREAKING CHANGES + +* add support for sharded pubsub topics & remove support for named pubsub topics ([#1697](https://github.com/waku-org/js-waku/issues/1697)) +* change all instances of `PubSubTopic` to `PubsubTopic` ([#1703](https://github.com/waku-org/js-waku/issues/1703)) + +### Features + +* Add support for autosharded pubsub topics ([2bc3735](https://github.com/waku-org/js-waku/commit/2bc3735e4dcf85f06b3dee542024d7f20a40fac2)) +* Add support for sharded pubsub topics & remove support for named pubsub topics ([#1697](https://github.com/waku-org/js-waku/issues/1697)) ([4cf2ffe](https://github.com/waku-org/js-waku/commit/4cf2ffefa75e0571805036b71644d2cdd4fe3192)) +* Metadata protocol ([#1732](https://github.com/waku-org/js-waku/issues/1732)) ([9ac2a3f](https://github.com/waku-org/js-waku/commit/9ac2a3f36352523b79fcd8f8a94bd6e0e109fc30)) + + +### Miscellaneous Chores + +* Change all instances of `PubSubTopic` to `PubsubTopic` ([#1703](https://github.com/waku-org/js-waku/issues/1703)) ([3166a51](https://github.com/waku-org/js-waku/commit/3166a5135e77583da4fa722ee2aa47c785854a38)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @waku/utils bumped from 0.0.13 to 0.0.14 + * @waku/relay bumped from 0.0.8 to 0.0.9 + * @waku/core bumped from 0.0.25 to 0.0.26 + * @waku/dns-discovery bumped from 0.0.19 to 0.0.20 + * @waku/interfaces bumped from 0.0.20 to 0.0.21 + * @waku/peer-exchange bumped from ^0.0.18 to ^0.0.19 + ## [0.0.20](https://github.com/waku-org/js-waku/compare/sdk-v0.0.19...sdk-v0.0.20) (2023-10-16) diff --git a/packages/sdk/package.json b/packages/sdk/package.json index b69776715a..2a100202b5 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@waku/sdk", - "version": "0.0.21", + "version": "0.0.22", "description": "A unified SDK for easy creation and management of js-waku nodes.", "types": "./dist/index.d.ts", "module": "./dist/index.js", @@ -51,12 +51,12 @@ "@chainsafe/libp2p-noise": "^13.0.4", "@libp2p/mplex": "^9.0.10", "@libp2p/websockets": "^7.0.5", - "@waku/utils": "0.0.13", - "@waku/relay": "0.0.8", - "@waku/core": "0.0.25", - "@waku/dns-discovery": "0.0.19", - "@waku/interfaces": "0.0.20", - "@waku/peer-exchange": "^0.0.18", + "@waku/utils": "0.0.14", + "@waku/relay": "0.0.9", + "@waku/core": "0.0.26", + "@waku/dns-discovery": "0.0.20", + "@waku/interfaces": "0.0.21", + "@waku/peer-exchange": "^0.0.19", "libp2p": "^0.46.14" }, "devDependencies": { diff --git a/packages/tests/package.json b/packages/tests/package.json index bd802264ca..a1e6e15c55 100644 --- a/packages/tests/package.json +++ b/packages/tests/package.json @@ -60,6 +60,7 @@ "chai-as-promised": "^7.1.1", "debug": "^4.3.4", "dockerode": "^3.3.5", + "fast-check": "^3.15.0", "p-retry": "^6.1.0", "p-timeout": "^6.1.0", "portfinder": "^1.0.32", diff --git a/packages/tests/src/index.ts b/packages/tests/src/index.ts index d56386239b..43901b03af 100644 --- a/packages/tests/src/index.ts +++ b/packages/tests/src/index.ts @@ -13,3 +13,4 @@ export * from "./node/node.js"; export * from "./teardown.js"; export * from "./message_collector.js"; export * from "./utils.js"; +export * from "./waitForRemotePeerWithCodec.js"; diff --git a/packages/tests/src/waitForRemotePeerWithCodec.ts b/packages/tests/src/waitForRemotePeerWithCodec.ts new file mode 100644 index 0000000000..411b6116dd --- /dev/null +++ b/packages/tests/src/waitForRemotePeerWithCodec.ts @@ -0,0 +1,39 @@ +import type { IdentifyResult } from "@libp2p/interface"; +import type { PeerId } from "@libp2p/interface/peer-id"; +import type { LightNode } from "@waku/interfaces"; + +/** + * Wait for a remote peer to be identified with a given codec + * @param waku - Waku node + * @param codec - Codec to wait for + * @returns Promise that resolves when the peer is identified + * @internal + * This function is introduced as `core/waitForRemotePeer` only accounts for core protocols like Filter, LightPush & Store + * While this (currently) is not required by the SDK, it is required for the tests + */ +export async function waitForRemotePeerWithCodec( + waku: LightNode, + codec: string, + nodePeerId: PeerId +): Promise { + const connectedPeers = waku.libp2p + .getConnections() + .map((conn) => conn.remotePeer); + if ( + connectedPeers.find((connectedPeer) => connectedPeer.equals(nodePeerId)) + ) { + return; + } + + await new Promise((resolve) => { + const cb = (evt: CustomEvent): void => { + if (evt.detail.protocols.includes(codec)) { + waku.libp2p.removeEventListener("peer:identify", cb); + resolve(); + } + }; + waku.libp2p.addEventListener("peer:identify", cb); + }); + + return; +} diff --git a/packages/tests/tests/getPeers.spec.ts b/packages/tests/tests/getPeers.spec.ts index dc13ee243e..956eb51705 100644 --- a/packages/tests/tests/getPeers.spec.ts +++ b/packages/tests/tests/getPeers.spec.ts @@ -1,13 +1,22 @@ +import type { Connection } from "@libp2p/interface/connection"; +import type { PeerStore } from "@libp2p/interface/peer-store"; +import type { Peer } from "@libp2p/interface/peer-store"; +import { createSecp256k1PeerId } from "@libp2p/peer-id-factory"; import { LightPushCodec, waitForRemotePeer } from "@waku/core"; import { createLightNode, + Libp2pComponents, type LightNode, Protocols, - ShardInfo + ShardInfo, + Tags, + utf8ToBytes } from "@waku/sdk"; import { shardInfoToPubsubTopics } from "@waku/utils"; import { getConnectedPeersForProtocolAndShard } from "@waku/utils/libp2p"; import { expect } from "chai"; +import fc from "fast-check"; +import Sinon from "sinon"; import { makeLogFileName } from "../src/log_file.js"; import { NimGoNode } from "../src/node/node.js"; @@ -192,3 +201,317 @@ describe("getConnectedPeersForProtocolAndShard", function () { expect(peers.length).to.be.equal(1); }); }); +describe("getPeers", function () { + let peerStore: PeerStore; + let connectionManager: Libp2pComponents["connectionManager"]; + let waku: LightNode; + const lowPingBytes = utf8ToBytes("50"); + const midPingBytes = utf8ToBytes("100"); + const highPingBytes = utf8ToBytes("200"); + + let lowPingBootstrapPeer: Peer, + lowPingNonBootstrapPeer: Peer, + midPingBootstrapPeer: Peer, + midPingNonBootstrapPeer: Peer, + highPingBootstrapPeer: Peer, + highPingNonBootstrapPeer: Peer, + differentCodecPeer: Peer, + anotherDifferentCodecPeer: Peer; + + let bootstrapPeers: Peer[]; + let nonBootstrapPeers: Peer[]; + let allPeers: Peer[]; + + beforeEach(async function () { + this.timeout(10_000); + waku = await createLightNode(); + peerStore = waku.libp2p.peerStore; + connectionManager = waku.libp2p.components.connectionManager; + + const [ + lowPingBootstrapPeerId, + lowPingNonBootstrapPeerId, + midPingBootstrapPeerId, + midPingNonBootstrapPeerId, + highPingBootstrapPeerId, + highPingNonBootstrapPeerId, + differentCodecPeerId, + anotherDifferentCodecPeerId + ] = await Promise.all([ + createSecp256k1PeerId(), + createSecp256k1PeerId(), + createSecp256k1PeerId(), + createSecp256k1PeerId(), + createSecp256k1PeerId(), + createSecp256k1PeerId(), + createSecp256k1PeerId(), + createSecp256k1PeerId() + ]); + + lowPingBootstrapPeer = { + id: lowPingBootstrapPeerId, + protocols: [waku.lightPush.multicodec], + metadata: new Map().set("ping", lowPingBytes), + tags: new Map().set(Tags.BOOTSTRAP, {}) + } as Peer; + lowPingNonBootstrapPeer = { + id: lowPingNonBootstrapPeerId, + protocols: [waku.lightPush.multicodec], + metadata: new Map().set("ping", lowPingBytes), + tags: new Map().set(Tags.PEER_EXCHANGE, {}) + } as Peer; + midPingBootstrapPeer = { + id: midPingBootstrapPeerId, + protocols: [waku.lightPush.multicodec], + metadata: new Map().set("ping", midPingBytes), + tags: new Map().set(Tags.BOOTSTRAP, {}) + } as Peer; + midPingNonBootstrapPeer = { + id: midPingNonBootstrapPeerId, + protocols: [waku.lightPush.multicodec], + metadata: new Map().set("ping", midPingBytes), + tags: new Map().set(Tags.PEER_EXCHANGE, {}) + } as Peer; + highPingBootstrapPeer = { + id: highPingBootstrapPeerId, + protocols: [waku.lightPush.multicodec], + metadata: new Map().set("ping", highPingBytes), + tags: new Map().set(Tags.BOOTSTRAP, {}) + } as Peer; + highPingNonBootstrapPeer = { + id: highPingNonBootstrapPeerId, + protocols: [waku.lightPush.multicodec], + metadata: new Map().set("ping", highPingBytes), + tags: new Map().set(Tags.PEER_EXCHANGE, {}) + } as Peer; + differentCodecPeer = { + id: differentCodecPeerId, + protocols: ["different/1"], + metadata: new Map().set("ping", lowPingBytes), + tags: new Map().set(Tags.BOOTSTRAP, {}) + } as Peer; + anotherDifferentCodecPeer = { + id: anotherDifferentCodecPeerId, + protocols: ["different/2"], + metadata: new Map().set("ping", lowPingBytes), + tags: new Map().set(Tags.BOOTSTRAP, {}) + } as Peer; + + bootstrapPeers = [ + lowPingBootstrapPeer, + midPingBootstrapPeer, + highPingBootstrapPeer + ]; + + nonBootstrapPeers = [ + lowPingNonBootstrapPeer, + midPingNonBootstrapPeer, + highPingNonBootstrapPeer + ]; + + allPeers = [ + ...bootstrapPeers, + ...nonBootstrapPeers, + differentCodecPeer, + anotherDifferentCodecPeer + ]; + + Sinon.stub(peerStore, "get").callsFake(async (peerId) => { + return allPeers.find((peer) => peer.id.equals(peerId))!; + }); + + Sinon.stub(peerStore, "forEach").callsFake(async (callback) => { + for (const peer of allPeers) { + callback(peer); + } + }); + + // assume all peers have an opened connection + Sinon.stub(connectionManager, "getConnections").callsFake(() => { + const connections: Connection[] = []; + for (const peer of allPeers) { + connections.push({ + status: "open", + remotePeer: peer.id + } as unknown as Connection); + } + return connections; + }); + }); + + this.afterEach(function () { + Sinon.restore(); + }); + + describe("getPeers with varying maxBootstrapPeers", function () { + const maxBootstrapPeersValues = [1, 2, 3, 4, 5, 6, 7]; + + maxBootstrapPeersValues.forEach((maxBootstrapPeers) => { + describe(`maxBootstrapPeers=${maxBootstrapPeers}`, function () { + it(`numPeers=1 -- returns one bootstrap peer `, async function () { + const result = (await (waku.lightPush as any).getPeers({ + numPeers: 1, + maxBootstrapPeers + })) as Peer[]; + + // Should only have 1 peer + expect(result).to.have.lengthOf(1); + + // The peer should be a bootstrap peer + expect(result[0].tags.has(Tags.BOOTSTRAP)).to.be.true; + + // Peer should be of the same protocol + expect(result[0].protocols.includes(waku.lightPush.multicodec)).to.be + .true; + + // Peer should have the lowest ping + expect(result[0].metadata.get("ping")).to.equal(lowPingBytes); + }); + + it(`numPeers=2 -- returns total 2 peers, with max ${maxBootstrapPeers} bootstrap peers`, async function () { + const result = (await (waku.lightPush as any).getPeers({ + numPeers: 2, + maxBootstrapPeers + })) as Peer[]; + + // Should only have 2 peers + expect(result).to.have.lengthOf(2); + + // Should only have ${maxBootstrapPeers} bootstrap peers + expect( + result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length + ).to.be.lessThanOrEqual(maxBootstrapPeers); + + // Should return peers with the same protocol + expect( + result.every((peer: Peer) => + peer.protocols.includes(waku.lightPush.multicodec) + ) + ).to.be.true; + + // All peers should be sorted by latency + // 0th index should be the lowest ping of all peers returned + expect(result[0].metadata.get("ping")).to.equal(lowPingBytes); + }); + + it(`numPeers=3 -- returns total 3 peers, with max ${maxBootstrapPeers} bootstrap peers`, async function () { + const result = (await (waku.lightPush as any).getPeers({ + numPeers: 3, + maxBootstrapPeers + })) as Peer[]; + + // Should only have 3 peers + expect(result).to.have.lengthOf(3); + + // Should only have ${maxBootstrapPeers} bootstrap peers + expect( + result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length + ).to.be.lessThanOrEqual(maxBootstrapPeers); + + // Should return peers with the same protocol + expect( + result.every((peer: Peer) => + peer.protocols.includes(waku.lightPush.multicodec) + ) + ).to.be.true; + + // All peers should be sorted by latency + // 0th index should be the lowest ping of all peers returned + expect(result[0].metadata.get("ping")).to.equal(lowPingBytes); + }); + + it(`numPeers=4 -- returns total 4 peers, with max ${maxBootstrapPeers} bootstrap peers`, async function () { + const result = (await (waku.lightPush as any).getPeers({ + numPeers: 4, + maxBootstrapPeers + })) as Peer[]; + + // Should only have 4 peers + expect(result).to.have.lengthOf(4); + + // Should only have ${maxBootstrapPeers} bootstrap peers + expect( + result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length + ).to.be.lessThanOrEqual(maxBootstrapPeers); + + // Should return peers with the same protocol + expect( + result.every((peer: Peer) => + peer.protocols.includes(waku.lightPush.multicodec) + ) + ).to.be.true; + + // All peers should be sorted by latency + // 0th index should be the lowest ping of all peers returned + expect(result[0].metadata.get("ping")).to.equal(lowPingBytes); + }); + + it(`numPeers=0 -- returns all peers including all non-bootstrap with maxBootstrapPeers: ${maxBootstrapPeers}`, async function () { + const result = (await (waku.lightPush as any).getPeers({ + numPeers: 0, + maxBootstrapPeers + })) as Peer[]; + + // Should have all non-bootstrap peers + ${maxBootstrapPeers} bootstrap peers + // Unless bootstrapPeers.length < maxBootstrapPeers + // Then it should be all non-bootstrap peers + bootstrapPeers.length + if (maxBootstrapPeers > bootstrapPeers.length) { + expect(result).to.have.lengthOf( + nonBootstrapPeers.length + bootstrapPeers.length + ); + } else { + expect(result).to.have.lengthOf( + nonBootstrapPeers.length + maxBootstrapPeers + ); + } + + // All peers should be bootstrap peers + expect( + result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length + ).to.be.lessThanOrEqual(maxBootstrapPeers); + + // Peers should be of the same protocol + expect( + result.every((peer: Peer) => + peer.protocols.includes(waku.lightPush.multicodec) + ) + ).to.be.true; + + // All peers returned should be sorted by latency + // 0th index should be the lowest ping of all peers returned + expect(result[0].metadata.get("ping")).to.equal(lowPingBytes); + }); + }); + }); + }); + + describe("getPeers property-based tests", function () { + it("should return the correct number of peers based on numPeers and maxBootstrapPeers", async function () { + await fc.assert( + fc.asyncProperty( + //max bootstrap peers + fc.integer({ min: 1, max: 100 }), + //numPeers + fc.integer({ min: 0, max: 100 }), + async (maxBootstrapPeers, numPeers) => { + const result = (await (waku.lightPush as any).getPeers({ + numPeers, + maxBootstrapPeers + })) as Peer[]; + + if (numPeers === 0) { + // Expect all peers when numPeers is 0 + expect(result.length).to.be.greaterThanOrEqual(1); + } else { + // Expect up to numPeers peers + expect(result.length).to.be.lessThanOrEqual(numPeers); + } + } + ), + { + verbose: true + } + ); + }); + }); +}); diff --git a/packages/tests/tests/peer_exchange.node.spec.ts b/packages/tests/tests/peer_exchange.node.spec.ts index 657d2c1781..996e1e1adc 100644 --- a/packages/tests/tests/peer_exchange.node.spec.ts +++ b/packages/tests/tests/peer_exchange.node.spec.ts @@ -1,4 +1,6 @@ +import type { PeerId } from "@libp2p/interface/peer-id"; import tests from "@libp2p/interface-compliance-tests/peer-discovery"; +import type { Multiaddr } from "@multiformats/multiaddr"; import type { LightNode, PeerInfo } from "@waku/interfaces"; import { PeerExchangeCodec, @@ -9,7 +11,7 @@ import { createLightNode, Libp2pComponents } from "@waku/sdk"; import { expect } from "chai"; import { delay } from "../src/delay.js"; -import { tearDownNodes } from "../src/index.js"; +import { tearDownNodes, waitForRemotePeerWithCodec } from "../src/index.js"; import { makeLogFileName } from "../src/log_file.js"; import { NimGoNode } from "../src/node/node.js"; @@ -48,11 +50,13 @@ describe("Peer Exchange", () => { }); const nwaku1PeerId = await nwaku1.getPeerId(); + const nwaku2PeerId = await nwaku2.getPeerId(); const nwaku2Ma = await nwaku2.getMultiaddrWithId(); waku = await createLightNode(); await waku.start(); await waku.libp2p.dialProtocol(nwaku2Ma, PeerExchangeCodec); + await waitForRemotePeerWithCodec(waku, PeerExchangeCodec, nwaku2PeerId); const components = waku.libp2p.components as unknown as Libp2pComponents; const peerExchange = new WakuPeerExchange(components); @@ -62,6 +66,7 @@ describe("Peer Exchange", () => { let peerInfos: PeerInfo[] = []; while (peerInfos.length <= 0) { peerInfos = (await peerExchange.query({ + peerId: nwaku2PeerId, numPeers: numPeersToRequest })) as PeerInfo[]; await delay(3000); @@ -70,16 +75,35 @@ describe("Peer Exchange", () => { expect(peerInfos.length).to.be.greaterThan(0); expect(peerInfos.length).to.be.lessThanOrEqual(numPeersToRequest); expect(peerInfos[0].ENR).to.not.be.null; + expect(peerInfos[0].ENR?.peerInfo?.multiaddrs).to.not.be.null; - const doesPeerIdExistInResponse = - peerInfos.find( - ({ ENR }) => ENR?.peerInfo?.id.toString() === nwaku1PeerId.toString() - ) !== undefined; + let foundNodeMultiaddrs: Multiaddr[] = []; + let foundNodePeerId: PeerId | undefined = undefined; + const doesPeerIdExistInResponse = peerInfos.some(({ ENR }) => { + foundNodeMultiaddrs = ENR?.peerInfo?.multiaddrs ?? []; + foundNodePeerId = ENR?.peerInfo?.id; + return ENR?.peerInfo?.id.toString() === nwaku1PeerId.toString(); + }); + + if (!foundNodePeerId) { + throw new Error("Peer ID not found"); + } + + if (!foundNodePeerId) { + throw new Error("Peer ID not found"); + } expect(doesPeerIdExistInResponse).to.be.equal(true); - expect(await waku.libp2p.peerStore.has(await nwaku2.getPeerId())).to.be - .true; + await waku.libp2p.dialProtocol(foundNodeMultiaddrs, PeerExchangeCodec); + await waitForRemotePeerWithCodec( + waku, + PeerExchangeCodec, + foundNodePeerId + ); + + expect(await waku.libp2p.peerStore.has(nwaku1PeerId)).to.eq(true); + expect(waku.libp2p.getConnections()).has.length(2); }); }); diff --git a/packages/tests/tests/utils.spec.ts b/packages/tests/tests/utils.spec.ts index 3eac491d9e..9d2b0e9f93 100644 --- a/packages/tests/tests/utils.spec.ts +++ b/packages/tests/tests/utils.spec.ts @@ -1,16 +1,14 @@ -import type { PeerStore } from "@libp2p/interface/peer-store"; -import type { Peer } from "@libp2p/interface/peer-store"; -import { createSecp256k1PeerId } from "@libp2p/peer-id-factory"; import { createDecoder, createEncoder, waitForRemotePeer } from "@waku/core"; -import { DefaultPubsubTopic, LightNode } from "@waku/interfaces"; -import { Protocols } from "@waku/interfaces"; +import { + DefaultPubsubTopic, + type LightNode, + Protocols +} from "@waku/interfaces"; import { createLightNode } from "@waku/sdk"; import { toAsyncIterator } from "@waku/utils"; import { bytesToUtf8, utf8ToBytes } from "@waku/utils/bytes"; -import { selectPeerForProtocol } from "@waku/utils/libp2p"; import chai, { expect } from "chai"; import chaiAsPromised from "chai-as-promised"; -import sinon from "sinon"; import { delay, @@ -116,172 +114,3 @@ describe("Util: toAsyncIterator: Filter", () => { expect(result.done).to.eq(true); }); }); - -const TestCodec = "test/1"; - -describe("selectPeerForProtocol", () => { - let peerStore: PeerStore; - const protocols = [TestCodec]; - - let lowPingPeer: Peer, - midPingPeer: Peer, - highPingPeer: Peer, - differentCodecPeer: Peer, - anotherDifferentCodecPeer: Peer; - - beforeEach(async function () { - this.timeout(10000); - const waku = await createLightNode(); - await waku.start(); - await delay(3000); - peerStore = waku.libp2p.peerStore; - - const [ - lowPingPeerId, - midPingPeerId, - highPingPeerId, - differentCodecPeerId, - anotherDifferentCodecPeerId - ] = await Promise.all([ - createSecp256k1PeerId(), - createSecp256k1PeerId(), - createSecp256k1PeerId(), - createSecp256k1PeerId(), - createSecp256k1PeerId() - ]); - - lowPingPeer = { - id: lowPingPeerId, - protocols: [TestCodec], - metadata: new Map().set("ping", utf8ToBytes("50")) - } as Peer; - - midPingPeer = { - id: midPingPeerId, - protocols: [TestCodec], - metadata: new Map().set("ping", utf8ToBytes("100")) - } as Peer; - - highPingPeer = { - id: highPingPeerId, - protocols: [TestCodec], - metadata: new Map().set("ping", utf8ToBytes("500")) - } as Peer; - - differentCodecPeer = { - id: differentCodecPeerId, - protocols: ["DifferentCodec"] - } as Peer; - - anotherDifferentCodecPeer = { - id: anotherDifferentCodecPeerId, - protocols: ["AnotherDifferentCodec"] - } as Peer; - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should return the peer with the lowest ping", async function () { - const mockPeers = [highPingPeer, lowPingPeer, midPingPeer]; - - sinon.stub(peerStore, "get").callsFake(async (peerId) => { - return mockPeers.find((peer) => peer.id.equals(peerId))!; - }); - - sinon.stub(peerStore, "forEach").callsFake(async (callback) => { - for (const peer of mockPeers) { - callback(peer); - } - }); - - const result = await selectPeerForProtocol(peerStore, protocols); - - expect(result.peer).to.deep.equal(lowPingPeer); - expect(result.protocol).to.equal(TestCodec); - }); - - it("should return the peer with the provided peerId", async function () { - const targetPeer = await createSecp256k1PeerId(); - const mockPeer = { id: targetPeer, protocols: [TestCodec] } as Peer; - sinon.stub(peerStore, "get").withArgs(targetPeer).resolves(mockPeer); - - const result = await selectPeerForProtocol( - peerStore, - protocols, - targetPeer - ); - expect(result.peer).to.deep.equal(mockPeer); - }); - - it("should return a random peer when all peers have the same latency", async function () { - const mockPeers = [highPingPeer, highPingPeer, highPingPeer]; - - sinon.stub(peerStore, "get").callsFake(async (peerId) => { - return mockPeers.find((peer) => peer.id.equals(peerId))!; - }); - - sinon.stub(peerStore, "forEach").callsFake(async (callback) => { - for (const peer of mockPeers) { - callback(peer); - } - }); - - const result = await selectPeerForProtocol(peerStore, protocols); - - expect(mockPeers).to.deep.include(result.peer); - }); - - it("should throw an error when no peer matches the given protocols", async function () { - const mockPeers = [differentCodecPeer, anotherDifferentCodecPeer]; - - sinon.stub(peerStore, "forEach").callsFake(async (callback) => { - for (const peer of mockPeers) { - callback(peer); - } - }); - - await expect( - selectPeerForProtocol(peerStore, protocols) - ).to.be.rejectedWith( - `Failed to find known peer that registers protocols: ${protocols}` - ); - }); - - it("should throw an error when the selected peer does not register the required protocols", async function () { - const targetPeer = await createSecp256k1PeerId(); - const mockPeer = { id: targetPeer, protocols: ["DifferentCodec"] } as Peer; - sinon.stub(peerStore, "get").withArgs(targetPeer).resolves(mockPeer); - - await expect( - selectPeerForProtocol(peerStore, protocols, targetPeer) - ).to.be.rejectedWith( - `Peer does not register required protocols (${targetPeer.toString()}): ${protocols}` - ); - }); -}); - -describe("getConnectedPeersForProtocol", function () { - let waku: LightNode; - let nwaku: NimGoNode; - - beforeEach(async function () { - this.timeout(15000); - nwaku = new NimGoNode(makeLogFileName(this)); - await nwaku.start({ - filter: true, - lightpush: true, - relay: true - }); - waku = await createLightNode(); - await waku.start(); - await waku.dial(await nwaku.getMultiaddrWithId()); - await waitForRemotePeer(waku, [Protocols.Filter]); - }); - - afterEach(async function () { - this.timeout(10000); - await tearDownNodes(nwaku, waku); - }); -}); diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index fcb5eeedfd..9d8a4d2d9f 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -12,6 +12,33 @@ * devDependencies * @waku/interfaces bumped from 0.0.16 to 0.0.17 +## [0.0.14](https://github.com/waku-org/js-waku/compare/utils-v0.0.13...utils-v0.0.14) (2024-01-10) + + +### ⚠ BREAKING CHANGES + +* add support for sharded pubsub topics & remove support for named pubsub topics ([#1697](https://github.com/waku-org/js-waku/issues/1697)) +* change all instances of `PubSubTopic` to `PubsubTopic` ([#1703](https://github.com/waku-org/js-waku/issues/1703)) + +### Features + +* Add function for determining shard index from content topic ([86da696](https://github.com/waku-org/js-waku/commit/86da6962bac91a8719de1f9cd60e9f7bc13e48f1)) +* Add function to validate autoshard content topic ([1bc1eb5](https://github.com/waku-org/js-waku/commit/1bc1eb509166e6dfcb24c59a90eb05f5dc16de78)) +* Add support for autosharded pubsub topics ([2bc3735](https://github.com/waku-org/js-waku/commit/2bc3735e4dcf85f06b3dee542024d7f20a40fac2)) +* Add support for sharded pubsub topics & remove support for named pubsub topics ([#1697](https://github.com/waku-org/js-waku/issues/1697)) ([4cf2ffe](https://github.com/waku-org/js-waku/commit/4cf2ffefa75e0571805036b71644d2cdd4fe3192)) + + +### Miscellaneous Chores + +* Change all instances of `PubSubTopic` to `PubsubTopic` ([#1703](https://github.com/waku-org/js-waku/issues/1703)) ([3166a51](https://github.com/waku-org/js-waku/commit/3166a5135e77583da4fa722ee2aa47c785854a38)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @waku/interfaces bumped from 0.0.20 to 0.0.21 + ## [0.0.13](https://github.com/waku-org/js-waku/compare/utils-v0.0.12...utils-v0.0.13) (2023-11-01) diff --git a/packages/utils/package.json b/packages/utils/package.json index b5bfdc41ac..ac8c44fb19 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@waku/utils", - "version": "0.0.13", + "version": "0.0.14", "description": "Different utilities for Waku", "types": "./dist/index.d.ts", "module": "./dist/index.js", @@ -68,7 +68,7 @@ }, "dependencies": { "@noble/hashes": "^1.3.2", - "@waku/interfaces": "0.0.20", + "@waku/interfaces": "0.0.21", "chai": "^4.3.10", "debug": "^4.3.4", "fast-check": "^3.14.0", diff --git a/packages/utils/src/common/sharding.ts b/packages/utils/src/common/sharding.ts index f6f3cd7b34..44661df474 100644 --- a/packages/utils/src/common/sharding.ts +++ b/packages/utils/src/common/sharding.ts @@ -22,6 +22,7 @@ export const shardInfoToPubsubTopics = ( ): PubsubTopic[] => { if (shardInfo.clusterId === undefined) throw new Error("Cluster ID must be specified"); + if ("contentTopics" in shardInfo) { // Autosharding: explicitly defined content topics return Array.from( diff --git a/packages/utils/src/libp2p/index.ts b/packages/utils/src/libp2p/index.ts index 44de86d0e4..4bc305eca2 100644 --- a/packages/utils/src/libp2p/index.ts +++ b/packages/utils/src/libp2p/index.ts @@ -1,7 +1,6 @@ import type { Connection } from "@libp2p/interface/connection"; -import type { PeerId } from "@libp2p/interface/peer-id"; import type { Peer, PeerStore } from "@libp2p/interface/peer-store"; -import type { ShardInfo } from "@waku/interfaces"; +import type { ShardingParams } from "@waku/interfaces"; import { bytesToUtf8 } from "../bytes/index.js"; import { decodeRelayShard } from "../common/relay_shard_codec.js"; @@ -18,35 +17,39 @@ export function selectRandomPeer(peers: Peer[]): Peer | undefined { } /** - * Returns the peer with the lowest latency. + * Function to sort peers by latency from lowest to highest * @param peerStore - The Libp2p PeerStore * @param peers - The list of peers to choose from - * @returns The peer with the lowest latency, or undefined if no peer could be reached + * @returns Sorted array of peers by latency */ -export async function selectLowestLatencyPeer( +export async function sortPeersByLatency( peerStore: PeerStore, peers: Peer[] -): Promise { - if (peers.length === 0) return; +): Promise { + if (peers.length === 0) return []; const results = await Promise.all( peers.map(async (peer) => { - const pingBytes = (await peerStore.get(peer.id)).metadata.get("ping"); - if (!pingBytes) return { peer, ping: Infinity }; + try { + const pingBytes = (await peerStore.get(peer.id)).metadata.get("ping"); + if (!pingBytes) return { peer, ping: Infinity }; - const ping = Number(bytesToUtf8(pingBytes)) ?? Infinity; - return { peer, ping }; + const ping = Number(bytesToUtf8(pingBytes)); + return { peer, ping }; + } catch (error) { + return { peer, ping: Infinity }; + } }) ); - const lowestLatencyResult = results.sort((a, b) => a.ping - b.ping)[0]; - if (!lowestLatencyResult) { - return undefined; - } + // filter out null values + const validResults = results.filter( + (result): result is { peer: Peer; ping: number } => result !== null + ); - return lowestLatencyResult.ping !== Infinity - ? lowestLatencyResult.peer - : undefined; + return validResults + .sort((a, b) => a.ping - b.ping) + .map((result) => result.peer); } /** @@ -72,7 +75,7 @@ export async function getConnectedPeersForProtocolAndShard( connections: Connection[], peerStore: PeerStore, protocols: string[], - shardInfo?: ShardInfo + shardInfo?: ShardingParams ): Promise { const openConnections = connections.filter( (connection) => connection.status === "open" @@ -86,6 +89,12 @@ export async function getConnectedPeersForProtocolAndShard( if (supportsProtocol) { if (shardInfo) { + //TODO: support auto-sharding + if (!("shards" in shardInfo)) { + throw new Error( + `Connections Manager only supports static sharding for now. Autosharding is not supported.` + ); + } const encodedPeerShardInfo = peer.metadata.get("shardInfo"); const peerShardInfo = encodedPeerShardInfo && decodeRelayShard(encodedPeerShardInfo); @@ -104,53 +113,6 @@ export async function getConnectedPeersForProtocolAndShard( return peersWithNulls.filter((peer): peer is Peer => peer !== null); } -/** - * Returns a peer that supports the given protocol. - * If peerId is provided, the peer with that id is returned. - * Otherwise, the peer with the lowest latency is returned. - * If no peer is found from the above criteria, a random peer is returned. - */ -export async function selectPeerForProtocol( - peerStore: PeerStore, - protocols: string[], - peerId?: PeerId -): Promise<{ peer: Peer; protocol: string }> { - let peer: Peer | undefined; - if (peerId) { - peer = await peerStore.get(peerId); - if (!peer) { - throw new Error( - `Failed to retrieve connection details for provided peer in peer store: ${peerId.toString()}` - ); - } - } else { - const peers = await getPeersForProtocol(peerStore, protocols); - peer = await selectLowestLatencyPeer(peerStore, peers); - if (!peer) { - peer = selectRandomPeer(peers); - if (!peer) - throw new Error( - `Failed to find known peer that registers protocols: ${protocols}` - ); - } - } - - let protocol; - for (const codec of protocols) { - if (peer.protocols.includes(codec)) { - protocol = codec; - // Do not break as we want to keep the last value - } - } - if (!protocol) { - throw new Error( - `Peer does not register required protocols (${peer.id.toString()}): ${protocols}` - ); - } - - return { peer, protocol }; -} - export function selectConnection( connections: Connection[] ): Connection | undefined {