diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0b938c2e48..4917feab55 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -257,6 +257,7 @@ jobs: git update-index --assume-unchanged packages/libp2p/src/version.ts npm run --if-present release env: + GITHUB_TOKEN: ${{ secrets.UCI_GITHUB_TOKEN || github.token }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - if: ${{ !steps.release.outputs.releases_created }} name: Run release rc @@ -264,4 +265,5 @@ jobs: git update-index --assume-unchanged packages/libp2p/src/version.ts npm run --if-present release:rc env: + GITHUB_TOKEN: ${{ secrets.UCI_GITHUB_TOKEN || github.token }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c4974f9951..b9b7976ad1 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,25 +1 @@ -{ - "packages/crypto":"1.0.17", - "packages/interface":"0.0.1", - "packages/interface-compliance-tests":"3.0.7", - "packages/interface-internal":"0.0.1", - "packages/kad-dht":"9.3.6", - "packages/keychain":"2.0.1", - "packages/libp2p":"0.45.9", - "packages/logger":"2.1.1", - "packages/metrics-prometheus":"1.1.5", - "packages/multistream-select":"3.1.9", - "packages/peer-collections":"3.0.2", - "packages/peer-discovery-bootstrap":"8.0.0", - "packages/peer-discovery-mdns":"8.0.0", - "packages/peer-id":"2.0.3", - "packages/peer-id-factory":"2.0.3", - "packages/peer-record":"5.0.4", - "packages/peer-store":"8.2.1", - "packages/stream-multiplexer-mplex":"8.0.4", - "packages/transport-tcp":"7.0.3", - "packages/transport-webrtc":"2.0.10", - "packages/transport-websockets":"6.0.3", - "packages/transport-webtransport":"2.0.2", - "packages/utils":"3.0.12" -} \ No newline at end of file +{"packages/crypto":"2.0.0","packages/interface":"0.1.0","packages/interface-compliance-tests":"4.0.0","packages/interface-internal":"0.1.0","packages/kad-dht":"10.0.0","packages/keychain":"3.0.0","packages/libp2p":"0.46.1","packages/logger":"3.0.0","packages/metrics-prometheus":"2.0.0","packages/multistream-select":"4.0.0","packages/peer-collections":"4.0.0","packages/peer-discovery-bootstrap":"9.0.0","packages/peer-discovery-mdns":"9.0.0","packages/peer-id":"3.0.0","packages/peer-id-factory":"3.0.0","packages/peer-record":"6.0.0","packages/peer-store":"9.0.0","packages/pubsub":"8.0.1","packages/pubsub-floodsub":"8.0.1","packages/stream-multiplexer-mplex":"9.0.0","packages/transport-tcp":"8.0.0","packages/transport-webrtc":"3.1.1","packages/transport-websockets":"7.0.0","packages/transport-webtransport":"3.0.1","packages/utils":"4.0.0"} \ No newline at end of file diff --git a/.release-please.json b/.release-please.json index 7c35f24e85..f5f1cdd208 100644 --- a/.release-please.json +++ b/.release-please.json @@ -21,6 +21,8 @@ "packages/peer-id-factory": {}, "packages/peer-record": {}, "packages/peer-store": {}, + "packages/pubsub": {}, + "packages/pubsub-floodsub": {}, "packages/stream-multiplexer-mplex": {}, "packages/transport-tcp": {}, "packages/transport-webrtc": {}, diff --git a/doc/package.json b/doc/package.json index faba3d9d59..227b6afc4d 100644 --- a/doc/package.json +++ b/doc/package.json @@ -28,13 +28,13 @@ "doc-check": "aegir doc-check" }, "dependencies": { - "@chainsafe/libp2p-yamux": "^4.0.2", - "@libp2p/interface": "~0.0.1", - "@libp2p/mplex": "^8.0.4", - "@libp2p/prometheus-metrics": "^1.1.5", - "@libp2p/tcp": "^7.0.3", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/prometheus-metrics": "^2.0.0", + "@libp2p/tcp": "^8.0.0", "aegir": "^40.0.1", - "libp2p": "^0.45.9", + "libp2p": "^0.46.0", "prom-client": "^14.2.0" }, "private": true diff --git a/examples/auto-relay/package.json b/examples/auto-relay/package.json index e7afa1d087..a7caa2c4e9 100644 --- a/examples/auto-relay/package.json +++ b/examples/auto-relay/package.json @@ -29,12 +29,12 @@ "test:example": "node test.js" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/websockets": "^6.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/websockets": "^7.0.0", "@multiformats/multiaddr": "^12.1.3", - "libp2p": "^0.45.0" + "libp2p": "^0.46.0" }, "devDependencies": { "aegir": "^40.0.1", diff --git a/examples/chat/package.json b/examples/chat/package.json index 68106d5220..05daabb978 100644 --- a/examples/chat/package.json +++ b/examples/chat/package.json @@ -29,18 +29,18 @@ "test:example": "node test.js" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/peer-id-factory": "^2.0.0", - "@libp2p/tcp": "^7.0.0", - "@libp2p/websockets": "^6.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/peer-id-factory": "^3.0.0", + "@libp2p/tcp": "^8.0.0", + "@libp2p/websockets": "^7.0.0", "@multiformats/multiaddr": "^12.1.3", "@nodeutils/defaults-deep": "^1.1.0", "it-length-prefixed": "^9.0.1", "it-map": "^3.0.3", "it-pipe": "^3.0.1", - "libp2p": "^0.45.0", + "libp2p": "^0.46.0", "uint8arrays": "^4.0.4" }, "devDependencies": { diff --git a/examples/connection-encryption/package.json b/examples/connection-encryption/package.json index 324fd4a0ef..bf9deae5b7 100644 --- a/examples/connection-encryption/package.json +++ b/examples/connection-encryption/package.json @@ -29,12 +29,12 @@ "test:example": "node test.js" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/tcp": "^7.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/tcp": "^8.0.0", "it-pipe": "^3.0.1", - "libp2p": "^0.45.0", + "libp2p": "^0.46.0", "uint8arrays": "^4.0.4" }, "devDependencies": { diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 8d1c63d59d..da79b04c23 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -32,15 +32,15 @@ "start": "react-scripts start" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@libp2p/bootstrap": "^8.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@libp2p/bootstrap": "^9.0.0", "@libp2p/delegated-content-routing": "^4.0.0", "@libp2p/delegated-peer-routing": "^4.0.0", - "@libp2p/kad-dht": "^9.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/websockets": "^6.0.0", + "@libp2p/kad-dht": "^10.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/websockets": "^7.0.0", "kubo-rpc-client": "^3.0.1", - "libp2p": "^0.45.0", + "libp2p": "^0.46.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "^5.0.1" diff --git a/examples/discovery-mechanisms/package.json b/examples/discovery-mechanisms/package.json index 62f24dd0e3..770bea9090 100644 --- a/examples/discovery-mechanisms/package.json +++ b/examples/discovery-mechanisms/package.json @@ -30,15 +30,15 @@ "test:example": "node test.js" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/bootstrap": "^8.0.0", - "@libp2p/floodsub": "^7.0.0", - "@libp2p/mdns": "^8.0.0", - "@libp2p/mplex": "^8.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/bootstrap": "^9.0.0", + "@libp2p/floodsub": "^8.0.0", + "@libp2p/mdns": "^9.0.0", + "@libp2p/mplex": "^9.0.0", "@libp2p/pubsub-peer-discovery": "^8.0.4", - "@libp2p/tcp": "^7.0.0", - "libp2p": "^0.45.0" + "@libp2p/tcp": "^8.0.0", + "libp2p": "^0.46.0" }, "devDependencies": { "aegir": "^40.0.1", diff --git a/examples/echo/package.json b/examples/echo/package.json index 488c15b7d4..03c21a644e 100644 --- a/examples/echo/package.json +++ b/examples/echo/package.json @@ -29,16 +29,16 @@ "test:example": "node test.js" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/peer-id-factory": "^2.0.0", - "@libp2p/tcp": "^7.0.0", - "@libp2p/websockets": "^6.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/peer-id-factory": "^3.0.0", + "@libp2p/tcp": "^8.0.0", + "@libp2p/websockets": "^7.0.0", "@multiformats/multiaddr": "^12.1.3", "@nodeutils/defaults-deep": "^1.1.0", "it-pipe": "^3.0.1", - "libp2p": "^0.45.0", + "libp2p": "^0.46.0", "uint8arrays": "^4.0.4" }, "devDependencies": { diff --git a/examples/libp2p-in-the-browser/webrtc/browser-to-browser/package.json b/examples/libp2p-in-the-browser/webrtc/browser-to-browser/package.json index cfabb369fa..050ca2ce6a 100644 --- a/examples/libp2p-in-the-browser/webrtc/browser-to-browser/package.json +++ b/examples/libp2p-in-the-browser/webrtc/browser-to-browser/package.json @@ -12,13 +12,13 @@ "test": "npm run build && test-browser-example tests" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", "@libp2p/websockets": "^6.0.1", "@libp2p/mplex": "^8.0.1", "@libp2p/webrtc": "file:../../", "@multiformats/multiaddr": "^12.0.0", "it-pushable": "^3.2.0", - "libp2p": "^0.45.0", + "libp2p": "^0.46.0", "vite": "^4.2.1" }, "devDependencies": { diff --git a/examples/libp2p-in-the-browser/webrtc/browser-to-server/package.json b/examples/libp2p-in-the-browser/webrtc/browser-to-server/package.json index 0b084031a7..7abaa793c5 100644 --- a/examples/libp2p-in-the-browser/webrtc/browser-to-server/package.json +++ b/examples/libp2p-in-the-browser/webrtc/browser-to-server/package.json @@ -12,11 +12,11 @@ "test": "npm run build && test-browser-example tests" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", "@libp2p/webrtc": "file:../../", "@multiformats/multiaddr": "^12.0.0", "it-pushable": "^3.2.0", - "libp2p": "^0.45.0", + "libp2p": "^0.46.0", "vite": "^4.2.1" }, "devDependencies": { diff --git a/examples/libp2p-in-the-browser/websockets/package.json b/examples/libp2p-in-the-browser/websockets/package.json index c45b265ee0..aaf2a8bbd4 100644 --- a/examples/libp2p-in-the-browser/websockets/package.json +++ b/examples/libp2p-in-the-browser/websockets/package.json @@ -30,16 +30,16 @@ "test:example": "node test.js" }, "dependencies": { - "@chainsafe/libp2p-gossipsub": "^9.0.0", - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/bootstrap": "^8.0.0", - "@libp2p/kad-dht": "^9.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/webrtc": "^2.0.0", - "@libp2p/websockets": "^6.0.0", - "@libp2p/webtransport": "^2.0.0", - "libp2p": "^0.45.0" + "@chainsafe/libp2p-gossipsub": "^10.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/bootstrap": "^9.0.0", + "@libp2p/kad-dht": "^10.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/webrtc": "^3.0.0", + "@libp2p/websockets": "^7.0.0", + "@libp2p/webtransport": "^3.0.0", + "libp2p": "^0.46.0" }, "devDependencies": { "aegir": "^40.0.1", diff --git a/examples/libp2p-in-the-browser/webtransport/fetch-file-from-kubo/package.json b/examples/libp2p-in-the-browser/webtransport/fetch-file-from-kubo/package.json index e6aa5a2125..139ddfa1c3 100644 --- a/examples/libp2p-in-the-browser/webtransport/fetch-file-from-kubo/package.json +++ b/examples/libp2p-in-the-browser/webtransport/fetch-file-from-kubo/package.json @@ -10,12 +10,12 @@ "test": "npm run build && test-browser-example tests" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.1", + "@chainsafe/libp2p-noise": "^13.0.1", "@libp2p/webtransport": "../..", "@multiformats/multiaddr": "^12.1.2", "blockstore-core": "^4.1.0", "ipfs-bitswap": "^18.0.1", - "libp2p": "^0.45.9", + "libp2p": "^0.46.0", "multiformats": "^12.0.1" }, "devDependencies": { diff --git a/examples/peer-and-content-routing/package.json b/examples/peer-and-content-routing/package.json index e5a2fae00a..55b3bccfc6 100644 --- a/examples/peer-and-content-routing/package.json +++ b/examples/peer-and-content-routing/package.json @@ -29,14 +29,14 @@ "test:example": "node test.js" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/kad-dht": "^9.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/tcp": "^7.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/kad-dht": "^10.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/tcp": "^8.0.0", "delay": "^6.0.0", "it-all": "^3.0.2", - "libp2p": "^0.45.0", + "libp2p": "^0.46.0", "multiformats": "^12.0.1" }, "devDependencies": { diff --git a/examples/pnet/package.json b/examples/pnet/package.json index f0d2247738..aea4a85a03 100644 --- a/examples/pnet/package.json +++ b/examples/pnet/package.json @@ -29,12 +29,12 @@ "test:example": "node test.js" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/tcp": "^7.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/tcp": "^8.0.0", "it-pipe": "^3.0.1", - "libp2p": "^0.45.0", + "libp2p": "^0.46.0", "uint8arrays": "^4.0.4" }, "devDependencies": { diff --git a/examples/protocol-and-stream-muxing/package.json b/examples/protocol-and-stream-muxing/package.json index ae08d63228..6f5b6894aa 100644 --- a/examples/protocol-and-stream-muxing/package.json +++ b/examples/protocol-and-stream-muxing/package.json @@ -29,12 +29,12 @@ "test:example": "node test.js" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/tcp": "^7.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/tcp": "^8.0.0", "it-pipe": "^3.0.1", - "libp2p": "^0.45.0", + "libp2p": "^0.46.0", "uint8arrays": "^4.0.4" }, "devDependencies": { diff --git a/examples/pubsub/package.json b/examples/pubsub/package.json index 46c74b808f..3d250f03ea 100644 --- a/examples/pubsub/package.json +++ b/examples/pubsub/package.json @@ -29,12 +29,12 @@ "test:example": "node test.js" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/floodsub": "^7.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/tcp": "^7.0.0", - "libp2p": "^0.45.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/floodsub": "^8.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/tcp": "^8.0.0", + "libp2p": "^0.46.0", "uint8arrays": "^4.0.4" }, "devDependencies": { diff --git a/examples/transports/package.json b/examples/transports/package.json index 9ec94abe15..4a9b50f8f8 100644 --- a/examples/transports/package.json +++ b/examples/transports/package.json @@ -29,14 +29,14 @@ "test:example": "node test.js" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/tcp": "^7.0.0", - "@libp2p/websockets": "^6.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/tcp": "^8.0.0", + "@libp2p/websockets": "^7.0.0", "it-pipe": "^3.0.1", "it-to-buffer": "^4.0.2", - "libp2p": "^0.45.0", + "libp2p": "^0.46.0", "uint8arrays": "^4.0.4" }, "devDependencies": { diff --git a/interop/package.json b/interop/package.json index 3ba0f0d05d..38ac9beb07 100644 --- a/interop/package.json +++ b/interop/package.json @@ -39,17 +39,17 @@ "test:interop:multidim": "aegir test" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/tcp": "^7.0.0", - "@libp2p/webrtc": "^2.0.0", - "@libp2p/websockets": "^6.0.0", - "@libp2p/webtransport": "^2.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/tcp": "^8.0.0", + "@libp2p/webrtc": "^3.0.0", + "@libp2p/websockets": "^7.0.0", + "@libp2p/webtransport": "^3.0.0", "@multiformats/mafmt": "^12.1.2", "@multiformats/multiaddr": "^12.1.3", - "libp2p": "^0.45.0", - "redis": "4.5.1" + "libp2p": "^0.46.0", + "redis": "^4.5.1" }, "devDependencies": { "aegir": "^40.0.1" diff --git a/interop/tsconfig.json b/interop/tsconfig.json index cbf9215e81..5be3797bc7 100644 --- a/interop/tsconfig.json +++ b/interop/tsconfig.json @@ -8,18 +8,12 @@ "test" ], "references": [ - { - "path": "../packages/connection-encryption-noise" - }, { "path": "../packages/libp2p" }, { "path": "../packages/stream-multiplexer-mplex" }, - { - "path": "../packages/stream-multiplexer-yamux" - }, { "path": "../packages/transport-tcp" }, diff --git a/package.json b/package.json index e1bedeee69..b36a7e5759 100644 --- a/package.json +++ b/package.json @@ -30,10 +30,11 @@ "clean": "aegir run clean", "lint": "aegir run lint", "dep-check": "aegir run dep-check", - "release": "run-s build npm:release", + "release": "run-s build docs:no-publish npm:release docs", "npm:release": "aegir exec --bail false npm -- publish", "release:rc": "aegir release-rc", - "docs": "NODE_OPTIONS=--max_old_space_size=8192 aegir docs -- --exclude interop --exclude examples --exclude doc" + "docs": "NODE_OPTIONS=--max_old_space_size=8192 aegir docs -- --exclude interop --exclude examples --exclude doc", + "docs:no-publish": "NODE_OPTIONS=--max_old_space_size=8192 aegir docs --publish false -- --exclude interop --exclude examples --exclude doc" }, "devDependencies": { "aegir": "^40.0.1" diff --git a/packages/connection-encryption-noise/package.json b/packages/connection-encryption-noise/package.json deleted file mode 100644 index 7cda8e6f13..0000000000 --- a/packages/connection-encryption-noise/package.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "name": "@chainsafe/libp2p-noise", - "version": "12.0.1", - "author": "ChainSafe ", - "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/connection-encryption-noise#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/libp2p/js-libp2p.git" - }, - "bugs": { - "url": "https://github.com/libp2p/js-libp2p/issues" - }, - "keywords": [ - "crypto", - "libp2p", - "noise" - ], - "type": "module", - "types": "./dist/src/index.d.ts", - "files": [ - "src", - "dist", - "!dist/test", - "!**/*.tsbuildinfo" - ], - "exports": { - ".": { - "types": "./dist/src/index.d.ts", - "import": "./dist/src/index.js" - } - }, - "eslintConfig": { - "extends": "ipfs", - "parserOptions": { - "sourceType": "module" - }, - "rules": { - "@typescript-eslint/no-unused-vars": "error", - "@typescript-eslint/explicit-function-return-type": "warn", - "@typescript-eslint/strict-boolean-expressions": "off" - }, - "ignorePatterns": [ - "src/proto/payload.js", - "src/proto/payload.d.ts", - "test/fixtures/node-globals.js" - ] - }, - "scripts": { - "bench": "node benchmarks/benchmark.js", - "clean": "aegir clean", - "dep-check": "aegir dep-check", - "build": "aegir build", - "lint": "aegir lint", - "lint:fix": "aegir lint --fix", - "test": "aegir test", - "test:node": "aegir test -t node", - "test:browser": "aegir test -t browser -t webworker", - "test:electron-main": "aegir test -t electron-main", - "docs": "aegir docs", - "proto:gen": "protons ./src/proto/payload.proto", - "prepublish": "npm run build" - }, - "dependencies": { - "@libp2p/crypto": "^1.0.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/peer-id": "^2.0.0", - "@noble/hashes": "^1.3.0", - "@stablelib/chacha20poly1305": "^1.0.1", - "@stablelib/x25519": "^1.0.3", - "it-byte-stream": "^1.0.0", - "it-length-prefixed": "^9.0.1", - "it-length-prefixed-stream": "^1.0.0", - "it-pair": "^2.0.6", - "it-pipe": "^3.0.1", - "it-stream-types": "^2.0.1", - "protons-runtime": "^5.0.0", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^4.0.4" - }, - "devDependencies": { - "@libp2p/interface-compliance-tests": "^3.0.0", - "@libp2p/peer-id-factory": "^2.0.0", - "@types/sinon": "^10.0.15", - "aegir": "^40.0.1", - "iso-random-stream": "^2.0.2", - "protons": "^7.0.2", - "sinon": "^15.1.2" - }, - "browser": { - "./dist/src/alloc-unsafe.js": "./dist/src/alloc-unsafe-browser.js", - "util": false - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - }, - "private": true -} diff --git a/packages/connection-encryption-noise/src/@types/basic.ts b/packages/connection-encryption-noise/src/@types/basic.ts deleted file mode 100644 index 364d1f89ee..0000000000 --- a/packages/connection-encryption-noise/src/@types/basic.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type bytes = Uint8Array -export type bytes32 = Uint8Array -export type bytes16 = Uint8Array - -export type uint64 = number diff --git a/packages/connection-encryption-noise/src/@types/handshake-interface.ts b/packages/connection-encryption-noise/src/@types/handshake-interface.ts deleted file mode 100644 index 9b402b1fd3..0000000000 --- a/packages/connection-encryption-noise/src/@types/handshake-interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { bytes } from './basic.js' -import type { NoiseSession } from './handshake.js' -import type { NoiseExtensions } from '../proto/payload.js' -import type { PeerId } from '@libp2p/interface/peer-id' - -export interface IHandshake { - session: NoiseSession - remotePeer: PeerId - remoteExtensions: NoiseExtensions - encrypt: (plaintext: bytes, session: NoiseSession) => bytes - decrypt: (ciphertext: bytes, session: NoiseSession, dst?: Uint8Array) => { plaintext: bytes, valid: boolean } -} diff --git a/packages/connection-encryption-noise/src/@types/handshake.ts b/packages/connection-encryption-noise/src/@types/handshake.ts deleted file mode 100644 index ec333b703b..0000000000 --- a/packages/connection-encryption-noise/src/@types/handshake.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { bytes, bytes32, uint64 } from './basic.js' -import type { KeyPair } from './libp2p.js' -import type { Nonce } from '../nonce.js' - -export type Hkdf = [bytes, bytes, bytes] - -export interface MessageBuffer { - ne: bytes32 - ns: bytes - ciphertext: bytes -} - -export interface CipherState { - k: bytes32 - // For performance reasons, the nonce is represented as a Nonce object - // The nonce is treated as a uint64, even though the underlying `number` only has 52 safely-available bits. - n: Nonce -} - -export interface SymmetricState { - cs: CipherState - ck: bytes32 // chaining key - h: bytes32 // handshake hash -} - -export interface HandshakeState { - ss: SymmetricState - s: KeyPair - e?: KeyPair - rs: bytes32 - re: bytes32 - psk: bytes32 -} - -export interface NoiseSession { - hs: HandshakeState - h?: bytes32 - cs1?: CipherState - cs2?: CipherState - mc: uint64 - i: boolean -} - -export interface INoisePayload { - identityKey: bytes - identitySig: bytes - data: bytes -} diff --git a/packages/connection-encryption-noise/src/@types/libp2p.ts b/packages/connection-encryption-noise/src/@types/libp2p.ts deleted file mode 100644 index c20fe93952..0000000000 --- a/packages/connection-encryption-noise/src/@types/libp2p.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { bytes32 } from './basic.js' -import type { NoiseExtensions } from '../proto/payload.js' -import type { ConnectionEncrypter } from '@libp2p/interface/connection-encrypter' - -export interface KeyPair { - publicKey: bytes32 - privateKey: bytes32 -} - -export interface INoiseConnection extends ConnectionEncrypter {} diff --git a/packages/connection-encryption-noise/src/constants.ts b/packages/connection-encryption-noise/src/constants.ts deleted file mode 100644 index 7e8105c47b..0000000000 --- a/packages/connection-encryption-noise/src/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const NOISE_MSG_MAX_LENGTH_BYTES = 65535 -export const NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG = NOISE_MSG_MAX_LENGTH_BYTES - 16 - -export const DUMP_SESSION_KEYS = Boolean(globalThis.process?.env?.DUMP_SESSION_KEYS) diff --git a/packages/connection-encryption-noise/src/crypto.ts b/packages/connection-encryption-noise/src/crypto.ts deleted file mode 100644 index 108dfee1c6..0000000000 --- a/packages/connection-encryption-noise/src/crypto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { bytes32, bytes } from './@types/basic.js' -import type { Hkdf } from './@types/handshake.js' -import type { KeyPair } from './@types/libp2p.js' - -export interface ICryptoInterface { - hashSHA256: (data: Uint8Array) => Uint8Array - - getHKDF: (ck: bytes32, ikm: Uint8Array) => Hkdf - - generateX25519KeyPair: () => KeyPair - generateX25519KeyPairFromSeed: (seed: Uint8Array) => KeyPair - generateX25519SharedKey: (privateKey: Uint8Array, publicKey: Uint8Array) => Uint8Array - - chaCha20Poly1305Encrypt: (plaintext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32) => bytes - chaCha20Poly1305Decrypt: (ciphertext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32, dst?: Uint8Array) => bytes | null -} diff --git a/packages/connection-encryption-noise/src/crypto/js.ts b/packages/connection-encryption-noise/src/crypto/js.ts deleted file mode 100644 index 2a50102128..0000000000 --- a/packages/connection-encryption-noise/src/crypto/js.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { hkdf } from '@noble/hashes/hkdf' -import { sha256 } from '@noble/hashes/sha256' -import { ChaCha20Poly1305 } from '@stablelib/chacha20poly1305' -import * as x25519 from '@stablelib/x25519' -import type { bytes, bytes32 } from '../@types/basic.js' -import type { Hkdf } from '../@types/handshake.js' -import type { KeyPair } from '../@types/libp2p.js' -import type { ICryptoInterface } from '../crypto.js' - -export const pureJsCrypto: ICryptoInterface = { - hashSHA256 (data: Uint8Array): Uint8Array { - return sha256(data) - }, - - getHKDF (ck: bytes32, ikm: Uint8Array): Hkdf { - const okm = hkdf(sha256, ikm, ck, undefined, 96) - - const k1 = okm.subarray(0, 32) - const k2 = okm.subarray(32, 64) - const k3 = okm.subarray(64, 96) - - return [k1, k2, k3] - }, - - generateX25519KeyPair (): KeyPair { - const keypair = x25519.generateKeyPair() - - return { - publicKey: keypair.publicKey, - privateKey: keypair.secretKey - } - }, - - generateX25519KeyPairFromSeed (seed: Uint8Array): KeyPair { - const keypair = x25519.generateKeyPairFromSeed(seed) - - return { - publicKey: keypair.publicKey, - privateKey: keypair.secretKey - } - }, - - generateX25519SharedKey (privateKey: Uint8Array, publicKey: Uint8Array): Uint8Array { - return x25519.sharedKey(privateKey, publicKey) - }, - - chaCha20Poly1305Encrypt (plaintext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32): bytes { - const ctx = new ChaCha20Poly1305(k) - - return ctx.seal(nonce, plaintext, ad) - }, - - chaCha20Poly1305Decrypt (ciphertext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32, dst?: Uint8Array): bytes | null { - const ctx = new ChaCha20Poly1305(k) - - return ctx.open(nonce, ciphertext, ad, dst) - } -} diff --git a/packages/connection-encryption-noise/src/crypto/streaming.ts b/packages/connection-encryption-noise/src/crypto/streaming.ts deleted file mode 100644 index e785a649fd..0000000000 --- a/packages/connection-encryption-noise/src/crypto/streaming.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { TAG_LENGTH } from '@stablelib/chacha20poly1305' -import { NOISE_MSG_MAX_LENGTH_BYTES, NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG } from '../constants.js' -import { uint16BEEncode } from '../encoder.js' -import type { IHandshake } from '../@types/handshake-interface.js' -import type { MetricsRegistry } from '../metrics.js' -import type { Transform } from 'it-stream-types' -import type { Uint8ArrayList } from 'uint8arraylist' - -// Returns generator that encrypts payload from the user -export function encryptStream (handshake: IHandshake, metrics?: MetricsRegistry): Transform> { - return async function * (source) { - for await (const chunk of source) { - for (let i = 0; i < chunk.length; i += NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG) { - let end = i + NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG - if (end > chunk.length) { - end = chunk.length - } - - const data = handshake.encrypt(chunk.subarray(i, end), handshake.session) - metrics?.encryptedPackets.increment() - - yield uint16BEEncode(data.byteLength) - yield data - } - } - } -} - -// Decrypt received payload to the user -export function decryptStream (handshake: IHandshake, metrics?: MetricsRegistry): Transform, AsyncIterable> { - return async function * (source) { - for await (const chunk of source) { - for (let i = 0; i < chunk.length; i += NOISE_MSG_MAX_LENGTH_BYTES) { - let end = i + NOISE_MSG_MAX_LENGTH_BYTES - if (end > chunk.length) { - end = chunk.length - } - - if (end - TAG_LENGTH < i) { - throw new Error('Invalid chunk') - } - const encrypted = chunk.subarray(i, end) - // memory allocation is not cheap so reuse the encrypted Uint8Array - // see https://github.com/ChainSafe/js-libp2p-noise/pull/242#issue-1422126164 - // this is ok because chacha20 reads bytes one by one and don't reread after that - // it's also tested in https://github.com/ChainSafe/as-chacha20poly1305/pull/1/files#diff-25252846b58979dcaf4e41d47b3eadd7e4f335e7fb98da6c049b1f9cd011f381R48 - const dst = chunk.subarray(i, end - TAG_LENGTH) - const { plaintext: decrypted, valid } = handshake.decrypt(encrypted, handshake.session, dst) - if (!valid) { - metrics?.decryptErrors.increment() - throw new Error('Failed to validate decrypted chunk') - } - metrics?.decryptedPackets.increment() - yield decrypted - } - } - } -} diff --git a/packages/connection-encryption-noise/src/encoder.ts b/packages/connection-encryption-noise/src/encoder.ts deleted file mode 100644 index f1f963f3a7..0000000000 --- a/packages/connection-encryption-noise/src/encoder.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { concat as uint8ArrayConcat } from 'uint8arrays/concat' -import type { bytes } from './@types/basic.js' -import type { MessageBuffer } from './@types/handshake.js' -import type { LengthDecoderFunction } from 'it-length-prefixed' -import type { Uint8ArrayList } from 'uint8arraylist' - -const allocUnsafe = (len: number): Uint8Array => { - if (globalThis.Buffer) { - return globalThis.Buffer.allocUnsafe(len) - } - - return new Uint8Array(len) -} - -export const uint16BEEncode = (value: number): Uint8Array => { - const target = allocUnsafe(2) - new DataView(target.buffer, target.byteOffset, target.byteLength).setUint16(0, value, false) - return target -} -uint16BEEncode.bytes = 2 - -export const uint16BEDecode: LengthDecoderFunction = (data: Uint8Array | Uint8ArrayList): number => { - if (data.length < 2) throw RangeError('Could not decode int16BE') - - if (data instanceof Uint8Array) { - return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint16(0, false) - } - - return data.getUint16(0) -} -uint16BEDecode.bytes = 2 - -// Note: IK and XX encoder usage is opposite (XX uses in stages encode0 where IK uses encode1) - -export function encode0 (message: MessageBuffer): bytes { - return uint8ArrayConcat([message.ne, message.ciphertext], message.ne.length + message.ciphertext.length) -} - -export function encode1 (message: MessageBuffer): bytes { - return uint8ArrayConcat([message.ne, message.ns, message.ciphertext], message.ne.length + message.ns.length + message.ciphertext.length) -} - -export function encode2 (message: MessageBuffer): bytes { - return uint8ArrayConcat([message.ns, message.ciphertext], message.ns.length + message.ciphertext.length) -} - -export function decode0 (input: bytes): MessageBuffer { - if (input.length < 32) { - throw new Error('Cannot decode stage 0 MessageBuffer: length less than 32 bytes.') - } - - return { - ne: input.subarray(0, 32), - ciphertext: input.subarray(32, input.length), - ns: new Uint8Array(0) - } -} - -export function decode1 (input: bytes): MessageBuffer { - if (input.length < 80) { - throw new Error('Cannot decode stage 1 MessageBuffer: length less than 80 bytes.') - } - - return { - ne: input.subarray(0, 32), - ns: input.subarray(32, 80), - ciphertext: input.subarray(80, input.length) - } -} - -export function decode2 (input: bytes): MessageBuffer { - if (input.length < 48) { - throw new Error('Cannot decode stage 2 MessageBuffer: length less than 48 bytes.') - } - - return { - ne: new Uint8Array(0), - ns: input.subarray(0, 48), - ciphertext: input.subarray(48, input.length) - } -} diff --git a/packages/connection-encryption-noise/src/handshake-xx.ts b/packages/connection-encryption-noise/src/handshake-xx.ts deleted file mode 100644 index 92a6cf6e01..0000000000 --- a/packages/connection-encryption-noise/src/handshake-xx.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { InvalidCryptoExchangeError, UnexpectedPeerError } from '@libp2p/interface/errors' -import { decode0, decode1, decode2, encode0, encode1, encode2 } from './encoder.js' -import { XX } from './handshakes/xx.js' -import { - logger, - logLocalStaticKeys, - logLocalEphemeralKeys, - logRemoteEphemeralKey, - logRemoteStaticKey, - logCipherState -} from './logger.js' -import { - decodePayload, - getPeerIdFromPayload, - verifySignedPayload -} from './utils.js' -import type { bytes, bytes32 } from './@types/basic.js' -import type { IHandshake } from './@types/handshake-interface.js' -import type { CipherState, NoiseSession } from './@types/handshake.js' -import type { KeyPair } from './@types/libp2p.js' -import type { ICryptoInterface } from './crypto.js' -import type { NoiseExtensions } from './proto/payload.js' -import type { PeerId } from '@libp2p/interface/peer-id' -import type { LengthPrefixedStream } from 'it-length-prefixed-stream' - -export class XXHandshake implements IHandshake { - public isInitiator: boolean - public session: NoiseSession - public remotePeer!: PeerId - public remoteExtensions: NoiseExtensions = { webtransportCerthashes: [] } - - protected payload: bytes - protected connection: LengthPrefixedStream - protected xx: XX - protected staticKeypair: KeyPair - - private readonly prologue: bytes32 - - constructor ( - isInitiator: boolean, - payload: bytes, - prologue: bytes32, - crypto: ICryptoInterface, - staticKeypair: KeyPair, - connection: LengthPrefixedStream, - remotePeer?: PeerId, - handshake?: XX - ) { - this.isInitiator = isInitiator - this.payload = payload - this.prologue = prologue - this.staticKeypair = staticKeypair - this.connection = connection - if (remotePeer) { - this.remotePeer = remotePeer - } - this.xx = handshake ?? new XX(crypto) - this.session = this.xx.initSession(this.isInitiator, this.prologue, this.staticKeypair) - } - - // stage 0 - public async propose (): Promise { - logLocalStaticKeys(this.session.hs.s) - if (this.isInitiator) { - logger.trace('Stage 0 - Initiator starting to send first message.') - const messageBuffer = this.xx.sendMessage(this.session, new Uint8Array(0)) - await this.connection.write(encode0(messageBuffer)) - logger.trace('Stage 0 - Initiator finished sending first message.') - logLocalEphemeralKeys(this.session.hs.e) - } else { - logger.trace('Stage 0 - Responder waiting to receive first message...') - const receivedMessageBuffer = decode0((await this.connection.read()).subarray()) - const { valid } = this.xx.recvMessage(this.session, receivedMessageBuffer) - if (!valid) { - throw new InvalidCryptoExchangeError('xx handshake stage 0 validation fail') - } - logger.trace('Stage 0 - Responder received first message.') - logRemoteEphemeralKey(this.session.hs.re) - } - } - - // stage 1 - public async exchange (): Promise { - if (this.isInitiator) { - logger.trace('Stage 1 - Initiator waiting to receive first message from responder...') - const receivedMessageBuffer = decode1((await this.connection.read()).subarray()) - const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer) - if (!valid) { - throw new InvalidCryptoExchangeError('xx handshake stage 1 validation fail') - } - logger.trace('Stage 1 - Initiator received the message.') - logRemoteEphemeralKey(this.session.hs.re) - logRemoteStaticKey(this.session.hs.rs) - - logger.trace("Initiator going to check remote's signature...") - try { - const decodedPayload = decodePayload(plaintext) - this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload) - await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer) - this.setRemoteNoiseExtension(decodedPayload.extensions) - } catch (e) { - const err = e as Error - throw new UnexpectedPeerError(`Error occurred while verifying signed payload: ${err.message}`) - } - logger.trace('All good with the signature!') - } else { - logger.trace('Stage 1 - Responder sending out first message with signed payload and static key.') - const messageBuffer = this.xx.sendMessage(this.session, this.payload) - await this.connection.write(encode1(messageBuffer)) - logger.trace('Stage 1 - Responder sent the second handshake message with signed payload.') - logLocalEphemeralKeys(this.session.hs.e) - } - } - - // stage 2 - public async finish (): Promise { - if (this.isInitiator) { - logger.trace('Stage 2 - Initiator sending third handshake message.') - const messageBuffer = this.xx.sendMessage(this.session, this.payload) - await this.connection.write(encode2(messageBuffer)) - logger.trace('Stage 2 - Initiator sent message with signed payload.') - } else { - logger.trace('Stage 2 - Responder waiting for third handshake message...') - const receivedMessageBuffer = decode2((await this.connection.read()).subarray()) - const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer) - if (!valid) { - throw new InvalidCryptoExchangeError('xx handshake stage 2 validation fail') - } - logger.trace('Stage 2 - Responder received the message, finished handshake.') - - try { - const decodedPayload = decodePayload(plaintext) - this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload) - await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer) - this.setRemoteNoiseExtension(decodedPayload.extensions) - } catch (e) { - const err = e as Error - throw new UnexpectedPeerError(`Error occurred while verifying signed payload: ${err.message}`) - } - } - logCipherState(this.session) - } - - public encrypt (plaintext: Uint8Array, session: NoiseSession): bytes { - const cs = this.getCS(session) - - return this.xx.encryptWithAd(cs, new Uint8Array(0), plaintext) - } - - public decrypt (ciphertext: Uint8Array, session: NoiseSession, dst?: Uint8Array): { plaintext: bytes, valid: boolean } { - const cs = this.getCS(session, false) - - return this.xx.decryptWithAd(cs, new Uint8Array(0), ciphertext, dst) - } - - public getRemoteStaticKey (): bytes { - return this.session.hs.rs - } - - private getCS (session: NoiseSession, encryption = true): CipherState { - if (!session.cs1 || !session.cs2) { - throw new InvalidCryptoExchangeError('Handshake not completed properly, cipher state does not exist.') - } - - if (this.isInitiator) { - return encryption ? session.cs1 : session.cs2 - } else { - return encryption ? session.cs2 : session.cs1 - } - } - - protected setRemoteNoiseExtension (e: NoiseExtensions | null | undefined): void { - if (e) { - this.remoteExtensions = e - } - } -} diff --git a/packages/connection-encryption-noise/src/handshakes/abstract-handshake.ts b/packages/connection-encryption-noise/src/handshakes/abstract-handshake.ts deleted file mode 100644 index 714f8141fc..0000000000 --- a/packages/connection-encryption-noise/src/handshakes/abstract-handshake.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { fromString as uint8ArrayFromString } from 'uint8arrays' -import { concat as uint8ArrayConcat } from 'uint8arrays/concat' -import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { logger } from '../logger.js' -import { Nonce } from '../nonce.js' -import type { bytes, bytes32 } from '../@types/basic.js' -import type { CipherState, MessageBuffer, SymmetricState } from '../@types/handshake.js' -import type { ICryptoInterface } from '../crypto.js' - -export interface DecryptedResult { - plaintext: bytes - valid: boolean -} - -export interface SplitState { - cs1: CipherState - cs2: CipherState -} - -export abstract class AbstractHandshake { - public crypto: ICryptoInterface - - constructor (crypto: ICryptoInterface) { - this.crypto = crypto - } - - public encryptWithAd (cs: CipherState, ad: Uint8Array, plaintext: Uint8Array): bytes { - const e = this.encrypt(cs.k, cs.n, ad, plaintext) - cs.n.increment() - - return e - } - - public decryptWithAd (cs: CipherState, ad: Uint8Array, ciphertext: Uint8Array, dst?: Uint8Array): DecryptedResult { - const { plaintext, valid } = this.decrypt(cs.k, cs.n, ad, ciphertext, dst) - if (valid) cs.n.increment() - - return { plaintext, valid } - } - - // Cipher state related - protected hasKey (cs: CipherState): boolean { - return !this.isEmptyKey(cs.k) - } - - protected createEmptyKey (): bytes32 { - return new Uint8Array(32) - } - - protected isEmptyKey (k: bytes32): boolean { - const emptyKey = this.createEmptyKey() - return uint8ArrayEquals(emptyKey, k) - } - - protected encrypt (k: bytes32, n: Nonce, ad: Uint8Array, plaintext: Uint8Array): bytes { - n.assertValue() - - return this.crypto.chaCha20Poly1305Encrypt(plaintext, n.getBytes(), ad, k) - } - - protected encryptAndHash (ss: SymmetricState, plaintext: bytes): bytes { - let ciphertext - if (this.hasKey(ss.cs)) { - ciphertext = this.encryptWithAd(ss.cs, ss.h, plaintext) - } else { - ciphertext = plaintext - } - - this.mixHash(ss, ciphertext) - return ciphertext - } - - protected decrypt (k: bytes32, n: Nonce, ad: bytes, ciphertext: bytes, dst?: Uint8Array): DecryptedResult { - n.assertValue() - - const encryptedMessage = this.crypto.chaCha20Poly1305Decrypt(ciphertext, n.getBytes(), ad, k, dst) - - if (encryptedMessage) { - return { - plaintext: encryptedMessage, - valid: true - } - } else { - return { - plaintext: new Uint8Array(0), - valid: false - } - } - } - - protected decryptAndHash (ss: SymmetricState, ciphertext: bytes): DecryptedResult { - let plaintext: bytes; let valid = true - if (this.hasKey(ss.cs)) { - ({ plaintext, valid } = this.decryptWithAd(ss.cs, ss.h, ciphertext)) - } else { - plaintext = ciphertext - } - - this.mixHash(ss, ciphertext) - return { plaintext, valid } - } - - protected dh (privateKey: bytes32, publicKey: bytes32): bytes32 { - try { - const derivedU8 = this.crypto.generateX25519SharedKey(privateKey, publicKey) - - if (derivedU8.length === 32) { - return derivedU8 - } - - return derivedU8.subarray(0, 32) - } catch (e) { - const err = e as Error - logger.error(err) - return new Uint8Array(32) - } - } - - protected mixHash (ss: SymmetricState, data: bytes): void { - ss.h = this.getHash(ss.h, data) - } - - protected getHash (a: Uint8Array, b: Uint8Array): bytes32 { - const u = this.crypto.hashSHA256(uint8ArrayConcat([a, b], a.length + b.length)) - return u - } - - protected mixKey (ss: SymmetricState, ikm: bytes32): void { - const [ck, tempK] = this.crypto.getHKDF(ss.ck, ikm) - ss.cs = this.initializeKey(tempK) - ss.ck = ck - } - - protected initializeKey (k: bytes32): CipherState { - return { k, n: new Nonce() } - } - - // Symmetric state related - - protected initializeSymmetric (protocolName: string): SymmetricState { - const protocolNameBytes = uint8ArrayFromString(protocolName, 'utf-8') - const h = this.hashProtocolName(protocolNameBytes) - - const ck = h - const key = this.createEmptyKey() - const cs: CipherState = this.initializeKey(key) - - return { cs, ck, h } - } - - protected hashProtocolName (protocolName: Uint8Array): bytes32 { - if (protocolName.length <= 32) { - const h = new Uint8Array(32) - h.set(protocolName) - return h - } else { - return this.getHash(protocolName, new Uint8Array(0)) - } - } - - protected split (ss: SymmetricState): SplitState { - const [tempk1, tempk2] = this.crypto.getHKDF(ss.ck, new Uint8Array(0)) - const cs1 = this.initializeKey(tempk1) - const cs2 = this.initializeKey(tempk2) - - return { cs1, cs2 } - } - - protected writeMessageRegular (cs: CipherState, payload: bytes): MessageBuffer { - const ciphertext = this.encryptWithAd(cs, new Uint8Array(0), payload) - const ne = this.createEmptyKey() - const ns = new Uint8Array(0) - - return { ne, ns, ciphertext } - } - - protected readMessageRegular (cs: CipherState, message: MessageBuffer): DecryptedResult { - return this.decryptWithAd(cs, new Uint8Array(0), message.ciphertext) - } -} diff --git a/packages/connection-encryption-noise/src/handshakes/xx.ts b/packages/connection-encryption-noise/src/handshakes/xx.ts deleted file mode 100644 index 44d26fa4c0..0000000000 --- a/packages/connection-encryption-noise/src/handshakes/xx.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { isValidPublicKey } from '../utils.js' -import { AbstractHandshake, type DecryptedResult } from './abstract-handshake.js' -import type { bytes32, bytes } from '../@types/basic.js' -import type { CipherState, HandshakeState, MessageBuffer, NoiseSession } from '../@types/handshake.js' -import type { KeyPair } from '../@types/libp2p.js' - -export class XX extends AbstractHandshake { - private initializeInitiator (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState { - const name = 'Noise_XX_25519_ChaChaPoly_SHA256' - const ss = this.initializeSymmetric(name) - this.mixHash(ss, prologue) - const re = new Uint8Array(32) - - return { ss, s, rs, psk, re } - } - - private initializeResponder (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState { - const name = 'Noise_XX_25519_ChaChaPoly_SHA256' - const ss = this.initializeSymmetric(name) - this.mixHash(ss, prologue) - const re = new Uint8Array(32) - - return { ss, s, rs, psk, re } - } - - private writeMessageA (hs: HandshakeState, payload: bytes, e?: KeyPair): MessageBuffer { - const ns = new Uint8Array(0) - - if (e !== undefined) { - hs.e = e - } else { - hs.e = this.crypto.generateX25519KeyPair() - } - - const ne = hs.e.publicKey - - this.mixHash(hs.ss, ne) - const ciphertext = this.encryptAndHash(hs.ss, payload) - - return { ne, ns, ciphertext } - } - - private writeMessageB (hs: HandshakeState, payload: bytes): MessageBuffer { - hs.e = this.crypto.generateX25519KeyPair() - const ne = hs.e.publicKey - this.mixHash(hs.ss, ne) - - this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re)) - const spk = hs.s.publicKey - const ns = this.encryptAndHash(hs.ss, spk) - - this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)) - const ciphertext = this.encryptAndHash(hs.ss, payload) - - return { ne, ns, ciphertext } - } - - private writeMessageC (hs: HandshakeState, payload: bytes): { messageBuffer: MessageBuffer, cs1: CipherState, cs2: CipherState, h: bytes } { - const spk = hs.s.publicKey - const ns = this.encryptAndHash(hs.ss, spk) - this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)) - const ciphertext = this.encryptAndHash(hs.ss, payload) - const ne = this.createEmptyKey() - const messageBuffer: MessageBuffer = { ne, ns, ciphertext } - const { cs1, cs2 } = this.split(hs.ss) - - return { h: hs.ss.h, messageBuffer, cs1, cs2 } - } - - private readMessageA (hs: HandshakeState, message: MessageBuffer): DecryptedResult { - if (isValidPublicKey(message.ne)) { - hs.re = message.ne - } - - this.mixHash(hs.ss, hs.re) - return this.decryptAndHash(hs.ss, message.ciphertext) - } - - private readMessageB (hs: HandshakeState, message: MessageBuffer): DecryptedResult { - if (isValidPublicKey(message.ne)) { - hs.re = message.ne - } - - this.mixHash(hs.ss, hs.re) - if (!hs.e) { - throw new Error('Handshake state `e` param is missing.') - } - this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re)) - const { plaintext: ns, valid: valid1 } = this.decryptAndHash(hs.ss, message.ns) - if (valid1 && isValidPublicKey(ns)) { - hs.rs = ns - } - this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs)) - const { plaintext, valid: valid2 } = this.decryptAndHash(hs.ss, message.ciphertext) - return { plaintext, valid: (valid1 && valid2) } - } - - private readMessageC (hs: HandshakeState, message: MessageBuffer): { h: bytes, plaintext: bytes, valid: boolean, cs1: CipherState, cs2: CipherState } { - const { plaintext: ns, valid: valid1 } = this.decryptAndHash(hs.ss, message.ns) - if (valid1 && isValidPublicKey(ns)) { - hs.rs = ns - } - if (!hs.e) { - throw new Error('Handshake state `e` param is missing.') - } - this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs)) - - const { plaintext, valid: valid2 } = this.decryptAndHash(hs.ss, message.ciphertext) - const { cs1, cs2 } = this.split(hs.ss) - - return { h: hs.ss.h, plaintext, valid: (valid1 && valid2), cs1, cs2 } - } - - public initSession (initiator: boolean, prologue: bytes32, s: KeyPair): NoiseSession { - const psk = this.createEmptyKey() - const rs = new Uint8Array(32) // no static key yet - let hs - - if (initiator) { - hs = this.initializeInitiator(prologue, s, rs, psk) - } else { - hs = this.initializeResponder(prologue, s, rs, psk) - } - - return { - hs, - i: initiator, - mc: 0 - } - } - - public sendMessage (session: NoiseSession, message: bytes, ephemeral?: KeyPair): MessageBuffer { - let messageBuffer: MessageBuffer - if (session.mc === 0) { - messageBuffer = this.writeMessageA(session.hs, message, ephemeral) - } else if (session.mc === 1) { - messageBuffer = this.writeMessageB(session.hs, message) - } else if (session.mc === 2) { - const { h, messageBuffer: resultingBuffer, cs1, cs2 } = this.writeMessageC(session.hs, message) - messageBuffer = resultingBuffer - session.h = h - session.cs1 = cs1 - session.cs2 = cs2 - } else if (session.mc > 2) { - if (session.i) { - if (!session.cs1) { - throw new Error('CS1 (cipher state) is not defined') - } - - messageBuffer = this.writeMessageRegular(session.cs1, message) - } else { - if (!session.cs2) { - throw new Error('CS2 (cipher state) is not defined') - } - - messageBuffer = this.writeMessageRegular(session.cs2, message) - } - } else { - throw new Error('Session invalid.') - } - - session.mc++ - return messageBuffer - } - - public recvMessage (session: NoiseSession, message: MessageBuffer): DecryptedResult { - let plaintext: bytes = new Uint8Array(0) - let valid = false - if (session.mc === 0) { - ({ plaintext, valid } = this.readMessageA(session.hs, message)) - } else if (session.mc === 1) { - ({ plaintext, valid } = this.readMessageB(session.hs, message)) - } else if (session.mc === 2) { - const { h, plaintext: resultingPlaintext, valid: resultingValid, cs1, cs2 } = this.readMessageC(session.hs, message) - plaintext = resultingPlaintext - valid = resultingValid - session.h = h - session.cs1 = cs1 - session.cs2 = cs2 - } - session.mc++ - return { plaintext, valid } - } -} diff --git a/packages/connection-encryption-noise/src/index.ts b/packages/connection-encryption-noise/src/index.ts deleted file mode 100644 index 3a42c89722..0000000000 --- a/packages/connection-encryption-noise/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Noise } from './noise.js' -import type { NoiseInit } from './noise.js' -import type { NoiseExtensions } from './proto/payload.js' -import type { ConnectionEncrypter } from '@libp2p/interface/connection-encrypter' -export type { ICryptoInterface } from './crypto.js' -export { pureJsCrypto } from './crypto/js.js' - -export function noise (init: NoiseInit = {}): () => ConnectionEncrypter { - return () => new Noise(init) -} diff --git a/packages/connection-encryption-noise/src/logger.ts b/packages/connection-encryption-noise/src/logger.ts deleted file mode 100644 index b44ca7b420..0000000000 --- a/packages/connection-encryption-noise/src/logger.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { type Logger, logger } from '@libp2p/logger' -import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { DUMP_SESSION_KEYS } from './constants.js' -import type { NoiseSession } from './@types/handshake.js' -import type { KeyPair } from './@types/libp2p.js' - -const log = logger('libp2p:noise') - -export { log as logger } - -let keyLogger: Logger -if (DUMP_SESSION_KEYS) { - keyLogger = log -} else { - keyLogger = Object.assign(() => { /* do nothing */ }, { - enabled: false, - trace: () => {}, - error: () => {} - }) -} - -export function logLocalStaticKeys (s: KeyPair): void { - keyLogger(`LOCAL_STATIC_PUBLIC_KEY ${uint8ArrayToString(s.publicKey, 'hex')}`) - keyLogger(`LOCAL_STATIC_PRIVATE_KEY ${uint8ArrayToString(s.privateKey, 'hex')}`) -} - -export function logLocalEphemeralKeys (e: KeyPair | undefined): void { - if (e) { - keyLogger(`LOCAL_PUBLIC_EPHEMERAL_KEY ${uint8ArrayToString(e.publicKey, 'hex')}`) - keyLogger(`LOCAL_PRIVATE_EPHEMERAL_KEY ${uint8ArrayToString(e.privateKey, 'hex')}`) - } else { - keyLogger('Missing local ephemeral keys.') - } -} - -export function logRemoteStaticKey (rs: Uint8Array): void { - keyLogger(`REMOTE_STATIC_PUBLIC_KEY ${uint8ArrayToString(rs, 'hex')}`) -} - -export function logRemoteEphemeralKey (re: Uint8Array): void { - keyLogger(`REMOTE_EPHEMERAL_PUBLIC_KEY ${uint8ArrayToString(re, 'hex')}`) -} - -export function logCipherState (session: NoiseSession): void { - if (session.cs1 && session.cs2) { - keyLogger(`CIPHER_STATE_1 ${session.cs1.n.getUint64()} ${uint8ArrayToString(session.cs1.k, 'hex')}`) - keyLogger(`CIPHER_STATE_2 ${session.cs2.n.getUint64()} ${uint8ArrayToString(session.cs2.k, 'hex')}`) - } else { - keyLogger('Missing cipher state.') - } -} diff --git a/packages/connection-encryption-noise/src/metrics.ts b/packages/connection-encryption-noise/src/metrics.ts deleted file mode 100644 index 8d0b3a4e70..0000000000 --- a/packages/connection-encryption-noise/src/metrics.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Counter, Metrics } from '@libp2p/interface/metrics' - -export type MetricsRegistry = Record - -export function registerMetrics (metrics: Metrics): MetricsRegistry { - return { - xxHandshakeSuccesses: metrics.registerCounter( - 'libp2p_noise_xxhandshake_successes_total', { - help: 'Total count of noise xxHandshakes successes_' - }), - - xxHandshakeErrors: metrics.registerCounter( - 'libp2p_noise_xxhandshake_error_total', { - help: 'Total count of noise xxHandshakes errors' - }), - - encryptedPackets: metrics.registerCounter( - 'libp2p_noise_encrypted_packets_total', { - help: 'Total count of noise encrypted packets successfully' - }), - - decryptedPackets: metrics.registerCounter( - 'libp2p_noise_decrypted_packets_total', { - help: 'Total count of noise decrypted packets' - }), - - decryptErrors: metrics.registerCounter( - 'libp2p_noise_decrypt_errors_total', { - help: 'Total count of noise decrypt errors' - }) - } -} diff --git a/packages/connection-encryption-noise/src/noise.ts b/packages/connection-encryption-noise/src/noise.ts deleted file mode 100644 index 2277a751ec..0000000000 --- a/packages/connection-encryption-noise/src/noise.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { decode } from 'it-length-prefixed' -import { lpStream, type LengthPrefixedStream } from 'it-length-prefixed-stream' -import { duplexPair } from 'it-pair/duplex' -import { pipe } from 'it-pipe' -import { NOISE_MSG_MAX_LENGTH_BYTES } from './constants.js' -import { pureJsCrypto } from './crypto/js.js' -import { decryptStream, encryptStream } from './crypto/streaming.js' -import { uint16BEDecode, uint16BEEncode } from './encoder.js' -import { XXHandshake } from './handshake-xx.js' -import { type MetricsRegistry, registerMetrics } from './metrics.js' -import { getPayload } from './utils.js' -import type { bytes } from './@types/basic.js' -import type { IHandshake } from './@types/handshake-interface.js' -import type { INoiseConnection, KeyPair } from './@types/libp2p.js' -import type { ICryptoInterface } from './crypto.js' -import type { NoiseExtensions } from './proto/payload.js' -import type { SecuredConnection } from '@libp2p/interface/connection-encrypter' -import type { Metrics } from '@libp2p/interface/metrics' -import type { PeerId } from '@libp2p/interface/peer-id' -import type { Duplex, Source } from 'it-stream-types' - -interface HandshakeParams { - connection: LengthPrefixedStream - isInitiator: boolean - localPeer: PeerId - remotePeer?: PeerId -} - -export interface NoiseInit { - /** - * x25519 private key, reuse for faster handshakes - */ - staticNoiseKey?: bytes - extensions?: NoiseExtensions - crypto?: ICryptoInterface - prologueBytes?: Uint8Array - metrics?: Metrics -} - -export class Noise implements INoiseConnection { - public protocol = '/noise' - public crypto: ICryptoInterface - - private readonly prologue: Uint8Array - private readonly staticKeys: KeyPair - private readonly extensions?: NoiseExtensions - private readonly metrics?: MetricsRegistry - - constructor (init: NoiseInit = {}) { - const { staticNoiseKey, extensions, crypto, prologueBytes, metrics } = init - - this.crypto = crypto ?? pureJsCrypto - this.extensions = extensions - this.metrics = metrics ? registerMetrics(metrics) : undefined - - if (staticNoiseKey) { - // accepts x25519 private key of length 32 - this.staticKeys = this.crypto.generateX25519KeyPairFromSeed(staticNoiseKey) - } else { - this.staticKeys = this.crypto.generateX25519KeyPair() - } - this.prologue = prologueBytes ?? new Uint8Array(0) - } - - /** - * Encrypt outgoing data to the remote party (handshake as initiator) - * - * @param {PeerId} localPeer - PeerId of the receiving peer - * @param {Duplex, AsyncIterable, Promise>} connection - streaming iterable duplex that will be encrypted - * @param {PeerId} remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer. - * @returns {Promise} - */ - public async secureOutbound (localPeer: PeerId, connection: Duplex, AsyncIterable, Promise>, remotePeer?: PeerId): Promise> { - const wrappedConnection = lpStream( - connection, - { - lengthEncoder: uint16BEEncode, - lengthDecoder: uint16BEDecode, - maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES - } - ) - const handshake = await this.performHandshake({ - connection: wrappedConnection, - isInitiator: true, - localPeer, - remotePeer - }) - const conn = await this.createSecureConnection(wrappedConnection, handshake) - - return { - conn, - remoteExtensions: handshake.remoteExtensions, - remotePeer: handshake.remotePeer - } - } - - /** - * Decrypt incoming data (handshake as responder). - * - * @param {PeerId} localPeer - PeerId of the receiving peer. - * @param {Duplex, AsyncIterable, Promise>} connection - streaming iterable duplex that will be encryption. - * @param {PeerId} remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades. - * @returns {Promise} - */ - public async secureInbound (localPeer: PeerId, connection: Duplex, AsyncIterable, Promise>, remotePeer?: PeerId): Promise> { - const wrappedConnection = lpStream( - connection, - { - lengthEncoder: uint16BEEncode, - lengthDecoder: uint16BEDecode, - maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES - } - ) - const handshake = await this.performHandshake({ - connection: wrappedConnection, - isInitiator: false, - localPeer, - remotePeer - }) - const conn = await this.createSecureConnection(wrappedConnection, handshake) - - return { - conn, - remotePeer: handshake.remotePeer, - remoteExtensions: handshake.remoteExtensions - } - } - - /** - * If Noise pipes supported, tries IK handshake first with XX as fallback if it fails. - * If noise pipes disabled or remote peer static key is unknown, use XX. - * - * @param {HandshakeParams} params - */ - private async performHandshake (params: HandshakeParams): Promise { - const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.extensions) - - // run XX handshake - return this.performXXHandshake(params, payload) - } - - private async performXXHandshake ( - params: HandshakeParams, - payload: bytes - ): Promise { - const { isInitiator, remotePeer, connection } = params - const handshake = new XXHandshake( - isInitiator, - payload, - this.prologue, - this.crypto, - this.staticKeys, - connection, - remotePeer - ) - - try { - await handshake.propose() - await handshake.exchange() - await handshake.finish() - this.metrics?.xxHandshakeSuccesses.increment() - } catch (e: unknown) { - this.metrics?.xxHandshakeErrors.increment() - if (e instanceof Error) { - e.message = `Error occurred during XX handshake: ${e.message}` - throw e - } - } - - return handshake - } - - private async createSecureConnection ( - connection: LengthPrefixedStream, AsyncIterable, Promise>>, - handshake: IHandshake - ): Promise, Source, Promise>> { - // Create encryption box/unbox wrapper - const [secure, user] = duplexPair() - const network = connection.unwrap() - - await pipe( - secure, // write to wrapper - encryptStream(handshake, this.metrics), // encrypt data + prefix with message length - network, // send to the remote peer - (source) => decode(source, { lengthDecoder: uint16BEDecode }), // read message length prefix - decryptStream(handshake, this.metrics), // decrypt the incoming data - secure // pipe to the wrapper - ) - - return user - } -} diff --git a/packages/connection-encryption-noise/src/nonce.ts b/packages/connection-encryption-noise/src/nonce.ts deleted file mode 100644 index fab31ace7a..0000000000 --- a/packages/connection-encryption-noise/src/nonce.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { bytes, uint64 } from './@types/basic.js' - -export const MIN_NONCE = 0 -// For performance reasons, the nonce is represented as a JS `number` -// Although JS `number` can safely represent integers up to 2 ** 53 - 1, we choose to only use -// 4 bytes to store the data for performance reason. -// This is a slight deviation from the noise spec, which describes the max nonce as 2 ** 64 - 2 -// The effect is that this implementation will need a new handshake to be performed after fewer messages are exchanged than other implementations with full uint64 nonces. -// this MAX_NONCE is still a large number of messages, so the practical effect of this is negligible. -export const MAX_NONCE = 0xffffffff - -const ERR_MAX_NONCE = 'Cipherstate has reached maximum n, a new handshake must be performed' - -/** - * The nonce is an uint that's increased over time. - * Maintaining different representations help improve performance. - */ -export class Nonce { - private n: uint64 - private readonly bytes: bytes - private readonly view: DataView - - constructor (n = MIN_NONCE) { - this.n = n - this.bytes = new Uint8Array(12) - this.view = new DataView(this.bytes.buffer, this.bytes.byteOffset, this.bytes.byteLength) - this.view.setUint32(4, n, true) - } - - increment (): void { - this.n++ - // Even though we're treating the nonce as 8 bytes, RFC7539 specifies 12 bytes for a nonce. - this.view.setUint32(4, this.n, true) - } - - getBytes (): bytes { - return this.bytes - } - - getUint64 (): uint64 { - return this.n - } - - assertValue (): void { - if (this.n > MAX_NONCE) { - throw new Error(ERR_MAX_NONCE) - } - } -} diff --git a/packages/connection-encryption-noise/src/proto/payload.proto b/packages/connection-encryption-noise/src/proto/payload.proto deleted file mode 100644 index cdb2383cb0..0000000000 --- a/packages/connection-encryption-noise/src/proto/payload.proto +++ /dev/null @@ -1,11 +0,0 @@ -syntax = "proto3"; - -message NoiseExtensions { - repeated bytes webtransport_certhashes = 1; -} - -message NoiseHandshakePayload { - bytes identity_key = 1; - bytes identity_sig = 2; - optional NoiseExtensions extensions = 4; -} \ No newline at end of file diff --git a/packages/connection-encryption-noise/src/proto/payload.ts b/packages/connection-encryption-noise/src/proto/payload.ts deleted file mode 100644 index 50acd62e39..0000000000 --- a/packages/connection-encryption-noise/src/proto/payload.ts +++ /dev/null @@ -1,148 +0,0 @@ -/* eslint-disable import/export */ -/* eslint-disable complexity */ -/* eslint-disable @typescript-eslint/no-namespace */ -/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ -/* eslint-disable @typescript-eslint/no-empty-interface */ - -import { encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' -import type { Uint8ArrayList } from 'uint8arraylist' - -export interface NoiseExtensions { - webtransportCerthashes: Uint8Array[] -} - -export namespace NoiseExtensions { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (obj.webtransportCerthashes != null) { - for (const value of obj.webtransportCerthashes) { - w.uint32(10) - w.bytes(value) - } - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - webtransportCerthashes: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.webtransportCerthashes.push(reader.bytes()) - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, NoiseExtensions.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): NoiseExtensions => { - return decodeMessage(buf, NoiseExtensions.codec()) - } -} - -export interface NoiseHandshakePayload { - identityKey: Uint8Array - identitySig: Uint8Array - extensions?: NoiseExtensions -} - -export namespace NoiseHandshakePayload { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (opts.writeDefaults === true || (obj.identityKey != null && obj.identityKey.byteLength > 0)) { - w.uint32(10) - w.bytes(obj.identityKey ?? new Uint8Array(0)) - } - - if (opts.writeDefaults === true || (obj.identitySig != null && obj.identitySig.byteLength > 0)) { - w.uint32(18) - w.bytes(obj.identitySig ?? new Uint8Array(0)) - } - - if (obj.extensions != null) { - w.uint32(34) - NoiseExtensions.codec().encode(obj.extensions, w, { - writeDefaults: false - }) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - identityKey: new Uint8Array(0), - identitySig: new Uint8Array(0) - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.identityKey = reader.bytes() - break - case 2: - obj.identitySig = reader.bytes() - break - case 4: - obj.extensions = NoiseExtensions.codec().decode(reader, reader.uint32()) - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, NoiseHandshakePayload.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): NoiseHandshakePayload => { - return decodeMessage(buf, NoiseHandshakePayload.codec()) - } -} diff --git a/packages/connection-encryption-noise/src/utils.ts b/packages/connection-encryption-noise/src/utils.ts deleted file mode 100644 index 993c9628c3..0000000000 --- a/packages/connection-encryption-noise/src/utils.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { unmarshalPublicKey, unmarshalPrivateKey } from '@libp2p/crypto/keys' -import { peerIdFromKeys } from '@libp2p/peer-id' -import { concat as uint8ArrayConcat } from 'uint8arrays/concat' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { type NoiseExtensions, NoiseHandshakePayload } from './proto/payload.js' -import type { bytes } from './@types/basic.js' -import type { PeerId } from '@libp2p/interface/peer-id' - -export async function getPayload ( - localPeer: PeerId, - staticPublicKey: bytes, - extensions?: NoiseExtensions -): Promise { - const signedPayload = await signPayload(localPeer, getHandshakePayload(staticPublicKey)) - - if (localPeer.publicKey == null) { - throw new Error('PublicKey was missing from local PeerId') - } - - return createHandshakePayload( - localPeer.publicKey, - signedPayload, - extensions - ) -} - -export function createHandshakePayload ( - libp2pPublicKey: Uint8Array, - signedPayload: Uint8Array, - extensions?: NoiseExtensions -): bytes { - return NoiseHandshakePayload.encode({ - identityKey: libp2pPublicKey, - identitySig: signedPayload, - extensions: extensions ?? { webtransportCerthashes: [] } - }).subarray() -} - -export async function signPayload (peerId: PeerId, payload: bytes): Promise { - if (peerId.privateKey == null) { - throw new Error('PrivateKey was missing from PeerId') - } - - const privateKey = await unmarshalPrivateKey(peerId.privateKey) - - return privateKey.sign(payload) -} - -export async function getPeerIdFromPayload (payload: NoiseHandshakePayload): Promise { - return peerIdFromKeys(payload.identityKey) -} - -export function decodePayload (payload: bytes | Uint8Array): NoiseHandshakePayload { - return NoiseHandshakePayload.decode(payload) -} - -export function getHandshakePayload (publicKey: bytes): bytes { - const prefix = uint8ArrayFromString('noise-libp2p-static-key:') - return uint8ArrayConcat([prefix, publicKey], prefix.length + publicKey.length) -} - -/** - * Verifies signed payload, throws on any irregularities. - * - * @param {bytes} noiseStaticKey - owner's noise static key - * @param {bytes} payload - decoded payload - * @param {PeerId} remotePeer - owner's libp2p peer ID - * @returns {Promise} - peer ID of payload owner - */ -export async function verifySignedPayload ( - noiseStaticKey: bytes, - payload: NoiseHandshakePayload, - remotePeer: PeerId -): Promise { - // Unmarshaling from PublicKey protobuf - const payloadPeerId = await peerIdFromKeys(payload.identityKey) - if (!payloadPeerId.equals(remotePeer)) { - throw new Error(`Payload identity key ${payloadPeerId.toString()} does not match expected remote peer ${remotePeer.toString()}`) - } - const generatedPayload = getHandshakePayload(noiseStaticKey) - - if (payloadPeerId.publicKey == null) { - throw new Error('PublicKey was missing from PeerId') - } - - if (payload.identitySig == null) { - throw new Error('Signature was missing from message') - } - - const publicKey = unmarshalPublicKey(payloadPeerId.publicKey) - - const valid = await publicKey.verify(generatedPayload, payload.identitySig) - - if (!valid) { - throw new Error("Static key doesn't match to peer that signed payload!") - } - - return payloadPeerId -} - -export function isValidPublicKey (pk: bytes): boolean { - if (!(pk instanceof Uint8Array)) { - return false - } - - if (pk.length !== 32) { - return false - } - - return true -} diff --git a/packages/connection-encryption-noise/test/compliance.spec.ts b/packages/connection-encryption-noise/test/compliance.spec.ts deleted file mode 100644 index e97dc0ad7f..0000000000 --- a/packages/connection-encryption-noise/test/compliance.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import tests from '@libp2p/interface-compliance-tests/connection-encryption' -import { Noise } from '../src/noise.js' - -describe('spec compliance tests', function () { - tests({ - async setup () { - return new Noise() - }, - async teardown () {} - }) -}) diff --git a/packages/connection-encryption-noise/test/fixtures/peer.ts b/packages/connection-encryption-noise/test/fixtures/peer.ts deleted file mode 100644 index 753d9d6927..0000000000 --- a/packages/connection-encryption-noise/test/fixtures/peer.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { createEd25519PeerId, createFromJSON } from '@libp2p/peer-id-factory' -import type { PeerId } from '@libp2p/interface/peer-id' - -// ed25519 keys -const peers = [{ - id: '12D3KooWH45PiqBjfnEfDfCD6TqJrpqTBJvQDwGHvjGpaWwms46D', - privKey: 'CAESYBtKXrMwawAARmLScynQUuSwi/gGSkwqDPxi15N3dqDHa4T4iWupkMe5oYGwGH3Hyfvd/QcgSTqg71oYZJadJ6prhPiJa6mQx7mhgbAYfcfJ+939ByBJOqDvWhhklp0nqg==', - pubKey: 'CAESIGuE+IlrqZDHuaGBsBh9x8n73f0HIEk6oO9aGGSWnSeq' -}, { - id: '12D3KooWP63uzL78BRMpkQ7augMdNi1h3VBrVWZucKjyhzGVaSi1', - privKey: 'CAESYPxO3SHyfc2578hDmfkGGBY255JjiLuVavJWy+9ivlpsxSyVKf36ipyRGL6szGzHuFs5ceEuuGVrPMg/rW2Ch1bFLJUp/fqKnJEYvqzMbMe4Wzlx4S64ZWs8yD+tbYKHVg==', - pubKey: 'CAESIMUslSn9+oqckRi+rMxsx7hbOXHhLrhlazzIP61tgodW' -}, { - id: '12D3KooWF85R7CM2Wikdtb2sjwnd24e1tgojf3MEWwizmVB8PA6U', - privKey: 'CAESYNXoQ5CnooE939AEqE2JJGPqvhoFJn0xP+j9KwjfOfDkTtPyfn2kJ1gn3uOYTcmoHFU1bbETNtRVuPMi1fmDmqFO0/J+faQnWCfe45hNyagcVTVtsRM21FW48yLV+YOaoQ==', - pubKey: 'CAESIE7T8n59pCdYJ97jmE3JqBxVNW2xEzbUVbjzItX5g5qh' -}, { - id: '12D3KooWPCofiCjhdtezP4eMnqBjjutFZNHjV39F5LWNrCvaLnzT', - privKey: 'CAESYLhUut01XPu+yIPbtZ3WnxOd26FYuTMRn/BbdFYsZE2KxueKRlo9yIAxmFReoNFUKztUU4G2aUiTbqDQaA6i0MDG54pGWj3IgDGYVF6g0VQrO1RTgbZpSJNuoNBoDqLQwA==', - pubKey: 'CAESIMbnikZaPciAMZhUXqDRVCs7VFOBtmlIk26g0GgOotDA' -}] - -export async function createPeerIdsFromFixtures (length: number): Promise { - return Promise.all( - Array.from({ length }).map(async (_, i) => createFromJSON(peers[i])) - ) -} - -export async function createPeerIds (length: number): Promise { - const peerIds: PeerId[] = [] - for (let i = 0; i < length; i++) { - const id = await createEd25519PeerId() - peerIds.push(id) - } - - return peerIds -} diff --git a/packages/connection-encryption-noise/test/handshakes/xx.spec.ts b/packages/connection-encryption-noise/test/handshakes/xx.spec.ts deleted file mode 100644 index 69e2733a73..0000000000 --- a/packages/connection-encryption-noise/test/handshakes/xx.spec.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { Buffer } from 'buffer' -import { expect, assert } from 'aegir/chai' -import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { pureJsCrypto } from '../../src/crypto/js.js' -import { XX } from '../../src/handshakes/xx.js' -import { createHandshakePayload, getHandshakePayload } from '../../src/utils.js' -import { generateEd25519Keys } from '../utils.js' -import type { NoiseSession } from '../../src/@types/handshake.js' -import type { KeyPair } from '../../src/@types/libp2p.js' - -describe('XX Handshake', () => { - const prologue = Buffer.alloc(0) - - it('Test creating new XX session', async () => { - try { - const xx = new XX(pureJsCrypto) - - const kpInitiator: KeyPair = pureJsCrypto.generateX25519KeyPair() - - xx.initSession(true, prologue, kpInitiator) - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('Test get HKDF', () => { - const ckBytes = Buffer.from('4e6f6973655f58585f32353531395f58436861436861506f6c795f53484132353600000000000000000000000000000000000000000000000000000000000000', 'hex') - const ikm = Buffer.from('a3eae50ea37a47e8a7aa0c7cd8e16528670536dcd538cebfd724fb68ce44f1910ad898860666227d4e8dd50d22a9a64d1c0a6f47ace092510161e9e442953da3', 'hex') - const ck = Buffer.alloc(32) - ckBytes.copy(ck) - - const [k1, k2, k3] = pureJsCrypto.getHKDF(ck, ikm) - expect(uint8ArrayToString(k1, 'hex')).to.equal('cc5659adff12714982f806e2477a8d5ddd071def4c29bb38777b7e37046f6914') - expect(uint8ArrayToString(k2, 'hex')).to.equal('a16ada915e551ab623f38be674bb4ef15d428ae9d80688899c9ef9b62ef208fa') - expect(uint8ArrayToString(k3, 'hex')).to.equal('ff67bf9727e31b06efc203907e6786667d2c7a74ac412b4d31a80ba3fd766f68') - }) - - async function doHandshake (xx: XX): Promise<{ nsInit: NoiseSession, nsResp: NoiseSession }> { - const kpInit = pureJsCrypto.generateX25519KeyPair() - const kpResp = pureJsCrypto.generateX25519KeyPair() - - // initiator setup - const libp2pInitKeys = await generateEd25519Keys() - const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInit.publicKey)) - - // responder setup - const libp2pRespKeys = await generateEd25519Keys() - const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResp.publicKey)) - - // initiator: new XX noise session - const nsInit = xx.initSession(true, prologue, kpInit) - // responder: new XX noise session - const nsResp = xx.initSession(false, prologue, kpResp) - - /* STAGE 0 */ - - // initiator creates payload - libp2pInitKeys.marshal().slice(0, 32) - const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64) - - const payloadInitEnc = createHandshakePayload(libp2pInitPubKey, initSignedPayload) - - // initiator sends message - const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc]) - const messageBuffer = xx.sendMessage(nsInit, message) - - expect(messageBuffer.ne.length).not.equal(0) - - // responder receives message - xx.recvMessage(nsResp, messageBuffer) - - /* STAGE 1 */ - - // responder creates payload - libp2pRespKeys.marshal().slice(0, 32) - const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64) - const payloadRespEnc = createHandshakePayload(libp2pRespPubKey, respSignedPayload) - - const message1 = Buffer.concat([message, payloadRespEnc]) - const messageBuffer2 = xx.sendMessage(nsResp, message1) - - expect(messageBuffer2.ne.length).not.equal(0) - expect(messageBuffer2.ns.length).not.equal(0) - - // initiator receive payload - xx.recvMessage(nsInit, messageBuffer2) - - /* STAGE 2 */ - - // initiator send message - const messageBuffer3 = xx.sendMessage(nsInit, Buffer.alloc(0)) - - // responder receive message - xx.recvMessage(nsResp, messageBuffer3) - - if (nsInit.cs1 == null || nsResp.cs1 == null || nsInit.cs2 == null || nsResp.cs2 == null) { - throw new Error('CipherState missing') - } - - assert(uint8ArrayEquals(nsInit.cs1.k, nsResp.cs1.k)) - assert(uint8ArrayEquals(nsInit.cs2.k, nsResp.cs2.k)) - - return { nsInit, nsResp } - } - - it('Test handshake', async () => { - try { - const xx = new XX(pureJsCrypto) - await doHandshake(xx) - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('Test symmetric encrypt and decrypt', async () => { - try { - const xx = new XX(pureJsCrypto) - const { nsInit, nsResp } = await doHandshake(xx) - const ad = Buffer.from('authenticated') - const message = Buffer.from('HelloCrypto') - - if (nsInit.cs1 == null || nsResp.cs1 == null || nsInit.cs2 == null || nsResp.cs2 == null) { - throw new Error('CipherState missing') - } - - const ciphertext = xx.encryptWithAd(nsInit.cs1, ad, message) - assert(!uint8ArrayEquals(Buffer.from('HelloCrypto'), ciphertext), 'Encrypted message should not be same as plaintext.') - const { plaintext: decrypted, valid } = xx.decryptWithAd(nsResp.cs1, ad, ciphertext) - - assert(uint8ArrayEquals(Buffer.from('HelloCrypto'), decrypted), 'Decrypted text not equal to original message.') - assert(valid) - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('Test multiple messages encryption and decryption', async () => { - const xx = new XX(pureJsCrypto) - const { nsInit, nsResp } = await doHandshake(xx) - const ad = Buffer.from('authenticated') - const message = Buffer.from('ethereum1') - - if (nsInit.cs1 == null || nsResp.cs1 == null || nsInit.cs2 == null || nsResp.cs2 == null) { - throw new Error('CipherState missing') - } - - const encrypted = xx.encryptWithAd(nsInit.cs1, ad, message) - const { plaintext: decrypted } = xx.decryptWithAd(nsResp.cs1, ad, encrypted) - assert.equal('ethereum1', uint8ArrayToString(decrypted, 'utf8'), 'Decrypted text not equal to original message.') - - const message2 = Buffer.from('ethereum2') - const encrypted2 = xx.encryptWithAd(nsInit.cs1, ad, message2) - const { plaintext: decrypted2 } = xx.decryptWithAd(nsResp.cs1, ad, encrypted2) - assert.equal('ethereum2', uint8ArrayToString(decrypted2, 'utf-8'), 'Decrypted text not equal to original message.') - }) -}) diff --git a/packages/connection-encryption-noise/test/index.spec.ts b/packages/connection-encryption-noise/test/index.spec.ts deleted file mode 100644 index 5ce901d262..0000000000 --- a/packages/connection-encryption-noise/test/index.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { expect } from 'aegir/chai' -import { lpStream } from 'it-length-prefixed-stream' -import { duplexPair } from 'it-pair/duplex' -import sinon from 'sinon' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { noise } from '../src/index.js' -import { Noise } from '../src/noise.js' -import { createPeerIdsFromFixtures } from './fixtures/peer.js' -import type { Metrics } from '@libp2p/interface/metrics' - -function createCounterSpy (): ReturnType { - return sinon.spy({ - increment: () => {}, - reset: () => {} - }) -} - -describe('Index', () => { - it('should expose class with tag and required functions', () => { - const noiseInstance = noise()() - expect(noiseInstance.protocol).to.equal('/noise') - expect(typeof (noiseInstance.secureInbound)).to.equal('function') - expect(typeof (noiseInstance.secureOutbound)).to.equal('function') - }) - - it('should collect metrics', async () => { - const [localPeer, remotePeer] = await createPeerIdsFromFixtures(2) - const metricsRegistry = new Map>() - const metrics = { - registerCounter: (name: string) => { - const counter = createCounterSpy() - metricsRegistry.set(name, counter) - return counter - } - } - const noiseInit = new Noise({ metrics: metrics as any as Metrics }) - const noiseResp = new Noise({}) - - const [inboundConnection, outboundConnection] = duplexPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) - ]) - const wrappedInbound = lpStream(inbound.conn) - const wrappedOutbound = lpStream(outbound.conn) - - await wrappedOutbound.write(uint8ArrayFromString('test')) - await wrappedInbound.read() - expect(metricsRegistry.get('libp2p_noise_xxhandshake_successes_total')?.increment.callCount).to.equal(1) - expect(metricsRegistry.get('libp2p_noise_xxhandshake_error_total')?.increment.callCount).to.equal(0) - expect(metricsRegistry.get('libp2p_noise_encrypted_packets_total')?.increment.callCount).to.equal(1) - expect(metricsRegistry.get('libp2p_noise_decrypt_errors_total')?.increment.callCount).to.equal(0) - }) -}) diff --git a/packages/connection-encryption-noise/test/noise.spec.ts b/packages/connection-encryption-noise/test/noise.spec.ts deleted file mode 100644 index 8b2c6937f4..0000000000 --- a/packages/connection-encryption-noise/test/noise.spec.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { Buffer } from 'buffer' -import { assert, expect } from 'aegir/chai' -import { randomBytes } from 'iso-random-stream' -import { byteStream } from 'it-byte-stream' -import { lpStream } from 'it-length-prefixed-stream' -import { duplexPair } from 'it-pair/duplex' -import sinon from 'sinon' -import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { NOISE_MSG_MAX_LENGTH_BYTES } from '../src/constants.js' -import { pureJsCrypto } from '../src/crypto/js.js' -import { decode0, decode2, encode1, uint16BEDecode, uint16BEEncode } from '../src/encoder.js' -import { XXHandshake } from '../src/handshake-xx.js' -import { XX } from '../src/handshakes/xx.js' -import { Noise } from '../src/noise.js' -import { createHandshakePayload, getHandshakePayload, getPayload, signPayload } from '../src/utils.js' -import { createPeerIdsFromFixtures } from './fixtures/peer.js' -import { getKeyPairFromPeerId } from './utils.js' -import type { PeerId } from '@libp2p/interface/peer-id' - -describe('Noise', () => { - let remotePeer: PeerId, localPeer: PeerId - const sandbox = sinon.createSandbox() - - before(async () => { - [localPeer, remotePeer] = await createPeerIdsFromFixtures(2) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('should communicate through encrypted streams without noise pipes', async () => { - try { - const noiseInit = new Noise({ staticNoiseKey: undefined, extensions: undefined }) - const noiseResp = new Noise({ staticNoiseKey: undefined, extensions: undefined }) - - const [inboundConnection, outboundConnection] = duplexPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) - ]) - const wrappedInbound = lpStream(inbound.conn) - const wrappedOutbound = lpStream(outbound.conn) - - await wrappedOutbound.write(Buffer.from('test')) - const response = await wrappedInbound.read() - expect(uint8ArrayToString(response.slice())).equal('test') - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('should test that secureOutbound is spec compliant', async () => { - const noiseInit = new Noise({ staticNoiseKey: undefined }) - const [inboundConnection, outboundConnection] = duplexPair() - - const [outbound, { wrapped, handshake }] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - (async () => { - const wrapped = lpStream( - inboundConnection, - { - lengthEncoder: uint16BEEncode, - lengthDecoder: uint16BEDecode, - maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES - } - ) - const prologue = Buffer.alloc(0) - const staticKeys = pureJsCrypto.generateX25519KeyPair() - const xx = new XX(pureJsCrypto) - - const payload = await getPayload(remotePeer, staticKeys.publicKey) - const handshake = new XXHandshake(false, payload, prologue, pureJsCrypto, staticKeys, wrapped, localPeer, xx) - - let receivedMessageBuffer = decode0((await wrapped.read()).slice()) - // The first handshake message contains the initiator's ephemeral public key - expect(receivedMessageBuffer.ne.length).equal(32) - xx.recvMessage(handshake.session, receivedMessageBuffer) - - // Stage 1 - const { publicKey: libp2pPubKey } = getKeyPairFromPeerId(remotePeer) - const signedPayload = await signPayload(remotePeer, getHandshakePayload(staticKeys.publicKey)) - const handshakePayload = createHandshakePayload(libp2pPubKey, signedPayload) - - const messageBuffer = xx.sendMessage(handshake.session, handshakePayload) - await wrapped.write(encode1(messageBuffer)) - - // Stage 2 - finish handshake - receivedMessageBuffer = decode2((await wrapped.read()).slice()) - xx.recvMessage(handshake.session, receivedMessageBuffer) - return { wrapped, handshake } - })() - ]) - - const wrappedOutbound = byteStream(outbound.conn) - await wrappedOutbound.write(uint8ArrayFromString('test')) - - // Check that noise message is prefixed with 16-bit big-endian unsigned integer - const data = (await wrapped.read()).slice() - const { plaintext: decrypted, valid } = handshake.decrypt(data, handshake.session) - // Decrypted data should match - expect(uint8ArrayEquals(decrypted, uint8ArrayFromString('test'))).to.be.true() - expect(valid).to.be.true() - }) - - it('should test large payloads', async function () { - this.timeout(10000) - try { - const noiseInit = new Noise({ staticNoiseKey: undefined }) - const noiseResp = new Noise({ staticNoiseKey: undefined }) - - const [inboundConnection, outboundConnection] = duplexPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) - ]) - const wrappedInbound = byteStream(inbound.conn) - const wrappedOutbound = lpStream(outbound.conn) - - const largePlaintext = randomBytes(60000) - await wrappedOutbound.write(Buffer.from(largePlaintext)) - const response = await wrappedInbound.read(60000) - - expect(response.length).equals(largePlaintext.length) - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('should working without remote peer provided in incoming connection', async () => { - try { - const staticKeysInitiator = pureJsCrypto.generateX25519KeyPair() - const noiseInit = new Noise({ staticNoiseKey: staticKeysInitiator.privateKey }) - const staticKeysResponder = pureJsCrypto.generateX25519KeyPair() - const noiseResp = new Noise({ staticNoiseKey: staticKeysResponder.privateKey }) - - const [inboundConnection, outboundConnection] = duplexPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection) - ]) - const wrappedInbound = lpStream(inbound.conn) - const wrappedOutbound = lpStream(outbound.conn) - - await wrappedOutbound.write(Buffer.from('test v2')) - const response = await wrappedInbound.read() - expect(uint8ArrayToString(response.slice())).equal('test v2') - - if (inbound.remotePeer.publicKey == null || localPeer.publicKey == null || - outbound.remotePeer.publicKey == null || remotePeer.publicKey == null) { - throw new Error('Public key missing from PeerId') - } - - assert(uint8ArrayEquals(inbound.remotePeer.publicKey, localPeer.publicKey)) - assert(uint8ArrayEquals(outbound.remotePeer.publicKey, remotePeer.publicKey)) - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('should accept and return Noise extension from remote peer', async () => { - try { - const certhashInit = Buffer.from('certhash data from init') - const staticKeysInitiator = pureJsCrypto.generateX25519KeyPair() - const noiseInit = new Noise({ staticNoiseKey: staticKeysInitiator.privateKey, extensions: { webtransportCerthashes: [certhashInit] } }) - const staticKeysResponder = pureJsCrypto.generateX25519KeyPair() - const certhashResp = Buffer.from('certhash data from respon') - const noiseResp = new Noise({ staticNoiseKey: staticKeysResponder.privateKey, extensions: { webtransportCerthashes: [certhashResp] } }) - - const [inboundConnection, outboundConnection] = duplexPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection) - ]) - - assert(uint8ArrayEquals(inbound.remoteExtensions?.webtransportCerthashes[0] ?? new Uint8Array(), certhashInit)) - assert(uint8ArrayEquals(outbound.remoteExtensions?.webtransportCerthashes[0] ?? new Uint8Array(), certhashResp)) - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('should accept a prologue', async () => { - try { - const noiseInit = new Noise({ staticNoiseKey: undefined, crypto: pureJsCrypto, prologueBytes: Buffer.from('Some prologue') }) - const noiseResp = new Noise({ staticNoiseKey: undefined, crypto: pureJsCrypto, prologueBytes: Buffer.from('Some prologue') }) - - const [inboundConnection, outboundConnection] = duplexPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) - ]) - const wrappedInbound = lpStream(inbound.conn) - const wrappedOutbound = lpStream(outbound.conn) - - await wrappedOutbound.write(Buffer.from('test')) - const response = await wrappedInbound.read() - expect(uint8ArrayToString(response.slice())).equal('test') - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) -}) diff --git a/packages/connection-encryption-noise/test/utils.ts b/packages/connection-encryption-noise/test/utils.ts deleted file mode 100644 index 4e5cd5dbef..0000000000 --- a/packages/connection-encryption-noise/test/utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { keys } from '@libp2p/crypto' -import type { KeyPair } from '../src/@types/libp2p.js' -import type { PrivateKey } from '@libp2p/interface/keys' -import type { PeerId } from '@libp2p/interface/peer-id' - -export async function generateEd25519Keys (): Promise { - return keys.generateKeyPair('Ed25519', 32) -} - -export function getKeyPairFromPeerId (peerId: PeerId): KeyPair { - if (peerId.privateKey == null || peerId.publicKey == null) { - throw new Error('PrivateKey or PublicKey missing from PeerId') - } - - return { - privateKey: peerId.privateKey.subarray(0, 32), - publicKey: peerId.publicKey - } -} diff --git a/packages/connection-encryption-noise/test/xx-handshake.spec.ts b/packages/connection-encryption-noise/test/xx-handshake.spec.ts deleted file mode 100644 index 7223e76536..0000000000 --- a/packages/connection-encryption-noise/test/xx-handshake.spec.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Buffer } from 'buffer' -import { assert, expect } from 'aegir/chai' -import { lpStream } from 'it-length-prefixed-stream' -import { duplexPair } from 'it-pair/duplex' -import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { pureJsCrypto } from '../src/crypto/js.js' -import { XXHandshake } from '../src/handshake-xx.js' -import { getPayload } from '../src/utils.js' -import { createPeerIdsFromFixtures } from './fixtures/peer.js' -import type { PeerId } from '@libp2p/interface/peer-id' - -describe('XX Handshake', () => { - let peerA: PeerId, peerB: PeerId, fakePeer: PeerId - - before(async () => { - [peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3) - }) - - it('should propose, exchange and finish handshake', async () => { - try { - const duplex = duplexPair() - const connectionFrom = lpStream(duplex[0]) - const connectionTo = lpStream(duplex[1]) - - const prologue = Buffer.alloc(0) - const staticKeysInitiator = pureJsCrypto.generateX25519KeyPair() - const staticKeysResponder = pureJsCrypto.generateX25519KeyPair() - - const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey) - const handshakeInitator = new XXHandshake(true, initPayload, prologue, pureJsCrypto, staticKeysInitiator, connectionFrom, peerB) - - const respPayload = await getPayload(peerB, staticKeysResponder.publicKey) - const handshakeResponder = new XXHandshake(false, respPayload, prologue, pureJsCrypto, staticKeysResponder, connectionTo, peerA) - - await handshakeInitator.propose() - await handshakeResponder.propose() - - await handshakeResponder.exchange() - await handshakeInitator.exchange() - - await handshakeInitator.finish() - await handshakeResponder.finish() - - const sessionInitator = handshakeInitator.session - const sessionResponder = handshakeResponder.session - - // Test shared key - if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) { - assert(uint8ArrayEquals(sessionInitator.cs1.k, sessionResponder.cs1.k)) - assert(uint8ArrayEquals(sessionInitator.cs2.k, sessionResponder.cs2.k)) - } else { - assert(false) - } - - // Test encryption and decryption - const encrypted = handshakeInitator.encrypt(Buffer.from('encryptthis'), handshakeInitator.session) - const { plaintext: decrypted, valid } = handshakeResponder.decrypt(encrypted, handshakeResponder.session) - assert(uint8ArrayEquals(decrypted, Buffer.from('encryptthis'))) - assert(valid) - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('Initiator should fail to exchange handshake if given wrong public key in payload', async () => { - try { - const duplex = duplexPair() - const connectionFrom = lpStream(duplex[0]) - const connectionTo = lpStream(duplex[1]) - - const prologue = Buffer.alloc(0) - const staticKeysInitiator = pureJsCrypto.generateX25519KeyPair() - const staticKeysResponder = pureJsCrypto.generateX25519KeyPair() - - const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey) - const handshakeInitator = new XXHandshake(true, initPayload, prologue, pureJsCrypto, staticKeysInitiator, connectionFrom, fakePeer) - - const respPayload = await getPayload(peerB, staticKeysResponder.publicKey) - const handshakeResponder = new XXHandshake(false, respPayload, prologue, pureJsCrypto, staticKeysResponder, connectionTo, peerA) - - await handshakeInitator.propose() - await handshakeResponder.propose() - - await handshakeResponder.exchange() - await handshakeInitator.exchange() - - assert(false, 'Should throw exception') - } catch (e) { - const err = e as Error - expect(err.message).equals(`Error occurred while verifying signed payload: Payload identity key ${peerB.toString()} does not match expected remote peer ${fakePeer.toString()}`) - } - }) - - it('Responder should fail to exchange handshake if given wrong public key in payload', async () => { - try { - const duplex = duplexPair() - const connectionFrom = lpStream(duplex[0]) - const connectionTo = lpStream(duplex[1]) - - const prologue = Buffer.alloc(0) - const staticKeysInitiator = pureJsCrypto.generateX25519KeyPair() - const staticKeysResponder = pureJsCrypto.generateX25519KeyPair() - - const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey) - const handshakeInitator = new XXHandshake(true, initPayload, prologue, pureJsCrypto, staticKeysInitiator, connectionFrom, peerB) - - const respPayload = await getPayload(peerB, staticKeysResponder.publicKey) - const handshakeResponder = new XXHandshake(false, respPayload, prologue, pureJsCrypto, staticKeysResponder, connectionTo, fakePeer) - - await handshakeInitator.propose() - await handshakeResponder.propose() - - await handshakeResponder.exchange() - await handshakeInitator.exchange() - - await handshakeInitator.finish() - await handshakeResponder.finish() - - assert(false, 'Should throw exception') - } catch (e) { - const err = e as Error - expect(err.message).equals(`Error occurred while verifying signed payload: Payload identity key ${peerA.toString()} does not match expected remote peer ${fakePeer.toString()}`) - } - }) -}) diff --git a/packages/connection-encryption-noise/tsconfig.json b/packages/connection-encryption-noise/tsconfig.json deleted file mode 100644 index 71d88cad96..0000000000 --- a/packages/connection-encryption-noise/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "extends": "aegir/src/config/tsconfig.aegir.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": [ - "src", - "test" - ], - "references": [ - { - "path": "../crypto" - }, - { - "path": "../interface" - }, - { - "path": "../interface-compliance-tests" - }, - { - "path": "../logger" - }, - { - "path": "../peer-id" - }, - { - "path": "../peer-id-factory" - } - ] -} diff --git a/packages/connection-encryption-noise/typedoc.json b/packages/connection-encryption-noise/typedoc.json deleted file mode 100644 index f599dc728d..0000000000 --- a/packages/connection-encryption-noise/typedoc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entryPoints": [ - "./src/index.ts" - ] -} diff --git a/packages/crypto/CHANGELOG.md b/packages/crypto/CHANGELOG.md index 2185a39d35..43c1d3d13f 100644 --- a/packages/crypto/CHANGELOG.md +++ b/packages/crypto/CHANGELOG.md @@ -5,6 +5,30 @@ * **dev:** bump aegir from 38.1.8 to 39.0.5 ([#320](https://github.com/libp2p/js-libp2p-crypto/issues/320)) ([f0b4c06](https://github.com/libp2p/js-libp2p-crypto/commit/f0b4c068a23d78b1376865c6adf6cce21ab91196)) +## [2.0.0](https://www.github.com/libp2p/js-libp2p/compare/crypto-v1.0.17...crypto-v2.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + ## [1.0.16](https://github.com/libp2p/js-libp2p-crypto/compare/v1.0.15...v1.0.16) (2023-05-05) @@ -735,4 +759,4 @@ chore: update deps ### Features -* **keys:** implement generateKeyPairFromSeed for ed25519 ([e5b7c1f](https://github.com/libp2p/js-libp2p-crypto/commit/e5b7c1f)) +* **keys:** implement generateKeyPairFromSeed for ed25519 ([e5b7c1f](https://github.com/libp2p/js-libp2p-crypto/commit/e5b7c1f)) \ No newline at end of file diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 92d5cd8db5..cfd378b4f9 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/crypto", - "version": "1.0.17", + "version": "2.0.0", "description": "Crypto primitives for libp2p", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/crypto#readme", @@ -85,7 +85,7 @@ "generate": "protons ./src/keys/keys.proto" }, "dependencies": { - "@libp2p/interface": "~0.0.1", + "@libp2p/interface": "^0.1.0", "@noble/ed25519": "^1.6.0", "@noble/secp256k1": "^1.5.4", "multiformats": "^12.0.1", diff --git a/packages/interface-compliance-tests/CHANGELOG.md b/packages/interface-compliance-tests/CHANGELOG.md index 35f9edbfff..5af11a3008 100644 --- a/packages/interface-compliance-tests/CHANGELOG.md +++ b/packages/interface-compliance-tests/CHANGELOG.md @@ -5,6 +5,39 @@ * bump aegir from 38.1.8 to 39.0.5 ([#393](https://github.com/libp2p/js-libp2p-interfaces/issues/393)) ([31f3797](https://github.com/libp2p/js-libp2p-interfaces/commit/31f3797b24f7c23f3f16e9db3a230bd5f7cd5175)) +## [4.0.0](https://www.github.com/libp2p/js-libp2p/compare/interface-compliance-tests-v3.0.7...interface-compliance-tests-v4.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* the `.close`, `closeRead` and `closeWrite` methods on the `Stream` interface are now asynchronous +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* mark connections with limits as transient ([#1890](https://www.github.com/libp2p/js-libp2p/issues/1890)) ([a1ec46b](https://www.github.com/libp2p/js-libp2p/commit/a1ec46b5f5606b7bdf3e5b085013fb88e26439f9)) +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* close streams gracefully ([#1864](https://www.github.com/libp2p/js-libp2p/issues/1864)) ([b36ec7f](https://www.github.com/libp2p/js-libp2p/commit/b36ec7f24e477af21cec31effc086a6c611bf271)), closes [#1793](https://www.github.com/libp2p/js-libp2p/issues/1793) [#656](https://www.github.com/libp2p/js-libp2p/issues/656) +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/interface-internal bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * @libp2p/multistream-select bumped from ^3.0.0 to ^4.0.0 + * @libp2p/peer-collections bumped from ^3.0.0 to ^4.0.0 + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-id-factory bumped from ^2.0.0 to ^3.0.0 + ## [@libp2p/interface-compliance-tests-v3.0.6](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-compliance-tests-v3.0.5...@libp2p/interface-compliance-tests-v3.0.6) (2023-01-18) @@ -527,4 +560,4 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ### BREAKING CHANGES -* the tests now live in the libp2p-interfaces-compliance-tests module +* the tests now live in the libp2p-interfaces-compliance-tests module \ No newline at end of file diff --git a/packages/interface-compliance-tests/package.json b/packages/interface-compliance-tests/package.json index 0e69737816..d516764208 100644 --- a/packages/interface-compliance-tests/package.json +++ b/packages/interface-compliance-tests/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/interface-compliance-tests", - "version": "3.0.7", + "version": "4.0.0", "description": "Compliance tests for JS libp2p interfaces", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-compliance-tests#readme", @@ -102,13 +102,13 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/interface-internal": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/multistream-select": "^3.0.0", - "@libp2p/peer-collections": "^3.0.0", - "@libp2p/peer-id": "^2.0.0", - "@libp2p/peer-id-factory": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/interface-internal": "^0.1.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/multistream-select": "^4.0.0", + "@libp2p/peer-collections": "^4.0.0", + "@libp2p/peer-id": "^3.0.0", + "@libp2p/peer-id-factory": "^3.0.0", "@multiformats/multiaddr": "^12.1.3", "abortable-iterator": "^5.0.1", "delay": "^6.0.0", diff --git a/packages/interface-internal/CHANGELOG.md b/packages/interface-internal/CHANGELOG.md new file mode 100644 index 0000000000..7c03af3c75 --- /dev/null +++ b/packages/interface-internal/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +## [0.1.0](https://www.github.com/libp2p/js-libp2p/compare/interface-internal-v0.0.1...interface-internal-v0.1.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* the `.close`, `closeRead` and `closeWrite` methods on the `Stream` interface are now asynchronous +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* mark connections with limits as transient ([#1890](https://www.github.com/libp2p/js-libp2p/issues/1890)) ([a1ec46b](https://www.github.com/libp2p/js-libp2p/commit/a1ec46b5f5606b7bdf3e5b085013fb88e26439f9)) +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* close streams gracefully ([#1864](https://www.github.com/libp2p/js-libp2p/issues/1864)) ([b36ec7f](https://www.github.com/libp2p/js-libp2p/commit/b36ec7f24e477af21cec31effc086a6c611bf271)), closes [#1793](https://www.github.com/libp2p/js-libp2p/issues/1793) [#656](https://www.github.com/libp2p/js-libp2p/issues/656) +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/peer-collections bumped from ^3.0.0 to ^4.0.0 \ No newline at end of file diff --git a/packages/interface-internal/package.json b/packages/interface-internal/package.json index 5e0e634ba1..6967265b72 100644 --- a/packages/interface-internal/package.json +++ b/packages/interface-internal/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/interface-internal", - "version": "0.0.1", + "version": "0.1.0", "description": "Interfaces implemented by internal libp2p components", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface-internal#readme", @@ -78,8 +78,8 @@ "build": "aegir build" }, "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/peer-collections": "^3.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/peer-collections": "^4.0.0", "@multiformats/multiaddr": "^12.1.3", "uint8arraylist": "^2.4.3" }, diff --git a/packages/interface/CHANGELOG.md b/packages/interface/CHANGELOG.md index b8e2e0e2cf..7d03df1fd5 100644 --- a/packages/interface/CHANGELOG.md +++ b/packages/interface/CHANGELOG.md @@ -5,6 +5,27 @@ * add start/stop events to libp2p interface ([#407](https://github.com/libp2p/js-libp2p-interfaces/issues/407)) ([016c1e8](https://github.com/libp2p/js-libp2p-interfaces/commit/016c1e82b060c93c80546cd8c493ec6e6c97cbec)) +## [0.1.0](https://www.github.com/libp2p/js-libp2p/compare/interface-v0.0.1...interface-v0.1.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* the `.close`, `closeRead` and `closeWrite` methods on the `Stream` interface are now asynchronous +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* mark connections with limits as transient ([#1890](https://www.github.com/libp2p/js-libp2p/issues/1890)) ([a1ec46b](https://www.github.com/libp2p/js-libp2p/commit/a1ec46b5f5606b7bdf3e5b085013fb88e26439f9)) +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* add pubsub interfaces to @libp2p/interface ([#1857](https://www.github.com/libp2p/js-libp2p/issues/1857)) ([2e561fe](https://www.github.com/libp2p/js-libp2p/commit/2e561fe9d2d3a4e7c38bd0bf4baf41978c4d9438)) +* close streams gracefully ([#1864](https://www.github.com/libp2p/js-libp2p/issues/1864)) ([b36ec7f](https://www.github.com/libp2p/js-libp2p/commit/b36ec7f24e477af21cec31effc086a6c611bf271)), closes [#1793](https://www.github.com/libp2p/js-libp2p/issues/1793) [#656](https://www.github.com/libp2p/js-libp2p/issues/656) +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + ## [@libp2p/interface-v3.1.0](https://github.com/libp2p/js-libp2p-interfaces/compare/@libp2p/interface-v3.0.1...@libp2p/interface-v3.1.0) (2023-05-05) diff --git a/packages/interface/package.json b/packages/interface/package.json index dce3225aa8..f62bc6d58c 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/interface", - "version": "0.0.1", + "version": "0.1.0", "description": "The interface implemented by a libp2p node", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/interface#readme", diff --git a/packages/kad-dht/CHANGELOG.md b/packages/kad-dht/CHANGELOG.md index a1660bd03c..ed3140115e 100644 --- a/packages/kad-dht/CHANGELOG.md +++ b/packages/kad-dht/CHANGELOG.md @@ -5,6 +5,41 @@ * skip self-query if not running ([#479](https://github.com/libp2p/js-libp2p-kad-dht/issues/479)) ([7095290](https://github.com/libp2p/js-libp2p-kad-dht/commit/70952907a27fd8778773172059879656b4f08855)) +## [10.0.0](https://www.github.com/libp2p/js-libp2p/compare/kad-dht-v9.3.6...kad-dht-v10.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* the `.close`, `closeRead` and `closeWrite` methods on the `Stream` interface are now asynchronous +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* close streams gracefully ([#1864](https://www.github.com/libp2p/js-libp2p/issues/1864)) ([b36ec7f](https://www.github.com/libp2p/js-libp2p/commit/b36ec7f24e477af21cec31effc086a6c611bf271)), closes [#1793](https://www.github.com/libp2p/js-libp2p/issues/1793) [#656](https://www.github.com/libp2p/js-libp2p/issues/656) +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^1.0.0 to ^2.0.0 + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/interface-internal bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-collections bumped from ^3.0.0 to ^4.0.0 + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^3.0.0 to ^4.0.0 + * @libp2p/peer-id-factory bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-store bumped from ^8.0.0 to ^9.0.0 + ## [9.3.5](https://github.com/libp2p/js-libp2p-kad-dht/compare/v9.3.4...v9.3.5) (2023-05-26) @@ -1651,4 +1686,4 @@ Co-Authored-By: vasco-santos ### Features -* v0.1.0 ([4bd1fbc](https://github.com/libp2p/js-libp2p-kad-dht/commit/4bd1fbc)) +* v0.1.0 ([4bd1fbc](https://github.com/libp2p/js-libp2p-kad-dht/commit/4bd1fbc)) \ No newline at end of file diff --git a/packages/kad-dht/package.json b/packages/kad-dht/package.json index 81e4ea5c02..d29e31f64c 100644 --- a/packages/kad-dht/package.json +++ b/packages/kad-dht/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/kad-dht", - "version": "9.3.6", + "version": "10.0.0", "description": "JavaScript implementation of the Kad-DHT for libp2p", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/kad-dht#readme", @@ -51,12 +51,12 @@ "dep-check": "aegir dep-check -i events" }, "dependencies": { - "@libp2p/crypto": "^1.0.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/interface-internal": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/peer-collections": "^3.0.0", - "@libp2p/peer-id": "^2.0.0", + "@libp2p/crypto": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/interface-internal": "^0.1.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/peer-collections": "^4.0.0", + "@libp2p/peer-id": "^3.0.0", "@multiformats/multiaddr": "^12.1.3", "@types/sinon": "^10.0.15", "abortable-iterator": "^5.0.1", @@ -88,9 +88,9 @@ "varint": "^6.0.0" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^3.0.0", - "@libp2p/peer-id-factory": "^2.0.0", - "@libp2p/peer-store": "^8.0.0", + "@libp2p/interface-compliance-tests": "^4.0.0", + "@libp2p/peer-id-factory": "^3.0.0", + "@libp2p/peer-store": "^9.0.0", "@types/lodash.random": "^3.2.6", "@types/lodash.range": "^3.2.6", "@types/varint": "^6.0.0", diff --git a/packages/keychain/CHANGELOG.md b/packages/keychain/CHANGELOG.md index 0f7c302ac5..3c3f0e4563 100644 --- a/packages/keychain/CHANGELOG.md +++ b/packages/keychain/CHANGELOG.md @@ -11,6 +11,35 @@ * **dev:** bump aegir from 38.1.8 to 39.0.10 ([#70](https://github.com/libp2p/js-libp2p-keychain/issues/70)) ([4da4a08](https://github.com/libp2p/js-libp2p-keychain/commit/4da4a08b86f436c36e2fae48ecc48817e9b8066f)) +## [3.0.0](https://www.github.com/libp2p/js-libp2p/compare/keychain-v2.0.1...keychain-v3.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^1.0.0 to ^2.0.0 + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + * devDependencies + * @libp2p/peer-id-factory bumped from ^2.0.0 to ^3.0.0 + ## [2.0.0](https://github.com/libp2p/js-libp2p-keychain/compare/v1.0.1...v2.0.0) (2023-03-13) @@ -201,4 +230,4 @@ Co-Authored-By: Vasco Santos ### Features * move bits from https://github.com/richardschneider/ipfs-encryption ([1a96ae8](https://github.com/libp2p/js-libp2p-keychain/commit/1a96ae8)) -* use libp2p-crypto ([#18](https://github.com/libp2p/js-libp2p-keychain/issues/18)) ([c1627a9](https://github.com/libp2p/js-libp2p-keychain/commit/c1627a9)) +* use libp2p-crypto ([#18](https://github.com/libp2p/js-libp2p-keychain/issues/18)) ([c1627a9](https://github.com/libp2p/js-libp2p-keychain/commit/c1627a9)) \ No newline at end of file diff --git a/packages/keychain/package.json b/packages/keychain/package.json index 7fcfa12b14..427662a410 100644 --- a/packages/keychain/package.json +++ b/packages/keychain/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/keychain", - "version": "2.0.1", + "version": "3.0.0", "description": "Key management and cryptographically protected messages", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/keychain#readme", @@ -53,17 +53,17 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/crypto": "^1.0.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/peer-id": "^2.0.0", + "@libp2p/crypto": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/peer-id": "^3.0.0", "interface-datastore": "^8.2.0", "merge-options": "^3.0.4", "sanitize-filename": "^1.6.3", "uint8arrays": "^4.0.4" }, "devDependencies": { - "@libp2p/peer-id-factory": "^2.0.0", + "@libp2p/peer-id-factory": "^3.0.0", "aegir": "^40.0.1", "datastore-core": "^9.1.1", "multiformats": "^12.0.1" diff --git a/packages/libp2p-daemon-client/.aegir.js b/packages/libp2p-daemon-client/.aegir.js deleted file mode 100644 index 135a6a2211..0000000000 --- a/packages/libp2p-daemon-client/.aegir.js +++ /dev/null @@ -1,8 +0,0 @@ - -export default { - build: { - config: { - platform: 'node' - } - } -} diff --git a/packages/libp2p-daemon-client/API.md b/packages/libp2p-daemon-client/API.md deleted file mode 100644 index 67732fea6d..0000000000 --- a/packages/libp2p-daemon-client/API.md +++ /dev/null @@ -1,500 +0,0 @@ -# API - -* [Getting started](#getting-started) -* [`close`](#close) -* [`connect`](#connect) -* [`identify`](#identify) -* [`listPeers`](#listPeers) -* [`openStream`](#openStream) -* [`registerStream`](#registerStream) -* [`dht.put`](#dht.put) -* [`dht.get`](#dht.get) -* [`dht.findPeer`](#dht.findPeer) -* [`dht.provide`](#dht.provide) -* [`dht.findProviders`](#dht.findProviders) -* [`dht.getClosestPeers`](#dht.getClosestPeers) -* [`dht.getPublicKey`](#dht.getPublicKey) -* [`pubsub.getTopics`](#pubsub.getTopics) -* [`pubsub.publish`](#pubsub.publish) -* [`pubsub.subscribe`](#pubsub.subscribe) - -## Getting started - -Create a new daemon client, using a unix socket. - -### `Client(socketPath)` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| socketPath | `String` | unix socket path | - -#### Returns - -Client instance - -#### Example - -```js -const Client = require('libp2p-daemon-client') - -const defaultSock = '/tmp/p2pd.sock' -const client = new Client(defaultSock) - -// client.{} -``` - -## close - -Closes the socket. - -### `client.close()` - -#### Returns - -| Type | Description | -|------|-------------| -| `Promise` | Promise resolves when socket is closed | - -#### Example - -```js -const Client = require('libp2p-daemon-client') - -const defaultSock = '/tmp/p2pd.sock' -const client = new Client(defaultSock) - -// close the socket -await client.close() -``` - -## connect - -Requests a connection to a known peer on a given set of addresses. - -### `client.connect(peerId, addrs)` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| peerId | [`PeerId`](https://github.com/libp2p/js-peer-id) | peer ID to connect | -| options | `Object` | set of addresses to connect | - -#### Example - -```js -const client = new Client(defaultSock) - -try { - await client.connect(peerId, addrs) -} catch (err) { - // -} -``` - -## identify - -Query the daemon for its peer ID and listen addresses. - -### `client.identify()` - -#### Returns - -| Type | Description | -|------|-------------| -| `Object` | Identify response | -| `Object.peerId` | Peer id of the daemon | -| `Object.addrs` | Addresses of the daemon | - -#### Example - -```js -const client = new Client(defaultSock) - -let identify - -try { - identify = await client.identify() -} catch (err) { - // -} -``` - -## listPeers - -Get a list of IDs of peers the node is connected to. - -### `client.listPeers()` - -#### Returns - -| Type | Description | -|------|-------------| -| `Array` | array of peer id's | -| `Array.` | Peer id of a node | - -#### Example - -```js -const client = new Client(defaultSock) - -let identify - -try { - identify = await client.identify() -} catch (err) { - // -} -``` - -## openStream - -Initiate an outbound stream to a peer on one of a set of protocols. - -### `client.openStream(peerId, protocol)` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| peerId | [`PeerId`](https://github.com/libp2p/js-peer-id) | peer ID to connect | -| protocol | `string` | protocol to use | - -#### Returns - -| Type | Description | -|------|-------------| -| `Socket` | socket to write data | - -#### Example - -```js -const protocol = '/protocol/1.0.0' -const client = new Client(defaultSock) - -let socket - -try { - socket = await client.openStream(peerId, protocol) -} catch (err) { - // -} - -socket.write(uint8ArrayFromString('data')) -``` - -## registerStreamHandler - -Register a handler for inbound streams on a given protocol. - -### `client.registerStreamHandler(path, protocol)` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| path | `string` | socket path | -| protocol | `string` | protocol to use | - -#### Example - -```js -const protocol = '/protocol/1.0.0' -const client = new Client(defaultSock) - -await client.registerStreamHandler(path, protocol) -``` - -## dht.put - -Write a value to a key in the DHT. - -### `client.dht.put(key, value)` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| key | `Uint8Array` | key to add to the dht | -| value | `Uint8Array` | value to add to the dht | - -#### Example - -```js -const client = new Client(defaultSock) - -const key = '/key' -const value = uint8ArrayFromString('oh hello there') - -try { - await client.dht.put(key, value) -} catch (err) { - // -} -``` - -## dht.get - -Query the DHT for a value stored through a key in the DHT. - -### `client.dht.get(key)` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| key | `Uint8Array` | key to get from the dht | - -#### Returns - -| Type | Description | -|------|-------------| -| `Uint8Array` | Value obtained from the DHT | - -#### Example - -```js -const client = new Client(defaultSock) - -const key = '/key' -let value - -try { - value = await client.dht.get(key, value) -} catch (err) { - // -} -``` - -## dht.findPeer - -Query the DHT for a given peer's known addresses. - -### `client.dht.findPeer(peerId)` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| peerId | [`PeerId`](https://github.com/libp2p/js-peer-id) | ID of the peer to find | - -#### Returns - -| Type | Description | -|------|-------------| -| `PeerInfo` | Peer info of a known peer | - -#### Example - -```js -const client = new Client(defaultSock) - -let peerInfo - -try { - peerInfo = await client.dht.findPeer(peerId) -} catch (err) { - // -} -``` - -## dht.provide - -Announce that have data addressed by a given CID. - -### `client.dht.provide(cid)` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| cid | [`CID`](https://github.com/multiformats/js-cid) | cid to provide | - -#### Example - -```js -const client = new Client(defaultSock) - -try { - await client.dht.provide(cid) -} catch (err) { - // -} -``` - -## dht.findProviders - -Query the DHT for peers that have a piece of content, identified by a CID. - -### `client.dht.findProviders(cid, [count])` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| cid | [`CID`](https://github.com/multiformats/js-cid) | cid to find | -| count | `number` | number or results aimed | - -#### Returns - -| Type | Description | -|------|-------------| -| `Array` | array of peer info | -| `Array.` | Peer info of a node | - -#### Example - -```js -const client = new Client(defaultSock) - -let peerInfos - -try { - peerInfos = await client.dht.findProviders(cid) -} catch (err) { - // -} -``` - -## dht.getClosestPeers - -Query the DHT routing table for peers that are closest to a provided key. - -### `client.dht.getClosestPeers(key)` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| key | `Uint8Array` | key to get from the dht | - -#### Returns - -| Type | Description | -|------|-------------| -| `Array` | array of peer info | -| `Array.` | Peer info of a node | - -#### Example - -```js -const client = new Client(defaultSock) - -let peerInfos - -try { - peerInfos = await client.dht.getClosestPeers(key) -} catch (err) { - // -} -``` - -## dht.getPublicKey - -Query the DHT routing table for a given peer's public key. - -### `client.dht.getPublicKey(peerId)` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| peerId | [`PeerId`](https://github.com/libp2p/js-peer-id) | ID of the peer to find | - -#### Returns - -| Type | Description | -|------|-------------| -| `PublicKey` | public key of the peer | - -#### Example - -```js -const client = new Client(defaultSock) - -let publicKey - -try { - publicKey = await client.dht.getPublicKey(peerId) -} catch (err) { - // -} -``` - -### `client.pubsub.getTopics()` - -#### Returns - -| Type | Description | -|------|-------------| -| `Array` | topics the node is subscribed to | - -#### Example - -```js -const client = new Client(defaultSock) - -let topics - -try { - topics = await client.pubsub.getTopics() -} catch (err) { - // -} -``` - -### `client.pubsub.publish()` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| topic | `string` | topic to publish | -| data | `Uint8Array` | data to publish | - -#### Returns - -| Type | Description | -|------|-------------| -| `Promise` | publish success | - -#### Example - -```js -const topic = 'topic' -const data = uint8ArrayFromString('data') -const client = new Client(defaultSock) - -try { - await client.pubsub.publish(topic, data) -} catch (err) { - // -} -``` - -### `client.pubsub.subscribe()` - -#### Parameters - -| Name | Type | Description | -|------|------|-------------| -| topic | `string` | topic to subscribe | - -#### Returns - -| Type | Description | -|------|-------------| -| `AsyncIterator` | data published | - -#### Example - -```js -const topic = 'topic' -const client = new Client(defaultSock) - -for await (const msg of client.pubsub.subscribe(topic)) { - // msg.data - pubsub data received -} -``` diff --git a/packages/libp2p-daemon-client/CHANGELOG.md b/packages/libp2p-daemon-client/CHANGELOG.md deleted file mode 100644 index 2b00f16859..0000000000 --- a/packages/libp2p-daemon-client/CHANGELOG.md +++ /dev/null @@ -1,463 +0,0 @@ -## [@libp2p/daemon-client-v6.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v6.0.2...@libp2p/daemon-client-v6.0.3) (2023-04-27) - - -### Bug Fixes - -* use interface-libp2p to ensure the correct services are set ([#203](https://github.com/libp2p/js-libp2p-daemon/issues/203)) ([8602a70](https://github.com/libp2p/js-libp2p-daemon/commit/8602a704e45cfa768ad55974d025b2d4be6f42a9)) - -## [@libp2p/daemon-client-v6.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v6.0.1...@libp2p/daemon-client-v6.0.2) (2023-04-24) - - -### Dependencies - -* bump @libp2p/interface-peer-store from 1.2.9 to 2.0.0 ([#201](https://github.com/libp2p/js-libp2p-daemon/issues/201)) ([9b146a8](https://github.com/libp2p/js-libp2p-daemon/commit/9b146a8c38c30a13401be6da5259cd9da6bdc25c)) - -## [@libp2p/daemon-client-v6.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v6.0.0...@libp2p/daemon-client-v6.0.1) (2023-04-24) - - -### Dependencies - -* **dev:** bump @libp2p/interface-mocks from 10.0.3 to 11.0.0 ([#199](https://github.com/libp2p/js-libp2p-daemon/issues/199)) ([76f7b6f](https://github.com/libp2p/js-libp2p-daemon/commit/76f7b6fdd1af129ac278c5d2313d466db3e28a78)) - -## [@libp2p/daemon-client-v6.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v5.0.3...@libp2p/daemon-client-v6.0.0) (2023-04-19) - - -### ⚠ BREAKING CHANGES - -* the type of the source/sink properties have changed - -### Dependencies - -* update it-stream-types to 2.x.x ([#196](https://github.com/libp2p/js-libp2p-daemon/issues/196)) ([a09f6d5](https://github.com/libp2p/js-libp2p-daemon/commit/a09f6d58942033b08b579735aaa1537b3a324776)) -* update sibling dependencies ([db50405](https://github.com/libp2p/js-libp2p-daemon/commit/db50405ddec3a68ad265c3d3233595187bc4895d)) -* update sibling dependencies ([e0ec5ec](https://github.com/libp2p/js-libp2p-daemon/commit/e0ec5ecf5bfd7f801274d37d51c3dcce652de2ba)) - -## [@libp2p/daemon-client-v5.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v5.0.2...@libp2p/daemon-client-v5.0.3) (2023-04-12) - - -### Dependencies - -* bump @libp2p/interface-connection from 3.1.1 to 4.0.0 ([#195](https://github.com/libp2p/js-libp2p-daemon/issues/195)) ([798ecc5](https://github.com/libp2p/js-libp2p-daemon/commit/798ecc594bc64c8e34aad13e1b9884011f0b1f29)) - -## [@libp2p/daemon-client-v5.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v5.0.1...@libp2p/daemon-client-v5.0.2) (2023-04-03) - - -### Dependencies - -* update all it-* deps to the latest versions ([#193](https://github.com/libp2p/js-libp2p-daemon/issues/193)) ([cb0aa85](https://github.com/libp2p/js-libp2p-daemon/commit/cb0aa85bbbad651db088594622a9438a127d2a10)) - -## [@libp2p/daemon-client-v5.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v5.0.0...@libp2p/daemon-client-v5.0.1) (2023-03-17) - - -### Dependencies - -* bump @multiformats/multiaddr from 11.6.1 to 12.0.0 ([#189](https://github.com/libp2p/js-libp2p-daemon/issues/189)) ([aaf7e2e](https://github.com/libp2p/js-libp2p-daemon/commit/aaf7e2e37423cae78cd16d8e16e06db40fdcd1e3)) - -## [@libp2p/daemon-client-v5.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v4.1.0...@libp2p/daemon-client-v5.0.0) (2023-02-24) - - -### ⚠ BREAKING CHANGES - -* update pubsub subscribe method to return subscription (#186) - -### Bug Fixes - -* update pubsub subscribe method to return subscription ([#186](https://github.com/libp2p/js-libp2p-daemon/issues/186)) ([88e4bf5](https://github.com/libp2p/js-libp2p-daemon/commit/88e4bf54ee5189e808cee451f08467c7db302b8d)) - -## [@libp2p/daemon-client-v4.1.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v4.0.3...@libp2p/daemon-client-v4.1.0) (2023-02-23) - - -### Features - -* add get subscribers for pubsub topics ([#184](https://github.com/libp2p/js-libp2p-daemon/issues/184)) ([c8be43e](https://github.com/libp2p/js-libp2p-daemon/commit/c8be43e5acd6a74cfdd01857343af6f6d8210d5d)) - -## [@libp2p/daemon-client-v4.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v4.0.2...@libp2p/daemon-client-v4.0.3) (2023-02-22) - - -### Dependencies - -* bump aegir from 37.12.1 to 38.1.6 ([#183](https://github.com/libp2p/js-libp2p-daemon/issues/183)) ([6725a0a](https://github.com/libp2p/js-libp2p-daemon/commit/6725a0aeba9acb56a7530dece6c65a0f3eadfec5)) - -## [@libp2p/daemon-client-v4.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v4.0.1...@libp2p/daemon-client-v4.0.2) (2023-02-22) - - -### Trivial Changes - -* remove lerna ([#171](https://github.com/libp2p/js-libp2p-daemon/issues/171)) ([367f912](https://github.com/libp2p/js-libp2p-daemon/commit/367f9122f2fe1c31c8de7a136cda18d024ff08d7)) -* replace err-code with CodeError ([#172](https://github.com/libp2p/js-libp2p-daemon/issues/172)) ([c330fd5](https://github.com/libp2p/js-libp2p-daemon/commit/c330fd5fabac7efb016d1f23e781ce88c38a3b37)), closes [#1269](https://github.com/libp2p/js-libp2p-daemon/issues/1269) - - -### Dependencies - -* **dev:** bump sinon from 14.0.2 to 15.0.1 ([#166](https://github.com/libp2p/js-libp2p-daemon/issues/166)) ([1702efb](https://github.com/libp2p/js-libp2p-daemon/commit/1702efb4248bea4cb9ec19c694c1caae1c0ff16d)) - -## [@libp2p/daemon-client-v4.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v4.0.0...@libp2p/daemon-client-v4.0.1) (2023-01-07) - - -### Dependencies - -* bump @libp2p/tcp from 5.0.2 to 6.0.8 ([#165](https://github.com/libp2p/js-libp2p-daemon/issues/165)) ([fb676ab](https://github.com/libp2p/js-libp2p-daemon/commit/fb676ab66348b3c704d2385b4da0d7173bc4a04d)) - -## [@libp2p/daemon-client-v4.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v3.0.6...@libp2p/daemon-client-v4.0.0) (2023-01-07) - - -### ⚠ BREAKING CHANGES - -* Update multiformats and related dependencies (#170) - -### Dependencies - -* Update multiformats and related dependencies ([#170](https://github.com/libp2p/js-libp2p-daemon/issues/170)) ([06744a7](https://github.com/libp2p/js-libp2p-daemon/commit/06744a77006dc77dcfb7bd860e4dc6f36a535603)) -* update sibling dependencies ([775bd83](https://github.com/libp2p/js-libp2p-daemon/commit/775bd83a63ae99c4b892f0169f76dbe39163e2d4)) - -## [@libp2p/daemon-client-v3.0.6](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v3.0.5...@libp2p/daemon-client-v3.0.6) (2022-10-17) - - -### Dependencies - -* **dev:** bump it-all from 1.0.6 to 2.0.0 ([#148](https://github.com/libp2p/js-libp2p-daemon/issues/148)) ([1caa500](https://github.com/libp2p/js-libp2p-daemon/commit/1caa5006157e864bcbe4efb8f9474328b08821c3)) - -## [@libp2p/daemon-client-v3.0.5](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v3.0.4...@libp2p/daemon-client-v3.0.5) (2022-10-14) - - -### Bug Fixes - -* handle empty responses ([#145](https://github.com/libp2p/js-libp2p-daemon/issues/145)) ([0dfb823](https://github.com/libp2p/js-libp2p-daemon/commit/0dfb8236a0ab57a55fa0ebb91ac7a776a9f709da)) - -## [@libp2p/daemon-client-v3.0.4](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v3.0.3...@libp2p/daemon-client-v3.0.4) (2022-10-14) - - -### Dependencies - -* **dev:** bump sinon-ts from 0.0.2 to 1.0.0 ([#144](https://github.com/libp2p/js-libp2p-daemon/issues/144)) ([cfc8755](https://github.com/libp2p/js-libp2p-daemon/commit/cfc8755aa1280ac4fc2aae67cf47d7b0b93f605d)) - -## [@libp2p/daemon-client-v3.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v3.0.2...@libp2p/daemon-client-v3.0.3) (2022-10-13) - - -### Dependencies - -* update uint8arrays, protons and multiformats ([#143](https://github.com/libp2p/js-libp2p-daemon/issues/143)) ([661139c](https://github.com/libp2p/js-libp2p-daemon/commit/661139c674c9994724e32227d7d9ae2c5da1cea2)) - -## [@libp2p/daemon-client-v3.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v3.0.1...@libp2p/daemon-client-v3.0.2) (2022-10-07) - - -### Dependencies - -* bump @libp2p/tcp from 3.0.0 to 4.0.1 ([4e64dce](https://github.com/libp2p/js-libp2p-daemon/commit/4e64dce5e6d18dadaa54a20fff7b2da8bbca11ae)) -* **dev:** bump @libp2p/components from 2.1.1 to 3.0.1 ([#133](https://github.com/libp2p/js-libp2p-daemon/issues/133)) ([6d75a57](https://github.com/libp2p/js-libp2p-daemon/commit/6d75a5742040a594c02aa92ee6acf4ef9080ebac)) -* **dev:** bump @libp2p/interface-mocks from 4.0.3 to 6.0.0 ([#130](https://github.com/libp2p/js-libp2p-daemon/issues/130)) ([3807d1d](https://github.com/libp2p/js-libp2p-daemon/commit/3807d1dd9b037938dbe3dd9e9fb2560489d5d603)) - -## [@libp2p/daemon-client-v3.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v3.0.0...@libp2p/daemon-client-v3.0.1) (2022-09-21) - - -### Dependencies - -* update @multiformats/multiaddr to 11.0.0 ([#128](https://github.com/libp2p/js-libp2p-daemon/issues/128)) ([885d901](https://github.com/libp2p/js-libp2p-daemon/commit/885d9013d82a62e6756b06350932df1242a13296)) - -## [@libp2p/daemon-client-v3.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v2.0.4...@libp2p/daemon-client-v3.0.0) (2022-09-09) - - -### ⚠ BREAKING CHANGES - -* the stream type returned by `client.openStream` has changed - -### Bug Fixes - -* allow opening remote streams ([#126](https://github.com/libp2p/js-libp2p-daemon/issues/126)) ([361cc57](https://github.com/libp2p/js-libp2p-daemon/commit/361cc5750de505ab0381ae43609c67d5d4f659a7)) - - -### Dependencies - -* update sibling dependencies ([56711c4](https://github.com/libp2p/js-libp2p-daemon/commit/56711c4f14b0cf2370b8612fe07d42ed2ac8363c)) -* update sibling dependencies ([c3ebd58](https://github.com/libp2p/js-libp2p-daemon/commit/c3ebd588abc36ef45667e8e4e4c0e220303b7510)) - -## [@libp2p/daemon-client-v2.0.4](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v2.0.3...@libp2p/daemon-client-v2.0.4) (2022-08-10) - - -### Bug Fixes - -* update all deps ([#124](https://github.com/libp2p/js-libp2p-daemon/issues/124)) ([5e46e1e](https://github.com/libp2p/js-libp2p-daemon/commit/5e46e1e26c23428046a6007ab158420d3d830145)) - -## [@libp2p/daemon-client-v2.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v2.0.2...@libp2p/daemon-client-v2.0.3) (2022-07-31) - - -### Trivial Changes - -* update project config ([#111](https://github.com/libp2p/js-libp2p-daemon/issues/111)) ([345e663](https://github.com/libp2p/js-libp2p-daemon/commit/345e663e34278e780fc2f3a6b595294f925c4521)) - - -### Dependencies - -* update uint8arraylist and protons deps ([#115](https://github.com/libp2p/js-libp2p-daemon/issues/115)) ([34a8334](https://github.com/libp2p/js-libp2p-daemon/commit/34a83340ba855a9c08319ae1cd735dfa8b71c248)) - -## [@libp2p/daemon-client-v2.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v2.0.1...@libp2p/daemon-client-v2.0.2) (2022-06-17) - - -### Trivial Changes - -* update deps ([#105](https://github.com/libp2p/js-libp2p-daemon/issues/105)) ([0bdab0e](https://github.com/libp2p/js-libp2p-daemon/commit/0bdab0ee254e32d6dca0e5fe239d4ef16db41b87)) - -## [@libp2p/daemon-client-v2.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v2.0.0...@libp2p/daemon-client-v2.0.1) (2022-06-15) - - -### Trivial Changes - -* update deps ([#103](https://github.com/libp2p/js-libp2p-daemon/issues/103)) ([2bfaa37](https://github.com/libp2p/js-libp2p-daemon/commit/2bfaa37e2f056dcd5de5a3882b77f52553c595d4)) - -## [@libp2p/daemon-client-v2.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v1.0.5...@libp2p/daemon-client-v2.0.0) (2022-06-15) - - -### ⚠ BREAKING CHANGES - -* uses new single-issue libp2p interface modules - -### Features - -* update to latest libp2p interfaces ([#102](https://github.com/libp2p/js-libp2p-daemon/issues/102)) ([f5e9121](https://github.com/libp2p/js-libp2p-daemon/commit/f5e91210654ab3c411e316c1c657356c037a0f6a)) - -## [@libp2p/daemon-client-v1.0.5](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v1.0.4...@libp2p/daemon-client-v1.0.5) (2022-05-25) - - -### Trivial Changes - -* update docs ([#91](https://github.com/libp2p/js-libp2p-daemon/issues/91)) ([5b072ff](https://github.com/libp2p/js-libp2p-daemon/commit/5b072ff89f30fd6cf55a3387bf0961c8ad78a22f)), closes [#83](https://github.com/libp2p/js-libp2p-daemon/issues/83) - -## [@libp2p/daemon-client-v1.0.4](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v1.0.3...@libp2p/daemon-client-v1.0.4) (2022-05-23) - - -### Bug Fixes - -* update deps ([#90](https://github.com/libp2p/js-libp2p-daemon/issues/90)) ([b50eba3](https://github.com/libp2p/js-libp2p-daemon/commit/b50eba3770e47969dbc30cbcf87c41672cd9c175)) - -## [@libp2p/daemon-client-v1.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v1.0.2...@libp2p/daemon-client-v1.0.3) (2022-05-10) - - -### Bug Fixes - -* encode enums correctly ([#86](https://github.com/libp2p/js-libp2p-daemon/issues/86)) ([6ce4633](https://github.com/libp2p/js-libp2p-daemon/commit/6ce4633f3db41ab66f9b8b1abbe84955dde3e9be)) - -## [@libp2p/daemon-client-v1.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v1.0.1...@libp2p/daemon-client-v1.0.2) (2022-04-20) - - -### Bug Fixes - -* update interfaces and deps ([#84](https://github.com/libp2p/js-libp2p-daemon/issues/84)) ([25173d5](https://github.com/libp2p/js-libp2p-daemon/commit/25173d5b2edf0e9dd9132707d349cdc862caecdb)) - -## [@libp2p/daemon-client-v1.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-client-v1.0.0...@libp2p/daemon-client-v1.0.1) (2022-04-07) - - -### Bug Fixes - -* remove protobufjs and replace with protons ([#81](https://github.com/libp2p/js-libp2p-daemon/issues/81)) ([78dd02a](https://github.com/libp2p/js-libp2p-daemon/commit/78dd02a679e55f22c7e24c1ee2b6f92a4679a0b9)) - - -### Trivial Changes - -* update aegir to latest version ([#80](https://github.com/libp2p/js-libp2p-daemon/issues/80)) ([3a98959](https://github.com/libp2p/js-libp2p-daemon/commit/3a98959617d9c19bba9fb064defee3d51acfcc29)) - -## @libp2p/daemon-client-v1.0.0 (2022-03-28) - - -### ⚠ BREAKING CHANGES - -* This module is now ESM only - -### Features - -* convert to typescript ([#78](https://github.com/libp2p/js-libp2p-daemon/issues/78)) ([f18b2a4](https://github.com/libp2p/js-libp2p-daemon/commit/f18b2a45871a2704db51b03e8583eefdcd13554c)) - -# [0.11.0](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.10.0...v0.11.0) (2022-01-17) - - -### Features - -* async peerstore ([#110](https://github.com/libp2p/js-libp2p-daemon-client/issues/110)) ([41dc8a5](https://github.com/libp2p/js-libp2p-daemon-client/commit/41dc8a59ce14447b9b5ab7ba9930f4140bda3652)) - - -### BREAKING CHANGES - -* peerstore methods are now all async - - - -# [0.10.0](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.9.0...v0.10.0) (2021-12-29) - - -### chore - -* update deps ([#103](https://github.com/libp2p/js-libp2p-daemon-client/issues/103)) ([cdbc4b2](https://github.com/libp2p/js-libp2p-daemon-client/commit/cdbc4b22f3599f33911be1b406b02d06515389b8)) - - -### BREAKING CHANGES - -* only node15+ is supported - - - -# [0.9.0](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.7.0...v0.9.0) (2021-11-18) - - - -# [0.7.0](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.6.0...v0.7.0) (2021-07-30) - - - -# [0.6.0](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.5.0...v0.6.0) (2021-05-04) - - - - -# [0.5.0](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.4.0...v0.5.0) (2020-08-23) - - -### Bug Fixes - -* replace node buffers with uint8arrays ([#42](https://github.com/libp2p/js-libp2p-daemon-client/issues/42)) ([33be887](https://github.com/libp2p/js-libp2p-daemon-client/commit/33be887)) - - -### BREAKING CHANGES - -* - All deps of this module now use uint8arrays in place of node buffers -- DHT keys/values are Uint8Arrays, not Strings or Buffers - -* chore: bump daemon dep - -Co-authored-by: Jacob Heun - - - - -# [0.4.0](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.3.1...v0.4.0) (2020-06-08) - - - - -## [0.3.1](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.3.0...v0.3.1) (2020-04-20) - - - - -# [0.3.0](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.2.2...v0.3.0) (2020-01-31) - - -### Chores - -* update deps ([#18](https://github.com/libp2p/js-libp2p-daemon-client/issues/18)) ([61813b9](https://github.com/libp2p/js-libp2p-daemon-client/commit/61813b9)) - - -### BREAKING CHANGES - -* api changed as attach is not needed anymore - -* chore: apply suggestions from code review - -Co-Authored-By: Jacob Heun - -* chore: update aegir - -* chore: update daemon version - -Co-authored-by: Jacob Heun - - - - -## [0.2.2](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.2.1...v0.2.2) (2019-09-05) - - - - -## [0.2.1](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.2.0...v0.2.1) (2019-07-09) - - -### Bug Fixes - -* **client.connect:** handle empty response ([#13](https://github.com/libp2p/js-libp2p-daemon-client/issues/13)) ([ace789d](https://github.com/libp2p/js-libp2p-daemon-client/commit/ace789d)) -* **client.connect:** handle unspecified error in response ([#12](https://github.com/libp2p/js-libp2p-daemon-client/issues/12)) ([7db681b](https://github.com/libp2p/js-libp2p-daemon-client/commit/7db681b)) - - - - -# [0.2.0](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.1.2...v0.2.0) (2019-07-09) - - -### Bug Fixes - -* use error as field name instead of ErrorResponse ([#14](https://github.com/libp2p/js-libp2p-daemon-client/issues/14)) ([0ff9eda](https://github.com/libp2p/js-libp2p-daemon-client/commit/0ff9eda)) - - -### BREAKING CHANGES - -* errors property name is now `error` instead of `ErrorResponse` - - - - -## [0.1.2](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.1.1...v0.1.2) (2019-03-29) - - -### Bug Fixes - -* dht find providers stream ([24eb727](https://github.com/libp2p/js-libp2p-daemon-client/commit/24eb727)) - - - - -## [0.1.1](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.1.0...v0.1.1) (2019-03-25) - - -### Bug Fixes - -* code review feedback ([7fd02d9](https://github.com/libp2p/js-libp2p-daemon-client/commit/7fd02d9)) - - -### Features - -* pubsub ([c485f50](https://github.com/libp2p/js-libp2p-daemon-client/commit/c485f50)) - - - - -# [0.1.0](https://github.com/libp2p/js-libp2p-daemon-client/compare/v0.0.4...v0.1.0) (2019-03-22) - - -### Bug Fixes - -* update code to work with latest daemon ([#6](https://github.com/libp2p/js-libp2p-daemon-client/issues/6)) ([0ada86c](https://github.com/libp2p/js-libp2p-daemon-client/commit/0ada86c)) - - - - -## [0.0.4](https://github.com/libp2p/js-libp2p-daemon-client/compare/0.0.3...v0.0.4) (2019-03-15) - - -### Features - -* streams ([7cefefd](https://github.com/libp2p/js-libp2p-daemon-client/commit/7cefefd)) - - - - -## [0.0.3](https://github.com/libp2p/js-libp2p-daemon-client/compare/0.0.2...0.0.3) (2019-02-13) - - -### Bug Fixes - -* connect should use peer id in bytes ([b9e4e44](https://github.com/libp2p/js-libp2p-daemon-client/commit/b9e4e44)) - - - - -## [0.0.2](https://github.com/libp2p/js-libp2p-daemon-client/compare/e748b7c...0.0.2) (2019-02-11) - - -### Bug Fixes - -* code review ([6ae7ce0](https://github.com/libp2p/js-libp2p-daemon-client/commit/6ae7ce0)) -* code review ([80e3d62](https://github.com/libp2p/js-libp2p-daemon-client/commit/80e3d62)) -* main on package.json ([8fcc62b](https://github.com/libp2p/js-libp2p-daemon-client/commit/8fcc62b)) - - -### Features - -* initial implementation ([e748b7c](https://github.com/libp2p/js-libp2p-daemon-client/commit/e748b7c)) diff --git a/packages/libp2p-daemon-client/LICENSE b/packages/libp2p-daemon-client/LICENSE deleted file mode 100644 index 20ce483c86..0000000000 --- a/packages/libp2p-daemon-client/LICENSE +++ /dev/null @@ -1,4 +0,0 @@ -This project is dual licensed under MIT and Apache-2.0. - -MIT: https://www.opensource.org/licenses/mit -Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/libp2p-daemon-client/LICENSE-APACHE b/packages/libp2p-daemon-client/LICENSE-APACHE deleted file mode 100644 index 14478a3b60..0000000000 --- a/packages/libp2p-daemon-client/LICENSE-APACHE +++ /dev/null @@ -1,5 +0,0 @@ -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/libp2p-daemon-client/LICENSE-MIT b/packages/libp2p-daemon-client/LICENSE-MIT deleted file mode 100644 index 72dc60d84b..0000000000 --- a/packages/libp2p-daemon-client/LICENSE-MIT +++ /dev/null @@ -1,19 +0,0 @@ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/libp2p-daemon-client/README.md b/packages/libp2p-daemon-client/README.md deleted file mode 100644 index 996f69192b..0000000000 --- a/packages/libp2p-daemon-client/README.md +++ /dev/null @@ -1,87 +0,0 @@ -# @libp2p/daemon-client - -[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) -[![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.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) -[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/main.yml?query=branch%3Amaster) - -> libp2p-daemon client implementation - -## Table of contents - -- [Install](#install) -- [Specs](#specs) -- [Usage](#usage) - - [Run a daemon process](#run-a-daemon-process) - - [Interact with the daemon process using the client](#interact-with-the-daemon-process-using-the-client) -- [API](#api) -- [License](#license) -- [Contribution](#contribution) - -## Install - -```console -$ npm i @libp2p/daemon-client -``` - -## Specs - -The specs for the daemon are currently housed in the go implementation. You can read them at [libp2p/go-libp2p-daemon](https://github.com/libp2p/go-libp2p-daemon/blob/master/specs/README.md) - -## Usage - -### Run a daemon process - -There are currently two implementations of the `libp2p-daemon`: - -- [js-libp2p-daemon](https://github.com/libp2p/js-libp2p-daemon) -- [go-libp2p-daemon](https://github.com/libp2p/go-libp2p-daemon) - -### Interact with the daemon process using the client - -```js -import { createClient } from '@libp2p/daemon-client' -import { multiaddr } from '@multiformats/multiaddr' - -const serverAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') -const client = createClient(serverAddr) - -// interact with the daemon -let identify -try { - identify = await client.identify() -} catch (err) { - // ... -} - -// close the socket -await client.close() -``` - -## API - -- [Getting started](API.md#getting-started) -- [`close`](API.md#close) -- [`connect`](API.md#connect) -- [`identify`](API.md#identify) -- [`listPeers`](API.md#listPeers) -- [`openStream`](API.md#openStream) -- [`registerStream`](API.md#registerStream) -- [`dht.put`](API.md#dht.put) -- [`dht.get`](API.md#dht.get) -- [`dht.findPeer`](API.md#dht.findPeer) -- [`dht.provide`](API.md#dht.provide) -- [`dht.findProviders`](API.md#dht.findProviders) -- [`dht.getClosestPeers`](API.md#dht.getClosestPeers) -- [`dht.getPublicKey`](API.md#dht.getPublicKey) - -## License - -Licensed under either of - -- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) -- MIT ([LICENSE-MIT](LICENSE-MIT) / ) - -## Contribution - -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/packages/libp2p-daemon-client/package.json b/packages/libp2p-daemon-client/package.json deleted file mode 100644 index c5cc3d0926..0000000000 --- a/packages/libp2p-daemon-client/package.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "name": "@libp2p/daemon-client", - "version": "6.0.3", - "description": "libp2p-daemon client implementation", - "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/libp2p-daemon-client#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/libp2p/js-libp2p.git" - }, - "bugs": { - "url": "https://github.com/libp2p/js-libp2p/issues" - }, - "keywords": [ - "libp2p" - ], - "type": "module", - "types": "./dist/src/index.d.ts", - "files": [ - "src", - "dist", - "!dist/test", - "!**/*.tsbuildinfo" - ], - "exports": { - ".": { - "types": "./src/index.d.ts", - "import": "./dist/src/index.js" - } - }, - "eslintConfig": { - "extends": "ipfs", - "parserOptions": { - "sourceType": "module" - } - }, - "scripts": { - "clean": "aegir clean", - "lint": "aegir lint", - "dep-check": "aegir dep-check", - "build": "aegir build", - "pretest": "npm run build", - "test": "aegir test -t node", - "test:node": "aegir test -t node" - }, - "dependencies": { - "@chainsafe/libp2p-gossipsub": "^9.0.0", - "@libp2p/daemon-protocol": "^4.0.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/kad-dht": "^9.0.0", - "@libp2p/logger": "^2.0.0", - "@libp2p/peer-id": "^2.0.0", - "@libp2p/tcp": "^7.0.0", - "@multiformats/multiaddr": "^12.1.3", - "it-stream-types": "^2.0.1", - "multiformats": "^12.0.1", - "uint8arraylist": "^2.4.3" - }, - "devDependencies": { - "@libp2p/daemon-server": "^5.0.0", - "@libp2p/interface-compliance-tests": "^3.0.0", - "aegir": "^40.0.1", - "it-all": "^3.0.1", - "it-pipe": "^3.0.1", - "sinon": "^15.1.2", - "sinon-ts": "^1.0.0", - "uint8arrays": "^4.0.4" - }, - "private": true -} diff --git a/packages/libp2p-daemon-client/src/dht.ts b/packages/libp2p-daemon-client/src/dht.ts deleted file mode 100644 index 9511eaacad..0000000000 --- a/packages/libp2p-daemon-client/src/dht.ts +++ /dev/null @@ -1,326 +0,0 @@ -import { - Request, - Response, - DHTRequest, - DHTResponse -} from '@libp2p/daemon-protocol' -import { CodeError } from '@libp2p/interface/errors' -import { isPeerId, type PeerId } from '@libp2p/interface/peer-id' -import { peerIdFromBytes } from '@libp2p/peer-id' -import { multiaddr } from '@multiformats/multiaddr' -import { CID } from 'multiformats/cid' -import type { DaemonClient } from './index.js' -import type { PeerInfo } from '@libp2p/interface/peer-info' - -export class DHT { - private readonly client: DaemonClient - - constructor (client: DaemonClient) { - this.client = client - } - - /** - * Write a value to a key in the DHT - */ - async put (key: Uint8Array, value: Uint8Array): Promise { - if (!(key instanceof Uint8Array)) { - throw new CodeError('invalid key received', 'ERR_INVALID_KEY') - } - - if (!(value instanceof Uint8Array)) { - throw new CodeError('value received is not a Uint8Array', 'ERR_INVALID_VALUE') - } - - const sh = await this.client.send({ - type: Request.Type.DHT, - dht: { - type: DHTRequest.Type.PUT_VALUE, - key, - value - } - }) - - const message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - await sh.close() - - if (response.type !== Response.Type.OK) { - throw new CodeError(response.error?.msg ?? 'DHT put failed', 'ERR_DHT_PUT_FAILED') - } - } - - /** - * Query the DHT for a value stored at a key in the DHT - */ - async get (key: Uint8Array): Promise { - if (!(key instanceof Uint8Array)) { - throw new CodeError('invalid key received', 'ERR_INVALID_KEY') - } - - const sh = await this.client.send({ - type: Request.Type.DHT, - dht: { - type: DHTRequest.Type.GET_VALUE, - key - } - }) - - const message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - await sh.close() - - if (response.type !== Response.Type.OK) { - throw new CodeError(response.error?.msg ?? 'DHT get failed', 'ERR_DHT_GET_FAILED') - } - - if (response.dht == null || response.dht.value == null) { - throw new CodeError('Invalid DHT get response', 'ERR_DHT_GET_FAILED') - } - - return response.dht.value - } - - /** - * Query the DHT for a given peer's known addresses. - */ - async findPeer (peerId: PeerId): Promise { - if (!isPeerId(peerId)) { - throw new CodeError('invalid peer id received', 'ERR_INVALID_PEER_ID') - } - - const sh = await this.client.send({ - type: Request.Type.DHT, - dht: { - type: DHTRequest.Type.FIND_PEER, - peer: peerId.toBytes() - } - }) - - const message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - await sh.close() - - if (response.type !== Response.Type.OK) { - throw new CodeError(response.error?.msg ?? 'DHT find peer failed', 'ERR_DHT_FIND_PEER_FAILED') - } - - if (response.dht == null || response.dht.peer == null || response.dht.peer.addrs == null) { - throw new CodeError('Invalid response', 'ERR_DHT_FIND_PEER_FAILED') - } - - return { - id: peerIdFromBytes(response.dht.peer.id), - multiaddrs: response.dht.peer.addrs.map((a) => multiaddr(a)), - protocols: [] - } - } - - /** - * Announce to the network that the peer have data addressed by the provided CID - */ - async provide (cid: CID): Promise { - if (cid == null || CID.asCID(cid) == null) { - throw new CodeError('invalid cid received', 'ERR_INVALID_CID') - } - - const sh = await this.client.send({ - type: Request.Type.DHT, - dht: { - type: DHTRequest.Type.PROVIDE, - cid: cid.bytes - } - }) - - const message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - await sh.close() - - if (response.type !== Response.Type.OK) { - throw new CodeError(response.error?.msg ?? 'DHT provide failed', 'ERR_DHT_PROVIDE_FAILED') - } - } - - /** - * Query the DHT for peers that have a piece of content, identified by a CID - */ - async * findProviders (cid: CID, count: number = 1): AsyncIterable { - if (cid == null || CID.asCID(cid) == null) { - throw new CodeError('invalid cid received', 'ERR_INVALID_CID') - } - - const sh = await this.client.send({ - type: Request.Type.DHT, - dht: { - type: DHTRequest.Type.FIND_PROVIDERS, - cid: cid.bytes, - count - } - }) - - let message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - // stream begin message - const response = Response.decode(message) - - if (response.type !== Response.Type.OK) { - await sh.close() - throw new CodeError(response.error?.msg ?? 'DHT find providers failed', 'ERR_DHT_FIND_PROVIDERS_FAILED') - } - - while (true) { - message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = DHTResponse.decode(message) - - // Stream end - if (response.type === DHTResponse.Type.END) { - await sh.close() - return - } - - // Stream values - if (response.type === DHTResponse.Type.VALUE && response.peer != null && response.peer?.addrs != null) { - yield { - id: peerIdFromBytes(response.peer.id), - multiaddrs: response.peer.addrs.map((a) => multiaddr(a)), - protocols: [] - } - } else { - // Unexpected message received - await sh.close() - throw new CodeError('unexpected message received', 'ERR_UNEXPECTED_MESSAGE_RECEIVED') - } - } - } - - /** - * Query the DHT routing table for peers that are closest to a provided key. - */ - async * getClosestPeers (key: Uint8Array): AsyncIterable { - if (!(key instanceof Uint8Array)) { - throw new CodeError('invalid key received', 'ERR_INVALID_KEY') - } - - const sh = await this.client.send({ - type: Request.Type.DHT, - dht: { - type: DHTRequest.Type.GET_CLOSEST_PEERS, - key - } - }) - - // stream begin message - let message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - if (response.type !== Response.Type.OK) { - await sh.close() - throw new CodeError(response.error?.msg ?? 'DHT find providers failed', 'ERR_DHT_FIND_PROVIDERS_FAILED') - } - - while (true) { - message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = DHTResponse.decode(message) - - // Stream end - if (response.type === DHTResponse.Type.END) { - await sh.close() - return - } - - // Stream values - if (response.type === DHTResponse.Type.VALUE && response.value != null) { - const peerId = peerIdFromBytes(response.value) - - yield { - id: peerId, - multiaddrs: [], - protocols: [] - } - } else { - // Unexpected message received - await sh.close() - throw new CodeError('unexpected message received', 'ERR_UNEXPECTED_MESSAGE_RECEIVED') - } - } - } - - /** - * Query the DHT routing table for a given peer's public key. - */ - async getPublicKey (peerId: PeerId): Promise { - if (!isPeerId(peerId)) { - throw new CodeError('invalid peer id received', 'ERR_INVALID_PEER_ID') - } - - const sh = await this.client.send({ - type: Request.Type.DHT, - dht: { - type: DHTRequest.Type.GET_PUBLIC_KEY, - peer: peerId.toBytes() - } - }) - - const message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - await sh.close() - - if (response.type !== Response.Type.OK) { - throw new CodeError(response.error?.msg ?? 'DHT get public key failed', 'ERR_DHT_GET_PUBLIC_KEY_FAILED') - } - - if (response.dht == null) { - throw new CodeError('Invalid response', 'ERR_DHT_GET_PUBLIC_KEY_FAILED') - } - - return response.dht.value - } -} diff --git a/packages/libp2p-daemon-client/src/index.ts b/packages/libp2p-daemon-client/src/index.ts deleted file mode 100644 index 3ecb5ed785..0000000000 --- a/packages/libp2p-daemon-client/src/index.ts +++ /dev/null @@ -1,323 +0,0 @@ -import { type PSMessage, Request, Response, StreamInfo } from '@libp2p/daemon-protocol' -import { StreamHandler } from '@libp2p/daemon-protocol/stream-handler' -import { passThroughUpgrader } from '@libp2p/daemon-protocol/upgrader' -import { CodeError } from '@libp2p/interface/errors' -import { isPeerId, type PeerId } from '@libp2p/interface/peer-id' -import { logger } from '@libp2p/logger' -import { peerIdFromBytes } from '@libp2p/peer-id' -import { tcp } from '@libp2p/tcp' -import { multiaddr, isMultiaddr } from '@multiformats/multiaddr' -import { DHT } from './dht.js' -import { Pubsub } from './pubsub.js' -import type { MultiaddrConnection } from '@libp2p/interface/connection' -import type { PeerInfo } from '@libp2p/interface/peer-info' -import type { Transport } from '@libp2p/interface/transport' -import type { Multiaddr } from '@multiformats/multiaddr' -import type { Duplex, Source } from 'it-stream-types' -import type { CID } from 'multiformats/cid' -import type { Uint8ArrayList } from 'uint8arraylist' - -const log = logger('libp2p:daemon-client') - -class Client implements DaemonClient { - private readonly multiaddr: Multiaddr - public dht: DHT - public pubsub: Pubsub - private readonly tcp: Transport - - constructor (addr: Multiaddr) { - this.multiaddr = addr - this.tcp = tcp()() - this.dht = new DHT(this) - this.pubsub = new Pubsub(this) - } - - /** - * Connects to a daemon at the unix socket path the daemon - * was created with - * - * @async - * @returns {MultiaddrConnection} - */ - async connectDaemon (): Promise { - // @ts-expect-error because we use a passthrough upgrader, - // this is actually a MultiaddrConnection and not a Connection - return this.tcp.dial(this.multiaddr, { - upgrader: passThroughUpgrader - }) - } - - /** - * Sends the request to the daemon and returns a stream. This - * should only be used when sending daemon requests. - */ - async send (request: Request): Promise { - const maConn = await this.connectDaemon() - - const subtype = request.pubsub?.type ?? request.dht?.type ?? request.peerStore?.type ?? '' - log('send', request.type, subtype) - - const streamHandler = new StreamHandler({ stream: maConn }) - streamHandler.write(Request.encode(request)) - return streamHandler - } - - /** - * Connect requests a connection to a known peer on a given set of addresses - */ - async connect (peerId: PeerId, addrs: Multiaddr[]): Promise { - if (!isPeerId(peerId)) { - throw new CodeError('invalid peer id received', 'ERR_INVALID_PEER_ID') - } - - if (!Array.isArray(addrs)) { - throw new CodeError('addrs received are not in an array', 'ERR_INVALID_ADDRS_TYPE') - } - - addrs.forEach((addr) => { - if (!isMultiaddr(addr)) { - throw new CodeError('received an address that is not a multiaddr', 'ERR_NO_MULTIADDR_RECEIVED') - } - }) - - const sh = await this.send({ - type: Request.Type.CONNECT, - connect: { - peer: peerId.toBytes(), - addrs: addrs.map((a) => a.bytes) - } - }) - - const message = await sh.read() - if (message == null) { - throw new CodeError('unspecified', 'ERR_CONNECT_FAILED') - } - - const response = Response.decode(message) - if (response.type !== Response.Type.OK) { - const errResponse = response.error ?? { msg: 'unspecified' } - throw new CodeError(errResponse.msg ?? 'unspecified', 'ERR_CONNECT_FAILED') - } - - await sh.close() - } - - /** - * @typedef {object} IdentifyResponse - * @property {PeerId} peerId - * @property {Array.} addrs - */ - - /** - * Identify queries the daemon for its peer ID and listen addresses. - */ - async identify (): Promise { - const sh = await this.send({ - type: Request.Type.IDENTIFY - }) - - const message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - if (response.type !== Response.Type.OK) { - throw new CodeError(response.error?.msg ?? 'Identify failed', 'ERR_IDENTIFY_FAILED') - } - - if (response.identify == null || response.identify.addrs == null) { - throw new CodeError('Invalid response', 'ERR_IDENTIFY_FAILED') - } - - const peerId = peerIdFromBytes(response.identify?.id) - const addrs = response.identify.addrs.map((a) => multiaddr(a)) - - await sh.close() - - return ({ peerId, addrs }) - } - - /** - * Get a list of IDs of peers the node is connected to - */ - async listPeers (): Promise { - const sh = await this.send({ - type: Request.Type.LIST_PEERS - }) - - const message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - if (response.type !== Response.Type.OK) { - throw new CodeError(response.error?.msg ?? 'List peers failed', 'ERR_LIST_PEERS_FAILED') - } - - await sh.close() - - return response.peers.map((peer) => peerIdFromBytes(peer.id)) - } - - /** - * Initiate an outbound stream to a peer on one of a set of protocols. - */ - async openStream (peerId: PeerId, protocol: string): Promise, Source, Promise>> { - if (!isPeerId(peerId)) { - throw new CodeError('invalid peer id received', 'ERR_INVALID_PEER_ID') - } - - if (typeof protocol !== 'string') { - throw new CodeError('invalid protocol received', 'ERR_INVALID_PROTOCOL') - } - - const sh = await this.send({ - type: Request.Type.STREAM_OPEN, - streamOpen: { - peer: peerId.toBytes(), - proto: [protocol] - } - }) - - const message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - if (response.type !== Response.Type.OK) { - await sh.close() - throw new CodeError(response.error?.msg ?? 'Open stream failed', 'ERR_OPEN_STREAM_FAILED') - } - - return sh.rest() - } - - /** - * Register a handler for inbound streams on a given protocol - */ - async registerStreamHandler (protocol: string, handler: StreamHandlerFunction): Promise { - if (typeof protocol !== 'string') { - throw new CodeError('invalid protocol received', 'ERR_INVALID_PROTOCOL') - } - - // open a tcp port, pipe any data from it to the handler function - const listener = this.tcp.createListener({ - upgrader: passThroughUpgrader, - handler: (connection) => { - Promise.resolve() - .then(async () => { - const sh = new StreamHandler({ - // @ts-expect-error because we are using a passthrough upgrader, this is a MultiaddrConnection - stream: connection - }) - const message = await sh.read() - - if (message == null) { - throw new CodeError('Could not read open stream response', 'ERR_OPEN_STREAM_FAILED') - } - - const response = StreamInfo.decode(message) - - if (response.proto !== protocol) { - throw new CodeError('Incorrect protocol', 'ERR_OPEN_STREAM_FAILED') - } - - await handler(sh.rest()) - }) - .finally(() => { - connection.close() - .catch(err => { - log.error(err) - }) - listener.close() - .catch(err => { - log.error(err) - }) - }) - } - }) - await listener.listen(multiaddr('/ip4/127.0.0.1/tcp/0')) - const address = listener.getAddrs()[0] - - if (address == null) { - throw new CodeError('Could not listen on port', 'ERR_REGISTER_STREAM_HANDLER_FAILED') - } - - const sh = await this.send({ - type: Request.Type.STREAM_HANDLER, - streamHandler: { - addr: address.bytes, - proto: [protocol] - } - }) - - const message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - await sh.close() - - if (response.type !== Response.Type.OK) { - throw new CodeError(response.error?.msg ?? 'Register stream handler failed', 'ERR_REGISTER_STREAM_HANDLER_FAILED') - } - } -} - -export interface IdentifyResult { - peerId: PeerId - addrs: Multiaddr[] -} - -export interface StreamHandlerFunction { - (stream: Duplex, Source, Promise>): Promise -} - -export interface DHTClient { - put: (key: Uint8Array, value: Uint8Array) => Promise - get: (key: Uint8Array) => Promise - provide: (cid: CID) => Promise - findProviders: (cid: CID, count?: number) => AsyncIterable - findPeer: (peerId: PeerId) => Promise - getClosestPeers: (key: Uint8Array) => AsyncIterable -} - -export interface Subscription { - messages: () => AsyncIterable - cancel: () => Promise -} - -export interface PubSubClient { - publish: (topic: string, data: Uint8Array) => Promise - subscribe: (topic: string) => Promise - getTopics: () => Promise - getSubscribers: (topic: string) => Promise -} - -export interface DaemonClient { - identify: () => Promise - listPeers: () => Promise - connect: (peerId: PeerId, addrs: Multiaddr[]) => Promise - dht: DHTClient - pubsub: PubSubClient - - send: (request: Request) => Promise - openStream: (peerId: PeerId, protocol: string) => Promise, Source, Promise>> - registerStreamHandler: (protocol: string, handler: StreamHandlerFunction) => Promise -} - -export function createClient (multiaddr: Multiaddr): DaemonClient { - return new Client(multiaddr) -} diff --git a/packages/libp2p-daemon-client/src/pubsub.ts b/packages/libp2p-daemon-client/src/pubsub.ts deleted file mode 100644 index a6f56cdaf8..0000000000 --- a/packages/libp2p-daemon-client/src/pubsub.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { - Request, - Response, - PSRequest, - PSMessage -} from '@libp2p/daemon-protocol' -import { CodeError } from '@libp2p/interface/errors' -import { peerIdFromBytes } from '@libp2p/peer-id' -import type { DaemonClient, Subscription } from './index.js' -import type { PeerId } from '@libp2p/interface/peer-id' - -export class Pubsub { - private readonly client: DaemonClient - - constructor (client: DaemonClient) { - this.client = client - } - - /** - * Get a list of topics the node is subscribed to. - * - * @returns {Array} topics - */ - async getTopics (): Promise { - const sh = await this.client.send({ - type: Request.Type.PUBSUB, - pubsub: { - type: PSRequest.Type.GET_TOPICS - } - }) - - const message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - await sh.close() - - if (response.type !== Response.Type.OK) { - throw new CodeError(response.error?.msg ?? 'Pubsub get topics failed', 'ERR_PUBSUB_GET_TOPICS_FAILED') - } - - if (response.pubsub == null || response.pubsub.topics == null) { - throw new CodeError('Invalid response', 'ERR_PUBSUB_GET_TOPICS_FAILED') - } - - return response.pubsub.topics - } - - /** - * Publish data under a topic - */ - async publish (topic: string, data: Uint8Array): Promise { - if (typeof topic !== 'string') { - throw new CodeError('invalid topic received', 'ERR_INVALID_TOPIC') - } - - if (!(data instanceof Uint8Array)) { - throw new CodeError('data received is not a Uint8Array', 'ERR_INVALID_DATA') - } - - const sh = await this.client.send({ - type: Request.Type.PUBSUB, - pubsub: { - type: PSRequest.Type.PUBLISH, - topic, - data - } - }) - - const message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - await sh.close() - - if (response.type !== Response.Type.OK) { - throw new CodeError(response.error?.msg ?? 'Pubsub publish failed', 'ERR_PUBSUB_PUBLISH_FAILED') - } - } - - /** - * Request to subscribe a certain topic - */ - async subscribe (topic: string): Promise { - if (typeof topic !== 'string') { - throw new CodeError('invalid topic received', 'ERR_INVALID_TOPIC') - } - - const sh = await this.client.send({ - type: Request.Type.PUBSUB, - pubsub: { - type: PSRequest.Type.SUBSCRIBE, - topic - } - }) - - let message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - if (response.type !== Response.Type.OK) { - throw new CodeError(response.error?.msg ?? 'Pubsub publish failed', 'ERR_PUBSUB_PUBLISH_FAILED') - } - - let subscribed = true - - const subscription: Subscription = { - async * messages () { - while (subscribed) { // eslint-disable-line no-unmodified-loop-condition - message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - yield PSMessage.decode(message) - } - }, - async cancel () { - subscribed = false - await sh.close() - } - } - - return subscription - } - - async getSubscribers (topic: string): Promise { - if (typeof topic !== 'string') { - throw new CodeError('invalid topic received', 'ERR_INVALID_TOPIC') - } - - const sh = await this.client.send({ - type: Request.Type.PUBSUB, - pubsub: { - type: PSRequest.Type.LIST_PEERS, - topic - } - }) - - const message = await sh.read() - - if (message == null) { - throw new CodeError('Empty response from remote', 'ERR_EMPTY_RESPONSE') - } - - const response = Response.decode(message) - - await sh.close() - - if (response.type !== Response.Type.OK) { - throw new CodeError(response.error?.msg ?? 'Pubsub get subscribers failed', 'ERR_PUBSUB_GET_SUBSCRIBERS_FAILED') - } - - if (response.pubsub == null || response.pubsub.topics == null) { - throw new CodeError('Invalid response', 'ERR_PUBSUB_GET_SUBSCRIBERS_FAILED') - } - - return response.pubsub.peerIDs.map(buf => peerIdFromBytes(buf)) - } -} diff --git a/packages/libp2p-daemon-client/test/dht.spec.ts b/packages/libp2p-daemon-client/test/dht.spec.ts deleted file mode 100644 index 9d1e5e0d8f..0000000000 --- a/packages/libp2p-daemon-client/test/dht.spec.ts +++ /dev/null @@ -1,242 +0,0 @@ -/* eslint-env mocha */ - -import { createServer, type Libp2pServer } from '@libp2p/daemon-server' -import { type DualKadDHT, type ValueEvent, type FinalPeerEvent, type PeerResponseEvent, MessageType, EventTypes, type KadDHT } from '@libp2p/kad-dht' -import { peerIdFromString } from '@libp2p/peer-id' -import { multiaddr } from '@multiformats/multiaddr' -import { expect } from 'aegir/chai' -import all from 'it-all' -import { CID } from 'multiformats/cid' -import sinon from 'sinon' -import { type StubbedInstance, stubInterface } from 'sinon-ts' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { createClient, type DaemonClient } from '../src/index.js' -import type { GossipSub } from '@chainsafe/libp2p-gossipsub' -import type { Libp2p } from '@libp2p/interface' - -const defaultMultiaddr = multiaddr('/ip4/0.0.0.0/tcp/12345') - -function match (cid: CID): sinon.SinonMatcher { - return sinon.match((c: CID) => c.toString() === cid.toString(), 'cid') -} - -describe('daemon dht client', function () { - this.timeout(30e3) - - let libp2p: StubbedInstance> - let server: Libp2pServer - let client: DaemonClient - let dht: StubbedInstance - - beforeEach(async function () { - dht = stubInterface() - libp2p = stubInterface>() - libp2p.services.dht = dht - - server = createServer(defaultMultiaddr, libp2p) - - await server.start() - - client = createClient(server.getMultiaddr()) - }) - - afterEach(async () => { - if (server != null) { - await server.stop() - } - - sinon.restore() - }) - - describe('put', () => { - const key = uint8ArrayFromString('/key') - const value = uint8ArrayFromString('oh hello there') - - it('should be able to put a value to the dht', async function () { - dht.put.returns(async function * () {}()) - - await client.dht.put(key, value) - - expect(dht.put.calledWith(key, value)).to.be.true() - }) - - it('should error if receive an error message', async () => { - dht.put.returns(async function * () { // eslint-disable-line require-yield - throw new Error('Urk!') - }()) - - await expect(client.dht.put(key, value)).to.eventually.be.rejectedWith(/Urk!/) - }) - }) - - describe('get', () => { - it('should be able to get a value from the dht', async function () { - const key = uint8ArrayFromString('/key') - const value = uint8ArrayFromString('oh hello there') - - dht.get.withArgs(key).returns(async function * () { - const event: ValueEvent = { - name: 'VALUE', - type: EventTypes.VALUE, - value, - from: peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - } - - yield event - }()) - - const result = await client.dht.get(key) - - expect(result).to.equalBytes(value) - }) - - it('should error if receive an error message', async function () { - const key = uint8ArrayFromString('/key') - - dht.get.returns(async function * () { // eslint-disable-line require-yield - throw new Error('Urk!') - }()) - - await expect(client.dht.get(key)).to.eventually.be.rejectedWith(/Urk!/) - }) - }) - - describe('findPeer', () => { - it('should be able to find a peer', async () => { - const id = peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - - dht.findPeer.withArgs(id).returns(async function * () { - const event: FinalPeerEvent = { - name: 'FINAL_PEER', - type: EventTypes.FINAL_PEER, - peer: { - id, - multiaddrs: [], - protocols: [] - }, - from: peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - } - - yield event - }()) - - const result = await client.dht.findPeer(id) - - expect(result.id.equals(id)).to.be.true() - }) - - it('should error if receive an error message', async () => { - const id = peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - - dht.findPeer.returns(async function * () { // eslint-disable-line require-yield - throw new Error('Urk!') - }()) - - await expect(client.dht.findPeer(id)).to.eventually.be.rejectedWith(/Urk!/) - }) - }) - - describe('provide', () => { - it('should be able to provide', async () => { - const cid = CID.parse('QmVzw6MPsF96TyXBSRs1ptLoVMWRv5FCYJZZGJSVB2Hp38') - - dht.provide.returns(async function * () {}()) - - await client.dht.provide(cid) - - expect(dht.provide.calledWith(match(cid))).to.be.true() - }) - - it('should error if receive an error message', async () => { - const cid = CID.parse('QmVzw6MPsF96TyXBSRs1ptLoVMWRv5FCYJZZGJSVB2Hp38') - - dht.provide.returns(async function * () { // eslint-disable-line require-yield - throw new Error('Urk!') - }()) - - await expect(client.dht.provide(cid)).to.eventually.be.rejectedWith(/Urk!/) - }) - }) - - describe('findProviders', () => { - it('should be able to find providers', async () => { - const cid = CID.parse('QmVzw6MPsF96TyXBSRs1ptLoVMWRv5FCYJZZGJSVB2Hp38') - const id = peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - - dht.findProviders.withArgs(match(cid)).returns(async function * () { - const event: PeerResponseEvent = { - name: 'PEER_RESPONSE', - type: EventTypes.PEER_RESPONSE, - providers: [{ - id, - multiaddrs: [], - protocols: [] - }], - closer: [], - from: id, - messageName: 'GET_PROVIDERS', - messageType: MessageType.GET_PROVIDERS - } - - yield event - }()) - - const result = await all(client.dht.findProviders(cid)) - - expect(result).to.have.lengthOf(1) - expect(result[0].id.equals(id)).to.be.true() - }) - - // skipped because the protocol doesn't handle streaming errors - it.skip('should error if receive an error message', async () => { - const cid = CID.parse('QmVzw6MPsF96TyXBSRs1ptLoVMWRv5FCYJZZGJSVB2Hp38') - - dht.findProviders.returns(async function * () { // eslint-disable-line require-yield - throw new Error('Urk!') - }()) - - await expect(all(client.dht.findProviders(cid))).to.eventually.be.rejectedWith(/Urk!/) - }) - }) - - describe('getClosestPeers', () => { - it('should be able to get the closest peers', async () => { - const cid = CID.parse('QmVzw6MPsF96TyXBSRs1ptLoVMWRv5FCYJZZGJSVB2Hp38') - const id = peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - - dht.getClosestPeers.returns(async function * () { - const event: PeerResponseEvent = { - name: 'PEER_RESPONSE', - type: EventTypes.PEER_RESPONSE, - providers: [], - closer: [{ - id, - multiaddrs: [], - protocols: [] - }], - from: id, - messageName: 'GET_PROVIDERS', - messageType: MessageType.GET_PROVIDERS - } - - yield event - }()) - - const result = await all(client.dht.getClosestPeers(cid.bytes)) - - expect(result).to.have.lengthOf(1) - expect(result[0].id.equals(id)).to.be.true() - }) - - // skipped because the protocol doesn't handle streaming errors - it.skip('should error if it gets an invalid key', async () => { - const cid = CID.parse('QmVzw6MPsF96TyXBSRs1ptLoVMWRv5FCYJZZGJSVB2Hp38') - - dht.getClosestPeers.returns(async function * () { // eslint-disable-line require-yield - throw new Error('Urk!') - }()) - - await expect(all(client.dht.getClosestPeers(cid.bytes))).to.eventually.be.rejectedWith(/Urk!/) - }) - }) -}) diff --git a/packages/libp2p-daemon-client/test/index.spec.ts b/packages/libp2p-daemon-client/test/index.spec.ts deleted file mode 100644 index 0074446a83..0000000000 --- a/packages/libp2p-daemon-client/test/index.spec.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* eslint-env mocha */ - -import { createServer, type Libp2pServer } from '@libp2p/daemon-server' -import { isPeerId } from '@libp2p/interface/peer-id' -import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' -import { peerIdFromString } from '@libp2p/peer-id' -import { multiaddr } from '@multiformats/multiaddr' -import { expect } from 'aegir/chai' -import sinon from 'sinon' -import { type StubbedInstance, stubInterface } from 'sinon-ts' -import { createClient, type DaemonClient } from '../src/index.js' -import type { GossipSub } from '@chainsafe/libp2p-gossipsub' -import type { Libp2p } from '@libp2p/interface' -import type { PeerStore } from '@libp2p/interface/peer-store' -import type { KadDHT } from '@libp2p/kad-dht' - -const defaultMultiaddr = multiaddr('/ip4/0.0.0.0/tcp/0') - -describe('daemon client', function () { - this.timeout(30e3) - - let libp2p: StubbedInstance> - let server: Libp2pServer - let client: DaemonClient - - beforeEach(async function () { - libp2p = stubInterface>() - libp2p.peerStore = stubInterface() - - server = createServer(defaultMultiaddr, libp2p) - - await server.start() - - client = createClient(server.getMultiaddr()) - }) - - afterEach(async () => { - if (server != null) { - await server.stop() - } - - sinon.restore() - }) - - describe('identify', () => { - it('should be able to identify', async () => { - libp2p.peerId = peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - libp2p.getMultiaddrs.returns([ - multiaddr('/ip4/0.0.0.0/tcp/1234/p2p/12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - ]) - - const identify = await client.identify() - - expect(identify).to.exist() - expect(identify.peerId).to.exist() - expect(identify.addrs).to.exist() - expect(isPeerId(identify.peerId)) - }) - - it('should error if receive an error message', async () => { - libp2p.peerId = peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - libp2p.getMultiaddrs.throws(new Error('Urk!')) - - await expect(client.identify()).to.eventually.be.rejectedWith(/Urk!/) - }) - }) - - describe('listPeers', () => { - it('should be able to listPeers', async () => { - const remotePeer = peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - - libp2p.getConnections.returns([ - mockConnection(mockMultiaddrConnection(mockDuplex(), remotePeer)) - ]) - - const peers = await client.listPeers() - - expect(peers).to.have.lengthOf(1) - expect(peers[0].equals(remotePeer)).to.be.true() - }) - - it('should error if receive an error message', async () => { - libp2p.getConnections.throws(new Error('Urk!')) - - await expect(client.listPeers()).to.eventually.be.rejectedWith(/Urk!/) - }) - }) - - describe('connect', () => { - it('should be able to connect', async () => { - const remotePeer = peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - const ma = multiaddr('/ip4/1.2.3.4/tcp/1234') - - await client.connect(remotePeer, [ma]) - - expect(libp2p.dial.calledWith(remotePeer)).to.be.true() - }) - - it('should error if receive an error message', async () => { - const remotePeer = peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - const ma = multiaddr('/ip4/1.2.3.4/tcp/1234') - - libp2p.dial.rejects(new Error('Urk!')) - - await expect(client.connect(remotePeer, [ma])).to.eventually.be.rejectedWith(/Urk!/) - }) - }) -}) diff --git a/packages/libp2p-daemon-client/test/pubsub.spec.ts b/packages/libp2p-daemon-client/test/pubsub.spec.ts deleted file mode 100644 index 39adbd7d80..0000000000 --- a/packages/libp2p-daemon-client/test/pubsub.spec.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* eslint-env mocha */ - -import { createServer, type Libp2pServer } from '@libp2p/daemon-server' -import { peerIdFromString } from '@libp2p/peer-id' -import { multiaddr } from '@multiformats/multiaddr' -import { expect } from 'aegir/chai' -import sinon from 'sinon' -import { type StubbedInstance, stubInterface } from 'sinon-ts' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { createClient, type DaemonClient } from '../src/index.js' -import type { GossipSub } from '@chainsafe/libp2p-gossipsub' -import type { Libp2p } from '@libp2p/interface' -import type { KadDHT } from '@libp2p/kad-dht' - -const defaultMultiaddr = multiaddr('/ip4/0.0.0.0/tcp/12345') - -describe('daemon pubsub client', function () { - this.timeout(30e3) - - let libp2p: StubbedInstance> - let server: Libp2pServer - let client: DaemonClient - let pubsub: StubbedInstance - - beforeEach(async function () { - pubsub = stubInterface() - libp2p = stubInterface>() - libp2p.services.pubsub = pubsub - - server = createServer(defaultMultiaddr, libp2p) - - await server.start() - - client = createClient(server.getMultiaddr()) - }) - - afterEach(async () => { - if (server != null) { - await server.stop() - } - - sinon.restore() - }) - - describe('getTopics', () => { - it('should get empty list of topics when no subscriptions exist', async () => { - pubsub.getTopics.returns([]) - - const topics = await client.pubsub.getTopics() - - expect(topics).to.have.lengthOf(0) - }) - - it('should get a list with a topic when subscribed', async () => { - const topic = 'test-topic' - pubsub.getTopics.returns([topic]) - - const topics = await client.pubsub.getTopics() - - expect(topics).to.have.lengthOf(1) - expect(topics[0]).to.equal(topic) - }) - - it('should error if receive an error message', async () => { - pubsub.getTopics.throws(new Error('Urk!')) - - await expect(client.pubsub.getTopics()).to.eventually.be.rejectedWith(/Urk!/) - }) - }) - - describe('publish', () => { - it('should publish an event', async () => { - const topic = 'test-topic' - const data = uint8ArrayFromString('hello world') - - await client.pubsub.publish(topic, data) - - expect(pubsub.publish.called).to.be.true() - - const call = pubsub.publish.getCall(0) - - expect(call).to.have.nested.property('args[0]', topic) - expect(call).to.have.deep.nested.property('args[1]', data) - }) - - it('should error if receive an error message', async () => { - const topic = 'test-topic' - const data = uint8ArrayFromString('hello world') - pubsub.publish.throws(new Error('Urk!')) - - await expect(client.pubsub.publish(topic, data)).to.eventually.be.rejectedWith(/Urk!/) - }) - }) - - describe('getSubscribers', () => { - it('should get empty list of topics when no subscriptions exist', async () => { - pubsub.getSubscribers.returns([]) - - const topic = 'test-topic' - const topics = await client.pubsub.getSubscribers(topic) - - expect(topics).to.have.lengthOf(0) - }) - - it('should get a list with a peer when subscribed', async () => { - const topic = 'test-topic' - const peer = peerIdFromString('12D3KooWKnQbfH5t1XxJW5FBoMGNjmC9LTSbDdRJxtYj2bJV5XfP') - pubsub.getSubscribers.withArgs(topic).returns([peer]) - - const peers = await client.pubsub.getSubscribers(topic) - - expect(peers).to.have.lengthOf(1) - expect(peers[0].toString()).to.equal(peer.toString()) - }) - - it('should error if receive an error message', async () => { - const topic = 'test-topic' - pubsub.getSubscribers.throws(new Error('Urk!')) - - await expect(client.pubsub.getSubscribers(topic)).to.eventually.be.rejectedWith(/Urk!/) - }) - }) -}) diff --git a/packages/libp2p-daemon-client/test/stream.spec.ts b/packages/libp2p-daemon-client/test/stream.spec.ts deleted file mode 100644 index 76e1cf59e6..0000000000 --- a/packages/libp2p-daemon-client/test/stream.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* eslint-env mocha */ - -import { createServer, type Libp2pServer } from '@libp2p/daemon-server' -import { mockRegistrar, connectionPair } from '@libp2p/interface-compliance-tests/mocks' -import { peerIdFromString } from '@libp2p/peer-id' -import { multiaddr } from '@multiformats/multiaddr' -import { expect } from 'aegir/chai' -import all from 'it-all' -import { pipe } from 'it-pipe' -import sinon from 'sinon' -import { type StubbedInstance, stubInterface } from 'sinon-ts' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { createClient, type DaemonClient } from '../src/index.js' -import type { GossipSub } from '@chainsafe/libp2p-gossipsub' -import type { Libp2p } from '@libp2p/interface' -import type { PeerStore } from '@libp2p/interface/peer-store' -import type { KadDHT } from '@libp2p/kad-dht' - -const defaultMultiaddr = multiaddr('/ip4/0.0.0.0/tcp/0') - -describe('daemon stream client', function () { - this.timeout(50e3) - - let libp2p: StubbedInstance> - let server: Libp2pServer - let client: DaemonClient - - beforeEach(async function () { - libp2p = stubInterface>() - libp2p.peerStore = stubInterface() - - server = createServer(defaultMultiaddr, libp2p) - - await server.start() - - client = createClient(server.getMultiaddr()) - }) - - afterEach(async () => { - if (server != null) { - await server.stop() - } - - sinon.restore() - }) - - it('should be able to open a stream, write to it and a stream handler, should handle the message', async () => { - const protocol = '/echo/1.0.0' - - const peerA = peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsa') - const registrarA = mockRegistrar() - await registrarA.handle(protocol, (data) => { - void pipe( - data.stream, - data.stream - ) - }) - - const peerB = peerIdFromString('12D3KooWJKCJW8Y26pRFNv78TCMGLNTfyN8oKaFswMRYXTzSbSsb') - const registrarB = mockRegistrar() - await registrarB.handle(protocol, (data) => { - void pipe( - data.stream, - data.stream - ) - }) - - const [peerAtoPeerB] = connectionPair({ - peerId: peerA, - registrar: registrarA - }, { - peerId: peerB, - registrar: registrarB - } - ) - - libp2p.dial.withArgs(peerB).resolves(peerAtoPeerB) - - const stream = await client.openStream(peerB, protocol) - - const data = await pipe( - [uint8ArrayFromString('hello world')], - stream, - async (source) => all(source) - ) - - expect(data).to.have.lengthOf(1) - expect(uint8ArrayToString(data[0].subarray())).to.equal('hello world') - }) -}) diff --git a/packages/libp2p-daemon-client/tsconfig.json b/packages/libp2p-daemon-client/tsconfig.json deleted file mode 100644 index f4c92c7e77..0000000000 --- a/packages/libp2p-daemon-client/tsconfig.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "extends": "aegir/src/config/tsconfig.aegir.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": [ - "src", - "test" - ], - "references": [ - { - "path": "../interface" - }, - { - "path": "../interface-compliance-tests" - }, - { - "path": "../kad-dht" - }, - { - "path": "../libp2p-daemon-protocol" - }, - { - "path": "../libp2p-daemon-server" - }, - { - "path": "../logger" - }, - { - "path": "../peer-id" - }, - { - "path": "../pubsub-gossipsub" - }, - { - "path": "../transport-tcp" - } - ] -} diff --git a/packages/libp2p-daemon-client/typedoc.json b/packages/libp2p-daemon-client/typedoc.json deleted file mode 100644 index f599dc728d..0000000000 --- a/packages/libp2p-daemon-client/typedoc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entryPoints": [ - "./src/index.ts" - ] -} diff --git a/packages/libp2p-daemon-protocol/CHANGELOG.md b/packages/libp2p-daemon-protocol/CHANGELOG.md deleted file mode 100644 index 0814425181..0000000000 --- a/packages/libp2p-daemon-protocol/CHANGELOG.md +++ /dev/null @@ -1,159 +0,0 @@ -## [@libp2p/daemon-protocol-v4.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v4.0.0...@libp2p/daemon-protocol-v4.0.1) (2023-04-24) - - -### Dependencies - -* bump @libp2p/interface-peer-store from 1.2.9 to 2.0.0 ([#201](https://github.com/libp2p/js-libp2p-daemon/issues/201)) ([9b146a8](https://github.com/libp2p/js-libp2p-daemon/commit/9b146a8c38c30a13401be6da5259cd9da6bdc25c)) - -## [@libp2p/daemon-protocol-v4.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v3.0.6...@libp2p/daemon-protocol-v4.0.0) (2023-04-19) - - -### ⚠ BREAKING CHANGES - -* the type of the source/sink properties have changed - -### Dependencies - -* update it-stream-types to 2.x.x ([#196](https://github.com/libp2p/js-libp2p-daemon/issues/196)) ([a09f6d5](https://github.com/libp2p/js-libp2p-daemon/commit/a09f6d58942033b08b579735aaa1537b3a324776)) - -## [@libp2p/daemon-protocol-v3.0.6](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v3.0.5...@libp2p/daemon-protocol-v3.0.6) (2023-02-22) - - -### Dependencies - -* bump aegir from 37.12.1 to 38.1.6 ([#183](https://github.com/libp2p/js-libp2p-daemon/issues/183)) ([6725a0a](https://github.com/libp2p/js-libp2p-daemon/commit/6725a0aeba9acb56a7530dece6c65a0f3eadfec5)) - -## [@libp2p/daemon-protocol-v3.0.5](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v3.0.4...@libp2p/daemon-protocol-v3.0.5) (2023-02-22) - - -### Trivial Changes - -* remove lerna ([#171](https://github.com/libp2p/js-libp2p-daemon/issues/171)) ([367f912](https://github.com/libp2p/js-libp2p-daemon/commit/367f9122f2fe1c31c8de7a136cda18d024ff08d7)) - - -### Dependencies - -* **dev:** bump protons from 6.1.3 to 7.0.2 ([#179](https://github.com/libp2p/js-libp2p-daemon/issues/179)) ([07d5872](https://github.com/libp2p/js-libp2p-daemon/commit/07d5872e04f95e2e8957f083dae3721aa8dc307e)) - -## [@libp2p/daemon-protocol-v3.0.4](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v3.0.3...@libp2p/daemon-protocol-v3.0.4) (2022-10-14) - - -### Bug Fixes - -* handle empty responses ([#145](https://github.com/libp2p/js-libp2p-daemon/issues/145)) ([0dfb823](https://github.com/libp2p/js-libp2p-daemon/commit/0dfb8236a0ab57a55fa0ebb91ac7a776a9f709da)) -* restore proto2 compatibility ([#146](https://github.com/libp2p/js-libp2p-daemon/issues/146)) ([9fe8e04](https://github.com/libp2p/js-libp2p-daemon/commit/9fe8e042757ec107cc137a9452fd021a62620b3c)) - -## [@libp2p/daemon-protocol-v3.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v3.0.2...@libp2p/daemon-protocol-v3.0.3) (2022-10-13) - - -### Dependencies - -* update uint8arrays, protons and multiformats ([#143](https://github.com/libp2p/js-libp2p-daemon/issues/143)) ([661139c](https://github.com/libp2p/js-libp2p-daemon/commit/661139c674c9994724e32227d7d9ae2c5da1cea2)) - -## [@libp2p/daemon-protocol-v3.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v3.0.1...@libp2p/daemon-protocol-v3.0.2) (2022-10-07) - - -### Dependencies - -* bump @libp2p/interface-transport from 1.0.4 to 2.0.0 ([#132](https://github.com/libp2p/js-libp2p-daemon/issues/132)) ([1a7b2cc](https://github.com/libp2p/js-libp2p-daemon/commit/1a7b2cc653dfb51e92edb1f652452e3c793156c3)) - -## [@libp2p/daemon-protocol-v3.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v3.0.0...@libp2p/daemon-protocol-v3.0.1) (2022-09-14) - - -### Bug Fixes - -* fix proto file and generated code ([#94](https://github.com/libp2p/js-libp2p-daemon/issues/94)) ([5c22052](https://github.com/libp2p/js-libp2p-daemon/commit/5c22052c8da0da4febf88582e9e27a93ac1f710b)), closes [#66](https://github.com/libp2p/js-libp2p-daemon/issues/66) - -## [@libp2p/daemon-protocol-v3.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v2.0.2...@libp2p/daemon-protocol-v3.0.0) (2022-09-09) - - -### ⚠ BREAKING CHANGES - -* the stream type returned by `client.openStream` has changed - -### Bug Fixes - -* allow opening remote streams ([#126](https://github.com/libp2p/js-libp2p-daemon/issues/126)) ([361cc57](https://github.com/libp2p/js-libp2p-daemon/commit/361cc5750de505ab0381ae43609c67d5d4f659a7)) - -## [@libp2p/daemon-protocol-v2.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v2.0.1...@libp2p/daemon-protocol-v2.0.2) (2022-08-10) - - -### Bug Fixes - -* update all deps ([#124](https://github.com/libp2p/js-libp2p-daemon/issues/124)) ([5e46e1e](https://github.com/libp2p/js-libp2p-daemon/commit/5e46e1e26c23428046a6007ab158420d3d830145)) - -## [@libp2p/daemon-protocol-v2.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v2.0.0...@libp2p/daemon-protocol-v2.0.1) (2022-07-31) - - -### Trivial Changes - -* update project config ([#111](https://github.com/libp2p/js-libp2p-daemon/issues/111)) ([345e663](https://github.com/libp2p/js-libp2p-daemon/commit/345e663e34278e780fc2f3a6b595294f925c4521)) - - -### Dependencies - -* update uint8arraylist and protons deps ([#115](https://github.com/libp2p/js-libp2p-daemon/issues/115)) ([34a8334](https://github.com/libp2p/js-libp2p-daemon/commit/34a83340ba855a9c08319ae1cd735dfa8b71c248)) - -## [@libp2p/daemon-protocol-v2.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v1.0.6...@libp2p/daemon-protocol-v2.0.0) (2022-06-15) - - -### ⚠ BREAKING CHANGES - -* uses new single-issue libp2p interface modules - -### Features - -* update to latest libp2p interfaces ([#102](https://github.com/libp2p/js-libp2p-daemon/issues/102)) ([f5e9121](https://github.com/libp2p/js-libp2p-daemon/commit/f5e91210654ab3c411e316c1c657356c037a0f6a)) - -## [@libp2p/daemon-protocol-v1.0.6](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v1.0.5...@libp2p/daemon-protocol-v1.0.6) (2022-05-25) - - -### Trivial Changes - -* update docs ([#91](https://github.com/libp2p/js-libp2p-daemon/issues/91)) ([5b072ff](https://github.com/libp2p/js-libp2p-daemon/commit/5b072ff89f30fd6cf55a3387bf0961c8ad78a22f)), closes [#83](https://github.com/libp2p/js-libp2p-daemon/issues/83) - -## [@libp2p/daemon-protocol-v1.0.5](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v1.0.4...@libp2p/daemon-protocol-v1.0.5) (2022-05-10) - - -### Bug Fixes - -* encode enums correctly ([#86](https://github.com/libp2p/js-libp2p-daemon/issues/86)) ([6ce4633](https://github.com/libp2p/js-libp2p-daemon/commit/6ce4633f3db41ab66f9b8b1abbe84955dde3e9be)) - -## [@libp2p/daemon-protocol-v1.0.4](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v1.0.3...@libp2p/daemon-protocol-v1.0.4) (2022-04-20) - - -### Bug Fixes - -* update interfaces and deps ([#84](https://github.com/libp2p/js-libp2p-daemon/issues/84)) ([25173d5](https://github.com/libp2p/js-libp2p-daemon/commit/25173d5b2edf0e9dd9132707d349cdc862caecdb)) - -## [@libp2p/daemon-protocol-v1.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v1.0.2...@libp2p/daemon-protocol-v1.0.3) (2022-04-07) - - -### Bug Fixes - -* update generated file ([#82](https://github.com/libp2p/js-libp2p-daemon/issues/82)) ([fc66301](https://github.com/libp2p/js-libp2p-daemon/commit/fc66301b6da3d24bc065f37337705753873c6e60)) - -## [@libp2p/daemon-protocol-v1.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v1.0.1...@libp2p/daemon-protocol-v1.0.2) (2022-04-07) - - -### Bug Fixes - -* remove protobufjs and replace with protons ([#81](https://github.com/libp2p/js-libp2p-daemon/issues/81)) ([78dd02a](https://github.com/libp2p/js-libp2p-daemon/commit/78dd02a679e55f22c7e24c1ee2b6f92a4679a0b9)) - -## [@libp2p/daemon-protocol-v1.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-protocol-v1.0.0...@libp2p/daemon-protocol-v1.0.1) (2022-04-07) - - -### Trivial Changes - -* update aegir to latest version ([#80](https://github.com/libp2p/js-libp2p-daemon/issues/80)) ([3a98959](https://github.com/libp2p/js-libp2p-daemon/commit/3a98959617d9c19bba9fb064defee3d51acfcc29)) - -## @libp2p/daemon-protocol-v1.0.0 (2022-03-28) - - -### ⚠ BREAKING CHANGES - -* This module is now ESM only - -### Features - -* convert to typescript ([#78](https://github.com/libp2p/js-libp2p-daemon/issues/78)) ([f18b2a4](https://github.com/libp2p/js-libp2p-daemon/commit/f18b2a45871a2704db51b03e8583eefdcd13554c)) diff --git a/packages/libp2p-daemon-protocol/LICENSE b/packages/libp2p-daemon-protocol/LICENSE deleted file mode 100644 index 20ce483c86..0000000000 --- a/packages/libp2p-daemon-protocol/LICENSE +++ /dev/null @@ -1,4 +0,0 @@ -This project is dual licensed under MIT and Apache-2.0. - -MIT: https://www.opensource.org/licenses/mit -Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/libp2p-daemon-protocol/LICENSE-APACHE b/packages/libp2p-daemon-protocol/LICENSE-APACHE deleted file mode 100644 index 14478a3b60..0000000000 --- a/packages/libp2p-daemon-protocol/LICENSE-APACHE +++ /dev/null @@ -1,5 +0,0 @@ -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/libp2p-daemon-protocol/LICENSE-MIT b/packages/libp2p-daemon-protocol/LICENSE-MIT deleted file mode 100644 index 72dc60d84b..0000000000 --- a/packages/libp2p-daemon-protocol/LICENSE-MIT +++ /dev/null @@ -1,19 +0,0 @@ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/libp2p-daemon-protocol/README.md b/packages/libp2p-daemon-protocol/README.md deleted file mode 100644 index d50e712fef..0000000000 --- a/packages/libp2p-daemon-protocol/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# @libp2p/daemon-protocol - -[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) -[![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.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) -[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/main.yml?query=branch%3Amaster) - -> Communication protocol between libp2p daemons and clients - -## Table of contents - -- [Install](#install) -- [License](#license) -- [Contribution](#contribution) - -## Install - -```console -$ npm i @libp2p/daemon-protocol -``` - -## License - -Licensed under either of - -- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) -- MIT ([LICENSE-MIT](LICENSE-MIT) / ) - -## Contribution - -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/packages/libp2p-daemon-protocol/package.json b/packages/libp2p-daemon-protocol/package.json deleted file mode 100644 index 884e4db98e..0000000000 --- a/packages/libp2p-daemon-protocol/package.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "name": "@libp2p/daemon-protocol", - "version": "4.0.1", - "description": "Communication protocol between libp2p daemons and clients", - "author": "", - "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/libp2p-daemon-protocol#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/libp2p/js-libp2p.git" - }, - "bugs": { - "url": "https://github.com/libp2p/js-libp2p/issues" - }, - "type": "module", - "types": "./dist/src/index.d.ts", - "typesVersions": { - "*": { - "*": [ - "*", - "dist/*", - "dist/src/*", - "dist/src/*/index" - ], - "src/*": [ - "*", - "dist/*", - "dist/src/*", - "dist/src/*/index" - ] - } - }, - "files": [ - "src", - "dist", - "!dist/test", - "!**/*.tsbuildinfo" - ], - "exports": { - ".": { - "types": "./src/index.d.ts", - "import": "./dist/src/index.js" - }, - "./stream-handler": { - "types": "./dist/src/stream-handler.d.ts", - "import": "./dist/src/stream-handler.js" - }, - "./upgrader": { - "types": "./dist/src/upgrader.d.ts", - "import": "./dist/src/upgrader.js" - } - }, - "eslintConfig": { - "extends": "ipfs", - "parserOptions": { - "sourceType": "module" - }, - "ignorePatterns": [ - "*.d.ts", - "src/index.js" - ] - }, - "scripts": { - "clean": "aegir clean", - "lint": "aegir lint", - "generate": "protons ./src/index.proto", - "build": "aegir build" - }, - "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "it-handshake": "^4.1.3", - "protons-runtime": "^5.0.0", - "uint8arraylist": "^2.4.3" - }, - "devDependencies": { - "aegir": "^40.0.1", - "protons": "^7.0.2" - }, - "private": true -} diff --git a/packages/libp2p-daemon-protocol/src/index.proto b/packages/libp2p-daemon-protocol/src/index.proto deleted file mode 100644 index 038a513cb3..0000000000 --- a/packages/libp2p-daemon-protocol/src/index.proto +++ /dev/null @@ -1,209 +0,0 @@ -syntax = "proto3"; - -message Request { - enum Type { - IDENTIFY = 0; - CONNECT = 1; - STREAM_OPEN = 2; - STREAM_HANDLER = 3; - DHT = 4; - LIST_PEERS = 5; - CONNMANAGER = 6; - DISCONNECT = 7; - PUBSUB = 8; - PEERSTORE = 9; - } - - // the proto2 version of this field is "required" which means it will have - // no default value. the default for proto3 is "singluar" which omits the - // value on the wire if it's the default so for proto3 we make it "optional" - // to ensure a value is always written on to the wire - optional Type type = 1; - - optional ConnectRequest connect = 2; - optional StreamOpenRequest streamOpen = 3; - optional StreamHandlerRequest streamHandler = 4; - optional DHTRequest dht = 5; - optional ConnManagerRequest connManager = 6; - optional DisconnectRequest disconnect = 7; - optional PSRequest pubsub = 8; - optional PeerstoreRequest peerStore = 9; -} - -message Response { - enum Type { - OK = 0; - ERROR = 1; - } - - // the proto2 version of this field is "required" which means it will have - // no default value. the default for proto3 is "singluar" which omits the - // value on the wire if it's the default so for proto3 we make it "optional" - // to ensure a value is always written on to the wire - optional Type type = 1; - - optional ErrorResponse error = 2; - optional StreamInfo streamInfo = 3; - optional IdentifyResponse identify = 4; - optional DHTResponse dht = 5; - repeated PeerInfo peers = 6; - optional PSResponse pubsub = 7; - optional PeerstoreResponse peerStore = 8; -} - -message IdentifyResponse { - bytes id = 1; - repeated bytes addrs = 2; -} - -message ConnectRequest { - bytes peer = 1; - repeated bytes addrs = 2; - optional int64 timeout = 3; -} - -message StreamOpenRequest { - bytes peer = 1; - repeated string proto = 2; - optional int64 timeout = 3; -} - -message StreamHandlerRequest { - bytes addr = 1; - repeated string proto = 2; -} - -message ErrorResponse { - string msg = 1; -} - -message StreamInfo { - bytes peer = 1; - bytes addr = 2; - string proto = 3; -} - -message DHTRequest { - enum Type { - FIND_PEER = 0; - FIND_PEERS_CONNECTED_TO_PEER = 1; - FIND_PROVIDERS = 2; - GET_CLOSEST_PEERS = 3; - GET_PUBLIC_KEY = 4; - GET_VALUE = 5; - SEARCH_VALUE = 6; - PUT_VALUE = 7; - PROVIDE = 8; - } - - // the proto2 version of this field is "required" which means it will have - // no default value. the default for proto3 is "singluar" which omits the - // value on the wire if it's the default so for proto3 we make it "optional" - // to ensure a value is always written on to the wire - optional Type type = 1; - - optional bytes peer = 2; - optional bytes cid = 3; - optional bytes key = 4; - optional bytes value = 5; - optional int32 count = 6; - optional int64 timeout = 7; -} - -message DHTResponse { - enum Type { - BEGIN = 0; - VALUE = 1; - END = 2; - } - - // the proto2 version of this field is "required" which means it will have - // no default value. the default for proto3 is "singluar" which omits the - // value on the wire if it's the default so for proto3 we make it "optional" - // to ensure a value is always written on to the wire - optional Type type = 1; - - optional PeerInfo peer = 2; - optional bytes value = 3; -} - -message PeerInfo { - bytes id = 1; - repeated bytes addrs = 2; -} - -message ConnManagerRequest { - enum Type { - TAG_PEER = 0; - UNTAG_PEER = 1; - TRIM = 2; - } - - // the proto2 version of this field is "required" which means it will have - // no default value. the default for proto3 is "singluar" which omits the - // value on the wire if it's the default so for proto3 we make it "optional" - // to ensure a value is always written on to the wire - optional Type type = 1; - - optional bytes peer = 2; - optional string tag = 3; - optional int64 weight = 4; -} - -message DisconnectRequest { - bytes peer = 1; -} - -message PSRequest { - enum Type { - GET_TOPICS = 0; - LIST_PEERS = 1; - PUBLISH = 2; - SUBSCRIBE = 3; - } - - // the proto2 version of this field is "required" which means it will have - // no default value. the default for proto3 is "singluar" which omits the - // value on the wire if it's the default so for proto3 we make it "optional" - // to ensure a value is always written on to the wire - optional Type type = 1; - - optional string topic = 2; - optional bytes data = 3; -} - -message PSMessage { - optional bytes from = 1; - optional bytes data = 2; - optional bytes seqno = 3; - repeated string topicIDs = 4; - optional bytes signature = 5; - optional bytes key = 6; -} - -message PSResponse { - repeated string topics = 1; - repeated bytes peerIDs = 2; -} - -message PeerstoreRequest { - enum Type { - UNSPECIFIED = 0; - GET_PROTOCOLS = 1; - GET_PEER_INFO = 2; - } - - // the proto2 version of this field is "required" which means it will have - // no default value. the default for proto3 is "singluar" which omits the - // value on the wire if it's the default so for proto3 we make it "optional" - // to ensure a value is always written on to the wire - optional Type type = 1; - - optional bytes id = 2; - repeated string protos = 3; -} - -message PeerstoreResponse { - optional PeerInfo peer = 1; - repeated string protos = 2; -} diff --git a/packages/libp2p-daemon-protocol/src/index.ts b/packages/libp2p-daemon-protocol/src/index.ts deleted file mode 100644 index b9b517682c..0000000000 --- a/packages/libp2p-daemon-protocol/src/index.ts +++ /dev/null @@ -1,1639 +0,0 @@ -/* eslint-disable import/export */ -/* eslint-disable complexity */ -/* eslint-disable @typescript-eslint/no-namespace */ -/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ -/* eslint-disable @typescript-eslint/no-empty-interface */ - -import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' -import type { Uint8ArrayList } from 'uint8arraylist' - -export interface Request { - type?: Request.Type - connect?: ConnectRequest - streamOpen?: StreamOpenRequest - streamHandler?: StreamHandlerRequest - dht?: DHTRequest - connManager?: ConnManagerRequest - disconnect?: DisconnectRequest - pubsub?: PSRequest - peerStore?: PeerstoreRequest -} - -export namespace Request { - export enum Type { - IDENTIFY = 'IDENTIFY', - CONNECT = 'CONNECT', - STREAM_OPEN = 'STREAM_OPEN', - STREAM_HANDLER = 'STREAM_HANDLER', - DHT = 'DHT', - LIST_PEERS = 'LIST_PEERS', - CONNMANAGER = 'CONNMANAGER', - DISCONNECT = 'DISCONNECT', - PUBSUB = 'PUBSUB', - PEERSTORE = 'PEERSTORE' - } - - enum __TypeValues { - IDENTIFY = 0, - CONNECT = 1, - STREAM_OPEN = 2, - STREAM_HANDLER = 3, - DHT = 4, - LIST_PEERS = 5, - CONNMANAGER = 6, - DISCONNECT = 7, - PUBSUB = 8, - PEERSTORE = 9 - } - - export namespace Type { - export const codec = (): Codec => { - return enumeration(__TypeValues) - } - } - - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (obj.type != null) { - w.uint32(8) - Request.Type.codec().encode(obj.type, w) - } - - if (obj.connect != null) { - w.uint32(18) - ConnectRequest.codec().encode(obj.connect, w) - } - - if (obj.streamOpen != null) { - w.uint32(26) - StreamOpenRequest.codec().encode(obj.streamOpen, w) - } - - if (obj.streamHandler != null) { - w.uint32(34) - StreamHandlerRequest.codec().encode(obj.streamHandler, w) - } - - if (obj.dht != null) { - w.uint32(42) - DHTRequest.codec().encode(obj.dht, w) - } - - if (obj.connManager != null) { - w.uint32(50) - ConnManagerRequest.codec().encode(obj.connManager, w) - } - - if (obj.disconnect != null) { - w.uint32(58) - DisconnectRequest.codec().encode(obj.disconnect, w) - } - - if (obj.pubsub != null) { - w.uint32(66) - PSRequest.codec().encode(obj.pubsub, w) - } - - if (obj.peerStore != null) { - w.uint32(74) - PeerstoreRequest.codec().encode(obj.peerStore, w) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.type = Request.Type.codec().decode(reader) - break - case 2: - obj.connect = ConnectRequest.codec().decode(reader, reader.uint32()) - break - case 3: - obj.streamOpen = StreamOpenRequest.codec().decode(reader, reader.uint32()) - break - case 4: - obj.streamHandler = StreamHandlerRequest.codec().decode(reader, reader.uint32()) - break - case 5: - obj.dht = DHTRequest.codec().decode(reader, reader.uint32()) - break - case 6: - obj.connManager = ConnManagerRequest.codec().decode(reader, reader.uint32()) - break - case 7: - obj.disconnect = DisconnectRequest.codec().decode(reader, reader.uint32()) - break - case 8: - obj.pubsub = PSRequest.codec().decode(reader, reader.uint32()) - break - case 9: - obj.peerStore = PeerstoreRequest.codec().decode(reader, reader.uint32()) - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, Request.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): Request => { - return decodeMessage(buf, Request.codec()) - } -} - -export interface Response { - type?: Response.Type - error?: ErrorResponse - streamInfo?: StreamInfo - identify?: IdentifyResponse - dht?: DHTResponse - peers: PeerInfo[] - pubsub?: PSResponse - peerStore?: PeerstoreResponse -} - -export namespace Response { - export enum Type { - OK = 'OK', - ERROR = 'ERROR' - } - - enum __TypeValues { - OK = 0, - ERROR = 1 - } - - export namespace Type { - export const codec = (): Codec => { - return enumeration(__TypeValues) - } - } - - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (obj.type != null) { - w.uint32(8) - Response.Type.codec().encode(obj.type, w) - } - - if (obj.error != null) { - w.uint32(18) - ErrorResponse.codec().encode(obj.error, w) - } - - if (obj.streamInfo != null) { - w.uint32(26) - StreamInfo.codec().encode(obj.streamInfo, w) - } - - if (obj.identify != null) { - w.uint32(34) - IdentifyResponse.codec().encode(obj.identify, w) - } - - if (obj.dht != null) { - w.uint32(42) - DHTResponse.codec().encode(obj.dht, w) - } - - if (obj.peers != null) { - for (const value of obj.peers) { - w.uint32(50) - PeerInfo.codec().encode(value, w) - } - } - - if (obj.pubsub != null) { - w.uint32(58) - PSResponse.codec().encode(obj.pubsub, w) - } - - if (obj.peerStore != null) { - w.uint32(66) - PeerstoreResponse.codec().encode(obj.peerStore, w) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - peers: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.type = Response.Type.codec().decode(reader) - break - case 2: - obj.error = ErrorResponse.codec().decode(reader, reader.uint32()) - break - case 3: - obj.streamInfo = StreamInfo.codec().decode(reader, reader.uint32()) - break - case 4: - obj.identify = IdentifyResponse.codec().decode(reader, reader.uint32()) - break - case 5: - obj.dht = DHTResponse.codec().decode(reader, reader.uint32()) - break - case 6: - obj.peers.push(PeerInfo.codec().decode(reader, reader.uint32())) - break - case 7: - obj.pubsub = PSResponse.codec().decode(reader, reader.uint32()) - break - case 8: - obj.peerStore = PeerstoreResponse.codec().decode(reader, reader.uint32()) - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, Response.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): Response => { - return decodeMessage(buf, Response.codec()) - } -} - -export interface IdentifyResponse { - id: Uint8Array - addrs: Uint8Array[] -} - -export namespace IdentifyResponse { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if ((obj.id != null && obj.id.byteLength > 0)) { - w.uint32(10) - w.bytes(obj.id) - } - - if (obj.addrs != null) { - for (const value of obj.addrs) { - w.uint32(18) - w.bytes(value) - } - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - id: new Uint8Array(0), - addrs: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.id = reader.bytes() - break - case 2: - obj.addrs.push(reader.bytes()) - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, IdentifyResponse.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): IdentifyResponse => { - return decodeMessage(buf, IdentifyResponse.codec()) - } -} - -export interface ConnectRequest { - peer: Uint8Array - addrs: Uint8Array[] - timeout?: bigint -} - -export namespace ConnectRequest { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if ((obj.peer != null && obj.peer.byteLength > 0)) { - w.uint32(10) - w.bytes(obj.peer) - } - - if (obj.addrs != null) { - for (const value of obj.addrs) { - w.uint32(18) - w.bytes(value) - } - } - - if (obj.timeout != null) { - w.uint32(24) - w.int64(obj.timeout) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - peer: new Uint8Array(0), - addrs: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.peer = reader.bytes() - break - case 2: - obj.addrs.push(reader.bytes()) - break - case 3: - obj.timeout = reader.int64() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, ConnectRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): ConnectRequest => { - return decodeMessage(buf, ConnectRequest.codec()) - } -} - -export interface StreamOpenRequest { - peer: Uint8Array - proto: string[] - timeout?: bigint -} - -export namespace StreamOpenRequest { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if ((obj.peer != null && obj.peer.byteLength > 0)) { - w.uint32(10) - w.bytes(obj.peer) - } - - if (obj.proto != null) { - for (const value of obj.proto) { - w.uint32(18) - w.string(value) - } - } - - if (obj.timeout != null) { - w.uint32(24) - w.int64(obj.timeout) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - peer: new Uint8Array(0), - proto: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.peer = reader.bytes() - break - case 2: - obj.proto.push(reader.string()) - break - case 3: - obj.timeout = reader.int64() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, StreamOpenRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): StreamOpenRequest => { - return decodeMessage(buf, StreamOpenRequest.codec()) - } -} - -export interface StreamHandlerRequest { - addr: Uint8Array - proto: string[] -} - -export namespace StreamHandlerRequest { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if ((obj.addr != null && obj.addr.byteLength > 0)) { - w.uint32(10) - w.bytes(obj.addr) - } - - if (obj.proto != null) { - for (const value of obj.proto) { - w.uint32(18) - w.string(value) - } - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - addr: new Uint8Array(0), - proto: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.addr = reader.bytes() - break - case 2: - obj.proto.push(reader.string()) - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, StreamHandlerRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): StreamHandlerRequest => { - return decodeMessage(buf, StreamHandlerRequest.codec()) - } -} - -export interface ErrorResponse { - msg: string -} - -export namespace ErrorResponse { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if ((obj.msg != null && obj.msg !== '')) { - w.uint32(10) - w.string(obj.msg) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - msg: '' - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.msg = reader.string() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, ErrorResponse.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): ErrorResponse => { - return decodeMessage(buf, ErrorResponse.codec()) - } -} - -export interface StreamInfo { - peer: Uint8Array - addr: Uint8Array - proto: string -} - -export namespace StreamInfo { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if ((obj.peer != null && obj.peer.byteLength > 0)) { - w.uint32(10) - w.bytes(obj.peer) - } - - if ((obj.addr != null && obj.addr.byteLength > 0)) { - w.uint32(18) - w.bytes(obj.addr) - } - - if ((obj.proto != null && obj.proto !== '')) { - w.uint32(26) - w.string(obj.proto) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - peer: new Uint8Array(0), - addr: new Uint8Array(0), - proto: '' - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.peer = reader.bytes() - break - case 2: - obj.addr = reader.bytes() - break - case 3: - obj.proto = reader.string() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, StreamInfo.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): StreamInfo => { - return decodeMessage(buf, StreamInfo.codec()) - } -} - -export interface DHTRequest { - type?: DHTRequest.Type - peer?: Uint8Array - cid?: Uint8Array - key?: Uint8Array - value?: Uint8Array - count?: number - timeout?: bigint -} - -export namespace DHTRequest { - export enum Type { - FIND_PEER = 'FIND_PEER', - FIND_PEERS_CONNECTED_TO_PEER = 'FIND_PEERS_CONNECTED_TO_PEER', - FIND_PROVIDERS = 'FIND_PROVIDERS', - GET_CLOSEST_PEERS = 'GET_CLOSEST_PEERS', - GET_PUBLIC_KEY = 'GET_PUBLIC_KEY', - GET_VALUE = 'GET_VALUE', - SEARCH_VALUE = 'SEARCH_VALUE', - PUT_VALUE = 'PUT_VALUE', - PROVIDE = 'PROVIDE' - } - - enum __TypeValues { - FIND_PEER = 0, - FIND_PEERS_CONNECTED_TO_PEER = 1, - FIND_PROVIDERS = 2, - GET_CLOSEST_PEERS = 3, - GET_PUBLIC_KEY = 4, - GET_VALUE = 5, - SEARCH_VALUE = 6, - PUT_VALUE = 7, - PROVIDE = 8 - } - - export namespace Type { - export const codec = (): Codec => { - return enumeration(__TypeValues) - } - } - - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (obj.type != null) { - w.uint32(8) - DHTRequest.Type.codec().encode(obj.type, w) - } - - if (obj.peer != null) { - w.uint32(18) - w.bytes(obj.peer) - } - - if (obj.cid != null) { - w.uint32(26) - w.bytes(obj.cid) - } - - if (obj.key != null) { - w.uint32(34) - w.bytes(obj.key) - } - - if (obj.value != null) { - w.uint32(42) - w.bytes(obj.value) - } - - if (obj.count != null) { - w.uint32(48) - w.int32(obj.count) - } - - if (obj.timeout != null) { - w.uint32(56) - w.int64(obj.timeout) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.type = DHTRequest.Type.codec().decode(reader) - break - case 2: - obj.peer = reader.bytes() - break - case 3: - obj.cid = reader.bytes() - break - case 4: - obj.key = reader.bytes() - break - case 5: - obj.value = reader.bytes() - break - case 6: - obj.count = reader.int32() - break - case 7: - obj.timeout = reader.int64() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, DHTRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): DHTRequest => { - return decodeMessage(buf, DHTRequest.codec()) - } -} - -export interface DHTResponse { - type?: DHTResponse.Type - peer?: PeerInfo - value?: Uint8Array -} - -export namespace DHTResponse { - export enum Type { - BEGIN = 'BEGIN', - VALUE = 'VALUE', - END = 'END' - } - - enum __TypeValues { - BEGIN = 0, - VALUE = 1, - END = 2 - } - - export namespace Type { - export const codec = (): Codec => { - return enumeration(__TypeValues) - } - } - - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (obj.type != null) { - w.uint32(8) - DHTResponse.Type.codec().encode(obj.type, w) - } - - if (obj.peer != null) { - w.uint32(18) - PeerInfo.codec().encode(obj.peer, w) - } - - if (obj.value != null) { - w.uint32(26) - w.bytes(obj.value) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.type = DHTResponse.Type.codec().decode(reader) - break - case 2: - obj.peer = PeerInfo.codec().decode(reader, reader.uint32()) - break - case 3: - obj.value = reader.bytes() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, DHTResponse.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): DHTResponse => { - return decodeMessage(buf, DHTResponse.codec()) - } -} - -export interface PeerInfo { - id: Uint8Array - addrs: Uint8Array[] -} - -export namespace PeerInfo { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if ((obj.id != null && obj.id.byteLength > 0)) { - w.uint32(10) - w.bytes(obj.id) - } - - if (obj.addrs != null) { - for (const value of obj.addrs) { - w.uint32(18) - w.bytes(value) - } - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - id: new Uint8Array(0), - addrs: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.id = reader.bytes() - break - case 2: - obj.addrs.push(reader.bytes()) - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, PeerInfo.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerInfo => { - return decodeMessage(buf, PeerInfo.codec()) - } -} - -export interface ConnManagerRequest { - type?: ConnManagerRequest.Type - peer?: Uint8Array - tag?: string - weight?: bigint -} - -export namespace ConnManagerRequest { - export enum Type { - TAG_PEER = 'TAG_PEER', - UNTAG_PEER = 'UNTAG_PEER', - TRIM = 'TRIM' - } - - enum __TypeValues { - TAG_PEER = 0, - UNTAG_PEER = 1, - TRIM = 2 - } - - export namespace Type { - export const codec = (): Codec => { - return enumeration(__TypeValues) - } - } - - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (obj.type != null) { - w.uint32(8) - ConnManagerRequest.Type.codec().encode(obj.type, w) - } - - if (obj.peer != null) { - w.uint32(18) - w.bytes(obj.peer) - } - - if (obj.tag != null) { - w.uint32(26) - w.string(obj.tag) - } - - if (obj.weight != null) { - w.uint32(32) - w.int64(obj.weight) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.type = ConnManagerRequest.Type.codec().decode(reader) - break - case 2: - obj.peer = reader.bytes() - break - case 3: - obj.tag = reader.string() - break - case 4: - obj.weight = reader.int64() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, ConnManagerRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): ConnManagerRequest => { - return decodeMessage(buf, ConnManagerRequest.codec()) - } -} - -export interface DisconnectRequest { - peer: Uint8Array -} - -export namespace DisconnectRequest { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if ((obj.peer != null && obj.peer.byteLength > 0)) { - w.uint32(10) - w.bytes(obj.peer) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - peer: new Uint8Array(0) - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.peer = reader.bytes() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, DisconnectRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): DisconnectRequest => { - return decodeMessage(buf, DisconnectRequest.codec()) - } -} - -export interface PSRequest { - type?: PSRequest.Type - topic?: string - data?: Uint8Array -} - -export namespace PSRequest { - export enum Type { - GET_TOPICS = 'GET_TOPICS', - LIST_PEERS = 'LIST_PEERS', - PUBLISH = 'PUBLISH', - SUBSCRIBE = 'SUBSCRIBE' - } - - enum __TypeValues { - GET_TOPICS = 0, - LIST_PEERS = 1, - PUBLISH = 2, - SUBSCRIBE = 3 - } - - export namespace Type { - export const codec = (): Codec => { - return enumeration(__TypeValues) - } - } - - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (obj.type != null) { - w.uint32(8) - PSRequest.Type.codec().encode(obj.type, w) - } - - if (obj.topic != null) { - w.uint32(18) - w.string(obj.topic) - } - - if (obj.data != null) { - w.uint32(26) - w.bytes(obj.data) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.type = PSRequest.Type.codec().decode(reader) - break - case 2: - obj.topic = reader.string() - break - case 3: - obj.data = reader.bytes() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, PSRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): PSRequest => { - return decodeMessage(buf, PSRequest.codec()) - } -} - -export interface PSMessage { - from?: Uint8Array - data?: Uint8Array - seqno?: Uint8Array - topicIDs: string[] - signature?: Uint8Array - key?: Uint8Array -} - -export namespace PSMessage { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (obj.from != null) { - w.uint32(10) - w.bytes(obj.from) - } - - if (obj.data != null) { - w.uint32(18) - w.bytes(obj.data) - } - - if (obj.seqno != null) { - w.uint32(26) - w.bytes(obj.seqno) - } - - if (obj.topicIDs != null) { - for (const value of obj.topicIDs) { - w.uint32(34) - w.string(value) - } - } - - if (obj.signature != null) { - w.uint32(42) - w.bytes(obj.signature) - } - - if (obj.key != null) { - w.uint32(50) - w.bytes(obj.key) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - topicIDs: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.from = reader.bytes() - break - case 2: - obj.data = reader.bytes() - break - case 3: - obj.seqno = reader.bytes() - break - case 4: - obj.topicIDs.push(reader.string()) - break - case 5: - obj.signature = reader.bytes() - break - case 6: - obj.key = reader.bytes() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, PSMessage.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): PSMessage => { - return decodeMessage(buf, PSMessage.codec()) - } -} - -export interface PSResponse { - topics: string[] - peerIDs: Uint8Array[] -} - -export namespace PSResponse { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (obj.topics != null) { - for (const value of obj.topics) { - w.uint32(10) - w.string(value) - } - } - - if (obj.peerIDs != null) { - for (const value of obj.peerIDs) { - w.uint32(18) - w.bytes(value) - } - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - topics: [], - peerIDs: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.topics.push(reader.string()) - break - case 2: - obj.peerIDs.push(reader.bytes()) - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, PSResponse.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): PSResponse => { - return decodeMessage(buf, PSResponse.codec()) - } -} - -export interface PeerstoreRequest { - type?: PeerstoreRequest.Type - id?: Uint8Array - protos: string[] -} - -export namespace PeerstoreRequest { - export enum Type { - UNSPECIFIED = 'UNSPECIFIED', - GET_PROTOCOLS = 'GET_PROTOCOLS', - GET_PEER_INFO = 'GET_PEER_INFO' - } - - enum __TypeValues { - UNSPECIFIED = 0, - GET_PROTOCOLS = 1, - GET_PEER_INFO = 2 - } - - export namespace Type { - export const codec = (): Codec => { - return enumeration(__TypeValues) - } - } - - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (obj.type != null) { - w.uint32(8) - PeerstoreRequest.Type.codec().encode(obj.type, w) - } - - if (obj.id != null) { - w.uint32(18) - w.bytes(obj.id) - } - - if (obj.protos != null) { - for (const value of obj.protos) { - w.uint32(26) - w.string(value) - } - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - protos: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.type = PeerstoreRequest.Type.codec().decode(reader) - break - case 2: - obj.id = reader.bytes() - break - case 3: - obj.protos.push(reader.string()) - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, PeerstoreRequest.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerstoreRequest => { - return decodeMessage(buf, PeerstoreRequest.codec()) - } -} - -export interface PeerstoreResponse { - peer?: PeerInfo - protos: string[] -} - -export namespace PeerstoreResponse { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (obj.peer != null) { - w.uint32(10) - PeerInfo.codec().encode(obj.peer, w) - } - - if (obj.protos != null) { - for (const value of obj.protos) { - w.uint32(18) - w.string(value) - } - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length) => { - const obj: any = { - protos: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.peer = PeerInfo.codec().decode(reader, reader.uint32()) - break - case 2: - obj.protos.push(reader.string()) - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, PeerstoreResponse.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerstoreResponse => { - return decodeMessage(buf, PeerstoreResponse.codec()) - } -} diff --git a/packages/libp2p-daemon-protocol/src/stream-handler.ts b/packages/libp2p-daemon-protocol/src/stream-handler.ts deleted file mode 100644 index 3a38a038db..0000000000 --- a/packages/libp2p-daemon-protocol/src/stream-handler.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { logger } from '@libp2p/logger' -import { handshake } from 'it-handshake' -import * as lp from 'it-length-prefixed' -import type { Handshake } from 'it-handshake' -import type { Duplex, Source } from 'it-stream-types' -import type { Uint8ArrayList } from 'uint8arraylist' - -const log = logger('libp2p:daemon-protocol:stream-handler') - -export interface StreamHandlerOptions { - stream: Duplex, Source, Promise> - maxLength?: number -} - -export class StreamHandler { - private readonly stream: Duplex, Source, Promise> - private readonly shake: Handshake - public decoder: Source - - /** - * Create a stream handler for connection - */ - constructor (opts: StreamHandlerOptions) { - const { stream, maxLength } = opts - - this.stream = stream - this.shake = handshake(this.stream) - this.decoder = lp.decode.fromReader(this.shake.reader, { maxDataLength: maxLength ?? 4096 }) - } - - /** - * Read and decode message - */ - async read (): Promise { - // @ts-expect-error decoder is really a generator - const msg = await this.decoder.next() - if (msg.value != null) { - return msg.value.subarray() - } - - log('read received no value, closing stream') - // End the stream, we didn't get data - await this.close() - } - - write (msg: Uint8Array | Uint8ArrayList): void { - log('write message') - this.shake.write( - lp.encode.single(msg).subarray() - ) - } - - /** - * Return the handshake rest stream and invalidate handler - */ - rest (): Duplex, Source, Promise> { - this.shake.rest() - return this.shake.stream - } - - /** - * Close the stream - */ - async close (): Promise { - log('closing the stream') - await this.rest().sink([]) - } -} diff --git a/packages/libp2p-daemon-protocol/src/upgrader.ts b/packages/libp2p-daemon-protocol/src/upgrader.ts deleted file mode 100644 index 33de547328..0000000000 --- a/packages/libp2p-daemon-protocol/src/upgrader.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { Upgrader } from '@libp2p/interface/transport' - -export const passThroughUpgrader: Upgrader = { - // @ts-expect-error should return a connection - upgradeInbound: async maConn => maConn, - // @ts-expect-error should return a connection - upgradeOutbound: async maConn => maConn -} diff --git a/packages/libp2p-daemon-protocol/tsconfig.json b/packages/libp2p-daemon-protocol/tsconfig.json deleted file mode 100644 index 1a5e68bbdb..0000000000 --- a/packages/libp2p-daemon-protocol/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "aegir/src/config/tsconfig.aegir.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": [ - "src" - ], - "exclude": [ - "src/index.js" - ], - "references": [ - { - "path": "../interface" - }, - { - "path": "../logger" - } - ] -} diff --git a/packages/libp2p-daemon-protocol/typedoc.json b/packages/libp2p-daemon-protocol/typedoc.json deleted file mode 100644 index 15cd5d50df..0000000000 --- a/packages/libp2p-daemon-protocol/typedoc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "entryPoints": [ - "./src/index.ts", - "./src/stream-handler.ts", - "./src/upgrader.ts" - ] -} diff --git a/packages/libp2p-daemon-server/.aegir.js b/packages/libp2p-daemon-server/.aegir.js deleted file mode 100644 index 135a6a2211..0000000000 --- a/packages/libp2p-daemon-server/.aegir.js +++ /dev/null @@ -1,8 +0,0 @@ - -export default { - build: { - config: { - platform: 'node' - } - } -} diff --git a/packages/libp2p-daemon-server/CHANGELOG.md b/packages/libp2p-daemon-server/CHANGELOG.md deleted file mode 100644 index f7c98c0a03..0000000000 --- a/packages/libp2p-daemon-server/CHANGELOG.md +++ /dev/null @@ -1,249 +0,0 @@ -## [@libp2p/daemon-server-v5.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v5.0.1...@libp2p/daemon-server-v5.0.2) (2023-04-27) - - -### Bug Fixes - -* use interface-libp2p to ensure the correct services are set ([#203](https://github.com/libp2p/js-libp2p-daemon/issues/203)) ([8602a70](https://github.com/libp2p/js-libp2p-daemon/commit/8602a704e45cfa768ad55974d025b2d4be6f42a9)) - -## [@libp2p/daemon-server-v5.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v5.0.0...@libp2p/daemon-server-v5.0.1) (2023-04-24) - - -### Dependencies - -* bump @libp2p/interface-peer-store from 1.2.9 to 2.0.0 ([#201](https://github.com/libp2p/js-libp2p-daemon/issues/201)) ([9b146a8](https://github.com/libp2p/js-libp2p-daemon/commit/9b146a8c38c30a13401be6da5259cd9da6bdc25c)) - -## [@libp2p/daemon-server-v5.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v4.1.4...@libp2p/daemon-server-v5.0.0) (2023-04-19) - - -### ⚠ BREAKING CHANGES - -* the type of the source/sink properties have changed - -### Dependencies - -* update it-stream-types to 2.x.x ([#196](https://github.com/libp2p/js-libp2p-daemon/issues/196)) ([a09f6d5](https://github.com/libp2p/js-libp2p-daemon/commit/a09f6d58942033b08b579735aaa1537b3a324776)) -* update sibling dependencies ([e0ec5ec](https://github.com/libp2p/js-libp2p-daemon/commit/e0ec5ecf5bfd7f801274d37d51c3dcce652de2ba)) - -## [@libp2p/daemon-server-v4.1.4](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v4.1.3...@libp2p/daemon-server-v4.1.4) (2023-04-12) - - -### Dependencies - -* bump @libp2p/interface-connection from 3.1.1 to 4.0.0 ([#195](https://github.com/libp2p/js-libp2p-daemon/issues/195)) ([798ecc5](https://github.com/libp2p/js-libp2p-daemon/commit/798ecc594bc64c8e34aad13e1b9884011f0b1f29)) - -## [@libp2p/daemon-server-v4.1.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v4.1.2...@libp2p/daemon-server-v4.1.3) (2023-04-03) - - -### Dependencies - -* update all it-* deps to the latest versions ([#193](https://github.com/libp2p/js-libp2p-daemon/issues/193)) ([cb0aa85](https://github.com/libp2p/js-libp2p-daemon/commit/cb0aa85bbbad651db088594622a9438a127d2a10)) - -## [@libp2p/daemon-server-v4.1.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v4.1.1...@libp2p/daemon-server-v4.1.2) (2023-03-31) - - -### Dependencies - -* bump it-drain from 2.0.1 to 3.0.1 ([#190](https://github.com/libp2p/js-libp2p-daemon/issues/190)) ([306bdc4](https://github.com/libp2p/js-libp2p-daemon/commit/306bdc4fc139c3af429314d7b7d78d0a2238d6f4)) - -## [@libp2p/daemon-server-v4.1.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v4.1.0...@libp2p/daemon-server-v4.1.1) (2023-03-17) - - -### Dependencies - -* bump @multiformats/multiaddr from 11.6.1 to 12.0.0 ([#189](https://github.com/libp2p/js-libp2p-daemon/issues/189)) ([aaf7e2e](https://github.com/libp2p/js-libp2p-daemon/commit/aaf7e2e37423cae78cd16d8e16e06db40fdcd1e3)) - -## [@libp2p/daemon-server-v4.1.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v4.0.3...@libp2p/daemon-server-v4.1.0) (2023-02-23) - - -### Features - -* add get subscribers for pubsub topics ([#184](https://github.com/libp2p/js-libp2p-daemon/issues/184)) ([c8be43e](https://github.com/libp2p/js-libp2p-daemon/commit/c8be43e5acd6a74cfdd01857343af6f6d8210d5d)) - -## [@libp2p/daemon-server-v4.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v4.0.2...@libp2p/daemon-server-v4.0.3) (2023-02-22) - - -### Dependencies - -* bump aegir from 37.12.1 to 38.1.6 ([#183](https://github.com/libp2p/js-libp2p-daemon/issues/183)) ([6725a0a](https://github.com/libp2p/js-libp2p-daemon/commit/6725a0aeba9acb56a7530dece6c65a0f3eadfec5)) - -## [@libp2p/daemon-server-v4.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v4.0.1...@libp2p/daemon-server-v4.0.2) (2023-02-22) - - -### Trivial Changes - -* remove lerna ([#171](https://github.com/libp2p/js-libp2p-daemon/issues/171)) ([367f912](https://github.com/libp2p/js-libp2p-daemon/commit/367f9122f2fe1c31c8de7a136cda18d024ff08d7)) - - -### Dependencies - -* **dev:** bump sinon from 14.0.2 to 15.0.1 ([#166](https://github.com/libp2p/js-libp2p-daemon/issues/166)) ([1702efb](https://github.com/libp2p/js-libp2p-daemon/commit/1702efb4248bea4cb9ec19c694c1caae1c0ff16d)) - -## [@libp2p/daemon-server-v4.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v4.0.0...@libp2p/daemon-server-v4.0.1) (2023-01-07) - - -### Dependencies - -* bump @libp2p/tcp from 5.0.2 to 6.0.8 ([#165](https://github.com/libp2p/js-libp2p-daemon/issues/165)) ([fb676ab](https://github.com/libp2p/js-libp2p-daemon/commit/fb676ab66348b3c704d2385b4da0d7173bc4a04d)) - -## [@libp2p/daemon-server-v4.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v3.0.5...@libp2p/daemon-server-v4.0.0) (2023-01-07) - - -### ⚠ BREAKING CHANGES - -* Update multiformats and related dependencies (#170) - -### Dependencies - -* Update multiformats and related dependencies ([#170](https://github.com/libp2p/js-libp2p-daemon/issues/170)) ([06744a7](https://github.com/libp2p/js-libp2p-daemon/commit/06744a77006dc77dcfb7bd860e4dc6f36a535603)) - -## [@libp2p/daemon-server-v3.0.5](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v3.0.4...@libp2p/daemon-server-v3.0.5) (2022-10-17) - - -### Dependencies - -* bump it-drain from 1.0.5 to 2.0.0 ([#147](https://github.com/libp2p/js-libp2p-daemon/issues/147)) ([56663f8](https://github.com/libp2p/js-libp2p-daemon/commit/56663f83255a0720b4bf4c7e3805ee4ced8dc86d)) - -## [@libp2p/daemon-server-v3.0.4](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v3.0.3...@libp2p/daemon-server-v3.0.4) (2022-10-14) - - -### Dependencies - -* **dev:** bump sinon-ts from 0.0.2 to 1.0.0 ([#144](https://github.com/libp2p/js-libp2p-daemon/issues/144)) ([cfc8755](https://github.com/libp2p/js-libp2p-daemon/commit/cfc8755aa1280ac4fc2aae67cf47d7b0b93f605d)) - -## [@libp2p/daemon-server-v3.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v3.0.2...@libp2p/daemon-server-v3.0.3) (2022-10-13) - - -### Dependencies - -* update uint8arrays, protons and multiformats ([#143](https://github.com/libp2p/js-libp2p-daemon/issues/143)) ([661139c](https://github.com/libp2p/js-libp2p-daemon/commit/661139c674c9994724e32227d7d9ae2c5da1cea2)) - -## [@libp2p/daemon-server-v3.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v3.0.1...@libp2p/daemon-server-v3.0.2) (2022-10-07) - - -### Dependencies - -* bump @libp2p/interface-transport from 1.0.4 to 2.0.0 ([#132](https://github.com/libp2p/js-libp2p-daemon/issues/132)) ([1a7b2cc](https://github.com/libp2p/js-libp2p-daemon/commit/1a7b2cc653dfb51e92edb1f652452e3c793156c3)) -* bump @libp2p/tcp from 3.0.0 to 4.0.1 ([4e64dce](https://github.com/libp2p/js-libp2p-daemon/commit/4e64dce5e6d18dadaa54a20fff7b2da8bbca11ae)) - -## [@libp2p/daemon-server-v3.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v3.0.0...@libp2p/daemon-server-v3.0.1) (2022-09-21) - - -### Dependencies - -* update @multiformats/multiaddr to 11.0.0 ([#128](https://github.com/libp2p/js-libp2p-daemon/issues/128)) ([885d901](https://github.com/libp2p/js-libp2p-daemon/commit/885d9013d82a62e6756b06350932df1242a13296)) - -## [@libp2p/daemon-server-v3.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v2.0.4...@libp2p/daemon-server-v3.0.0) (2022-09-09) - - -### ⚠ BREAKING CHANGES - -* the stream type returned by `client.openStream` has changed - -### Bug Fixes - -* allow opening remote streams ([#126](https://github.com/libp2p/js-libp2p-daemon/issues/126)) ([361cc57](https://github.com/libp2p/js-libp2p-daemon/commit/361cc5750de505ab0381ae43609c67d5d4f659a7)) - - -### Dependencies - -* update sibling dependencies ([c3ebd58](https://github.com/libp2p/js-libp2p-daemon/commit/c3ebd588abc36ef45667e8e4e4c0e220303b7510)) - -## [@libp2p/daemon-server-v2.0.4](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v2.0.3...@libp2p/daemon-server-v2.0.4) (2022-08-10) - - -### Bug Fixes - -* update all deps ([#124](https://github.com/libp2p/js-libp2p-daemon/issues/124)) ([5e46e1e](https://github.com/libp2p/js-libp2p-daemon/commit/5e46e1e26c23428046a6007ab158420d3d830145)) - - -### Documentation - -* readme update ([f569ffc](https://github.com/libp2p/js-libp2p-daemon/commit/f569ffc5c3956248e685d99904408fd3f4d868f4)) - -## [@libp2p/daemon-server-v2.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v2.0.2...@libp2p/daemon-server-v2.0.3) (2022-07-31) - - -### Trivial Changes - -* update project config ([#111](https://github.com/libp2p/js-libp2p-daemon/issues/111)) ([345e663](https://github.com/libp2p/js-libp2p-daemon/commit/345e663e34278e780fc2f3a6b595294f925c4521)) - - -### Dependencies - -* update uint8arraylist and protons deps ([#115](https://github.com/libp2p/js-libp2p-daemon/issues/115)) ([34a8334](https://github.com/libp2p/js-libp2p-daemon/commit/34a83340ba855a9c08319ae1cd735dfa8b71c248)) - -## [@libp2p/daemon-server-v2.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v2.0.1...@libp2p/daemon-server-v2.0.2) (2022-06-17) - - -### Trivial Changes - -* update deps ([#105](https://github.com/libp2p/js-libp2p-daemon/issues/105)) ([0bdab0e](https://github.com/libp2p/js-libp2p-daemon/commit/0bdab0ee254e32d6dca0e5fe239d4ef16db41b87)) - -## [@libp2p/daemon-server-v2.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v2.0.0...@libp2p/daemon-server-v2.0.1) (2022-06-15) - - -### Trivial Changes - -* update deps ([#103](https://github.com/libp2p/js-libp2p-daemon/issues/103)) ([2bfaa37](https://github.com/libp2p/js-libp2p-daemon/commit/2bfaa37e2f056dcd5de5a3882b77f52553c595d4)) - -## [@libp2p/daemon-server-v2.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v1.0.5...@libp2p/daemon-server-v2.0.0) (2022-06-15) - - -### ⚠ BREAKING CHANGES - -* uses new single-issue libp2p interface modules - -### Features - -* update to latest libp2p interfaces ([#102](https://github.com/libp2p/js-libp2p-daemon/issues/102)) ([f5e9121](https://github.com/libp2p/js-libp2p-daemon/commit/f5e91210654ab3c411e316c1c657356c037a0f6a)) - -## [@libp2p/daemon-server-v1.0.5](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v1.0.4...@libp2p/daemon-server-v1.0.5) (2022-05-25) - - -### Trivial Changes - -* update docs ([#91](https://github.com/libp2p/js-libp2p-daemon/issues/91)) ([5b072ff](https://github.com/libp2p/js-libp2p-daemon/commit/5b072ff89f30fd6cf55a3387bf0961c8ad78a22f)), closes [#83](https://github.com/libp2p/js-libp2p-daemon/issues/83) - -## [@libp2p/daemon-server-v1.0.4](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v1.0.3...@libp2p/daemon-server-v1.0.4) (2022-05-23) - - -### Bug Fixes - -* update deps ([#90](https://github.com/libp2p/js-libp2p-daemon/issues/90)) ([b50eba3](https://github.com/libp2p/js-libp2p-daemon/commit/b50eba3770e47969dbc30cbcf87c41672cd9c175)) - -## [@libp2p/daemon-server-v1.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v1.0.2...@libp2p/daemon-server-v1.0.3) (2022-05-10) - - -### Bug Fixes - -* encode enums correctly ([#86](https://github.com/libp2p/js-libp2p-daemon/issues/86)) ([6ce4633](https://github.com/libp2p/js-libp2p-daemon/commit/6ce4633f3db41ab66f9b8b1abbe84955dde3e9be)) - -## [@libp2p/daemon-server-v1.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v1.0.1...@libp2p/daemon-server-v1.0.2) (2022-04-20) - - -### Bug Fixes - -* update interfaces and deps ([#84](https://github.com/libp2p/js-libp2p-daemon/issues/84)) ([25173d5](https://github.com/libp2p/js-libp2p-daemon/commit/25173d5b2edf0e9dd9132707d349cdc862caecdb)) - -## [@libp2p/daemon-server-v1.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-server-v1.0.0...@libp2p/daemon-server-v1.0.1) (2022-04-07) - - -### Bug Fixes - -* remove protobufjs and replace with protons ([#81](https://github.com/libp2p/js-libp2p-daemon/issues/81)) ([78dd02a](https://github.com/libp2p/js-libp2p-daemon/commit/78dd02a679e55f22c7e24c1ee2b6f92a4679a0b9)) - - -### Trivial Changes - -* update aegir to latest version ([#80](https://github.com/libp2p/js-libp2p-daemon/issues/80)) ([3a98959](https://github.com/libp2p/js-libp2p-daemon/commit/3a98959617d9c19bba9fb064defee3d51acfcc29)) - -## @libp2p/daemon-server-v1.0.0 (2022-03-28) - - -### ⚠ BREAKING CHANGES - -* This module is now ESM only - -### Features - -* convert to typescript ([#78](https://github.com/libp2p/js-libp2p-daemon/issues/78)) ([f18b2a4](https://github.com/libp2p/js-libp2p-daemon/commit/f18b2a45871a2704db51b03e8583eefdcd13554c)) diff --git a/packages/libp2p-daemon-server/LICENSE b/packages/libp2p-daemon-server/LICENSE deleted file mode 100644 index 20ce483c86..0000000000 --- a/packages/libp2p-daemon-server/LICENSE +++ /dev/null @@ -1,4 +0,0 @@ -This project is dual licensed under MIT and Apache-2.0. - -MIT: https://www.opensource.org/licenses/mit -Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/libp2p-daemon-server/LICENSE-APACHE b/packages/libp2p-daemon-server/LICENSE-APACHE deleted file mode 100644 index 14478a3b60..0000000000 --- a/packages/libp2p-daemon-server/LICENSE-APACHE +++ /dev/null @@ -1,5 +0,0 @@ -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/libp2p-daemon-server/LICENSE-MIT b/packages/libp2p-daemon-server/LICENSE-MIT deleted file mode 100644 index 72dc60d84b..0000000000 --- a/packages/libp2p-daemon-server/LICENSE-MIT +++ /dev/null @@ -1,19 +0,0 @@ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/libp2p-daemon-server/README.md b/packages/libp2p-daemon-server/README.md deleted file mode 100644 index a04616c4d8..0000000000 --- a/packages/libp2p-daemon-server/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# @libp2p/daemon-server - -[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) -[![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.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) -[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/main.yml?query=branch%3Amaster) - -> API server for libp2p-daemon instances - -## Table of contents - -- [Install](#install) -- [Specs](#specs) -- [Usage](#usage) -- [License](#license) -- [Contribution](#contribution) - -## Install - -```console -$ npm i @libp2p/daemon-server -``` - -## Specs - -The specs for the daemon are currently housed in the go implementation. You can read them at [libp2p/go-libp2p-daemon](https://github.com/libp2p/go-libp2p-daemon/blob/master/specs/README.md) - -## Usage - -```js -import { createServer } from '@libp2p/daemon-server' -import { createLibp2p } from 'libp2p' -import { multiaddr } from '@multiformats/multiaddr' - -const libp2p = await createLibp2p({ - // ..config -}) - -const multiaddr = multiaddr('/ip4/0.0.0.0/tcp/0') - -const server = await createServer(multiaddr, libp2p) -await server.start() -``` - -## License - -Licensed under either of - -- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) -- MIT ([LICENSE-MIT](LICENSE-MIT) / ) - -## Contribution - -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/packages/libp2p-daemon-server/package.json b/packages/libp2p-daemon-server/package.json deleted file mode 100644 index 2a85a223d7..0000000000 --- a/packages/libp2p-daemon-server/package.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "name": "@libp2p/daemon-server", - "version": "5.0.2", - "description": "API server for libp2p-daemon instances", - "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/libp2p-daemon-server#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/libp2p/js-libp2p.git" - }, - "bugs": { - "url": "https://github.com/libp2p/js-libp2p/issues" - }, - "keywords": [ - "libp2p" - ], - "type": "module", - "types": "./dist/src/index.d.ts", - "files": [ - "src", - "dist", - "!dist/test", - "!**/*.tsbuildinfo" - ], - "exports": { - ".": { - "types": "./src/index.d.ts", - "import": "./dist/src/index.js" - } - }, - "eslintConfig": { - "extends": "ipfs", - "parserOptions": { - "sourceType": "module" - }, - "ignorePatterns": [ - "*.d.ts", - "src/profocol/index.js" - ] - }, - "scripts": { - "clean": "aegir clean", - "lint": "aegir lint", - "dep-check": "aegir dep-check", - "build": "aegir build", - "pretest": "npm run build", - "test": "aegir test -t node", - "test:node": "aegir test -t node" - }, - "dependencies": { - "@chainsafe/libp2p-gossipsub": "^9.0.0", - "@libp2p/daemon-protocol": "^4.0.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/kad-dht": "^9.0.0", - "@libp2p/logger": "^2.0.0", - "@libp2p/peer-id": "^2.0.0", - "@libp2p/tcp": "^7.0.0", - "@multiformats/multiaddr": "^12.1.3", - "it-drain": "^3.0.2", - "it-length-prefixed": "^9.0.1", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.0", - "multiformats": "^12.0.1", - "uint8arrays": "^4.0.4" - }, - "devDependencies": { - "aegir": "^40.0.1", - "sinon-ts": "^1.0.0" - }, - "private": true -} diff --git a/packages/libp2p-daemon-server/src/dht.ts b/packages/libp2p-daemon-server/src/dht.ts deleted file mode 100644 index f256f5e9b6..0000000000 --- a/packages/libp2p-daemon-server/src/dht.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* eslint max-depth: ["error", 6] */ - -import { - DHTResponse -} from '@libp2p/daemon-protocol' -import { logger } from '@libp2p/logger' -import drain from 'it-drain' -import { ErrorResponse, OkResponse } from './responses.js' -import type { PeerId } from '@libp2p/interface/peer-id' -import type { KadDHT } from '@libp2p/kad-dht' -import type { CID } from 'multiformats/cid' - -const log = logger('libp2p:daemon-server:dht') - -export interface DHTOperationsInit { - dht: KadDHT -} - -export class DHTOperations { - private readonly dht: KadDHT - - constructor (init: DHTOperationsInit) { - const { dht } = init - - this.dht = dht - } - - async * provide (cid: CID): AsyncGenerator { - try { - await drain(this.dht.provide(cid)) - yield OkResponse() - } catch (err: any) { - log.error(err) - yield ErrorResponse(err) - } - } - - async * getClosestPeers (key: Uint8Array): AsyncGenerator { - yield OkResponse({ - dht: { - type: DHTResponse.Type.BEGIN - } - }) - - for await (const event of this.dht.getClosestPeers(key)) { - if (event.name === 'PEER_RESPONSE') { - yield * event.closer.map(peer => DHTResponse.encode({ - type: DHTResponse.Type.VALUE, - value: peer.id.toBytes() - })) - } - } - - yield DHTResponse.encode({ - type: DHTResponse.Type.END - }) - } - - async * getPublicKey (peerId: PeerId): AsyncGenerator { - yield ErrorResponse(new Error('FIX ME: not implemented')) - } - - async * getValue (key: Uint8Array): AsyncGenerator { - try { - for await (const event of this.dht.get(key)) { - if (event.name === 'VALUE') { - yield OkResponse({ - dht: { - type: DHTResponse.Type.VALUE, - value: event.value - } - }) - } - } - } catch (err: any) { - log.error(err) - yield ErrorResponse(err) - } - } - - async * putValue (key: Uint8Array, value: Uint8Array): AsyncGenerator { - try { - await drain(this.dht.put(key, value)) - - yield OkResponse() - } catch (err: any) { - log.error(err) - yield ErrorResponse(err) - } - } - - async * findPeer (peerId: PeerId): AsyncGenerator { - try { - for await (const event of this.dht.findPeer(peerId)) { - if (event.name === 'FINAL_PEER') { - yield OkResponse({ - dht: { - type: DHTResponse.Type.VALUE, - peer: { - id: event.peer.id.toBytes(), - addrs: event.peer.multiaddrs.map(m => m.bytes) - } - } - }) - } - } - - throw new Error('Peer not found') - } catch (err: any) { - log.error(err) - yield ErrorResponse(err) - } - } - - async * findProviders (cid: CID, count: number): AsyncGenerator { - yield OkResponse({ - dht: { - type: DHTResponse.Type.BEGIN - } - }) - - try { - const maxNumProviders = count - let found = 0 - - for await (const event of this.dht.findProviders(cid)) { - if (event.name === 'PEER_RESPONSE') { - for (const provider of event.providers) { - found++ - - yield DHTResponse.encode({ - type: DHTResponse.Type.VALUE, - peer: { - id: provider.id.toBytes(), - addrs: (provider.multiaddrs ?? []).map(m => m.bytes) - } - }) - } - - if (maxNumProviders === found) { - break - } - } - } - } catch (err: any) { - yield ErrorResponse(err) - } - - yield DHTResponse.encode({ - type: DHTResponse.Type.END - }) - } -} diff --git a/packages/libp2p-daemon-server/src/index.ts b/packages/libp2p-daemon-server/src/index.ts deleted file mode 100644 index acc9d26067..0000000000 --- a/packages/libp2p-daemon-server/src/index.ts +++ /dev/null @@ -1,535 +0,0 @@ -/* eslint max-depth: ["error", 6] */ - -import { - Request, - DHTRequest, - PeerstoreRequest, - PSRequest, - StreamInfo -} from '@libp2p/daemon-protocol' -import { StreamHandler } from '@libp2p/daemon-protocol/stream-handler' -import { passThroughUpgrader } from '@libp2p/daemon-protocol/upgrader' -import { logger } from '@libp2p/logger' -import { peerIdFromBytes } from '@libp2p/peer-id' -import { tcp } from '@libp2p/tcp' -import { multiaddr, protocols } from '@multiformats/multiaddr' -import * as lp from 'it-length-prefixed' -import { pipe } from 'it-pipe' -import { CID } from 'multiformats/cid' -import { DHTOperations } from './dht.js' -import { PubSubOperations } from './pubsub.js' -import { ErrorResponse, OkResponse } from './responses.js' -import type { GossipSub } from '@chainsafe/libp2p-gossipsub' -import type { Libp2p } from '@libp2p/interface' -import type { Connection, MultiaddrConnection, Stream } from '@libp2p/interface/connection' -import type { Listener, Transport } from '@libp2p/interface/transport' -import type { KadDHT } from '@libp2p/kad-dht' -import type { Multiaddr } from '@multiformats/multiaddr' - -const LIMIT = 1 << 22 // 4MB -const log = logger('libp2p:daemon-server') - -export interface OpenStream { - streamInfo: StreamInfo - connection: Stream -} - -export interface DaemonInit { - multiaddr: Multiaddr - libp2pNode: Libp2p<{ dht: KadDHT, pubsub: GossipSub }> -} - -export interface Libp2pServer { - start: () => Promise - stop: () => Promise - getMultiaddr: () => Multiaddr -} - -export class Server implements Libp2pServer { - private readonly multiaddr: Multiaddr - private readonly libp2p: Libp2p<{ dht: KadDHT, pubsub: GossipSub }> - private readonly tcp: Transport - private readonly listener: Listener - private readonly dhtOperations?: DHTOperations - private readonly pubsubOperations?: PubSubOperations - - constructor (init: DaemonInit) { - const { multiaddr, libp2pNode } = init - - this.multiaddr = multiaddr - this.libp2p = libp2pNode - this.tcp = tcp()() - this.listener = this.tcp.createListener({ - handler: this.handleConnection.bind(this), - upgrader: passThroughUpgrader - }) - this._onExit = this._onExit.bind(this) - - if (libp2pNode.services.dht != null) { - this.dhtOperations = new DHTOperations({ dht: libp2pNode.services.dht }) - } - - if (libp2pNode.services.pubsub != null) { - this.pubsubOperations = new PubSubOperations({ pubsub: libp2pNode.services.pubsub }) - } - } - - /** - * Connects the daemons libp2p node to the peer provided - */ - async connect (request: Request): Promise { - if (request.connect == null || request.connect.addrs == null) { - throw new Error('Invalid request') - } - - const peer = request.connect.peer - const addrs = request.connect.addrs.map((a) => multiaddr(a)) - const peerId = peerIdFromBytes(peer) - - await this.libp2p.peerStore.merge(peerId, { - multiaddrs: addrs - }) - return this.libp2p.dial(peerId) - } - - /** - * Opens a stream on one of the given protocols to the given peer - */ - async openStream (request: Request): Promise { - if (request.streamOpen == null || request.streamOpen.proto == null) { - throw new Error('Invalid request') - } - - const { peer, proto } = request.streamOpen - const peerId = peerIdFromBytes(peer) - const connection = await this.libp2p.dial(peerId) - const stream = await connection.newStream(proto, { - runOnTransientConnection: true - }) - - return { - streamInfo: { - peer: peerId.toBytes(), - addr: connection.remoteAddr.bytes, - proto: stream.protocol ?? '' - }, - connection: stream - } - } - - /** - * Sends inbound requests for the given protocol - * to the unix socket path provided. If an existing handler - * is registered at the path, it will be overridden. - */ - async registerStreamHandler (request: Request): Promise { - if (request.streamHandler == null || request.streamHandler.proto == null) { - throw new Error('Invalid request') - } - - const protocols = request.streamHandler.proto - const addr = multiaddr(request.streamHandler.addr) - let conn: MultiaddrConnection - - await this.libp2p.handle(protocols, ({ connection, stream }) => { - Promise.resolve() - .then(async () => { - // Connect the client socket with the libp2p connection - // @ts-expect-error because we use a passthrough upgrader, - // this is actually a MultiaddrConnection and not a Connection - conn = await this.tcp.dial(addr, { - upgrader: passThroughUpgrader - }) - - const message = StreamInfo.encode({ - peer: connection.remotePeer.toBytes(), - addr: connection.remoteAddr.bytes, - proto: stream.protocol ?? '' - }) - const encodedMessage = lp.encode.single(message) - - // Tell the client about the new connection - // And then begin piping the client and peer connection - await pipe( - (async function * () { - yield encodedMessage - yield * stream.source - }()), - async function * (source) { - for await (const list of source) { - // convert Uint8ArrayList to Uint8Arrays for the socket - yield * list - } - }, - conn, - stream.sink - ) - }) - .catch(async err => { - log.error(err) - - if (conn != null) { - await conn.close(err) - } - }) - .finally(() => { - if (conn != null) { - conn.close() - .catch(err => { - log.error(err) - }) - } - }) - }, { - runOnTransientConnection: true - }) - } - - /** - * Listens for process exit to handle cleanup - */ - _listen (): void { - // listen for graceful termination - process.on('SIGTERM', this._onExit) - process.on('SIGINT', this._onExit) - process.on('SIGHUP', this._onExit) - } - - _onExit (): void { - void this.stop({ exit: true }).catch(err => { - log.error(err) - }) - } - - /** - * Starts the daemon - */ - async start (): Promise { - this._listen() - await this.libp2p.start() - await this.listener.listen(this.multiaddr) - } - - getMultiaddr (): Multiaddr { - const addrs = this.listener.getAddrs() - - if (addrs.length > 0) { - return addrs[0] - } - - throw new Error('Not started') - } - - /** - * Stops the daemon - */ - async stop (options = { exit: false }): Promise { - await this.libp2p.stop() - await this.listener.close() - if (options.exit) { - log('server closed, exiting') - } - process.removeListener('SIGTERM', this._onExit) - process.removeListener('SIGINT', this._onExit) - process.removeListener('SIGHUP', this._onExit) - } - - async * handlePeerStoreRequest (request: PeerstoreRequest): AsyncGenerator { - try { - switch (request.type) { - case PeerstoreRequest.Type.GET_PROTOCOLS: - if (request.id == null) { - throw new Error('Invalid request') - } - - const peerId = peerIdFromBytes(request.id) // eslint-disable-line no-case-declarations - const peer = await this.libp2p.peerStore.get(peerId) // eslint-disable-line no-case-declarations - const protos = peer.protocols // eslint-disable-line no-case-declarations - yield OkResponse({ peerStore: { protos } }) - return - case PeerstoreRequest.Type.GET_PEER_INFO: - throw new Error('ERR_NOT_IMPLEMENTED') - default: - throw new Error('ERR_INVALID_REQUEST_TYPE') - } - } catch (err: any) { - log.error(err) - yield ErrorResponse(err) - } - } - - /** - * Parses and responds to PSRequests - */ - async * handlePubsubRequest (request: PSRequest): AsyncGenerator { - try { - if (this.libp2p.services.pubsub == null || (this.pubsubOperations == null)) { - throw new Error('PubSub not configured') - } - - switch (request.type) { - case PSRequest.Type.GET_TOPICS: - yield * this.pubsubOperations.getTopics() - return - case PSRequest.Type.SUBSCRIBE: - if (request.topic == null) { - throw new Error('Invalid request') - } - - yield * this.pubsubOperations.subscribe(request.topic) - return - case PSRequest.Type.PUBLISH: - if (request.topic == null || request.data == null) { - throw new Error('Invalid request') - } - - yield * this.pubsubOperations.publish(request.topic, request.data) - return - case PSRequest.Type.LIST_PEERS: - if (request.topic == null) { - throw new Error('Invalid request') - } - - yield * this.pubsubOperations.listPeers(request.topic) - return - default: - throw new Error('ERR_INVALID_REQUEST_TYPE') - } - } catch (err: any) { - log.error(err) - yield ErrorResponse(err) - } - } - - /** - * Parses and responds to DHTRequests - */ - async * handleDHTRequest (request: DHTRequest): AsyncGenerator { - try { - if (this.libp2p.services.dht == null || (this.dhtOperations == null)) { - throw new Error('DHT not configured') - } - - switch (request.type) { - case DHTRequest.Type.FIND_PEER: - if (request.peer == null) { - throw new Error('Invalid request') - } - - yield * this.dhtOperations.findPeer(peerIdFromBytes(request.peer)) - return - case DHTRequest.Type.FIND_PROVIDERS: - if (request.cid == null) { - throw new Error('Invalid request') - } - - yield * this.dhtOperations.findProviders(CID.decode(request.cid), request.count ?? 20) - return - case DHTRequest.Type.PROVIDE: - if (request.cid == null) { - throw new Error('Invalid request') - } - - yield * this.dhtOperations.provide(CID.decode(request.cid)) - return - case DHTRequest.Type.GET_CLOSEST_PEERS: - if (request.key == null) { - throw new Error('Invalid request') - } - - yield * this.dhtOperations.getClosestPeers(request.key) - return - case DHTRequest.Type.GET_PUBLIC_KEY: - if (request.peer == null) { - throw new Error('Invalid request') - } - - yield * this.dhtOperations.getPublicKey(peerIdFromBytes(request.peer)) - return - case DHTRequest.Type.GET_VALUE: - if (request.key == null) { - throw new Error('Invalid request') - } - - yield * this.dhtOperations.getValue(request.key) - return - case DHTRequest.Type.PUT_VALUE: - if (request.key == null || request.value == null) { - throw new Error('Invalid request') - } - - yield * this.dhtOperations.putValue(request.key, request.value) - return - default: - throw new Error('ERR_INVALID_REQUEST_TYPE') - } - } catch (err: any) { - log.error(err) - yield ErrorResponse(err) - } - } - - /** - * Handles requests for the given connection - */ - handleConnection (connection: Connection): void { - const daemon = this // eslint-disable-line @typescript-eslint/no-this-alias - // @ts-expect-error connection may actually be a maconn? - const streamHandler = new StreamHandler({ stream: connection, maxLength: LIMIT }) - - void pipe( - streamHandler.decoder, - source => (async function * () { - let request: Request - - for await (const buf of source) { - try { - request = Request.decode(buf) - - switch (request.type) { - // Connect to another peer - case Request.Type.CONNECT: { - try { - await daemon.connect(request) - } catch (err: any) { - yield ErrorResponse(err) - break - } - yield OkResponse() - break - } - // Get the daemon peer id and addresses - case Request.Type.IDENTIFY: { - yield OkResponse({ - identify: { - id: daemon.libp2p.peerId.toBytes(), - addrs: daemon.libp2p.getMultiaddrs().map(ma => ma.decapsulateCode(protocols('p2p').code)).map(m => m.bytes) - } - }) - break - } - // Get a list of our current peers - case Request.Type.LIST_PEERS: { - const peers = [] - const seen = new Set() - - for (const connection of daemon.libp2p.getConnections()) { - const peerId = connection.remotePeer.toString() - - if (seen.has(peerId)) { - continue - } - - seen.add(peerId) - - peers.push({ - id: connection.remotePeer.toBytes(), - addrs: [connection.remoteAddr.bytes] - }) - } - - yield OkResponse({ peers }) - break - } - case Request.Type.STREAM_OPEN: { - let response - try { - response = await daemon.openStream(request) - } catch (err: any) { - yield ErrorResponse(err) - break - } - - // write the response - yield OkResponse({ - streamInfo: response.streamInfo - }) - - const stream = streamHandler.rest() - // then pipe the connection to the client - await pipe( - stream, - response.connection, - async function * (source) { - for await (const list of source) { - yield * list - } - }, - stream - ) - // Exit the iterator, no more requests can come through - return - } - case Request.Type.STREAM_HANDLER: { - try { - await daemon.registerStreamHandler(request) - } catch (err: any) { - yield ErrorResponse(err) - break - } - - // write the response - yield OkResponse() - break - } - case Request.Type.PEERSTORE: { - if (request.peerStore == null) { - yield ErrorResponse(new Error('ERR_INVALID_REQUEST')) - break - } - - yield * daemon.handlePeerStoreRequest(request.peerStore) - break - } - case Request.Type.PUBSUB: { - if (request.pubsub == null) { - yield ErrorResponse(new Error('ERR_INVALID_REQUEST')) - break - } - - yield * daemon.handlePubsubRequest(request.pubsub) - break - } - case Request.Type.DHT: { - if (request.dht == null) { - yield ErrorResponse(new Error('ERR_INVALID_REQUEST')) - break - } - - yield * daemon.handleDHTRequest(request.dht) - break - } - // Not yet supported or doesn't exist - default: - yield ErrorResponse(new Error('ERR_INVALID_REQUEST_TYPE')) - break - } - } catch (err: any) { - log.error(err) - yield ErrorResponse(err) - continue - } - } - })(), - async function (source) { - for await (const result of source) { - streamHandler.write(result) - } - } - ).catch(err => { - log(err) - }) - } -} - -/** - * Creates a daemon from the provided Daemon Options - */ -export const createServer = (multiaddr: Multiaddr, libp2pNode: Libp2p<{ dht: KadDHT, pubsub: GossipSub }>): Libp2pServer => { - const daemon = new Server({ - multiaddr, - libp2pNode - }) - - return daemon -} diff --git a/packages/libp2p-daemon-server/src/pubsub.ts b/packages/libp2p-daemon-server/src/pubsub.ts deleted file mode 100644 index c59d1424a8..0000000000 --- a/packages/libp2p-daemon-server/src/pubsub.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* eslint max-depth: ["error", 6] */ - -import { - PSMessage -} from '@libp2p/daemon-protocol' -import { logger } from '@libp2p/logger' -import { pushable } from 'it-pushable' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { ErrorResponse, OkResponse } from './responses.js' -import type { GossipSub } from '@chainsafe/libp2p-gossipsub' - -const log = logger('libp2p:daemon-server:pubsub') - -export interface PubSubOperationsInit { - pubsub: GossipSub -} - -export class PubSubOperations { - private readonly pubsub: GossipSub - - constructor (init: PubSubOperationsInit) { - const { pubsub } = init - - this.pubsub = pubsub - } - - async * getTopics (): AsyncGenerator { - try { - yield OkResponse({ - pubsub: { - topics: this.pubsub.getTopics(), - peerIDs: [] - } - }) - } catch (err: any) { - log.error(err) - yield ErrorResponse(err) - } - } - - async * subscribe (topic: string): AsyncGenerator { - try { - const onMessage = pushable() - this.pubsub.subscribe(topic) - - this.pubsub.addEventListener('message', (evt) => { - const msg = evt.detail - - if (msg.topic !== topic) { - return - } - - if (msg.type === 'signed') { - onMessage.push(PSMessage.encode({ - from: msg.from.toBytes(), - data: msg.data, - seqno: msg.sequenceNumber == null ? undefined : uint8ArrayFromString(msg.sequenceNumber.toString(16).padStart(16, '0'), 'base16'), - topicIDs: [msg.topic], - signature: msg.signature, - key: msg.key - }).subarray()) - } else { - onMessage.push(PSMessage.encode({ - data: msg.data, - topicIDs: [msg.topic] - }).subarray()) - } - }) - - yield OkResponse() - yield * onMessage - } catch (err: any) { - log.error(err) - yield ErrorResponse(err) - } - } - - async * publish (topic: string, data: Uint8Array): AsyncGenerator { - try { - await this.pubsub.publish(topic, data) - yield OkResponse() - } catch (err: any) { - log.error(err) - yield ErrorResponse(err) - } - } - - async * listPeers (topic: string): AsyncGenerator { - try { - yield OkResponse({ - pubsub: { - topics: [topic], - peerIDs: this.pubsub.getSubscribers(topic).map(peer => peer.toBytes()) - } - }) - } catch (err: any) { - log.error(err) - yield ErrorResponse(err) - } - } -} diff --git a/packages/libp2p-daemon-server/src/responses.ts b/packages/libp2p-daemon-server/src/responses.ts deleted file mode 100644 index 96b1b9fbb4..0000000000 --- a/packages/libp2p-daemon-server/src/responses.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Response } from '@libp2p/daemon-protocol' - -/** - * Creates and encodes an OK response - */ -export function OkResponse (data?: Partial): Uint8Array { - return Response.encode({ - type: Response.Type.OK, - peers: [], - ...data - }).subarray() -} - -/** - * Creates and encodes an ErrorResponse - */ -export function ErrorResponse (err: Error): Uint8Array { - return Response.encode({ - type: Response.Type.ERROR, - error: { - msg: err.message - }, - peers: [] - }).subarray() -} diff --git a/packages/libp2p-daemon-server/test/index.spec.ts b/packages/libp2p-daemon-server/test/index.spec.ts deleted file mode 100644 index 14db18b477..0000000000 --- a/packages/libp2p-daemon-server/test/index.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 5] */ - -import { multiaddr } from '@multiformats/multiaddr' -import { expect } from 'aegir/chai' -import { stubInterface } from 'sinon-ts' -import { createServer } from '../src/index.js' -import type { GossipSub } from '@chainsafe/libp2p-gossipsub' -import type { Libp2p } from '@libp2p/interface' -import type { KadDHT } from '@libp2p/kad-dht' - -const ma = multiaddr('/ip4/0.0.0.0/tcp/0') - -describe('server', () => { - it('should start', async () => { - const libp2p = stubInterface>() - - const server = createServer(ma, libp2p) - - await server.start() - - expect(libp2p.start.called).to.be.true() - - await server.stop() - }) - - it('should stop', async () => { - const libp2p = stubInterface>() - - const server = createServer(ma, libp2p) - - await server.start() - await server.stop() - - expect(libp2p.stop.called).to.be.true() - }) - - it('should return multiaddrs', async () => { - const libp2p = stubInterface>() - - const server = createServer(ma, libp2p) - - expect(() => server.getMultiaddr()).to.throw(/Not started/) - - await server.start() - - expect(server.getMultiaddr()).to.be.ok() - - await server.stop() - - expect(() => server.getMultiaddr()).to.throw(/Not started/) - }) -}) diff --git a/packages/libp2p-daemon-server/tsconfig.json b/packages/libp2p-daemon-server/tsconfig.json deleted file mode 100644 index 2909418dce..0000000000 --- a/packages/libp2p-daemon-server/tsconfig.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "extends": "aegir/src/config/tsconfig.aegir.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": [ - "src", - "test" - ], - "references": [ - { - "path": "../interface" - }, - { - "path": "../kad-dht" - }, - { - "path": "../libp2p-daemon-protocol" - }, - { - "path": "../logger" - }, - { - "path": "../peer-id" - }, - { - "path": "../pubsub-gossipsub" - }, - { - "path": "../transport-tcp" - } - ] -} diff --git a/packages/libp2p-daemon-server/typedoc.json b/packages/libp2p-daemon-server/typedoc.json deleted file mode 100644 index f599dc728d..0000000000 --- a/packages/libp2p-daemon-server/typedoc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entryPoints": [ - "./src/index.ts" - ] -} diff --git a/packages/libp2p-daemon/.aegir.js b/packages/libp2p-daemon/.aegir.js deleted file mode 100644 index 135a6a2211..0000000000 --- a/packages/libp2p-daemon/.aegir.js +++ /dev/null @@ -1,8 +0,0 @@ - -export default { - build: { - config: { - platform: 'node' - } - } -} diff --git a/packages/libp2p-daemon/CHANGELOG.md b/packages/libp2p-daemon/CHANGELOG.md deleted file mode 100644 index f47810eb9e..0000000000 --- a/packages/libp2p-daemon/CHANGELOG.md +++ /dev/null @@ -1,379 +0,0 @@ -## [@libp2p/daemon-v2.0.9](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v2.0.8...@libp2p/daemon-v2.0.9) (2023-04-19) - - -### Dependencies - -* update sibling dependencies ([db50405](https://github.com/libp2p/js-libp2p-daemon/commit/db50405ddec3a68ad265c3d3233595187bc4895d)) - -## [@libp2p/daemon-v2.0.8](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v2.0.7...@libp2p/daemon-v2.0.8) (2023-03-17) - - -### Dependencies - -* bump @multiformats/multiaddr from 11.6.1 to 12.0.0 ([#189](https://github.com/libp2p/js-libp2p-daemon/issues/189)) ([aaf7e2e](https://github.com/libp2p/js-libp2p-daemon/commit/aaf7e2e37423cae78cd16d8e16e06db40fdcd1e3)) - -## [@libp2p/daemon-v2.0.7](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v2.0.6...@libp2p/daemon-v2.0.7) (2023-02-22) - - -### Dependencies - -* bump aegir from 37.12.1 to 38.1.6 ([#183](https://github.com/libp2p/js-libp2p-daemon/issues/183)) ([6725a0a](https://github.com/libp2p/js-libp2p-daemon/commit/6725a0aeba9acb56a7530dece6c65a0f3eadfec5)) - -## [@libp2p/daemon-v2.0.6](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v2.0.5...@libp2p/daemon-v2.0.6) (2023-02-22) - - -### Trivial Changes - -* remove lerna ([#171](https://github.com/libp2p/js-libp2p-daemon/issues/171)) ([367f912](https://github.com/libp2p/js-libp2p-daemon/commit/367f9122f2fe1c31c8de7a136cda18d024ff08d7)) - - -### Dependencies - -* **dev:** bump sinon from 14.0.2 to 15.0.1 ([#166](https://github.com/libp2p/js-libp2p-daemon/issues/166)) ([1702efb](https://github.com/libp2p/js-libp2p-daemon/commit/1702efb4248bea4cb9ec19c694c1caae1c0ff16d)) - -## [@libp2p/daemon-v2.0.5](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v2.0.4...@libp2p/daemon-v2.0.5) (2023-01-07) - - -### Dependencies - -* update sibling dependencies ([775bd83](https://github.com/libp2p/js-libp2p-daemon/commit/775bd83a63ae99c4b892f0169f76dbe39163e2d4)) - -## [@libp2p/daemon-v2.0.4](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v2.0.3...@libp2p/daemon-v2.0.4) (2022-10-13) - - -### Dependencies - -* update uint8arrays, protons and multiformats ([#143](https://github.com/libp2p/js-libp2p-daemon/issues/143)) ([661139c](https://github.com/libp2p/js-libp2p-daemon/commit/661139c674c9994724e32227d7d9ae2c5da1cea2)) - -## [@libp2p/daemon-v2.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v2.0.2...@libp2p/daemon-v2.0.3) (2022-09-21) - - -### Dependencies - -* update @multiformats/multiaddr to 11.0.0 ([#128](https://github.com/libp2p/js-libp2p-daemon/issues/128)) ([885d901](https://github.com/libp2p/js-libp2p-daemon/commit/885d9013d82a62e6756b06350932df1242a13296)) - -## [@libp2p/daemon-v2.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v2.0.1...@libp2p/daemon-v2.0.2) (2022-09-09) - - -### Trivial Changes - -* update project config ([#111](https://github.com/libp2p/js-libp2p-daemon/issues/111)) ([345e663](https://github.com/libp2p/js-libp2p-daemon/commit/345e663e34278e780fc2f3a6b595294f925c4521)) - - -### Dependencies - -* update sibling dependencies ([56711c4](https://github.com/libp2p/js-libp2p-daemon/commit/56711c4f14b0cf2370b8612fe07d42ed2ac8363c)) - -## [@libp2p/daemon-v2.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v2.0.0...@libp2p/daemon-v2.0.1) (2022-06-15) - - -### Trivial Changes - -* update deps ([#103](https://github.com/libp2p/js-libp2p-daemon/issues/103)) ([2bfaa37](https://github.com/libp2p/js-libp2p-daemon/commit/2bfaa37e2f056dcd5de5a3882b77f52553c595d4)) - -## [@libp2p/daemon-v2.0.0](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v1.0.4...@libp2p/daemon-v2.0.0) (2022-06-15) - - -### ⚠ BREAKING CHANGES - -* uses new single-issue libp2p interface modules - -### Features - -* update to latest libp2p interfaces ([#102](https://github.com/libp2p/js-libp2p-daemon/issues/102)) ([f5e9121](https://github.com/libp2p/js-libp2p-daemon/commit/f5e91210654ab3c411e316c1c657356c037a0f6a)) - -## [@libp2p/daemon-v1.0.4](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v1.0.3...@libp2p/daemon-v1.0.4) (2022-05-25) - - -### Trivial Changes - -* update docs ([#91](https://github.com/libp2p/js-libp2p-daemon/issues/91)) ([5b072ff](https://github.com/libp2p/js-libp2p-daemon/commit/5b072ff89f30fd6cf55a3387bf0961c8ad78a22f)), closes [#83](https://github.com/libp2p/js-libp2p-daemon/issues/83) - -## [@libp2p/daemon-v1.0.3](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v1.0.2...@libp2p/daemon-v1.0.3) (2022-05-23) - - -### Bug Fixes - -* update deps ([#90](https://github.com/libp2p/js-libp2p-daemon/issues/90)) ([b50eba3](https://github.com/libp2p/js-libp2p-daemon/commit/b50eba3770e47969dbc30cbcf87c41672cd9c175)) - -## [@libp2p/daemon-v1.0.2](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v1.0.1...@libp2p/daemon-v1.0.2) (2022-04-20) - - -### Bug Fixes - -* update interfaces and deps ([#84](https://github.com/libp2p/js-libp2p-daemon/issues/84)) ([25173d5](https://github.com/libp2p/js-libp2p-daemon/commit/25173d5b2edf0e9dd9132707d349cdc862caecdb)) - -## [@libp2p/daemon-v1.0.1](https://github.com/libp2p/js-libp2p-daemon/compare/@libp2p/daemon-v1.0.0...@libp2p/daemon-v1.0.1) (2022-04-07) - - -### Trivial Changes - -* update aegir to latest version ([#80](https://github.com/libp2p/js-libp2p-daemon/issues/80)) ([3a98959](https://github.com/libp2p/js-libp2p-daemon/commit/3a98959617d9c19bba9fb064defee3d51acfcc29)) - -## @libp2p/daemon-v1.0.0 (2022-03-28) - - -### ⚠ BREAKING CHANGES - -* This module is now ESM only - -### Features - -* convert to typescript ([#78](https://github.com/libp2p/js-libp2p-daemon/issues/78)) ([f18b2a4](https://github.com/libp2p/js-libp2p-daemon/commit/f18b2a45871a2704db51b03e8583eefdcd13554c)) - -## [0.10.2](https://github.com/libp2p/js-libp2p-daemon/compare/v0.10.1...v0.10.2) (2022-01-26) - - - -## [0.10.1](https://github.com/libp2p/js-libp2p-daemon/compare/v0.10.0...v0.10.1) (2022-01-17) - - - -# [0.10.0](https://github.com/libp2p/js-libp2p-daemon/compare/v0.9.1...v0.10.0) (2022-01-17) - - -### Features - -* async peerstore ([#62](https://github.com/libp2p/js-libp2p-daemon/issues/62)) ([22e3cb0](https://github.com/libp2p/js-libp2p-daemon/commit/22e3cb05f7815f1f45e65398e87514f8ad961b49)) - - -### BREAKING CHANGES - -* peerstore methods are now all async - - - -## [0.9.1](https://github.com/libp2p/js-libp2p-daemon/compare/v0.9.0...v0.9.1) (2021-12-29) - - -### Bug Fixes - -* default nat hole punching to false ([#53](https://github.com/libp2p/js-libp2p-daemon/issues/53)) ([4bef1a3](https://github.com/libp2p/js-libp2p-daemon/commit/4bef1a384261fe442668f47b3799029cfb1043d3)) - - - -# [0.9.0](https://github.com/libp2p/js-libp2p-daemon/compare/v0.8.1...v0.9.0) (2021-12-29) - - -### chore - -* update deps ([#50](https://github.com/libp2p/js-libp2p-daemon/issues/50)) ([4231932](https://github.com/libp2p/js-libp2p-daemon/commit/42319320725fe248ee61a021981a5a065193ac99)) - - -### BREAKING CHANGES - -* only node15+ is supported - - - -## [0.8.1](https://github.com/libp2p/js-libp2p-daemon/compare/v0.8.0...v0.8.1) (2021-11-18) - - - -# [0.8.0](https://github.com/libp2p/js-libp2p-daemon/compare/v0.7.0...v0.8.0) (2021-11-18) - - -### chore - -* update dht ([#49](https://github.com/libp2p/js-libp2p-daemon/issues/49)) ([b1f1aaa](https://github.com/libp2p/js-libp2p-daemon/commit/b1f1aaab3466ec7ac693dcb5a211cd119aaa4f95)) - - -### BREAKING CHANGES - -* The DHT is now enabled by default - - - -# [0.7.0](https://github.com/libp2p/js-libp2p-daemon/compare/v0.6.1...v0.7.0) (2021-07-30) - - - -## [0.6.1](https://github.com/libp2p/js-libp2p-daemon/compare/v0.6.0...v0.6.1) (2021-06-11) - - - -# [0.6.0](https://github.com/libp2p/js-libp2p-daemon/compare/v0.5.2...v0.6.0) (2021-05-03) - - -### chore - -* update libp2p 0.31 ([#46](https://github.com/libp2p/js-libp2p-daemon/issues/46)) ([6625eba](https://github.com/libp2p/js-libp2p-daemon/commit/6625ebaa6027cee7cd8d08de09035f0edc894c1a)) - - -### BREAKING CHANGES - -* secio removed and noise is now default crypto, multiaddr@9 and libp2p@31 - - - -## [0.5.2](https://github.com/libp2p/js-libp2p-daemon/compare/v0.2.3...v0.5.2) (2021-02-16) - - -### Bug Fixes - -* replace node buffers with uint8arrays ([#41](https://github.com/libp2p/js-libp2p-daemon/issues/41)) ([cd009d5](https://github.com/libp2p/js-libp2p-daemon/commit/cd009d5e1f83724f907dd7f84239679633e8d197)) - - -### Features - -* add support for specifying noise ([#32](https://github.com/libp2p/js-libp2p-daemon/issues/32)) ([e5582cd](https://github.com/libp2p/js-libp2p-daemon/commit/e5582cdd00b7601cfe8ecc2b0d61a66bad71ab8a)) -* specify libp2p dependency through env ([#30](https://github.com/libp2p/js-libp2p-daemon/issues/30)) ([07b0695](https://github.com/libp2p/js-libp2p-daemon/commit/07b0695157da539774de75f2316748164fdbd72d)) - - - - -## [0.5.1](https://github.com/libp2p/js-libp2p-daemon/compare/v0.5.0...v0.5.1) (2020-08-26) - - - - -# [0.5.0](https://github.com/libp2p/js-libp2p-daemon/compare/v0.4.0...v0.5.0) (2020-08-23) - - -### Bug Fixes - -* replace node buffers with uint8arrays ([#41](https://github.com/libp2p/js-libp2p-daemon/issues/41)) ([cd009d5](https://github.com/libp2p/js-libp2p-daemon/commit/cd009d5)) - - -### BREAKING CHANGES - -* - All deps of this module now use uint8arrays in place of node buffers - -* chore: bump deps - -Co-authored-by: Jacob Heun - - - - -# [0.4.0](https://github.com/libp2p/js-libp2p-daemon/compare/v0.3.1...v0.4.0) (2020-06-05) - - - - -## [0.3.1](https://github.com/libp2p/js-libp2p-daemon/compare/v0.3.0...v0.3.1) (2020-04-22) - - -### Features - -* add support for specifying noise ([#32](https://github.com/libp2p/js-libp2p-daemon/issues/32)) ([e5582cd](https://github.com/libp2p/js-libp2p-daemon/commit/e5582cd)) - - - - -# [0.3.0](https://github.com/libp2p/js-libp2p-daemon/compare/v0.2.3...v0.3.0) (2020-01-31) - - -### Features - -* specify libp2p dependency through env ([#30](https://github.com/libp2p/js-libp2p-daemon/issues/30)) ([07b0695](https://github.com/libp2p/js-libp2p-daemon/commit/07b0695)) - - - - -## [0.2.3](https://github.com/libp2p/js-libp2p-daemon/compare/v0.2.2...v0.2.3) (2019-08-26) - - -### Bug Fixes - -* **tests:** fix secp256k1 test ([#26](https://github.com/libp2p/js-libp2p-daemon/issues/26)) ([fc46dbb](https://github.com/libp2p/js-libp2p-daemon/commit/fc46dbb)) - - -### Features - -* integrate gossipsub by default ([#19](https://github.com/libp2p/js-libp2p-daemon/issues/19)) ([2959fc8](https://github.com/libp2p/js-libp2p-daemon/commit/2959fc8)) - - - - -## [0.2.2](https://github.com/libp2p/js-libp2p-daemon/compare/v0.2.1...v0.2.2) (2019-07-10) - - -### Bug Fixes - -* **bin:** exit with status 1 on unhandled rejection ([#23](https://github.com/libp2p/js-libp2p-daemon/issues/23)) ([596005d](https://github.com/libp2p/js-libp2p-daemon/commit/596005d)) -* **main:** deal with unhandled rejections ([#20](https://github.com/libp2p/js-libp2p-daemon/issues/20)) ([49e685a](https://github.com/libp2p/js-libp2p-daemon/commit/49e685a)) -* **package.json:** fix main property ([#22](https://github.com/libp2p/js-libp2p-daemon/issues/22)) ([1d505b8](https://github.com/libp2p/js-libp2p-daemon/commit/1d505b8)) -* resolve loading of private key from file ([#21](https://github.com/libp2p/js-libp2p-daemon/issues/21)) ([3e70ace](https://github.com/libp2p/js-libp2p-daemon/commit/3e70ace)) - - - - -## [0.2.1](https://github.com/libp2p/js-libp2p-daemon/compare/v0.2.0...v0.2.1) (2019-04-29) - - -### Bug Fixes - -* peer info ([#17](https://github.com/libp2p/js-libp2p-daemon/issues/17)) ([69cf26b](https://github.com/libp2p/js-libp2p-daemon/commit/69cf26b)) - - -### Features - -* add support initial peerstore support ([#14](https://github.com/libp2p/js-libp2p-daemon/issues/14)) ([36a520c](https://github.com/libp2p/js-libp2p-daemon/commit/36a520c)) - - - - -# [0.2.0](https://github.com/libp2p/js-libp2p-daemon/compare/v0.1.2...v0.2.0) (2019-03-20) - - -### Bug Fixes - -* decapsulate ipfs protocol on daemon startup ([#11](https://github.com/libp2p/js-libp2p-daemon/issues/11)) ([190df09](https://github.com/libp2p/js-libp2p-daemon/commit/190df09)) - - -### Features - -* add pubsub support ([#12](https://github.com/libp2p/js-libp2p-daemon/issues/12)) ([5d27b90](https://github.com/libp2p/js-libp2p-daemon/commit/5d27b90)) -* add support for unix multiaddr listen ([#10](https://github.com/libp2p/js-libp2p-daemon/issues/10)) ([9106d68](https://github.com/libp2p/js-libp2p-daemon/commit/9106d68)) - - -### BREAKING CHANGES - -* The --sock param/flag has been replaced by --listen, which now expects a multiaddr string. - -Example: `jsp2pd --sock=/tmp/p2p.sock` would now be `jsp2pd --listen=/unix/tmp/p2p.sock` - -* feat: add support for unix multiaddr listen -* feat: add support for hostAddrs flag -* feat: add support for websockets -* feat: add announceAddrs support -* test: split up tests into files -* feat: use multiaddr instead of path for everything -* feat: update stream handler to use multiaddr bytes -* chore: fix lint -* chore: update multiaddr dep -* test: fix test runners -* fix: add a default host address -* fix: catch decapsulate errors when no ipfs present -* chore: fix feedback - - - - -## [0.1.2](https://github.com/libp2p/js-libp2p-daemon/compare/v0.1.1...v0.1.2) (2019-02-14) - - -### Bug Fixes - -* remove ipfs from identify multiaddrs ([7cee6ea](https://github.com/libp2p/js-libp2p-daemon/commit/7cee6ea)) - - - - -## [0.1.1](https://github.com/libp2p/js-libp2p-daemon/compare/v0.1.0...v0.1.1) (2019-02-13) - - -### Bug Fixes - -* connect should use peer id in bytes ([021b006](https://github.com/libp2p/js-libp2p-daemon/commit/021b006)) - - - - -# 0.1.0 (2019-01-31) - - -### Features - -* initial implementation of the libp2p daemon spec ([#1](https://github.com/libp2p/js-libp2p-daemon/issues/1)) ([383a6bd](https://github.com/libp2p/js-libp2p-daemon/commit/383a6bd)) diff --git a/packages/libp2p-daemon/LICENSE b/packages/libp2p-daemon/LICENSE deleted file mode 100644 index 20ce483c86..0000000000 --- a/packages/libp2p-daemon/LICENSE +++ /dev/null @@ -1,4 +0,0 @@ -This project is dual licensed under MIT and Apache-2.0. - -MIT: https://www.opensource.org/licenses/mit -Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/libp2p-daemon/LICENSE-APACHE b/packages/libp2p-daemon/LICENSE-APACHE deleted file mode 100644 index 14478a3b60..0000000000 --- a/packages/libp2p-daemon/LICENSE-APACHE +++ /dev/null @@ -1,5 +0,0 @@ -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/libp2p-daemon/LICENSE-MIT b/packages/libp2p-daemon/LICENSE-MIT deleted file mode 100644 index 72dc60d84b..0000000000 --- a/packages/libp2p-daemon/LICENSE-MIT +++ /dev/null @@ -1,19 +0,0 @@ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/libp2p-daemon/README.md b/packages/libp2p-daemon/README.md deleted file mode 100644 index 08787b3e45..0000000000 --- a/packages/libp2p-daemon/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# @libp2p/daemon - -[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) -[![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.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) -[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=master\&style=flat-square)](https://github.com/libp2p/js-libp2p/actions/workflows/main.yml?query=branch%3Amaster) - -> libp2p-daemon JavaScript implementation - -## Table of contents - -- [Install](#install) -- [Specs](#specs) -- [Usage](#usage) -- [License](#license) -- [Contribution](#contribution) - -## Install - -```console -$ npm i @libp2p/daemon -``` - -## Specs - -The specs for the daemon are currently housed in the go implementation. You can read them at [libp2p/go-libp2p-daemon](https://github.com/libp2p/go-libp2p-daemon/blob/master/specs/README.md) - -## Usage - -```console -$ jsp2pd --help -``` - -For a full list of options, you can run help `jsp2pd --help`. -Running the defaults, `jsp2pd`, will start the daemon and bind it to a local unix socket path. -Daemon clients will be able to communicate with the daemon over that unix socket. - -As an alternative, you can use this daemon with a different version of libp2p as the one specified in `package.json`. You just need to define its path through an environment variable as follows: - -```console -$ LIBP2P_JS=/path/to/js-libp2p/src/index.js jsp2pd -``` - -## License - -Licensed under either of - -- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) -- MIT ([LICENSE-MIT](LICENSE-MIT) / ) - -## Contribution - -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/packages/libp2p-daemon/package.json b/packages/libp2p-daemon/package.json deleted file mode 100644 index d572cc51b0..0000000000 --- a/packages/libp2p-daemon/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "@libp2p/daemon", - "version": "2.0.9", - "description": "libp2p-daemon JavaScript implementation", - "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/libp2p-daemon#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/libp2p/js-libp2p.git" - }, - "bugs": { - "url": "https://github.com/libp2p/js-libp2p/issues" - }, - "keywords": [ - "libp2p" - ], - "bin": { - "jsp2pd": "dist/src/index.js" - }, - "type": "module", - "types": "./dist/src/index.d.ts", - "files": [ - "src", - "dist", - "!dist/test", - "!**/*.tsbuildinfo" - ], - "exports": { - ".": { - "types": "./src/index.d.ts", - "import": "./dist/src/index.js" - } - }, - "eslintConfig": { - "extends": "ipfs", - "parserOptions": { - "sourceType": "module" - } - }, - "scripts": { - "clean": "aegir clean", - "lint": "aegir lint", - "dep-check": "aegir dep-check", - "build": "aegir build", - "test": "aegir test -t node", - "test:node": "aegir test -t node" - }, - "dependencies": { - "@libp2p/daemon-server": "^5.0.0", - "@multiformats/multiaddr": "^12.1.3", - "es-main": "^1.0.2", - "yargs": "^17.3.1", - "yargs-promise": "^1.1.0" - }, - "devDependencies": { - "aegir": "^40.0.1", - "sinon": "^15.1.2" - }, - "private": true -} diff --git a/packages/libp2p-daemon/src/index.ts b/packages/libp2p-daemon/src/index.ts deleted file mode 100755 index d49325d8ca..0000000000 --- a/packages/libp2p-daemon/src/index.ts +++ /dev/null @@ -1,138 +0,0 @@ -#! /usr/bin/env node -/* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */ - -import { multiaddr } from '@multiformats/multiaddr' -import esMain from 'es-main' -import yargs from 'yargs' -// @ts-expect-error no types -import YargsPromise from 'yargs-promise' -import type { Libp2pServer } from '@libp2p/daemon-server' -import type { Multiaddr } from '@multiformats/multiaddr' - -const args = process.argv.slice(2) -const parser = new YargsPromise(yargs) - -const log = console.log - -export default async function main (processArgs: string[]): Promise { - parser.yargs - .option('listen', { - desc: 'daemon control listen multiaddr', - type: 'string', - default: '/unix/tmp/p2pd.sock' - }) - .option('quiet', { - alias: 'q', - desc: 'be quiet', - type: 'boolean', - default: false - }) - .option('id', { - desc: 'peer identity; private key file', - type: 'string', - default: '' - }) - .option('hostAddrs', { - desc: 'Comma separated list of multiaddrs the host should listen on', - type: 'string', - default: '' - }) - .option('announceAddrs', { - desc: 'Comma separated list of multiaddrs the host should announce to the network', - type: 'string', - default: '' - }) - .option('bootstrap', { - alias: 'b', - desc: 'Connects to bootstrap peers and bootstraps the dht if enabled', - type: 'boolean', - default: false - }) - .option('bootstrapPeers', { - desc: 'Comma separated list of bootstrap peers; defaults to the IPFS DHT peers', - type: 'string', - default: '' - }) - .option('dht', { - desc: 'Enables the DHT in full node mode', - type: 'boolean', - default: false - }) - .option('dhtClient', { - desc: '(Not yet supported) Enables the DHT in client mode', - type: 'boolean', - default: false - }) - .option('nat', { - desc: 'Enables UPnP NAT hole punching', - type: 'boolean', - default: false - }) - .option('connMgr', { - desc: '(Not yet supported) Enables the Connection Manager', - type: 'boolean', - default: false - }) - .option('connMgrLo', { - desc: 'Number identifying the number of peers below which this node will not activate preemptive disconnections', - type: 'number' - }) - .option('connMgrHi', { - desc: 'Number identifying the maximum number of peers the current peer is willing to be connected to before is starts disconnecting', - type: 'number' - }) - .option('pubsub', { - desc: 'Enables pubsub', - type: 'boolean', - default: false - }) - .option('pubsubRouter', { - desc: 'Specifies the pubsub router implementation', - type: 'string', - default: 'gossipsub' - }) - .fail((msg: string, err: Error | undefined, yargs?: any) => { - if (err != null) { - throw err // preserve stack - } - - if (args.length > 0) { - // eslint-disable-next-line - log(msg) - } - - yargs.showHelp() - }) - - const { data, argv } = await parser.parse(processArgs) - - if (data != null) { - // Log help and exit - // eslint-disable-next-line - log(data) - process.exit(0) - } - - const daemon = await createLibp2pServer(multiaddr(argv.listen), argv) - await daemon.start() - - if (argv.quiet !== true) { - // eslint-disable-next-line - log('daemon has started') - } -} - -export async function createLibp2pServer (listenAddr: Multiaddr, argv: any): Promise { - // const libp2p = await createLibp2p(argv) - // const daemon = await createServer(multiaddr(argv.listen), libp2p) - - throw new Error('Not implemented yet') -} - -if (esMain(import.meta)) { - main(process.argv) - .catch((err) => { - console.error(err) - process.exit(1) - }) -} diff --git a/packages/libp2p-daemon/test/cli.spec.ts b/packages/libp2p-daemon/test/cli.spec.ts deleted file mode 100644 index 2712cb8ddd..0000000000 --- a/packages/libp2p-daemon/test/cli.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* eslint-env mocha */ - -import { expect } from 'aegir/chai' -import sinon from 'sinon' -import cli from '../src/index.js' - -describe.skip('cli', () => { - const daemon = { createDaemon: (options: any) => {} } - - afterEach(() => { - sinon.restore() - }) - - it('should create a daemon with default options', async () => { - sinon.stub(daemon, 'createDaemon').callsFake((options) => { - expect(options).to.include({ - b: false, - bootstrap: false, - 'bootstrap-peers': '', - bootstrapPeers: '', - hostAddrs: '', - announceAddrs: '', - 'conn-mgr': false, - connMgr: false, - dht: false, - 'dht-client': false, - dhtClient: false, - id: '', - q: false, - quiet: false, - listen: '/unix/tmp/p2pd.sock' - }) - return { - start: () => {}, - stop: () => {} - } - }) - - await cli([ - '/bin/node', - '/daemon/src/cli/bin.js' - ]) - }) - - it('should be able to specify options', async () => { - sinon.stub(daemon, 'createDaemon').callsFake((options) => { - expect(options).to.include({ - b: true, - bootstrap: true, - 'bootstrap-peers': '/p2p/Qm1,/p2p/Qm2', - bootstrapPeers: '/p2p/Qm1,/p2p/Qm2', - hostAddrs: '/ip4/0.0.0.0/tcp/0,/ip4/0.0.0.0/tcp/0/wss', - announceAddrs: '/ip4/0.0.0.0/tcp/8080', - 'conn-mgr': true, - connMgr: true, - dht: true, - 'dht-client': true, - dhtClient: true, - id: '/path/to/key', - q: true, - quiet: true, - listen: '/unix/tmp/d.sock' - }) - return { - start: () => {}, - stop: () => {} - } - }) - - await cli([ - '/bin/node', - '/daemon/src/cli/bin.js', - '--dht=true', - '--b=true', - '--bootstrapPeers=/p2p/Qm1,/p2p/Qm2', - '--hostAddrs=/ip4/0.0.0.0/tcp/0,/ip4/0.0.0.0/tcp/0/wss', - '--announceAddrs=/ip4/0.0.0.0/tcp/8080', - '--connMgr=true', - '--dhtClient=true', - '--quiet=true', - '--id=/path/to/key', - '--listen=/unix/tmp/d.sock' - ]) - }) -}) diff --git a/packages/libp2p-daemon/tsconfig.json b/packages/libp2p-daemon/tsconfig.json deleted file mode 100644 index 6b7a2ea443..0000000000 --- a/packages/libp2p-daemon/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "aegir/src/config/tsconfig.aegir.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": [ - "src", - "test" - ], - "references": [ - { - "path": "../libp2p-daemon-server" - } - ] -} diff --git a/packages/libp2p-daemon/typedoc.json b/packages/libp2p-daemon/typedoc.json deleted file mode 100644 index f599dc728d..0000000000 --- a/packages/libp2p-daemon/typedoc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entryPoints": [ - "./src/index.ts" - ] -} diff --git a/packages/libp2p/CHANGELOG.md b/packages/libp2p/CHANGELOG.md index 0a66a7b6b5..20c33eb733 100644 --- a/packages/libp2p/CHANGELOG.md +++ b/packages/libp2p/CHANGELOG.md @@ -5,6 +5,65 @@ * allow specifiying maxOutboundStreams in connection.newStream ([#1817](https://www.github.com/libp2p/js-libp2p/issues/1817)) ([b348fba](https://www.github.com/libp2p/js-libp2p/commit/b348fbaa7e16fd40f9a93e83a92c8152ad9e97e9)) +### [0.46.1](https://www.github.com/libp2p/js-libp2p/compare/libp2p-v0.46.0...libp2p-v0.46.1) (2023-08-01) + + +### Dependencies + +* The following workspace dependencies were updated + * devDependencies + * @libp2p/floodsub bumped from ^8.0.0 to ^8.0.1 + +## [0.46.0](https://www.github.com/libp2p/js-libp2p/compare/libp2p-v0.45.9...libp2p-v0.46.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* the `.close`, `closeRead` and `closeWrite` methods on the `Stream` interface are now asynchronous +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* enable manual identify ([#1784](https://www.github.com/libp2p/js-libp2p/issues/1784)) ([06f4901](https://www.github.com/libp2p/js-libp2p/commit/06f4901a367ef8e6b9f74bc9b896cdb091c31b12)) +* mark connections with limits as transient ([#1890](https://www.github.com/libp2p/js-libp2p/issues/1890)) ([a1ec46b](https://www.github.com/libp2p/js-libp2p/commit/a1ec46b5f5606b7bdf3e5b085013fb88e26439f9)) +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* close streams gracefully ([#1864](https://www.github.com/libp2p/js-libp2p/issues/1864)) ([b36ec7f](https://www.github.com/libp2p/js-libp2p/commit/b36ec7f24e477af21cec31effc086a6c611bf271)), closes [#1793](https://www.github.com/libp2p/js-libp2p/issues/1793) [#656](https://www.github.com/libp2p/js-libp2p/issues/656) +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) +* ignore peers with invalid multiaddrs ([#1902](https://www.github.com/libp2p/js-libp2p/issues/1902)) ([a41d25d](https://www.github.com/libp2p/js-libp2p/commit/a41d25d49696febd7fd903bbdcc95ebaeb5d4b35)) +* remove redundant nat-api override ([#1906](https://www.github.com/libp2p/js-libp2p/issues/1906)) ([1f7e18b](https://www.github.com/libp2p/js-libp2p/commit/1f7e18b07094046f10df89a1c6eab505d4c13225)) +* updated multiaddr logging ([#1797](https://www.github.com/libp2p/js-libp2p/issues/1797)) ([f427cfc](https://www.github.com/libp2p/js-libp2p/commit/f427cfc923a4bf9fd328386897a0e7181969c854)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^1.0.0 to ^2.0.0 + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/interface-internal bumped from ~0.0.1 to ^0.1.0 + * @libp2p/keychain bumped from ^2.0.0 to ^3.0.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * @libp2p/multistream-select bumped from ^3.0.0 to ^4.0.0 + * @libp2p/peer-collections bumped from ^3.0.0 to ^4.0.0 + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-id-factory bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-record bumped from ^5.0.0 to ^6.0.0 + * @libp2p/peer-store bumped from ^8.0.0 to ^9.0.0 + * @libp2p/utils bumped from ^3.0.0 to ^4.0.0 + * devDependencies + * @libp2p/bootstrap bumped from ^8.0.0 to ^9.0.0 + * @libp2p/interface-compliance-tests bumped from ^3.0.0 to ^4.0.0 + * @libp2p/kad-dht bumped from ^9.0.0 to ^10.0.0 + * @libp2p/mdns bumped from ^8.0.0 to ^9.0.0 + * @libp2p/mplex bumped from ^8.0.0 to ^9.0.0 + * @libp2p/tcp bumped from ^7.0.0 to ^8.0.0 + * @libp2p/websockets bumped from ^6.0.0 to ^7.0.0 + ### [0.45.8](https://www.github.com/libp2p/js-libp2p/compare/libp2p-v0.45.7...libp2p-v0.45.8) (2023-06-14) diff --git a/packages/libp2p/package.json b/packages/libp2p/package.json index 91a2e5d8dd..63c9acaa64 100644 --- a/packages/libp2p/package.json +++ b/packages/libp2p/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.45.9", + "version": "0.46.1", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/libp2p#readme", @@ -127,18 +127,18 @@ }, "dependencies": { "@achingbrain/nat-port-mapper": "^1.0.9", - "@libp2p/crypto": "^1.0.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/interface-internal": "~0.0.1", - "@libp2p/keychain": "^2.0.0", - "@libp2p/logger": "^2.0.0", - "@libp2p/multistream-select": "^3.0.0", - "@libp2p/peer-collections": "^3.0.0", - "@libp2p/peer-id": "^2.0.0", - "@libp2p/peer-id-factory": "^2.0.0", - "@libp2p/peer-record": "^5.0.0", - "@libp2p/peer-store": "^8.0.0", - "@libp2p/utils": "^3.0.0", + "@libp2p/crypto": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/interface-internal": "^0.1.0", + "@libp2p/keychain": "^3.0.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/multistream-select": "^4.0.0", + "@libp2p/peer-collections": "^4.0.0", + "@libp2p/peer-id": "^3.0.0", + "@libp2p/peer-id-factory": "^3.0.0", + "@libp2p/peer-record": "^6.0.0", + "@libp2p/peer-store": "^9.0.0", + "@libp2p/utils": "^4.0.0", "@multiformats/mafmt": "^12.1.2", "@multiformats/multiaddr": "^12.1.3", "abortable-iterator": "^5.0.1", @@ -172,20 +172,20 @@ "xsalsa20": "^1.1.0" }, "devDependencies": { - "@chainsafe/libp2p-gossipsub": "^9.0.0", - "@chainsafe/libp2p-noise": "^12.0.0", - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/bootstrap": "^8.0.0", - "@libp2p/daemon-client": "^6.0.0", - "@libp2p/daemon-server": "^5.0.0", - "@libp2p/floodsub": "^7.0.0", - "@libp2p/interface-compliance-tests": "^3.0.0", - "@libp2p/interop": "^8.0.0", - "@libp2p/kad-dht": "^9.0.0", - "@libp2p/mdns": "^8.0.0", - "@libp2p/mplex": "^8.0.0", - "@libp2p/tcp": "^7.0.0", - "@libp2p/websockets": "^6.0.0", + "@chainsafe/libp2p-gossipsub": "^10.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/bootstrap": "^9.0.0", + "@libp2p/daemon-client": "^7.0.0", + "@libp2p/daemon-server": "^6.0.0", + "@libp2p/floodsub": "^8.0.1", + "@libp2p/interface-compliance-tests": "^4.0.0", + "@libp2p/interop": "^9.0.0", + "@libp2p/kad-dht": "^10.0.0", + "@libp2p/mdns": "^9.0.0", + "@libp2p/mplex": "^9.0.0", + "@libp2p/tcp": "^8.0.0", + "@libp2p/websockets": "^7.0.0", "@types/varint": "^6.0.0", "@types/xsalsa20": "^1.1.0", "aegir": "^40.0.1", diff --git a/packages/libp2p/src/address-manager/README.md b/packages/libp2p/src/address-manager/README.md index 44b107c892..ae181eba92 100644 --- a/packages/libp2p/src/address-manager/README.md +++ b/packages/libp2p/src/address-manager/README.md @@ -2,7 +2,7 @@ The Address manager is responsible for keeping an updated register of the peer's addresses. It includes 2 different types of Addresses: `Listen Addresses` and `Announce Addresses`. -These Addresses should be specified in your libp2p [configuration](../../doc/CONFIGURATION.md) when you create your node. +These Addresses should be specified in your libp2p [configuration](../../../../doc/CONFIGURATION.md) when you create your node. ## Listen Addresses diff --git a/packages/libp2p/src/ping/README.md b/packages/libp2p/src/ping/README.md deleted file mode 100644 index 6dccd4be3c..0000000000 --- a/packages/libp2p/src/ping/README.md +++ /dev/null @@ -1,18 +0,0 @@ -libp2p-ping JavaScript Implementation -===================================== - -> IPFS ping protocol JavaScript implementation - -**Note**: git history prior to merging into js-libp2p can be found in the original repository, https://github.com/libp2p/js-libp2p-ping. - -## Usage - -```javascript -import Ping from 'libp2p/src/ping' - -Ping.mount(libp2p) // Enable this peer to echo Ping requests - -const latency = await Ping(libp2p, peerDst) - -Ping.unmount(libp2p) -``` diff --git a/packages/libp2p/tsconfig.json b/packages/libp2p/tsconfig.json index e3088b86ec..e52e22da16 100644 --- a/packages/libp2p/tsconfig.json +++ b/packages/libp2p/tsconfig.json @@ -8,9 +8,6 @@ "test" ], "references": [ - { - "path": "../connection-encryption-noise" - }, { "path": "../crypto" }, @@ -29,12 +26,6 @@ { "path": "../keychain" }, - { - "path": "../libp2p-daemon-client" - }, - { - "path": "../libp2p-daemon-server" - }, { "path": "../logger" }, @@ -65,15 +56,9 @@ { "path": "../pubsub-floodsub" }, - { - "path": "../pubsub-gossipsub" - }, { "path": "../stream-multiplexer-mplex" }, - { - "path": "../stream-multiplexer-yamux" - }, { "path": "../transport-tcp" }, diff --git a/packages/logger/CHANGELOG.md b/packages/logger/CHANGELOG.md index a05e46a74a..bcb5427f21 100644 --- a/packages/logger/CHANGELOG.md +++ b/packages/logger/CHANGELOG.md @@ -5,6 +5,32 @@ * specify updated formatter for multiaddrs ([#36](https://github.com/libp2p/js-libp2p-logger/issues/36)) ([abaefb4](https://github.com/libp2p/js-libp2p-logger/commit/abaefb490a0d9464a23b422d9fc5b80051532d10)) +## [3.0.0](https://www.github.com/libp2p/js-libp2p/compare/logger-v2.1.1...logger-v3.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * devDependencies + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + ## [2.1.0](https://github.com/libp2p/js-libp2p-logger/compare/v2.0.7...v2.1.0) (2023-05-31) @@ -181,4 +207,4 @@ ### Features -* add tracked-map ([#156](https://github.com/libp2p/js-libp2p-interfaces/issues/156)) ([c17730f](https://github.com/libp2p/js-libp2p-interfaces/commit/c17730f8bca172db85507740eaba81b3cf514d04)) +* add tracked-map ([#156](https://github.com/libp2p/js-libp2p-interfaces/issues/156)) ([c17730f](https://github.com/libp2p/js-libp2p-interfaces/commit/c17730f8bca172db85507740eaba81b3cf514d04)) \ No newline at end of file diff --git a/packages/logger/package.json b/packages/logger/package.json index f242c4c6fd..9d8f7b17ad 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/logger", - "version": "2.1.1", + "version": "3.0.0", "description": "A logging component for use in js-libp2p modules", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/logger#readme", @@ -48,14 +48,14 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "~0.0.1", + "@libp2p/interface": "^0.1.0", "@multiformats/multiaddr": "^12.1.3", "debug": "^4.3.4", "interface-datastore": "^8.2.0", "multiformats": "^12.0.1" }, "devDependencies": { - "@libp2p/peer-id": "^2.0.0", + "@libp2p/peer-id": "^3.0.0", "@types/debug": "^4.1.7", "aegir": "^40.0.1", "sinon": "^15.1.2", diff --git a/packages/metrics-prometheus/CHANGELOG.md b/packages/metrics-prometheus/CHANGELOG.md index 237f9f466c..75c52e5097 100644 --- a/packages/metrics-prometheus/CHANGELOG.md +++ b/packages/metrics-prometheus/CHANGELOG.md @@ -5,6 +5,34 @@ * move prom-client to deps ([#32](https://github.com/libp2p/js-libp2p-prometheus-metrics/issues/32)) ([73acad0](https://github.com/libp2p/js-libp2p-prometheus-metrics/commit/73acad0a20a9a0ad024cd47a53f154668dbae77b)) +## [2.0.0](https://www.github.com/libp2p/js-libp2p/compare/prometheus-metrics-v1.1.5...prometheus-metrics-v2.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^3.0.0 to ^4.0.0 + * @libp2p/peer-id-factory bumped from ^2.0.0 to ^3.0.0 + ## [1.1.4](https://github.com/libp2p/js-libp2p-prometheus-metrics/compare/v1.1.3...v1.1.4) (2023-05-12) @@ -83,4 +111,4 @@ ### Documentation -* update readme to link to correct branch ([1a7565b](https://github.com/libp2p/js-libp2p-prometheus-metrics/commit/1a7565b5986ba689eb7a6d555b15ca1a4e4d3f31)) +* update readme to link to correct branch ([1a7565b](https://github.com/libp2p/js-libp2p-prometheus-metrics/commit/1a7565b5986ba689eb7a6d555b15ca1a4e4d3f31)) \ No newline at end of file diff --git a/packages/metrics-prometheus/package.json b/packages/metrics-prometheus/package.json index 244446cd62..dad26ecf7b 100644 --- a/packages/metrics-prometheus/package.json +++ b/packages/metrics-prometheus/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/prometheus-metrics", - "version": "1.1.5", + "version": "2.0.0", "description": "Collect libp2p metrics for scraping by Prometheus or Graphana", "author": "", "license": "Apache-2.0 OR MIT", @@ -42,15 +42,15 @@ "test:electron-main": "aegir test -t electron-main --cov" }, "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/logger": "^3.0.0", "it-foreach": "^2.0.3", "it-stream-types": "^2.0.1", "prom-client": "^14.1.0" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^3.0.0", - "@libp2p/peer-id-factory": "^2.0.0", + "@libp2p/interface-compliance-tests": "^4.0.0", + "@libp2p/peer-id-factory": "^3.0.0", "@multiformats/multiaddr": "^12.1.3", "aegir": "^40.0.1", "it-drain": "^3.0.2", diff --git a/packages/multistream-select/CHANGELOG.md b/packages/multistream-select/CHANGELOG.md index 2b8fdbdeaf..1a0bfebe8f 100644 --- a/packages/multistream-select/CHANGELOG.md +++ b/packages/multistream-select/CHANGELOG.md @@ -11,6 +11,33 @@ * **dev:** bump aegir from 38.1.8 to 39.0.10 ([#70](https://github.com/libp2p/js-libp2p-multistream-select/issues/70)) ([f87b1c3](https://github.com/libp2p/js-libp2p-multistream-select/commit/f87b1c3505934ebeed6eff018af8d3042e7e6e06)) +## [4.0.0](https://www.github.com/libp2p/js-libp2p/compare/multistream-select-v3.1.9...multistream-select-v4.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* the `.close`, `closeRead` and `closeWrite` methods on the `Stream` interface are now asynchronous +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* close streams gracefully ([#1864](https://www.github.com/libp2p/js-libp2p/issues/1864)) ([b36ec7f](https://www.github.com/libp2p/js-libp2p/commit/b36ec7f24e477af21cec31effc086a6c611bf271)), closes [#1793](https://www.github.com/libp2p/js-libp2p/issues/1793) [#656](https://www.github.com/libp2p/js-libp2p/issues/656) +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + ## [3.1.8](https://github.com/libp2p/js-libp2p-multistream-select/compare/v3.1.7...v3.1.8) (2023-04-19) @@ -197,4 +224,4 @@ ### Bug Fixes -* add multistream-select and update pubsub types ([#170](https://github.com/libp2p/js-libp2p-interfaces/issues/170)) ([b9ecb2b](https://github.com/libp2p/js-libp2p-interfaces/commit/b9ecb2bee8f2abc0c41bfcf7bf2025894e37ddc2)) +* add multistream-select and update pubsub types ([#170](https://github.com/libp2p/js-libp2p-interfaces/issues/170)) ([b9ecb2b](https://github.com/libp2p/js-libp2p-interfaces/commit/b9ecb2bee8f2abc0c41bfcf7bf2025894e37ddc2)) \ No newline at end of file diff --git a/packages/multistream-select/package.json b/packages/multistream-select/package.json index 0aa6c16170..51e74199d0 100644 --- a/packages/multistream-select/package.json +++ b/packages/multistream-select/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/multistream-select", - "version": "3.1.9", + "version": "4.0.0", "description": "JavaScript implementation of multistream-select", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/multistream-select#readme", @@ -52,8 +52,8 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/logger": "^3.0.0", "abortable-iterator": "^5.0.1", "it-first": "^3.0.1", "it-handshake": "^4.1.3", diff --git a/packages/peer-collections/CHANGELOG.md b/packages/peer-collections/CHANGELOG.md index 2c070f5975..cbffd419f3 100644 --- a/packages/peer-collections/CHANGELOG.md +++ b/packages/peer-collections/CHANGELOG.md @@ -11,6 +11,33 @@ * **dev:** bump aegir from 38.1.8 to 39.0.10 ([#36](https://github.com/libp2p/js-libp2p-peer-collections/issues/36)) ([9fa3de6](https://github.com/libp2p/js-libp2p-peer-collections/commit/9fa3de6d85dbe1ade54fda86b597ed9ffe6d71d5)) +## [4.0.0](https://www.github.com/libp2p/js-libp2p/compare/peer-collections-v3.0.2...peer-collections-v4.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + * devDependencies + * @libp2p/peer-id-factory bumped from ^2.0.0 to ^3.0.0 + ## [3.0.1](https://github.com/libp2p/js-libp2p-peer-collections/compare/v3.0.0...v3.0.1) (2023-03-24) @@ -111,4 +138,4 @@ ### Bug Fixes -* simplify transport interface, update interfaces for use with libp2p ([#180](https://github.com/libp2p/js-libp2p-interfaces/issues/180)) ([ec81622](https://github.com/libp2p/js-libp2p-interfaces/commit/ec81622e5b7c6d256e0f8aed6d3695642473293b)) +* simplify transport interface, update interfaces for use with libp2p ([#180](https://github.com/libp2p/js-libp2p-interfaces/issues/180)) ([ec81622](https://github.com/libp2p/js-libp2p-interfaces/commit/ec81622e5b7c6d256e0f8aed6d3695642473293b)) \ No newline at end of file diff --git a/packages/peer-collections/package.json b/packages/peer-collections/package.json index c263150a3a..b95f2b8a0d 100644 --- a/packages/peer-collections/package.json +++ b/packages/peer-collections/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/peer-collections", - "version": "3.0.2", + "version": "4.0.0", "description": "Stores values against a peer id", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/peer-collections#readme", @@ -48,11 +48,11 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/peer-id": "^2.0.0" + "@libp2p/interface": "^0.1.0", + "@libp2p/peer-id": "^3.0.0" }, "devDependencies": { - "@libp2p/peer-id-factory": "^2.0.0", + "@libp2p/peer-id-factory": "^3.0.0", "aegir": "^40.0.1" } } diff --git a/packages/peer-discovery-bootstrap/CHANGELOG.md b/packages/peer-discovery-bootstrap/CHANGELOG.md index a74abf2fa2..7e7ce9c330 100644 --- a/packages/peer-discovery-bootstrap/CHANGELOG.md +++ b/packages/peer-discovery-bootstrap/CHANGELOG.md @@ -9,6 +9,34 @@ * update @libp2p/interface-peer-discovery to 2.0.0 ([#176](https://github.com/libp2p/js-libp2p-bootstrap/issues/176)) ([1954e75](https://github.com/libp2p/js-libp2p-bootstrap/commit/1954e75fa4b1e6b3b42f885f663f989fd0e422ab)) +## [9.0.0](https://www.github.com/libp2p/js-libp2p/compare/bootstrap-v8.0.0...bootstrap-v9.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^3.0.0 to ^4.0.0 + ## [7.0.1](https://github.com/libp2p/js-libp2p-bootstrap/compare/v7.0.0...v7.0.1) (2023-05-04) @@ -474,4 +502,4 @@ Co-authored-by: Alan Shaw -## [0.4.2](https://github.com/libp2p/js-ipfs-railing/compare/v0.4.1...v0.4.2) (2017-03-21) +## [0.4.2](https://github.com/libp2p/js-ipfs-railing/compare/v0.4.1...v0.4.2) (2017-03-21) \ No newline at end of file diff --git a/packages/peer-discovery-bootstrap/package.json b/packages/peer-discovery-bootstrap/package.json index dbdd4090f3..93f19ffcb8 100644 --- a/packages/peer-discovery-bootstrap/package.json +++ b/packages/peer-discovery-bootstrap/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/bootstrap", - "version": "8.0.0", + "version": "9.0.0", "description": "Peer discovery via a list of bootstrap peers", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/peer-discovery-bootstrap#readme", @@ -48,14 +48,14 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/peer-id": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/peer-id": "^3.0.0", "@multiformats/mafmt": "^12.1.2", "@multiformats/multiaddr": "^12.1.3" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^3.0.0", + "@libp2p/interface-compliance-tests": "^4.0.0", "aegir": "^40.0.1", "sinon-ts": "^1.0.0" } diff --git a/packages/peer-discovery-mdns/CHANGELOG.md b/packages/peer-discovery-mdns/CHANGELOG.md index faba3af6eb..b52450afa9 100644 --- a/packages/peer-discovery-mdns/CHANGELOG.md +++ b/packages/peer-discovery-mdns/CHANGELOG.md @@ -9,6 +9,36 @@ * update @libp2p/interface-peer-discovery to 2.0.0 ([#197](https://github.com/libp2p/js-libp2p-mdns/issues/197)) ([e8172af](https://github.com/libp2p/js-libp2p-mdns/commit/e8172af8b9856a934327195238b00e5fbba436a4)) +## [9.0.0](https://www.github.com/libp2p/js-libp2p/compare/mdns-v8.0.0...mdns-v9.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^3.0.0 to ^4.0.0 + * @libp2p/interface-internal bumped from ~0.0.1 to ^0.1.0 + * @libp2p/peer-id-factory bumped from ^2.0.0 to ^3.0.0 + ## [7.0.5](https://github.com/libp2p/js-libp2p-mdns/compare/v7.0.4...v7.0.5) (2023-05-04) @@ -497,4 +527,4 @@ Co-authored-by: Jacob Heun -## [0.6.2](https://github.com/libp2p/js-libp2p-mdns/compare/v0.6.1...v0.6.2) (2017-03-21) +## [0.6.2](https://github.com/libp2p/js-libp2p-mdns/compare/v0.6.1...v0.6.2) (2017-03-21) \ No newline at end of file diff --git a/packages/peer-discovery-mdns/package.json b/packages/peer-discovery-mdns/package.json index 3469e2ccdb..3b00cd0897 100644 --- a/packages/peer-discovery-mdns/package.json +++ b/packages/peer-discovery-mdns/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/mdns", - "version": "8.0.0", + "version": "9.0.0", "description": "Node.js libp2p mDNS discovery implementation for peer discovery", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/peer-discovery-mdns#readme", @@ -44,18 +44,18 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/peer-id": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/peer-id": "^3.0.0", "@multiformats/multiaddr": "^12.1.3", "@types/multicast-dns": "^7.2.1", "dns-packet": "^5.4.0", "multicast-dns": "^7.2.5" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^3.0.0", - "@libp2p/interface-internal": "~0.0.1", - "@libp2p/peer-id-factory": "^2.0.0", + "@libp2p/interface-compliance-tests": "^4.0.0", + "@libp2p/interface-internal": "^0.1.0", + "@libp2p/peer-id-factory": "^3.0.0", "aegir": "^40.0.1", "p-wait-for": "^5.0.2", "ts-sinon": "^2.0.2" diff --git a/packages/peer-id-factory/CHANGELOG.md b/packages/peer-id-factory/CHANGELOG.md index c674ac0dac..94545e6c5e 100644 --- a/packages/peer-id-factory/CHANGELOG.md +++ b/packages/peer-id-factory/CHANGELOG.md @@ -5,6 +5,32 @@ * update README.md ([#59](https://github.com/libp2p/js-libp2p-peer-id/issues/59)) ([aba6483](https://github.com/libp2p/js-libp2p-peer-id/commit/aba6483dad028ee5c24bfc01135b77568666cfd3)) +## [3.0.0](https://www.github.com/libp2p/js-libp2p/compare/peer-id-factory-v2.0.3...peer-id-factory-v3.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^1.0.0 to ^2.0.0 + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + ## [@libp2p/peer-id-factory-v2.0.2](https://github.com/libp2p/js-libp2p-peer-id/compare/@libp2p/peer-id-factory-v2.0.1...@libp2p/peer-id-factory-v2.0.2) (2023-03-10) @@ -211,4 +237,4 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ### Features -* simpler peer id ([#117](https://github.com/libp2p/js-libp2p-interfaces/issues/117)) ([fa2c4f5](https://github.com/libp2p/js-libp2p-interfaces/commit/fa2c4f5be74a5cfc11489771881e57b4e53bf174)) +* simpler peer id ([#117](https://github.com/libp2p/js-libp2p-interfaces/issues/117)) ([fa2c4f5](https://github.com/libp2p/js-libp2p-interfaces/commit/fa2c4f5be74a5cfc11489771881e57b4e53bf174)) \ No newline at end of file diff --git a/packages/peer-id-factory/package.json b/packages/peer-id-factory/package.json index d19904eaa3..f04ea45095 100644 --- a/packages/peer-id-factory/package.json +++ b/packages/peer-id-factory/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/peer-id-factory", - "version": "2.0.3", + "version": "3.0.0", "description": "Create PeerId instances", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/peer-id-factory#readme", @@ -52,9 +52,9 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/crypto": "^1.0.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/peer-id": "^2.0.0", + "@libp2p/crypto": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/peer-id": "^3.0.0", "multiformats": "^12.0.1", "protons-runtime": "^5.0.0", "uint8arraylist": "^2.4.3", diff --git a/packages/peer-id/CHANGELOG.md b/packages/peer-id/CHANGELOG.md index c2d9a92914..1ba35c446b 100644 --- a/packages/peer-id/CHANGELOG.md +++ b/packages/peer-id/CHANGELOG.md @@ -5,6 +5,30 @@ * update README.md ([#59](https://github.com/libp2p/js-libp2p-peer-id/issues/59)) ([aba6483](https://github.com/libp2p/js-libp2p-peer-id/commit/aba6483dad028ee5c24bfc01135b77568666cfd3)) +## [3.0.0](https://www.github.com/libp2p/js-libp2p/compare/peer-id-v2.0.3...peer-id-v3.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + ## [@libp2p/peer-id-v2.0.2](https://github.com/libp2p/js-libp2p-peer-id/compare/@libp2p/peer-id-v2.0.1...@libp2p/peer-id-v2.0.2) (2023-02-23) @@ -233,4 +257,4 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ### Features -* simpler peer id ([#117](https://github.com/libp2p/js-libp2p-interfaces/issues/117)) ([fa2c4f5](https://github.com/libp2p/js-libp2p-interfaces/commit/fa2c4f5be74a5cfc11489771881e57b4e53bf174)) +* simpler peer id ([#117](https://github.com/libp2p/js-libp2p-interfaces/issues/117)) ([fa2c4f5](https://github.com/libp2p/js-libp2p-interfaces/commit/fa2c4f5be74a5cfc11489771881e57b4e53bf174)) \ No newline at end of file diff --git a/packages/peer-id/package.json b/packages/peer-id/package.json index 61e5d610ff..6bbb5fbe42 100644 --- a/packages/peer-id/package.json +++ b/packages/peer-id/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/peer-id", - "version": "2.0.3", + "version": "3.0.0", "description": "Implementation of @libp2p/interface-peer-id", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/peer-id#readme", @@ -48,7 +48,7 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "~0.0.1", + "@libp2p/interface": "^0.1.0", "multiformats": "^12.0.1", "uint8arrays": "^4.0.4" }, diff --git a/packages/peer-record/CHANGELOG.md b/packages/peer-record/CHANGELOG.md index fb59f472bf..9d554d2d70 100644 --- a/packages/peer-record/CHANGELOG.md +++ b/packages/peer-record/CHANGELOG.md @@ -11,6 +11,35 @@ * **dev:** bump aegir from 38.1.8 to 39.0.10 ([#64](https://github.com/libp2p/js-libp2p-peer-record/issues/64)) ([ba3ac38](https://github.com/libp2p/js-libp2p-peer-record/commit/ba3ac38c79e9449a75c0a54fefe289ee9e2c78fb)) +## [6.0.0](https://www.github.com/libp2p/js-libp2p/compare/peer-record-v5.0.4...peer-record-v6.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/crypto bumped from ^1.0.0 to ^2.0.0 + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + * @libp2p/utils bumped from ^3.0.0 to ^4.0.0 + * devDependencies + * @libp2p/peer-id-factory bumped from ^2.0.0 to ^3.0.0 + ## [5.0.3](https://github.com/libp2p/js-libp2p-peer-record/compare/v5.0.2...v5.0.3) (2023-03-17) @@ -254,4 +283,4 @@ ### Features -* add peer store/records, and streams are just streams ([#160](https://github.com/libp2p/js-libp2p-interfaces/issues/160)) ([8860a0c](https://github.com/libp2p/js-libp2p-interfaces/commit/8860a0cd46b359a5648402d83870f7ff957222fe)) +* add peer store/records, and streams are just streams ([#160](https://github.com/libp2p/js-libp2p-interfaces/issues/160)) ([8860a0c](https://github.com/libp2p/js-libp2p-interfaces/commit/8860a0cd46b359a5648402d83870f7ff957222fe)) \ No newline at end of file diff --git a/packages/peer-record/package.json b/packages/peer-record/package.json index e94a846471..9466c5d14b 100644 --- a/packages/peer-record/package.json +++ b/packages/peer-record/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/peer-record", - "version": "5.0.4", + "version": "6.0.0", "description": "Used to transfer signed peer data across the network", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/peer-record#readme", @@ -55,10 +55,10 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/crypto": "^1.0.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/peer-id": "^2.0.0", - "@libp2p/utils": "^3.0.0", + "@libp2p/crypto": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/peer-id": "^3.0.0", + "@libp2p/utils": "^4.0.0", "@multiformats/multiaddr": "^12.1.3", "protons-runtime": "^5.0.0", "uint8-varint": "^1.0.2", @@ -66,7 +66,7 @@ "uint8arrays": "^4.0.4" }, "devDependencies": { - "@libp2p/peer-id-factory": "^2.0.0", + "@libp2p/peer-id-factory": "^3.0.0", "@types/varint": "^6.0.0", "aegir": "^40.0.1", "protons": "^7.0.2" diff --git a/packages/peer-store/CHANGELOG.md b/packages/peer-store/CHANGELOG.md index d9f685ac4b..758d08bd65 100644 --- a/packages/peer-store/CHANGELOG.md +++ b/packages/peer-store/CHANGELOG.md @@ -11,6 +11,35 @@ * **dev:** bump p-event from 5.0.1 to 6.0.0 ([#89](https://github.com/libp2p/js-libp2p-peer-store/issues/89)) ([9d96700](https://github.com/libp2p/js-libp2p-peer-store/commit/9d9670048b5e8feeac656cba92cb2e513e4a77be)) +## [9.0.0](https://www.github.com/libp2p/js-libp2p/compare/peer-store-v8.2.1...peer-store-v9.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-collections bumped from ^3.0.0 to ^4.0.0 + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-id-factory bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-record bumped from ^5.0.0 to ^6.0.0 + ## [8.2.0](https://github.com/libp2p/js-libp2p-peer-store/compare/v8.1.4...v8.2.0) (2023-06-11) @@ -391,4 +420,4 @@ Co-authored-by: Alex Potsides ### Features -* add peer store/records, and streams are just streams ([#160](https://github.com/libp2p/js-libp2p-interfaces/issues/160)) ([8860a0c](https://github.com/libp2p/js-libp2p-interfaces/commit/8860a0cd46b359a5648402d83870f7ff957222fe)) +* add peer store/records, and streams are just streams ([#160](https://github.com/libp2p/js-libp2p-interfaces/issues/160)) ([8860a0c](https://github.com/libp2p/js-libp2p-interfaces/commit/8860a0cd46b359a5648402d83870f7ff957222fe)) \ No newline at end of file diff --git a/packages/peer-store/package.json b/packages/peer-store/package.json index 93d955f666..24212fda85 100644 --- a/packages/peer-store/package.json +++ b/packages/peer-store/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/peer-store", - "version": "8.2.1", + "version": "9.0.0", "description": "Stores information about peers libp2p knows on the network", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/peer-store#readme", @@ -53,12 +53,12 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/peer-collections": "^3.0.0", - "@libp2p/peer-id": "^2.0.0", - "@libp2p/peer-id-factory": "^2.0.0", - "@libp2p/peer-record": "^5.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/peer-collections": "^4.0.0", + "@libp2p/peer-id": "^3.0.0", + "@libp2p/peer-id-factory": "^3.0.0", + "@libp2p/peer-record": "^6.0.0", "@multiformats/multiaddr": "^12.1.3", "interface-datastore": "^8.2.0", "it-all": "^3.0.2", diff --git a/packages/pubsub-floodsub/CHANGELOG.md b/packages/pubsub-floodsub/CHANGELOG.md index 62810c9210..8c698980a3 100644 --- a/packages/pubsub-floodsub/CHANGELOG.md +++ b/packages/pubsub-floodsub/CHANGELOG.md @@ -11,6 +11,20 @@ * **dev:** bump aegir from 38.1.8 to 39.0.10 ([#233](https://github.com/libp2p/js-libp2p-floodsub/issues/233)) ([e073298](https://github.com/libp2p/js-libp2p-floodsub/commit/e073298f324a89656b0ca6d9a629e60eaedc7873)) +### [8.0.1](https://www.github.com/libp2p/js-libp2p/compare/floodsub-v8.0.0...floodsub-v8.0.1) (2023-08-01) + + +### Bug Fixes + +* update package config ([#1919](https://www.github.com/libp2p/js-libp2p/issues/1919)) ([8d49602](https://www.github.com/libp2p/js-libp2p/commit/8d49602fb6f0c906f1920d397ff28705bb0bc845)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/pubsub bumped from ^8.0.0 to ^8.0.1 + ## [7.0.1](https://github.com/libp2p/js-libp2p-floodsub/compare/v7.0.0...v7.0.1) (2023-04-18) @@ -882,4 +896,4 @@ -# 0.1.0 (2016-09-14) +# 0.1.0 (2016-09-14) \ No newline at end of file diff --git a/packages/pubsub-floodsub/package.json b/packages/pubsub-floodsub/package.json index f2146d38ea..2e1f88a698 100644 --- a/packages/pubsub-floodsub/package.json +++ b/packages/pubsub-floodsub/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/floodsub", - "version": "7.0.2", + "version": "8.0.1", "description": "libp2p-floodsub, also known as pubsub-flood or just dumbsub, this implementation of pubsub focused on delivering an API for Publish/Subscribe, but with no CastTree Forming (it just floods the network).", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/pubsub-floodsub#readme", @@ -37,10 +37,7 @@ "extends": "ipfs", "parserOptions": { "sourceType": "module" - }, - "ignorePatterns": [ - "*.d.ts" - ] + } }, "scripts": { "clean": "aegir clean", @@ -57,17 +54,17 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/pubsub": "^7.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/pubsub": "^8.0.1", "protons-runtime": "^5.0.0", "uint8arraylist": "^2.4.3", "uint8arrays": "^4.0.4" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^3.0.0", - "@libp2p/peer-collections": "^3.0.0", - "@libp2p/peer-id-factory": "^2.0.0", + "@libp2p/interface-compliance-tests": "^4.0.0", + "@libp2p/peer-collections": "^4.0.0", + "@libp2p/peer-id-factory": "^3.0.0", "@multiformats/multiaddr": "^12.1.3", "@types/sinon": "^10.0.15", "aegir": "^40.0.1", diff --git a/packages/pubsub-gossipsub/package.json b/packages/pubsub-gossipsub/package.json deleted file mode 100644 index f5136dfb38..0000000000 --- a/packages/pubsub-gossipsub/package.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "name": "@chainsafe/libp2p-gossipsub", - "version": "9.0.0", - "description": "A typescript implementation of gossipsub", - "author": "Cayman Nava", - "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/pubsub-gossipsub#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/libp2p/js-libp2p.git" - }, - "bugs": { - "url": "https://github.com/libp2p/js-libp2p/issues" - }, - "keywords": [ - "gossip", - "libp2p", - "pubsub" - ], - "type": "module", - "types": "./dist/src/index.d.ts", - "typesVersions": { - "*": { - "*": [ - "*", - "dist/*", - "dist/src/*", - "dist/src/*/index" - ], - "src/*": [ - "*", - "dist/*", - "dist/src/*", - "dist/src/*/index" - ] - } - }, - "files": [ - "src", - "dist", - "!dist/test", - "!**/*.tsbuildinfo" - ], - "exports": { - ".": { - "types": "./dist/src/index.d.ts", - "import": "./dist/src/index.js" - }, - "./message": { - "types": "./dist/src/message/index.d.ts", - "import": "./dist/src/message/index.js" - }, - "./metrics": { - "types": "./dist/src/metrics.d.ts", - "import": "./dist/src/metrics.js" - }, - "./score": { - "types": "./dist/src/score/index.d.ts", - "import": "./dist/src/score/index.js" - }, - "./types": { - "types": "./dist/src/types.d.ts", - "import": "./dist/src/types.js" - } - }, - "eslintConfig": { - "extends": "ipfs", - "parserOptions": { - "sourceType": "module" - } - }, - "scripts": { - "copy": "mkdirp dist/src/message && cp src/message/*.* dist/src/message", - "build": "npm run copy && aegir build", - "pretest": "npm run build", - "pretest:e2e": "npm run build", - "benchmark": "node ./node_modules/.bin/benchmark 'dist/test/benchmark/*.test.js' --local", - "test": "aegir test -f './dist/test/*.spec.js'", - "test:unit": "aegir test -f './dist/test/unit/*.test.js' --target node", - "test:e2e": "aegir test -f './dist/test/e2e/*.spec.js'", - "test:node": "npm run test -- --target node", - "test:browser": "npm run test -- --target browser" - }, - "dependencies": { - "@libp2p/crypto": "^1.0.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/interface-internal": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/peer-id": "^2.0.0", - "@libp2p/pubsub": "^7.0.0", - "@multiformats/multiaddr": "^12.1.3", - "abortable-iterator": "^5.0.1", - "denque": "^2.1.0", - "it-length-prefixed": "^9.0.1", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.0", - "multiformats": "^12.0.1", - "protobufjs": "^7.2.4", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^4.0.4" - }, - "devDependencies": { - "@chainsafe/as-sha256": "^0.2.4", - "@dapplion/benchmark": "^0.2.4", - "@libp2p/floodsub": "^7.0.0", - "@libp2p/interface-compliance-tests": "^3.0.0", - "@libp2p/peer-id-factory": "^2.0.0", - "@libp2p/peer-store": "^8.0.0", - "aegir": "^40.0.1", - "datastore-core": "^9.1.1", - "delay": "^6.0.0", - "mkdirp": "^3.0.1", - "p-defer": "^4.0.0", - "p-event": "^6.0.0", - "p-retry": "^5.1.2", - "p-wait-for": "^5.0.2", - "prettier": "^2.0.5", - "sinon": "^15.1.2", - "time-cache": "^0.3.0", - "ts-node": "^10.7.0", - "ts-sinon": "^2.0.2" - }, - "engines": { - "npm": ">=8.7.0" - }, - "private": true -} diff --git a/packages/pubsub-gossipsub/src/config.ts b/packages/pubsub-gossipsub/src/config.ts deleted file mode 100644 index 8c6da4be10..0000000000 --- a/packages/pubsub-gossipsub/src/config.ts +++ /dev/null @@ -1,28 +0,0 @@ -export interface GossipsubOptsSpec { - /** D sets the optimal degree for a Gossipsub topic mesh. */ - D: number - /** Dlo sets the lower bound on the number of peers we keep in a Gossipsub topic mesh. */ - Dlo: number - /** Dhi sets the upper bound on the number of peers we keep in a Gossipsub topic mesh. */ - Dhi: number - /** Dscore affects how peers are selected when pruning a mesh due to over subscription. */ - Dscore: number - /** Dout sets the quota for the number of outbound connections to maintain in a topic mesh. */ - Dout: number - /** Dlazy affects how many peers we will emit gossip to at each heartbeat. */ - Dlazy: number - /** heartbeatInterval is the time between heartbeats in milliseconds */ - heartbeatInterval: number - /** - * fanoutTTL controls how long we keep track of the fanout state. If it's been - * fanoutTTL milliseconds since we've published to a topic that we're not subscribed to, - * we'll delete the fanout map for that topic. - */ - fanoutTTL: number - /** mcacheLength is the number of windows to retain full messages for IWANT responses */ - mcacheLength: number - /** mcacheGossip is the number of windows to gossip about */ - mcacheGossip: number - /** seenTTL is the number of milliseconds to retain message IDs in the seen cache */ - seenTTL: number -} diff --git a/packages/pubsub-gossipsub/src/constants.ts b/packages/pubsub-gossipsub/src/constants.ts deleted file mode 100644 index 4273cd60c1..0000000000 --- a/packages/pubsub-gossipsub/src/constants.ts +++ /dev/null @@ -1,243 +0,0 @@ -export const second = 1000 -export const minute = 60 * second - -// Protocol identifiers - -export const FloodsubID = '/floodsub/1.0.0' - -/** - * The protocol ID for version 1.0.0 of the Gossipsub protocol - * It is advertised along with GossipsubIDv11 for backwards compatability - */ -export const GossipsubIDv10 = '/meshsub/1.0.0' - -/** - * The protocol ID for version 1.1.0 of the Gossipsub protocol - * See the spec for details about how v1.1.0 compares to v1.0.0: - * https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md - */ -export const GossipsubIDv11 = '/meshsub/1.1.0' - -// Overlay parameters - -/** - * GossipsubD sets the optimal degree for a Gossipsub topic mesh. For example, if GossipsubD == 6, - * each peer will want to have about six peers in their mesh for each topic they're subscribed to. - * GossipsubD should be set somewhere between GossipsubDlo and GossipsubDhi. - */ -export const GossipsubD = 6 - -/** - * GossipsubDlo sets the lower bound on the number of peers we keep in a Gossipsub topic mesh. - * If we have fewer than GossipsubDlo peers, we will attempt to graft some more into the mesh at - * the next heartbeat. - */ -export const GossipsubDlo = 4 - -/** - * GossipsubDhi sets the upper bound on the number of peers we keep in a Gossipsub topic mesh. - * If we have more than GossipsubDhi peers, we will select some to prune from the mesh at the next heartbeat. - */ -export const GossipsubDhi = 12 - -/** - * GossipsubDscore affects how peers are selected when pruning a mesh due to over subscription. - * At least GossipsubDscore of the retained peers will be high-scoring, while the remainder are - * chosen randomly. - */ -export const GossipsubDscore = 4 - -/** - * GossipsubDout sets the quota for the number of outbound connections to maintain in a topic mesh. - * When the mesh is pruned due to over subscription, we make sure that we have outbound connections - * to at least GossipsubDout of the survivor peers. This prevents sybil attackers from overwhelming - * our mesh with incoming connections. - * - * GossipsubDout must be set below GossipsubDlo, and must not exceed GossipsubD / 2. - */ -export const GossipsubDout = 2 - -// Gossip parameters - -/** - * GossipsubHistoryLength controls the size of the message cache used for gossip. - * The message cache will remember messages for GossipsubHistoryLength heartbeats. - */ -export const GossipsubHistoryLength = 5 - -/** - * GossipsubHistoryGossip controls how many cached message ids we will advertise in - * IHAVE gossip messages. When asked for our seen message IDs, we will return - * only those from the most recent GossipsubHistoryGossip heartbeats. The slack between - * GossipsubHistoryGossip and GossipsubHistoryLength allows us to avoid advertising messages - * that will be expired by the time they're requested. - * - * GossipsubHistoryGossip must be less than or equal to GossipsubHistoryLength to - * avoid a runtime panic. - */ -export const GossipsubHistoryGossip = 3 - -/** - * GossipsubDlazy affects how many peers we will emit gossip to at each heartbeat. - * We will send gossip to at least GossipsubDlazy peers outside our mesh. The actual - * number may be more, depending on GossipsubGossipFactor and how many peers we're - * connected to. - */ -export const GossipsubDlazy = 6 - -/** - * GossipsubGossipFactor affects how many peers we will emit gossip to at each heartbeat. - * We will send gossip to GossipsubGossipFactor * (total number of non-mesh peers), or - * GossipsubDlazy, whichever is greater. - */ -export const GossipsubGossipFactor = 0.25 - -/** - * GossipsubGossipRetransmission controls how many times we will allow a peer to request - * the same message id through IWANT gossip before we start ignoring them. This is designed - * to prevent peers from spamming us with requests and wasting our resources. - */ -export const GossipsubGossipRetransmission = 3 - -// Heartbeat interval - -/** - * GossipsubHeartbeatInitialDelay is the short delay before the heartbeat timer begins - * after the router is initialized. - */ -export const GossipsubHeartbeatInitialDelay = 100 - -/** - * GossipsubHeartbeatInterval controls the time between heartbeats. - */ -export const GossipsubHeartbeatInterval = second - -/** - * GossipsubFanoutTTL controls how long we keep track of the fanout state. If it's been - * GossipsubFanoutTTL since we've published to a topic that we're not subscribed to, - * we'll delete the fanout map for that topic. - */ -export const GossipsubFanoutTTL = minute - -/** - * GossipsubPrunePeers controls the number of peers to include in prune Peer eXchange. - * When we prune a peer that's eligible for PX (has a good score, etc), we will try to - * send them signed peer records for up to GossipsubPrunePeers other peers that we - * know of. - */ -export const GossipsubPrunePeers = 16 - -/** - * GossipsubPruneBackoff controls the backoff time for pruned peers. This is how long - * a peer must wait before attempting to graft into our mesh again after being pruned. - * When pruning a peer, we send them our value of GossipsubPruneBackoff so they know - * the minimum time to wait. Peers running older versions may not send a backoff time, - * so if we receive a prune message without one, we will wait at least GossipsubPruneBackoff - * before attempting to re-graft. - */ -export const GossipsubPruneBackoff = minute - -/** - * GossipsubPruneBackoffTicks is the number of heartbeat ticks for attempting to prune expired - * backoff timers. - */ -export const GossipsubPruneBackoffTicks = 15 - -/** - * GossipsubConnectors controls the number of active connection attempts for peers obtained through PX. - */ -export const GossipsubConnectors = 8 - -/** - * GossipsubMaxPendingConnections sets the maximum number of pending connections for peers attempted through px. - */ -export const GossipsubMaxPendingConnections = 128 - -/** - * GossipsubConnectionTimeout controls the timeout for connection attempts. - */ -export const GossipsubConnectionTimeout = 30 * second - -/** - * GossipsubDirectConnectTicks is the number of heartbeat ticks for attempting to reconnect direct peers - * that are not currently connected. - */ -export const GossipsubDirectConnectTicks = 300 - -/** - * GossipsubDirectConnectInitialDelay is the initial delay before opening connections to direct peers - */ -export const GossipsubDirectConnectInitialDelay = second - -/** - * GossipsubOpportunisticGraftTicks is the number of heartbeat ticks for attempting to improve the mesh - * with opportunistic grafting. Every GossipsubOpportunisticGraftTicks we will attempt to select some - * high-scoring mesh peers to replace lower-scoring ones, if the median score of our mesh peers falls - * below a threshold - */ -export const GossipsubOpportunisticGraftTicks = 60 - -/** - * GossipsubOpportunisticGraftPeers is the number of peers to opportunistically graft. - */ -export const GossipsubOpportunisticGraftPeers = 2 - -/** - * If a GRAFT comes before GossipsubGraftFloodThreshold has elapsed since the last PRUNE, - * then there is an extra score penalty applied to the peer through P7. - */ -export const GossipsubGraftFloodThreshold = 10 * second - -/** - * GossipsubMaxIHaveLength is the maximum number of messages to include in an IHAVE message. - * Also controls the maximum number of IHAVE ids we will accept and request with IWANT from a - * peer within a heartbeat, to protect from IHAVE floods. You should adjust this value from the - * default if your system is pushing more than 5000 messages in GossipsubHistoryGossip heartbeats; - * with the defaults this is 1666 messages/s. - */ -export const GossipsubMaxIHaveLength = 5000 - -/** - * GossipsubMaxIHaveMessages is the maximum number of IHAVE messages to accept from a peer within a heartbeat. - */ -export const GossipsubMaxIHaveMessages = 10 - -/** - * Time to wait for a message requested through IWANT following an IHAVE advertisement. - * If the message is not received within this window, a broken promise is declared and - * the router may apply bahavioural penalties. - */ -export const GossipsubIWantFollowupTime = 3 * second - -/** - * Time in milliseconds to keep message ids in the seen cache - */ -export const GossipsubSeenTTL = 2 * minute - -export const TimeCacheDuration = 120 * 1000 - -export const ERR_TOPIC_VALIDATOR_REJECT = 'ERR_TOPIC_VALIDATOR_REJECT' -export const ERR_TOPIC_VALIDATOR_IGNORE = 'ERR_TOPIC_VALIDATOR_IGNORE' - -/** - * If peer score is better than this, we accept messages from this peer - * within ACCEPT_FROM_WHITELIST_DURATION_MS from the last time computing score. - **/ -export const ACCEPT_FROM_WHITELIST_THRESHOLD_SCORE = 0 - -/** - * If peer score >= ACCEPT_FROM_WHITELIST_THRESHOLD_SCORE, accept up to this - * number of messages from that peer. - */ -export const ACCEPT_FROM_WHITELIST_MAX_MESSAGES = 128 - -/** - * If peer score >= ACCEPT_FROM_WHITELIST_THRESHOLD_SCORE, accept messages from - * this peer up to this time duration. - */ -export const ACCEPT_FROM_WHITELIST_DURATION_MS = 1000 - -/** - * The default MeshMessageDeliveriesWindow to be used in metrics. - */ -export const DEFAULT_METRIC_MESH_MESSAGE_DELIVERIES_WINDOWS = 1000 diff --git a/packages/pubsub-gossipsub/src/index.ts b/packages/pubsub-gossipsub/src/index.ts deleted file mode 100644 index 15059f7bbf..0000000000 --- a/packages/pubsub-gossipsub/src/index.ts +++ /dev/null @@ -1,2931 +0,0 @@ -import { pipe } from 'it-pipe' -import type { Connection, Stream } from '@libp2p/interface/connection' -import { peerIdFromBytes, peerIdFromString } from '@libp2p/peer-id' -import { type Logger, logger } from '@libp2p/logger' -import type { PeerId } from '@libp2p/interface/peer-id' -import { CustomEvent, EventEmitter } from '@libp2p/interface/events' - -import { MessageCache, type MessageCacheRecord } from './message-cache.js' -import { RPC, type IRPC } from './message/rpc.js' -import * as constants from './constants.js' -import { shuffle, messageIdToString } from './utils/index.js' -import { - PeerScore, - type PeerScoreParams, - type PeerScoreThresholds, - createPeerScoreParams, - createPeerScoreThresholds, - type PeerScoreStatsDump -} from './score/index.js' -import { IWantTracer } from './tracer.js' -import { SimpleTimeCache } from './utils/time-cache.js' -import { - ACCEPT_FROM_WHITELIST_DURATION_MS, - ACCEPT_FROM_WHITELIST_MAX_MESSAGES, - ACCEPT_FROM_WHITELIST_THRESHOLD_SCORE -} from './constants.js' -import { - ChurnReason, - getMetrics, - IHaveIgnoreReason, - InclusionReason, - type Metrics, - type MetricsRegister, - ScorePenalty, - type TopicStrToLabel, - type ToSendGroupCount -} from './metrics.js' -import { - type MsgIdFn, - type PublishConfig, - type TopicStr, - type MsgIdStr, - ValidateError, - type PeerIdStr, - MessageStatus, - RejectReason, - type RejectReasonObj, - type FastMsgIdFn, - type AddrInfo, - type DataTransform, - rejectReasonFromAcceptance, - type MsgIdToStrFn, - type MessageId, - type PublishOpts -} from './types.js' -import { buildRawMessage, validateToRawMessage } from './utils/buildRawMessage.js' -import { msgIdFnStrictNoSign, msgIdFnStrictSign } from './utils/msgIdFn.js' -import { computeAllPeersScoreWeights } from './score/scoreMetrics.js' -import { getPublishConfigFromPeerId } from './utils/publishConfig.js' -import type { GossipsubOptsSpec } from './config.js' -import type { - Message, - PublishResult, - PubSub, - PubSubEvents, - PubSubInit, - SubscriptionChangeData, - TopicValidatorFn -} from '@libp2p/interface/pubsub' -import { - StrictSign, - StrictNoSign, - TopicValidatorResult -} from '@libp2p/interface/pubsub' -import type { IncomingStreamData, Registrar } from '@libp2p/interface-internal/registrar' -import { removeFirstNItemsFromSet, removeItemsFromSet } from './utils/set.js' -import { pushable } from 'it-pushable' -import { InboundStream, OutboundStream } from './stream.js' -import type { Uint8ArrayList } from 'uint8arraylist' -import { decodeRpc, type DecodeRPCLimits, defaultDecodeRpcLimits } from './message/decodeRpc.js' -import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager' -import type { Peer, PeerStore } from '@libp2p/interface/peer-store' -import type { Multiaddr } from '@multiformats/multiaddr' -import { multiaddrToIPStr } from './utils/multiaddr.js' - -type ConnectionDirection = 'inbound' | 'outbound' - -type ReceivedMessageResult = - | { code: MessageStatus.duplicate; msgIdStr: MsgIdStr } - | ({ code: MessageStatus.invalid; msgIdStr?: MsgIdStr } & RejectReasonObj) - | { code: MessageStatus.valid; messageId: MessageId; msg: Message } - -export const multicodec: string = constants.GossipsubIDv11 - -export interface GossipsubOpts extends GossipsubOptsSpec, PubSubInit { - /** if dial should fallback to floodsub */ - fallbackToFloodsub: boolean - /** if self-published messages should be sent to all peers */ - floodPublish: boolean - /** whether PX is enabled; this should be enabled in bootstrappers and other well connected/trusted nodes. */ - doPX: boolean - /** peers with which we will maintain direct connections */ - directPeers: AddrInfo[] - /** - * If true will not forward messages to mesh peers until reportMessageValidationResult() is called. - * Messages will be cached in mcache for some time after which they are evicted. Calling - * reportMessageValidationResult() after the message is dropped from mcache won't forward the message. - */ - asyncValidation: boolean - /** Do not throw `InsufficientPeers` error if publishing to zero peers */ - allowPublishToZeroPeers: boolean - /** Do not throw `PublishError.Duplicate` if publishing duplicate messages */ - ignoreDuplicatePublishError: boolean - /** For a single stream, await processing each RPC before processing the next */ - awaitRpcHandler: boolean - /** For a single RPC, await processing each message before processing the next */ - awaitRpcMessageHandler: boolean - - /** message id function */ - msgIdFn: MsgIdFn - /** fast message id function */ - fastMsgIdFn: FastMsgIdFn - /** Uint8Array message id to string function */ - msgIdToStrFn: MsgIdToStrFn - /** override the default MessageCache */ - messageCache: MessageCache - /** peer score parameters */ - scoreParams: Partial - /** peer score thresholds */ - scoreThresholds: Partial - /** customize GossipsubIWantFollowupTime in order not to apply IWANT penalties */ - gossipsubIWantFollowupMs: number - - /** override constants for fine tuning */ - prunePeers?: number - pruneBackoff?: number - graftFloodThreshold?: number - opportunisticGraftPeers?: number - opportunisticGraftTicks?: number - directConnectTicks?: number - - dataTransform?: DataTransform - metricsRegister?: MetricsRegister | null - metricsTopicStrToLabel?: TopicStrToLabel - - // Debug - /** Prefix tag for debug logs */ - debugName?: string - - /** - * Specify the maximum number of inbound gossipsub protocol - * streams that are allowed to be open concurrently - */ - maxInboundStreams?: number - - /** - * Specify the maximum number of outbound gossipsub protocol - * streams that are allowed to be open concurrently - */ - maxOutboundStreams?: number - - /** - * Specify max buffer size in bytes for OutboundStream. - * If full it will throw and reject sending any more data. - */ - maxOutboundBufferSize?: number - - /** - * Specify max size to skip decoding messages whose data - * section exceeds this size. - * - */ - maxInboundDataLength?: number - - /** - * If provided, only allow topics in this list - */ - allowedTopics?: string[] | Set - - /** - * Limits to bound protobuf decoding - */ - decodeRpcLimits?: DecodeRPCLimits -} - -export interface GossipsubMessage { - propagationSource: PeerId - msgId: MsgIdStr - msg: Message -} - -export interface GossipsubEvents extends PubSubEvents { - 'gossipsub:heartbeat': CustomEvent - 'gossipsub:message': CustomEvent -} - -enum GossipStatusCode { - started, - stopped -} - -type GossipStatus = - | { - code: GossipStatusCode.started - registrarTopologyIds: string[] - heartbeatTimeout: ReturnType - hearbeatStartMs: number - } - | { - code: GossipStatusCode.stopped - } - -interface GossipOptions extends GossipsubOpts { - scoreParams: PeerScoreParams - scoreThresholds: PeerScoreThresholds -} - -interface AcceptFromWhitelistEntry { - /** number of messages accepted since recomputing the peer's score */ - messagesAccepted: number - /** have to recompute score after this time */ - acceptUntil: number -} - -export interface GossipSubComponents { - peerId: PeerId - peerStore: PeerStore - registrar: Registrar - connectionManager: ConnectionManager -} - -export class GossipSub extends EventEmitter implements PubSub { - /** - * The signature policy to follow by default - */ - public readonly globalSignaturePolicy: typeof StrictSign | typeof StrictNoSign - public multicodecs: string[] = [constants.GossipsubIDv11, constants.GossipsubIDv10] - - private publishConfig: PublishConfig | undefined - - private readonly dataTransform: DataTransform | undefined - - // State - - public readonly peers = new Set() - public readonly streamsInbound = new Map() - public readonly streamsOutbound = new Map() - - /** Ensures outbound streams are created sequentially */ - private outboundInflightQueue = pushable<{ peerId: PeerId; connection: Connection }>({ objectMode: true }) - - /** Direct peers */ - public readonly direct = new Set() - - /** Floodsub peers */ - private readonly floodsubPeers = new Set() - - /** Cache of seen messages */ - private readonly seenCache: SimpleTimeCache - - /** - * Map of peer id and AcceptRequestWhileListEntry - */ - private readonly acceptFromWhitelist = new Map() - - /** - * Map of topics to which peers are subscribed to - */ - private readonly topics = new Map>() - - /** - * List of our subscriptions - */ - private readonly subscriptions = new Set() - - /** - * Map of topic meshes - * topic => peer id set - */ - public readonly mesh = new Map>() - - /** - * Map of topics to set of peers. These mesh peers are the ones to which we are publishing without a topic membership - * topic => peer id set - */ - public readonly fanout = new Map>() - - /** - * Map of last publish time for fanout topics - * topic => last publish time - */ - private readonly fanoutLastpub = new Map() - - /** - * Map of pending messages to gossip - * peer id => control messages - */ - public readonly gossip = new Map() - - /** - * Map of control messages - * peer id => control message - */ - public readonly control = new Map() - - /** - * Number of IHAVEs received from peer in the last heartbeat - */ - private readonly peerhave = new Map() - - /** Number of messages we have asked from peer in the last heartbeat */ - private readonly iasked = new Map() - - /** Prune backoff map */ - private readonly backoff = new Map>() - - /** - * Connection direction cache, marks peers with outbound connections - * peer id => direction - */ - private readonly outbound = new Map() - private readonly msgIdFn: MsgIdFn - - /** - * A fast message id function used for internal message de-duplication - */ - private readonly fastMsgIdFn: FastMsgIdFn | undefined - - private readonly msgIdToStrFn: MsgIdToStrFn - - /** Maps fast message-id to canonical message-id */ - private readonly fastMsgIdCache: SimpleTimeCache | undefined - - /** - * Short term cache for published message ids. This is used for penalizing peers sending - * our own messages back if the messages are anonymous or use a random author. - */ - private readonly publishedMessageIds: SimpleTimeCache - - /** - * A message cache that contains the messages for last few heartbeat ticks - */ - private readonly mcache: MessageCache - - /** Peer score tracking */ - public readonly score: PeerScore - - /** - * Custom validator function per topic. - * Must return or resolve quickly (< 100ms) to prevent causing penalties for late messages. - * If you need to apply validation that may require longer times use `asyncValidation` option and callback the - * validation result through `Gossipsub.reportValidationResult` - */ - public readonly topicValidators = new Map() - - /** - * Make this protected so child class may want to redirect to its own log. - */ - protected readonly log: Logger - - /** - * Number of heartbeats since the beginning of time - * This allows us to amortize some resource cleanup -- eg: backoff cleanup - */ - private heartbeatTicks = 0 - - /** - * Tracks IHAVE/IWANT promises broken by peers - */ - readonly gossipTracer: IWantTracer - - private readonly components: GossipSubComponents - - private directPeerInitial: ReturnType | null = null - - public static multicodec: string = constants.GossipsubIDv11 - - // Options - readonly opts: Required - private readonly decodeRpcLimits: DecodeRPCLimits - - private readonly metrics: Metrics | null - private status: GossipStatus = { code: GossipStatusCode.stopped } - private maxInboundStreams?: number - private maxOutboundStreams?: number - private allowedTopics: Set | null - - private heartbeatTimer: { - _intervalId: ReturnType | undefined - runPeriodically: (fn: () => void, period: number) => void - cancel: () => void - } | null = null - - constructor(components: GossipSubComponents, options: Partial = {}) { - super() - - const opts = { - fallbackToFloodsub: true, - floodPublish: true, - doPX: false, - directPeers: [], - D: constants.GossipsubD, - Dlo: constants.GossipsubDlo, - Dhi: constants.GossipsubDhi, - Dscore: constants.GossipsubDscore, - Dout: constants.GossipsubDout, - Dlazy: constants.GossipsubDlazy, - heartbeatInterval: constants.GossipsubHeartbeatInterval, - fanoutTTL: constants.GossipsubFanoutTTL, - mcacheLength: constants.GossipsubHistoryLength, - mcacheGossip: constants.GossipsubHistoryGossip, - seenTTL: constants.GossipsubSeenTTL, - gossipsubIWantFollowupMs: constants.GossipsubIWantFollowupTime, - prunePeers: constants.GossipsubPrunePeers, - pruneBackoff: constants.GossipsubPruneBackoff, - graftFloodThreshold: constants.GossipsubGraftFloodThreshold, - opportunisticGraftPeers: constants.GossipsubOpportunisticGraftPeers, - opportunisticGraftTicks: constants.GossipsubOpportunisticGraftTicks, - directConnectTicks: constants.GossipsubDirectConnectTicks, - ...options, - scoreParams: createPeerScoreParams(options.scoreParams), - scoreThresholds: createPeerScoreThresholds(options.scoreThresholds) - } - - this.components = components - this.decodeRpcLimits = opts.decodeRpcLimits ?? defaultDecodeRpcLimits - - this.globalSignaturePolicy = opts.globalSignaturePolicy ?? StrictSign - - // Also wants to get notified of peers connected using floodsub - if (opts.fallbackToFloodsub) { - this.multicodecs.push(constants.FloodsubID) - } - - // From pubsub - this.log = logger(opts.debugName ?? 'libp2p:gossipsub') - - // Gossipsub - - this.opts = opts as Required - this.direct = new Set(opts.directPeers.map((p) => p.id.toString())) - this.seenCache = new SimpleTimeCache({ validityMs: opts.seenTTL }) - this.publishedMessageIds = new SimpleTimeCache({ validityMs: opts.seenTTL }) - - if (options.msgIdFn) { - // Use custom function - this.msgIdFn = options.msgIdFn - } else { - switch (this.globalSignaturePolicy) { - case StrictSign: - this.msgIdFn = msgIdFnStrictSign - break - case StrictNoSign: - this.msgIdFn = msgIdFnStrictNoSign - break - } - } - - if (options.fastMsgIdFn) { - this.fastMsgIdFn = options.fastMsgIdFn - this.fastMsgIdCache = new SimpleTimeCache({ validityMs: opts.seenTTL }) - } - - // By default, gossipsub only provide a browser friendly function to convert Uint8Array message id to string. - this.msgIdToStrFn = options.msgIdToStrFn ?? messageIdToString - - this.mcache = options.messageCache || new MessageCache(opts.mcacheGossip, opts.mcacheLength, this.msgIdToStrFn) - - if (options.dataTransform) { - this.dataTransform = options.dataTransform - } - - if (options.metricsRegister) { - if (!options.metricsTopicStrToLabel) { - throw Error('Must set metricsTopicStrToLabel with metrics') - } - - // in theory, each topic has its own meshMessageDeliveriesWindow param - // however in lodestar, we configure it mostly the same so just pick the max of positive ones - // (some topics have meshMessageDeliveriesWindow as 0) - const maxMeshMessageDeliveriesWindowMs = Math.max( - ...Object.values(opts.scoreParams.topics).map((topicParam) => topicParam.meshMessageDeliveriesWindow), - constants.DEFAULT_METRIC_MESH_MESSAGE_DELIVERIES_WINDOWS - ) - - const metrics = getMetrics(options.metricsRegister, options.metricsTopicStrToLabel, { - gossipPromiseExpireSec: this.opts.gossipsubIWantFollowupMs / 1000, - behaviourPenaltyThreshold: opts.scoreParams.behaviourPenaltyThreshold, - maxMeshMessageDeliveriesWindowSec: maxMeshMessageDeliveriesWindowMs / 1000 - }) - - metrics.mcacheSize.addCollect(() => this.onScrapeMetrics(metrics)) - for (const protocol of this.multicodecs) { - metrics.protocolsEnabled.set({ protocol }, 1) - } - - this.metrics = metrics - } else { - this.metrics = null - } - - this.gossipTracer = new IWantTracer(this.opts.gossipsubIWantFollowupMs, this.msgIdToStrFn, this.metrics) - - /** - * libp2p - */ - this.score = new PeerScore(this.opts.scoreParams, this.metrics, { - scoreCacheValidityMs: opts.heartbeatInterval - }) - - this.maxInboundStreams = options.maxInboundStreams - this.maxOutboundStreams = options.maxOutboundStreams - - this.allowedTopics = opts.allowedTopics ? new Set(opts.allowedTopics) : null - } - - getPeers(): PeerId[] { - return [...this.peers.keys()].map((str) => peerIdFromString(str)) - } - - isStarted(): boolean { - return this.status.code === GossipStatusCode.started - } - - // LIFECYCLE METHODS - - /** - * Mounts the gossipsub protocol onto the libp2p node and sends our - * our subscriptions to every peer connected - */ - async start(): Promise { - // From pubsub - if (this.isStarted()) { - return - } - - this.log('starting') - - this.publishConfig = await getPublishConfigFromPeerId(this.globalSignaturePolicy, this.components.peerId) - - // Create the outbound inflight queue - // This ensures that outbound stream creation happens sequentially - this.outboundInflightQueue = pushable({ objectMode: true }) - pipe(this.outboundInflightQueue, async (source) => { - for await (const { peerId, connection } of source) { - await this.createOutboundStream(peerId, connection) - } - }).catch((e) => this.log.error('outbound inflight queue error', e)) - - // set direct peer addresses in the address book - await Promise.all( - this.opts.directPeers.map(async (p) => { - await this.components.peerStore.merge(p.id, { - multiaddrs: p.addrs - }) - }) - ) - - const registrar = this.components.registrar - // Incoming streams - // Called after a peer dials us - await Promise.all( - this.multicodecs.map((multicodec) => - registrar.handle(multicodec, this.onIncomingStream.bind(this), { - maxInboundStreams: this.maxInboundStreams, - maxOutboundStreams: this.maxOutboundStreams - }) - ) - ) - - // # How does Gossipsub interact with libp2p? Rough guide from Mar 2022 - // - // ## Setup: - // Gossipsub requests libp2p to callback, TBD - // - // `this.libp2p.handle()` registers a handler for `/meshsub/1.1.0` and other Gossipsub protocols - // The handler callback is registered in libp2p Upgrader.protocols map. - // - // Upgrader receives an inbound connection from some transport and (`Upgrader.upgradeInbound`): - // - Adds encryption (NOISE in our case) - // - Multiplex stream - // - Create a muxer and register that for each new stream call Upgrader.protocols handler - // - // ## Topology - // - new instance of Topology (unlinked to libp2p) with handlers - // - registar.register(topology) - - // register protocol with topology - // Topology callbacks called on connection manager changes - const topology = { - onConnect: this.onPeerConnected.bind(this), - onDisconnect: this.onPeerDisconnected.bind(this) - } - const registrarTopologyIds = await Promise.all( - this.multicodecs.map((multicodec) => registrar.register(multicodec, topology)) - ) - - // Schedule to start heartbeat after `GossipsubHeartbeatInitialDelay` - const heartbeatTimeout = setTimeout(this.runHeartbeat, constants.GossipsubHeartbeatInitialDelay) - // Then, run heartbeat every `heartbeatInterval` offset by `GossipsubHeartbeatInitialDelay` - - this.status = { - code: GossipStatusCode.started, - registrarTopologyIds, - heartbeatTimeout: heartbeatTimeout, - hearbeatStartMs: Date.now() + constants.GossipsubHeartbeatInitialDelay - } - - this.score.start() - // connect to direct peers - this.directPeerInitial = setTimeout(() => { - Promise.resolve() - .then(async () => { - await Promise.all(Array.from(this.direct).map(async (id) => await this.connect(id))) - }) - .catch((err) => { - this.log(err) - }) - }, constants.GossipsubDirectConnectInitialDelay) - - this.log('started') - } - - /** - * Unmounts the gossipsub protocol and shuts down every connection - */ - async stop(): Promise { - this.log('stopping') - // From pubsub - - if (this.status.code !== GossipStatusCode.started) { - return - } - - const { registrarTopologyIds } = this.status - this.status = { code: GossipStatusCode.stopped } - - // unregister protocol and handlers - const registrar = this.components.registrar - await Promise.all(this.multicodecs.map((multicodec) => registrar.unhandle(multicodec))) - registrarTopologyIds.forEach((id) => registrar.unregister(id)) - - this.outboundInflightQueue.end() - - for (const outboundStream of this.streamsOutbound.values()) { - outboundStream.close() - } - this.streamsOutbound.clear() - - for (const inboundStream of this.streamsInbound.values()) { - inboundStream.close() - } - this.streamsInbound.clear() - - this.peers.clear() - this.subscriptions.clear() - - // Gossipsub - - if (this.heartbeatTimer) { - this.heartbeatTimer.cancel() - this.heartbeatTimer = null - } - - this.score.stop() - - this.mesh.clear() - this.fanout.clear() - this.fanoutLastpub.clear() - this.gossip.clear() - this.control.clear() - this.peerhave.clear() - this.iasked.clear() - this.backoff.clear() - this.outbound.clear() - this.gossipTracer.clear() - this.seenCache.clear() - if (this.fastMsgIdCache) this.fastMsgIdCache.clear() - if (this.directPeerInitial) clearTimeout(this.directPeerInitial) - - this.log('stopped') - } - - /** FOR DEBUG ONLY - Dump peer stats for all peers. Data is cloned, safe to mutate */ - dumpPeerScoreStats(): PeerScoreStatsDump { - return this.score.dumpPeerScoreStats() - } - - /** - * On an inbound stream opened - */ - private onIncomingStream({ stream, connection }: IncomingStreamData) { - if (!this.isStarted()) { - return - } - - const peerId = connection.remotePeer - // add peer to router - this.addPeer(peerId, connection.direction, connection.remoteAddr) - // create inbound stream - this.createInboundStream(peerId, stream) - // attempt to create outbound stream - this.outboundInflightQueue.push({ peerId, connection }) - } - - /** - * Registrar notifies an established connection with pubsub protocol - */ - private onPeerConnected(peerId: PeerId, connection: Connection): void { - this.metrics?.newConnectionCount.inc({ status: connection.status }) - // libp2p may emit a closed connection and never issue peer:disconnect event - // see https://github.com/ChainSafe/js-libp2p-gossipsub/issues/398 - if (!this.isStarted() || connection.status !== 'open') { - return - } - - this.addPeer(peerId, connection.direction, connection.remoteAddr) - this.outboundInflightQueue.push({ peerId, connection }) - } - - /** - * Registrar notifies a closing connection with pubsub protocol - */ - private onPeerDisconnected(peerId: PeerId): void { - this.log('connection ended %p', peerId) - this.removePeer(peerId) - } - - private async createOutboundStream(peerId: PeerId, connection: Connection): Promise { - if (!this.isStarted()) { - return - } - - const id = peerId.toString() - - if (!this.peers.has(id)) { - return - } - - // TODO make this behavior more robust - // This behavior is different than for inbound streams - // If an outbound stream already exists, don't create a new stream - if (this.streamsOutbound.has(id)) { - return - } - - try { - const stream = new OutboundStream( - await connection.newStream(this.multicodecs), - (e) => this.log.error('outbound pipe error', e), - { maxBufferSize: this.opts.maxOutboundBufferSize } - ) - - this.log('create outbound stream %p', peerId) - - this.streamsOutbound.set(id, stream) - - const protocol = stream.protocol - if (protocol === constants.FloodsubID) { - this.floodsubPeers.add(id) - } - this.metrics?.peersPerProtocol.inc({ protocol }, 1) - - // Immediately send own subscriptions via the newly attached stream - if (this.subscriptions.size > 0) { - this.log('send subscriptions to', id) - this.sendSubscriptions(id, Array.from(this.subscriptions), true) - } - } catch (e) { - this.log.error('createOutboundStream error', e) - } - } - - private async createInboundStream(peerId: PeerId, stream: Stream): Promise { - if (!this.isStarted()) { - return - } - - const id = peerId.toString() - - if (!this.peers.has(id)) { - return - } - - // TODO make this behavior more robust - // This behavior is different than for outbound streams - // If a peer initiates a new inbound connection - // we assume that one is the new canonical inbound stream - const priorInboundStream = this.streamsInbound.get(id) - if (priorInboundStream !== undefined) { - this.log('replacing existing inbound steam %s', id) - priorInboundStream.close() - } - - this.log('create inbound stream %s', id) - - const inboundStream = new InboundStream(stream, { maxDataLength: this.opts.maxInboundDataLength }) - this.streamsInbound.set(id, inboundStream) - - this.pipePeerReadStream(peerId, inboundStream.source).catch((err) => this.log(err)) - } - - /** - * Add a peer to the router - */ - private addPeer(peerId: PeerId, direction: ConnectionDirection, addr: Multiaddr): void { - const id = peerId.toString() - - if (!this.peers.has(id)) { - this.log('new peer %p', peerId) - - this.peers.add(id) - - // Add to peer scoring - this.score.addPeer(id) - const currentIP = multiaddrToIPStr(addr) - if (currentIP !== null) { - this.score.addIP(id, currentIP) - } else { - this.log('Added peer has no IP in current address %s %s', id, addr.toString()) - } - - // track the connection direction. Don't allow to unset outbound - if (!this.outbound.has(id)) { - this.outbound.set(id, direction === 'outbound') - } - } - } - - /** - * Removes a peer from the router - */ - private removePeer(peerId: PeerId): void { - const id = peerId.toString() - - if (!this.peers.has(id)) { - return - } - - // delete peer - this.log('delete peer %p', peerId) - this.peers.delete(id) - - const outboundStream = this.streamsOutbound.get(id) - const inboundStream = this.streamsInbound.get(id) - - if (outboundStream) { - this.metrics?.peersPerProtocol.inc({ protocol: outboundStream.protocol }, -1) - } - - // close streams - outboundStream?.close() - inboundStream?.close() - - // remove streams - this.streamsOutbound.delete(id) - this.streamsInbound.delete(id) - - // remove peer from topics map - for (const peers of this.topics.values()) { - peers.delete(id) - } - - // Remove this peer from the mesh - for (const [topicStr, peers] of this.mesh) { - if (peers.delete(id) === true) { - this.metrics?.onRemoveFromMesh(topicStr, ChurnReason.Dc, 1) - } - } - - // Remove this peer from the fanout - for (const peers of this.fanout.values()) { - peers.delete(id) - } - - // Remove from floodsubPeers - this.floodsubPeers.delete(id) - // Remove from gossip mapping - this.gossip.delete(id) - // Remove from control mapping - this.control.delete(id) - // Remove from backoff mapping - this.outbound.delete(id) - - // Remove from peer scoring - this.score.removePeer(id) - - this.acceptFromWhitelist.delete(id) - } - - // API METHODS - - get started(): boolean { - return this.status.code === GossipStatusCode.started - } - - /** - * Get a the peer-ids in a topic mesh - */ - getMeshPeers(topic: TopicStr): PeerIdStr[] { - const peersInTopic = this.mesh.get(topic) - return peersInTopic ? Array.from(peersInTopic) : [] - } - - /** - * Get a list of the peer-ids that are subscribed to one topic. - */ - getSubscribers(topic: TopicStr): PeerId[] { - const peersInTopic = this.topics.get(topic) - return (peersInTopic ? Array.from(peersInTopic) : []).map((str) => peerIdFromString(str)) - } - - /** - * Get the list of topics which the peer is subscribed to. - */ - getTopics(): TopicStr[] { - return Array.from(this.subscriptions) - } - - // TODO: Reviewing Pubsub API - - // MESSAGE METHODS - - /** - * Responsible for processing each RPC message received by other peers. - */ - private async pipePeerReadStream(peerId: PeerId, stream: AsyncIterable): Promise { - try { - await pipe(stream, async (source) => { - for await (const data of source) { - try { - // TODO: Check max gossip message size, before decodeRpc() - const rpcBytes = data.subarray() - // Note: This function may throw, it must be wrapped in a try {} catch {} to prevent closing the stream. - // TODO: What should we do if the entire RPC is invalid? - const rpc = decodeRpc(rpcBytes, this.decodeRpcLimits) - - this.metrics?.onRpcRecv(rpc, rpcBytes.length) - - // Since processRpc may be overridden entirely in unsafe ways, - // the simplest/safest option here is to wrap in a function and capture all errors - // to prevent a top-level unhandled exception - // This processing of rpc messages should happen without awaiting full validation/execution of prior messages - if (this.opts.awaitRpcHandler) { - try { - await this.handleReceivedRpc(peerId, rpc) - } catch (err) { - this.metrics?.onRpcRecvError() - this.log(err) - } - } else { - this.handleReceivedRpc(peerId, rpc).catch((err) => { - this.metrics?.onRpcRecvError() - this.log(err) - }) - } - } catch (e) { - this.metrics?.onRpcDataError() - this.log(e as Error) - } - } - }) - } catch (err) { - this.metrics?.onPeerReadStreamError() - this.handlePeerReadStreamError(err as Error, peerId) - } - } - - /** - * Handle error when read stream pipe throws, less of the functional use but more - * to for testing purposes to spy on the error handling - * */ - private handlePeerReadStreamError(err: Error, peerId: PeerId): void { - this.log.error(err) - this.onPeerDisconnected(peerId) - } - - /** - * Handles an rpc request from a peer - */ - public async handleReceivedRpc(from: PeerId, rpc: IRPC): Promise { - // Check if peer is graylisted in which case we ignore the event - if (!this.acceptFrom(from.toString())) { - this.log('received message from unacceptable peer %p', from) - this.metrics?.rpcRecvNotAccepted.inc() - return - } - - const subscriptions = rpc.subscriptions ? rpc.subscriptions.length : 0 - const messages = rpc.messages ? rpc.messages.length : 0 - let ihave = 0 - let iwant = 0 - let graft = 0 - let prune = 0 - if (rpc.control) { - if (rpc.control.ihave) ihave = rpc.control.ihave.length - if (rpc.control.iwant) iwant = rpc.control.iwant.length - if (rpc.control.graft) graft = rpc.control.graft.length - if (rpc.control.prune) prune = rpc.control.prune.length - } - this.log( - `rpc.from ${from.toString()} subscriptions ${subscriptions} messages ${messages} ihave ${ihave} iwant ${iwant} graft ${graft} prune ${prune}` - ) - - // Handle received subscriptions - if (rpc.subscriptions && rpc.subscriptions.length > 0) { - // update peer subscriptions - - const subscriptions: { topic: TopicStr; subscribe: boolean }[] = [] - - rpc.subscriptions.forEach((subOpt) => { - const topic = subOpt.topic - const subscribe = subOpt.subscribe === true - - if (topic != null) { - if (this.allowedTopics && !this.allowedTopics.has(topic)) { - // Not allowed: subscription data-structures are not bounded by topic count - // TODO: Should apply behaviour penalties? - return - } - - this.handleReceivedSubscription(from, topic, subscribe) - - subscriptions.push({ topic, subscribe }) - } - }) - - this.dispatchEvent( - new CustomEvent('subscription-change', { - detail: { peerId: from, subscriptions } - }) - ) - } - - // Handle messages - // TODO: (up to limit) - if (rpc.messages) { - for (const message of rpc.messages) { - if (this.allowedTopics && !this.allowedTopics.has(message.topic)) { - // Not allowed: message cache data-structures are not bounded by topic count - // TODO: Should apply behaviour penalties? - continue - } - - const handleReceivedMessagePromise = this.handleReceivedMessage(from, message) - // Should never throw, but handle just in case - .catch((err) => { - this.metrics?.onMsgRecvError(message.topic) - this.log(err) - }) - - if (this.opts.awaitRpcMessageHandler) { - await handleReceivedMessagePromise - } - } - } - - // Handle control messages - if (rpc.control) { - await this.handleControlMessage(from.toString(), rpc.control) - } - } - - /** - * Handles a subscription change from a peer - */ - private handleReceivedSubscription(from: PeerId, topic: TopicStr, subscribe: boolean): void { - this.log('subscription update from %p topic %s', from, topic) - - let topicSet = this.topics.get(topic) - if (topicSet == null) { - topicSet = new Set() - this.topics.set(topic, topicSet) - } - - if (subscribe) { - // subscribe peer to new topic - topicSet.add(from.toString()) - } else { - // unsubscribe from existing topic - topicSet.delete(from.toString()) - } - - // TODO: rust-libp2p has A LOT more logic here - } - - /** - * Handles a newly received message from an RPC. - * May forward to all peers in the mesh. - */ - private async handleReceivedMessage(from: PeerId, rpcMsg: RPC.IMessage): Promise { - this.metrics?.onMsgRecvPreValidation(rpcMsg.topic) - - const validationResult = await this.validateReceivedMessage(from, rpcMsg) - - this.metrics?.onMsgRecvResult(rpcMsg.topic, validationResult.code) - - switch (validationResult.code) { - case MessageStatus.duplicate: - // Report the duplicate - this.score.duplicateMessage(from.toString(), validationResult.msgIdStr, rpcMsg.topic) - // due to the collision of fastMsgIdFn, 2 different messages may end up the same fastMsgId - // so we need to also mark the duplicate message as delivered or the promise is not resolved - // and peer gets penalized. See https://github.com/ChainSafe/js-libp2p-gossipsub/pull/385 - this.gossipTracer.deliverMessage(validationResult.msgIdStr, true) - this.mcache.observeDuplicate(validationResult.msgIdStr, from.toString()) - return - - case MessageStatus.invalid: - // invalid messages received - // metrics.register_invalid_message(&raw_message.topic) - // Tell peer_score about reject - // Reject the original source, and any duplicates we've seen from other peers. - if (validationResult.msgIdStr) { - const msgIdStr = validationResult.msgIdStr - this.score.rejectMessage(from.toString(), msgIdStr, rpcMsg.topic, validationResult.reason) - this.gossipTracer.rejectMessage(msgIdStr, validationResult.reason) - } else { - this.score.rejectInvalidMessage(from.toString(), rpcMsg.topic) - } - - this.metrics?.onMsgRecvInvalid(rpcMsg.topic, validationResult) - return - - case MessageStatus.valid: - // Tells score that message arrived (but is maybe not fully validated yet). - // Consider the message as delivered for gossip promises. - this.score.validateMessage(validationResult.messageId.msgIdStr) - this.gossipTracer.deliverMessage(validationResult.messageId.msgIdStr) - - // Add the message to our memcache - // if no validation is required, mark the message as validated - this.mcache.put(validationResult.messageId, rpcMsg, !this.opts.asyncValidation) - - // Dispatch the message to the user if we are subscribed to the topic - if (this.subscriptions.has(rpcMsg.topic)) { - const isFromSelf = this.components.peerId.equals(from) - - if (!isFromSelf || this.opts.emitSelf) { - super.dispatchEvent( - new CustomEvent('gossipsub:message', { - detail: { - propagationSource: from, - msgId: validationResult.messageId.msgIdStr, - msg: validationResult.msg - } - }) - ) - // TODO: Add option to switch between emit per topic or all messages in one - super.dispatchEvent(new CustomEvent('message', { detail: validationResult.msg })) - } - } - - // Forward the message to mesh peers, if no validation is required - // If asyncValidation is ON, expect the app layer to call reportMessageValidationResult(), then forward - if (!this.opts.asyncValidation) { - // TODO: in rust-libp2p - // .forward_msg(&msg_id, raw_message, Some(propagation_source)) - this.forwardMessage(validationResult.messageId.msgIdStr, rpcMsg, from.toString()) - } - } - } - - /** - * Handles a newly received message from an RPC. - * May forward to all peers in the mesh. - */ - private async validateReceivedMessage( - propagationSource: PeerId, - rpcMsg: RPC.IMessage - ): Promise { - // Fast message ID stuff - const fastMsgIdStr = this.fastMsgIdFn?.(rpcMsg) - const msgIdCached = fastMsgIdStr !== undefined ? this.fastMsgIdCache?.get(fastMsgIdStr) : undefined - - if (msgIdCached) { - // This message has been seen previously. Ignore it - return { code: MessageStatus.duplicate, msgIdStr: msgIdCached } - } - - // Perform basic validation on message and convert to RawGossipsubMessage for fastMsgIdFn() - const validationResult = await validateToRawMessage(this.globalSignaturePolicy, rpcMsg) - - if (!validationResult.valid) { - return { code: MessageStatus.invalid, reason: RejectReason.Error, error: validationResult.error } - } - - const msg = validationResult.message - - // Try and perform the data transform to the message. If it fails, consider it invalid. - try { - if (this.dataTransform) { - msg.data = this.dataTransform.inboundTransform(rpcMsg.topic, msg.data) - } - } catch (e) { - this.log('Invalid message, transform failed', e) - return { code: MessageStatus.invalid, reason: RejectReason.Error, error: ValidateError.TransformFailed } - } - - // TODO: Check if message is from a blacklisted source or propagation origin - // - Reject any message from a blacklisted peer - // - Also reject any message that originated from a blacklisted peer - // - reject messages claiming to be from ourselves but not locally published - - // Calculate the message id on the transformed data. - const msgId = await this.msgIdFn(msg) - const msgIdStr = this.msgIdToStrFn(msgId) - const messageId = { msgId, msgIdStr } - - // Add the message to the duplicate caches - if (fastMsgIdStr !== undefined && this.fastMsgIdCache) { - const collision = this.fastMsgIdCache.put(fastMsgIdStr, msgIdStr) - if (collision) { - this.metrics?.fastMsgIdCacheCollision.inc() - } - } - - if (this.seenCache.has(msgIdStr)) { - return { code: MessageStatus.duplicate, msgIdStr } - } else { - this.seenCache.put(msgIdStr) - } - - // (Optional) Provide custom validation here with dynamic validators per topic - // NOTE: This custom topicValidator() must resolve fast (< 100ms) to allow scores - // to not penalize peers for long validation times. - const topicValidator = this.topicValidators.get(rpcMsg.topic) - if (topicValidator != null) { - let acceptance: TopicValidatorResult - // Use try {} catch {} in case topicValidator() is synchronous - try { - acceptance = await topicValidator(propagationSource, msg) - } catch (e) { - const errCode = (e as { code: string }).code - if (errCode === constants.ERR_TOPIC_VALIDATOR_IGNORE) acceptance = TopicValidatorResult.Ignore - if (errCode === constants.ERR_TOPIC_VALIDATOR_REJECT) acceptance = TopicValidatorResult.Reject - else acceptance = TopicValidatorResult.Ignore - } - - if (acceptance !== TopicValidatorResult.Accept) { - return { code: MessageStatus.invalid, reason: rejectReasonFromAcceptance(acceptance), msgIdStr } - } - } - - return { code: MessageStatus.valid, messageId, msg } - } - - /** - * Return score of a peer. - */ - getScore(peerId: PeerIdStr): number { - return this.score.score(peerId) - } - - /** - * Send an rpc object to a peer with subscriptions - */ - private sendSubscriptions(toPeer: PeerIdStr, topics: string[], subscribe: boolean): void { - this.sendRpc(toPeer, { - subscriptions: topics.map((topic) => ({ topic, subscribe })) - }) - } - - /** - * Handles an rpc control message from a peer - */ - private async handleControlMessage(id: PeerIdStr, controlMsg: RPC.IControlMessage): Promise { - if (controlMsg === undefined) { - return - } - - const iwant = controlMsg.ihave ? this.handleIHave(id, controlMsg.ihave) : [] - const ihave = controlMsg.iwant ? this.handleIWant(id, controlMsg.iwant) : [] - const prune = controlMsg.graft ? await this.handleGraft(id, controlMsg.graft) : [] - controlMsg.prune && (await this.handlePrune(id, controlMsg.prune)) - - if (!iwant.length && !ihave.length && !prune.length) { - return - } - - const sent = this.sendRpc(id, { messages: ihave, control: { iwant, prune } }) - const iwantMessageIds = iwant[0]?.messageIDs - if (iwantMessageIds) { - if (sent) { - this.gossipTracer.addPromise(id, iwantMessageIds) - } else { - this.metrics?.iwantPromiseUntracked.inc(1) - } - } - } - - /** - * Whether to accept a message from a peer - */ - public acceptFrom(id: PeerIdStr): boolean { - if (this.direct.has(id)) { - return true - } - - const now = Date.now() - const entry = this.acceptFromWhitelist.get(id) - - if (entry && entry.messagesAccepted < ACCEPT_FROM_WHITELIST_MAX_MESSAGES && entry.acceptUntil >= now) { - entry.messagesAccepted += 1 - return true - } - - const score = this.score.score(id) - if (score >= ACCEPT_FROM_WHITELIST_THRESHOLD_SCORE) { - // peer is unlikely to be able to drop its score to `graylistThreshold` - // after 128 messages or 1s - this.acceptFromWhitelist.set(id, { - messagesAccepted: 0, - acceptUntil: now + ACCEPT_FROM_WHITELIST_DURATION_MS - }) - } else { - this.acceptFromWhitelist.delete(id) - } - - return score >= this.opts.scoreThresholds.graylistThreshold - } - - /** - * Handles IHAVE messages - */ - private handleIHave(id: PeerIdStr, ihave: RPC.IControlIHave[]): RPC.IControlIWant[] { - if (!ihave.length) { - return [] - } - - // we ignore IHAVE gossip from any peer whose score is below the gossips threshold - const score = this.score.score(id) - if (score < this.opts.scoreThresholds.gossipThreshold) { - this.log('IHAVE: ignoring peer %s with score below threshold [ score = %d ]', id, score) - this.metrics?.ihaveRcvIgnored.inc({ reason: IHaveIgnoreReason.LowScore }) - return [] - } - - // IHAVE flood protection - const peerhave = (this.peerhave.get(id) ?? 0) + 1 - this.peerhave.set(id, peerhave) - if (peerhave > constants.GossipsubMaxIHaveMessages) { - this.log( - 'IHAVE: peer %s has advertised too many times (%d) within this heartbeat interval; ignoring', - id, - peerhave - ) - this.metrics?.ihaveRcvIgnored.inc({ reason: IHaveIgnoreReason.MaxIhave }) - return [] - } - - const iasked = this.iasked.get(id) ?? 0 - if (iasked >= constants.GossipsubMaxIHaveLength) { - this.log('IHAVE: peer %s has already advertised too many messages (%d); ignoring', id, iasked) - this.metrics?.ihaveRcvIgnored.inc({ reason: IHaveIgnoreReason.MaxIasked }) - return [] - } - - // string msgId => msgId - const iwant = new Map() - - ihave.forEach(({ topicID, messageIDs }) => { - if (!topicID || !messageIDs || !this.mesh.has(topicID)) { - return - } - - let idonthave = 0 - - messageIDs.forEach((msgId) => { - const msgIdStr = this.msgIdToStrFn(msgId) - if (!this.seenCache.has(msgIdStr)) { - iwant.set(msgIdStr, msgId) - idonthave++ - } - }) - - this.metrics?.onIhaveRcv(topicID, messageIDs.length, idonthave) - }) - - if (!iwant.size) { - return [] - } - - let iask = iwant.size - if (iask + iasked > constants.GossipsubMaxIHaveLength) { - iask = constants.GossipsubMaxIHaveLength - iasked - } - - this.log('IHAVE: Asking for %d out of %d messages from %s', iask, iwant.size, id) - - let iwantList = Array.from(iwant.values()) - // ask in random order - shuffle(iwantList) - - // truncate to the messages we are actually asking for and update the iasked counter - iwantList = iwantList.slice(0, iask) - this.iasked.set(id, iasked + iask) - - // do not add gossipTracer promise here until a successful sendRpc() - - return [ - { - messageIDs: iwantList - } - ] - } - - /** - * Handles IWANT messages - * Returns messages to send back to peer - */ - private handleIWant(id: PeerIdStr, iwant: RPC.IControlIWant[]): RPC.IMessage[] { - if (!iwant.length) { - return [] - } - - // we don't respond to IWANT requests from any per whose score is below the gossip threshold - const score = this.score.score(id) - if (score < this.opts.scoreThresholds.gossipThreshold) { - this.log('IWANT: ignoring peer %s with score below threshold [score = %d]', id, score) - return [] - } - - const ihave = new Map() - const iwantByTopic = new Map() - let iwantDonthave = 0 - - iwant.forEach(({ messageIDs }) => { - messageIDs && - messageIDs.forEach((msgId) => { - const msgIdStr = this.msgIdToStrFn(msgId) - const entry = this.mcache.getWithIWantCount(msgIdStr, id) - if (entry == null) { - iwantDonthave++ - return - } - - iwantByTopic.set(entry.msg.topic, 1 + (iwantByTopic.get(entry.msg.topic) ?? 0)) - - if (entry.count > constants.GossipsubGossipRetransmission) { - this.log('IWANT: Peer %s has asked for message %s too many times: ignoring request', id, msgId) - return - } - - ihave.set(msgIdStr, entry.msg) - }) - }) - - this.metrics?.onIwantRcv(iwantByTopic, iwantDonthave) - - if (!ihave.size) { - this.log('IWANT: Could not provide any wanted messages to %s', id) - return [] - } - - this.log('IWANT: Sending %d messages to %s', ihave.size, id) - - return Array.from(ihave.values()) - } - - /** - * Handles Graft messages - */ - private async handleGraft(id: PeerIdStr, graft: RPC.IControlGraft[]): Promise { - const prune: TopicStr[] = [] - const score = this.score.score(id) - const now = Date.now() - let doPX = this.opts.doPX - - graft.forEach(({ topicID }) => { - if (!topicID) { - return - } - const peersInMesh = this.mesh.get(topicID) - if (!peersInMesh) { - // don't do PX when there is an unknown topic to avoid leaking our peers - doPX = false - // spam hardening: ignore GRAFTs for unknown topics - return - } - - // check if peer is already in the mesh; if so do nothing - if (peersInMesh.has(id)) { - return - } - - // we don't GRAFT to/from direct peers; complain loudly if this happens - if (this.direct.has(id)) { - this.log('GRAFT: ignoring request from direct peer %s', id) - // this is possibly a bug from a non-reciprical configuration; send a PRUNE - prune.push(topicID) - // but don't px - doPX = false - return - } - - // make sure we are not backing off that peer - const expire = this.backoff.get(topicID)?.get(id) - if (typeof expire === 'number' && now < expire) { - this.log('GRAFT: ignoring backed off peer %s', id) - // add behavioral penalty - this.score.addPenalty(id, 1, ScorePenalty.GraftBackoff) - // no PX - doPX = false - // check the flood cutoff -- is the GRAFT coming too fast? - const floodCutoff = expire + this.opts.graftFloodThreshold - this.opts.pruneBackoff - if (now < floodCutoff) { - // extra penalty - this.score.addPenalty(id, 1, ScorePenalty.GraftBackoff) - } - // refresh the backoff - this.addBackoff(id, topicID) - prune.push(topicID) - return - } - - // check the score - if (score < 0) { - // we don't GRAFT peers with negative score - this.log('GRAFT: ignoring peer %s with negative score: score=%d, topic=%s', id, score, topicID) - // we do send them PRUNE however, because it's a matter of protocol correctness - prune.push(topicID) - // but we won't PX to them - doPX = false - // add/refresh backoff so that we don't reGRAFT too early even if the score decays - this.addBackoff(id, topicID) - return - } - - // check the number of mesh peers; if it is at (or over) Dhi, we only accept grafts - // from peers with outbound connections; this is a defensive check to restrict potential - // mesh takeover attacks combined with love bombing - if (peersInMesh.size >= this.opts.Dhi && !this.outbound.get(id)) { - prune.push(topicID) - this.addBackoff(id, topicID) - return - } - - this.log('GRAFT: Add mesh link from %s in %s', id, topicID) - this.score.graft(id, topicID) - peersInMesh.add(id) - - this.metrics?.onAddToMesh(topicID, InclusionReason.Subscribed, 1) - }) - - if (!prune.length) { - return [] - } - - return await Promise.all(prune.map((topic) => this.makePrune(id, topic, doPX))) - } - - /** - * Handles Prune messages - */ - private async handlePrune(id: PeerIdStr, prune: RPC.IControlPrune[]): Promise { - const score = this.score.score(id) - - for (const { topicID, backoff, peers } of prune) { - if (topicID == null) { - continue - } - - const peersInMesh = this.mesh.get(topicID) - if (!peersInMesh) { - return - } - - this.log('PRUNE: Remove mesh link to %s in %s', id, topicID) - this.score.prune(id, topicID) - if (peersInMesh.has(id)) { - peersInMesh.delete(id) - this.metrics?.onRemoveFromMesh(topicID, ChurnReason.Unsub, 1) - } - - // is there a backoff specified by the peer? if so obey it - if (typeof backoff === 'number' && backoff > 0) { - this.doAddBackoff(id, topicID, backoff * 1000) - } else { - this.addBackoff(id, topicID) - } - - // PX - if (peers && peers.length) { - // we ignore PX from peers with insufficient scores - if (score < this.opts.scoreThresholds.acceptPXThreshold) { - this.log( - 'PRUNE: ignoring PX from peer %s with insufficient score [score = %d, topic = %s]', - id, - score, - topicID - ) - continue - } - await this.pxConnect(peers) - } - } - } - - /** - * Add standard backoff log for a peer in a topic - */ - private addBackoff(id: PeerIdStr, topic: TopicStr): void { - this.doAddBackoff(id, topic, this.opts.pruneBackoff) - } - - /** - * Add backoff expiry interval for a peer in a topic - * - * @param id - * @param topic - * @param interval - backoff duration in milliseconds - */ - private doAddBackoff(id: PeerIdStr, topic: TopicStr, interval: number): void { - let backoff = this.backoff.get(topic) - if (!backoff) { - backoff = new Map() - this.backoff.set(topic, backoff) - } - const expire = Date.now() + interval - const existingExpire = backoff.get(id) ?? 0 - if (existingExpire < expire) { - backoff.set(id, expire) - } - } - - /** - * Apply penalties from broken IHAVE/IWANT promises - */ - private applyIwantPenalties(): void { - this.gossipTracer.getBrokenPromises().forEach((count, p) => { - this.log("peer %s didn't follow up in %d IWANT requests; adding penalty", p, count) - this.score.addPenalty(p, count, ScorePenalty.BrokenPromise) - }) - } - - /** - * Clear expired backoff expiries - */ - private clearBackoff(): void { - // we only clear once every GossipsubPruneBackoffTicks ticks to avoid iterating over the maps too much - if (this.heartbeatTicks % constants.GossipsubPruneBackoffTicks !== 0) { - return - } - - const now = Date.now() - this.backoff.forEach((backoff, topic) => { - backoff.forEach((expire, id) => { - if (expire < now) { - backoff.delete(id) - } - }) - if (backoff.size === 0) { - this.backoff.delete(topic) - } - }) - } - - /** - * Maybe reconnect to direct peers - */ - private async directConnect(): Promise { - const toconnect: string[] = [] - this.direct.forEach((id) => { - if (!this.streamsOutbound.has(id)) { - toconnect.push(id) - } - }) - - await Promise.all(toconnect.map(async (id) => await this.connect(id))) - } - - /** - * Maybe attempt connection given signed peer records - */ - private async pxConnect(peers: RPC.IPeerInfo[]): Promise { - if (peers.length > this.opts.prunePeers) { - shuffle(peers) - peers = peers.slice(0, this.opts.prunePeers) - } - const toconnect: string[] = [] - - await Promise.all( - peers.map(async (pi) => { - if (!pi.peerID) { - return - } - - const peer = peerIdFromBytes(pi.peerID) - const p = peer.toString() - - if (this.peers.has(p)) { - return - } - - if (!pi.signedPeerRecord) { - toconnect.push(p) - return - } - - // The peer sent us a signed record - // This is not a record from the peer who sent the record, but another peer who is connected with it - // Ensure that it is valid - try { - if (!(await this.components.peerStore.consumePeerRecord(pi.signedPeerRecord, peer))) { - this.log('bogus peer record obtained through px: could not add peer record to address book') - return - } - toconnect.push(p) - } catch (e) { - this.log('bogus peer record obtained through px: invalid signature or not a peer record') - } - }) - ) - - if (!toconnect.length) { - return - } - - await Promise.all(toconnect.map(async (id) => await this.connect(id))) - } - - /** - * Connect to a peer using the gossipsub protocol - */ - private async connect(id: PeerIdStr): Promise { - this.log('Initiating connection with %s', id) - const peerId = peerIdFromString(id) - const connection = await this.components.connectionManager.openConnection(peerId) - for (const multicodec of this.multicodecs) { - for (const topology of this.components.registrar.getTopologies(multicodec)) { - topology.onConnect?.(peerId, connection) - } - } - } - - /** - * Subscribes to a topic - */ - subscribe(topic: TopicStr): void { - if (this.status.code !== GossipStatusCode.started) { - throw new Error('Pubsub has not started') - } - - if (!this.subscriptions.has(topic)) { - this.subscriptions.add(topic) - - for (const peerId of this.peers.keys()) { - this.sendSubscriptions(peerId, [topic], true) - } - } - - this.join(topic) - } - - /** - * Unsubscribe to a topic - */ - unsubscribe(topic: TopicStr): void { - if (this.status.code !== GossipStatusCode.started) { - throw new Error('Pubsub is not started') - } - - const wasSubscribed = this.subscriptions.delete(topic) - - this.log('unsubscribe from %s - am subscribed %s', topic, wasSubscribed) - - if (wasSubscribed) { - for (const peerId of this.peers.keys()) { - this.sendSubscriptions(peerId, [topic], false) - } - } - - this.leave(topic) - } - - /** - * Join topic - */ - private join(topic: TopicStr): void { - if (this.status.code !== GossipStatusCode.started) { - throw new Error('Gossipsub has not started') - } - - // if we are already in the mesh, return - if (this.mesh.has(topic)) { - return - } - - this.log('JOIN %s', topic) - this.metrics?.onJoin(topic) - - const toAdd = new Set() - - // check if we have mesh_n peers in fanout[topic] and add them to the mesh if we do, - // removing the fanout entry. - const fanoutPeers = this.fanout.get(topic) - if (fanoutPeers) { - // Remove fanout entry and the last published time - this.fanout.delete(topic) - this.fanoutLastpub.delete(topic) - - // remove explicit peers, peers with negative scores, and backoffed peers - fanoutPeers.forEach((id) => { - // TODO:rust-libp2p checks `self.backoffs.is_backoff_with_slack()` - if (!this.direct.has(id) && this.score.score(id) >= 0) { - toAdd.add(id) - } - }) - - this.metrics?.onAddToMesh(topic, InclusionReason.Fanout, toAdd.size) - } - - // check if we need to get more peers, which we randomly select - if (toAdd.size < this.opts.D) { - const fanoutCount = toAdd.size - const newPeers = this.getRandomGossipPeers( - topic, - this.opts.D, - (id: PeerIdStr): boolean => - // filter direct peers and peers with negative score - !toAdd.has(id) && !this.direct.has(id) && this.score.score(id) >= 0 - ) - - newPeers.forEach((peer) => { - toAdd.add(peer) - }) - - this.metrics?.onAddToMesh(topic, InclusionReason.Random, toAdd.size - fanoutCount) - } - - this.mesh.set(topic, toAdd) - - toAdd.forEach((id) => { - this.log('JOIN: Add mesh link to %s in %s', id, topic) - this.sendGraft(id, topic) - - // rust-libp2p - // - peer_score.graft() - // - Self::control_pool_add() - // - peer_added_to_mesh() - }) - } - - /** - * Leave topic - */ - private leave(topic: TopicStr): void { - if (this.status.code !== GossipStatusCode.started) { - throw new Error('Gossipsub has not started') - } - - this.log('LEAVE %s', topic) - this.metrics?.onLeave(topic) - - // Send PRUNE to mesh peers - const meshPeers = this.mesh.get(topic) - if (meshPeers) { - Promise.all( - Array.from(meshPeers).map(async (id) => { - this.log('LEAVE: Remove mesh link to %s in %s', id, topic) - return await this.sendPrune(id, topic) - }) - ).catch((err) => { - this.log('Error sending prunes to mesh peers', err) - }) - this.mesh.delete(topic) - } - } - - private selectPeersToForward(topic: TopicStr, propagationSource?: PeerIdStr, excludePeers?: Set) { - const tosend = new Set() - - // Add explicit peers - const peersInTopic = this.topics.get(topic) - if (peersInTopic) { - this.direct.forEach((peer) => { - if (peersInTopic.has(peer) && propagationSource !== peer && !excludePeers?.has(peer)) { - tosend.add(peer) - } - }) - - // As of Mar 2022, spec + golang-libp2p include this while rust-libp2p does not - // rust-libp2p: https://github.com/libp2p/rust-libp2p/blob/6cc3b4ec52c922bfcf562a29b5805c3150e37c75/protocols/gossipsub/src/behaviour.rs#L2693 - // spec: https://github.com/libp2p/specs/blob/10712c55ab309086a52eec7d25f294df4fa96528/pubsub/gossipsub/gossipsub-v1.0.md?plain=1#L361 - this.floodsubPeers.forEach((peer) => { - if ( - peersInTopic.has(peer) && - propagationSource !== peer && - !excludePeers?.has(peer) && - this.score.score(peer) >= this.opts.scoreThresholds.publishThreshold - ) { - tosend.add(peer) - } - }) - } - - // add mesh peers - const meshPeers = this.mesh.get(topic) - if (meshPeers && meshPeers.size > 0) { - meshPeers.forEach((peer) => { - if (propagationSource !== peer && !excludePeers?.has(peer)) { - tosend.add(peer) - } - }) - } - - return tosend - } - - private selectPeersToPublish(topic: TopicStr): { - tosend: Set - tosendCount: ToSendGroupCount - } { - const tosend = new Set() - const tosendCount: ToSendGroupCount = { - direct: 0, - floodsub: 0, - mesh: 0, - fanout: 0 - } - - const peersInTopic = this.topics.get(topic) - if (peersInTopic) { - // flood-publish behavior - // send to direct peers and _all_ peers meeting the publishThreshold - if (this.opts.floodPublish) { - peersInTopic.forEach((id) => { - if (this.direct.has(id)) { - tosend.add(id) - tosendCount.direct++ - } else if (this.score.score(id) >= this.opts.scoreThresholds.publishThreshold) { - tosend.add(id) - tosendCount.floodsub++ - } - }) - } else { - // non-flood-publish behavior - // send to direct peers, subscribed floodsub peers - // and some mesh peers above publishThreshold - - // direct peers (if subscribed) - this.direct.forEach((id) => { - if (peersInTopic.has(id)) { - tosend.add(id) - tosendCount.direct++ - } - }) - - // floodsub peers - // Note: if there are no floodsub peers, we save a loop through peersInTopic Map - this.floodsubPeers.forEach((id) => { - if (peersInTopic.has(id) && this.score.score(id) >= this.opts.scoreThresholds.publishThreshold) { - tosend.add(id) - tosendCount.floodsub++ - } - }) - - // Gossipsub peers handling - const meshPeers = this.mesh.get(topic) - if (meshPeers && meshPeers.size > 0) { - meshPeers.forEach((peer) => { - tosend.add(peer) - tosendCount.mesh++ - }) - } - - // We are not in the mesh for topic, use fanout peers - else { - const fanoutPeers = this.fanout.get(topic) - if (fanoutPeers && fanoutPeers.size > 0) { - fanoutPeers.forEach((peer) => { - tosend.add(peer) - tosendCount.fanout++ - }) - } - - // We have no fanout peers, select mesh_n of them and add them to the fanout - else { - // If we are not in the fanout, then pick peers in topic above the publishThreshold - const newFanoutPeers = this.getRandomGossipPeers(topic, this.opts.D, (id) => { - return this.score.score(id) >= this.opts.scoreThresholds.publishThreshold - }) - - if (newFanoutPeers.size > 0) { - // eslint-disable-line max-depth - this.fanout.set(topic, newFanoutPeers) - - newFanoutPeers.forEach((peer) => { - // eslint-disable-line max-depth - tosend.add(peer) - tosendCount.fanout++ - }) - } - } - - // We are publishing to fanout peers - update the time we published - this.fanoutLastpub.set(topic, Date.now()) - } - } - } - - return { tosend, tosendCount } - } - - /** - * Forwards a message from our peers. - * - * For messages published by us (the app layer), this class uses `publish` - */ - private forwardMessage( - msgIdStr: string, - rawMsg: RPC.IMessage, - propagationSource?: PeerIdStr, - excludePeers?: Set - ): void { - // message is fully validated inform peer_score - if (propagationSource) { - this.score.deliverMessage(propagationSource, msgIdStr, rawMsg.topic) - } - - const tosend = this.selectPeersToForward(rawMsg.topic, propagationSource, excludePeers) - - // Note: Don't throw if tosend is empty, we can have a mesh with a single peer - - // forward the message to peers - tosend.forEach((id) => { - // sendRpc may mutate RPC message on piggyback, create a new message for each peer - this.sendRpc(id, { messages: [rawMsg] }) - }) - - this.metrics?.onForwardMsg(rawMsg.topic, tosend.size) - } - - /** - * App layer publishes a message to peers, return number of peers this message is published to - * Note: `async` due to crypto only if `StrictSign`, otherwise it's a sync fn. - * - * For messages not from us, this class uses `forwardMessage`. - */ - async publish(topic: TopicStr, data: Uint8Array, opts?: PublishOpts): Promise { - const transformedData = this.dataTransform ? this.dataTransform.outboundTransform(topic, data) : data - - if (this.publishConfig == null) { - throw Error('PublishError.Uninitialized') - } - - // Prepare raw message with user's publishConfig - const { raw: rawMsg, msg } = await buildRawMessage(this.publishConfig, topic, data, transformedData) - - // calculate the message id from the un-transformed data - const msgId = await this.msgIdFn(msg) - const msgIdStr = this.msgIdToStrFn(msgId) - - // Current publish opt takes precedence global opts, while preserving false value - const ignoreDuplicatePublishError = opts?.ignoreDuplicatePublishError ?? this.opts.ignoreDuplicatePublishError - - if (this.seenCache.has(msgIdStr)) { - // This message has already been seen. We don't re-publish messages that have already - // been published on the network. - if (ignoreDuplicatePublishError) { - this.metrics?.onPublishDuplicateMsg(topic) - return { recipients: [] } - } - throw Error('PublishError.Duplicate') - } - - const { tosend, tosendCount } = this.selectPeersToPublish(topic) - const willSendToSelf = this.opts.emitSelf === true && this.subscriptions.has(topic) - - // Current publish opt takes precedence global opts, while preserving false value - const allowPublishToZeroPeers = opts?.allowPublishToZeroPeers ?? this.opts.allowPublishToZeroPeers - - if (tosend.size === 0 && !allowPublishToZeroPeers && !willSendToSelf) { - throw Error('PublishError.InsufficientPeers') - } - - // If the message isn't a duplicate and we have sent it to some peers add it to the - // duplicate cache and memcache. - this.seenCache.put(msgIdStr) - // all published messages are valid - this.mcache.put({ msgId, msgIdStr }, rawMsg, true) - - // If the message is anonymous or has a random author add it to the published message ids cache. - this.publishedMessageIds.put(msgIdStr) - - // Send to set of peers aggregated from direct, mesh, fanout - for (const id of tosend) { - // sendRpc may mutate RPC message on piggyback, create a new message for each peer - const sent = this.sendRpc(id, { messages: [rawMsg] }) - - // did not actually send the message - if (!sent) { - tosend.delete(id) - } - } - - this.metrics?.onPublishMsg(topic, tosendCount, tosend.size, rawMsg.data != null ? rawMsg.data.length : 0) - - // Dispatch the message to the user if we are subscribed to the topic - if (willSendToSelf) { - tosend.add(this.components.peerId.toString()) - - super.dispatchEvent( - new CustomEvent('gossipsub:message', { - detail: { - propagationSource: this.components.peerId, - msgId: msgIdStr, - msg - } - }) - ) - // TODO: Add option to switch between emit per topic or all messages in one - super.dispatchEvent(new CustomEvent('message', { detail: msg })) - } - - return { - recipients: Array.from(tosend.values()).map((str) => peerIdFromString(str)) - } - } - - /** - * This function should be called when `asyncValidation` is `true` after - * the message got validated by the caller. Messages are stored in the `mcache` and - * validation is expected to be fast enough that the messages should still exist in the cache. - * There are three possible validation outcomes and the outcome is given in acceptance. - * - * If acceptance = `MessageAcceptance.Accept` the message will get propagated to the - * network. The `propagation_source` parameter indicates who the message was received by and - * will not be forwarded back to that peer. - * - * If acceptance = `MessageAcceptance.Reject` the message will be deleted from the memcache - * and the P₄ penalty will be applied to the `propagationSource`. - * - * If acceptance = `MessageAcceptance.Ignore` the message will be deleted from the memcache - * but no P₄ penalty will be applied. - * - * This function will return true if the message was found in the cache and false if was not - * in the cache anymore. - * - * This should only be called once per message. - */ - reportMessageValidationResult(msgId: MsgIdStr, propagationSource: PeerIdStr, acceptance: TopicValidatorResult): void { - let cacheEntry: MessageCacheRecord | null - - if (acceptance === TopicValidatorResult.Accept) { - cacheEntry = this.mcache.validate(msgId) - - if (cacheEntry != null) { - const { message: rawMsg, originatingPeers } = cacheEntry - // message is fully validated inform peer_score - this.score.deliverMessage(propagationSource, msgId, rawMsg.topic) - - this.forwardMessage(msgId, cacheEntry.message, propagationSource, originatingPeers) - } - // else, Message not in cache. Ignoring forwarding - } - - // Not valid - else { - cacheEntry = this.mcache.remove(msgId) - - if (cacheEntry) { - const rejectReason = rejectReasonFromAcceptance(acceptance) - const { message: rawMsg, originatingPeers } = cacheEntry - - // Tell peer_score about reject - // Reject the original source, and any duplicates we've seen from other peers. - this.score.rejectMessage(propagationSource, msgId, rawMsg.topic, rejectReason) - for (const peer of originatingPeers) { - this.score.rejectMessage(peer, msgId, rawMsg.topic, rejectReason) - } - } - // else, Message not in cache. Ignoring forwarding - } - - const firstSeenTimestampMs = this.score.messageFirstSeenTimestampMs(msgId) - this.metrics?.onReportValidation(cacheEntry, acceptance, firstSeenTimestampMs) - } - - /** - * Sends a GRAFT message to a peer - */ - private sendGraft(id: PeerIdStr, topic: string): void { - const graft = [ - { - topicID: topic - } - ] - - this.sendRpc(id, { control: { graft } }) - } - - /** - * Sends a PRUNE message to a peer - */ - private async sendPrune(id: PeerIdStr, topic: string): Promise { - const prune = [await this.makePrune(id, topic, this.opts.doPX)] - - this.sendRpc(id, { control: { prune } }) - } - - /** - * Send an rpc object to a peer - */ - private sendRpc(id: PeerIdStr, rpc: IRPC): boolean { - const outboundStream = this.streamsOutbound.get(id) - if (!outboundStream) { - this.log(`Cannot send RPC to ${id} as there is no open stream to it available`) - return false - } - - // piggyback control message retries - const ctrl = this.control.get(id) - if (ctrl) { - this.piggybackControl(id, rpc, ctrl) - this.control.delete(id) - } - - // piggyback gossip - const ihave = this.gossip.get(id) - if (ihave) { - this.piggybackGossip(id, rpc, ihave) - this.gossip.delete(id) - } - - const rpcBytes = RPC.encode(rpc).finish() - try { - outboundStream.push(rpcBytes) - } catch (e) { - this.log.error(`Cannot send rpc to ${id}`, e) - - // if the peer had control messages or gossip, re-attach - if (ctrl) { - this.control.set(id, ctrl) - } - if (ihave) { - this.gossip.set(id, ihave) - } - - return false - } - - this.metrics?.onRpcSent(rpc, rpcBytes.length) - - return true - } - - /** Mutates `outRpc` adding graft and prune control messages */ - public piggybackControl(id: PeerIdStr, outRpc: IRPC, ctrl: RPC.IControlMessage): void { - if (ctrl.graft) { - if (!outRpc.control) outRpc.control = {} - if (!outRpc.control.graft) outRpc.control.graft = [] - for (const graft of ctrl.graft) { - if (graft.topicID && this.mesh.get(graft.topicID)?.has(id)) { - outRpc.control.graft.push(graft) - } - } - } - - if (ctrl.prune) { - if (!outRpc.control) outRpc.control = {} - if (!outRpc.control.prune) outRpc.control.prune = [] - for (const prune of ctrl.prune) { - if (prune.topicID && !this.mesh.get(prune.topicID)?.has(id)) { - outRpc.control.prune.push(prune) - } - } - } - } - - /** Mutates `outRpc` adding ihave control messages */ - private piggybackGossip(id: PeerIdStr, outRpc: IRPC, ihave: RPC.IControlIHave[]): void { - if (!outRpc.control) outRpc.control = {} - outRpc.control.ihave = ihave - } - - /** - * Send graft and prune messages - * - * @param tograft - peer id => topic[] - * @param toprune - peer id => topic[] - */ - private async sendGraftPrune( - tograft: Map, - toprune: Map, - noPX: Map - ): Promise { - const doPX = this.opts.doPX - for (const [id, topics] of tograft) { - const graft = topics.map((topicID) => ({ topicID })) - let prune: RPC.IControlPrune[] = [] - // If a peer also has prunes, process them now - const pruning = toprune.get(id) - if (pruning) { - prune = await Promise.all( - pruning.map(async (topicID) => await this.makePrune(id, topicID, doPX && !(noPX.get(id) ?? false))) - ) - toprune.delete(id) - } - - this.sendRpc(id, { control: { graft, prune } }) - } - for (const [id, topics] of toprune) { - const prune = await Promise.all( - topics.map(async (topicID) => await this.makePrune(id, topicID, doPX && !(noPX.get(id) ?? false))) - ) - this.sendRpc(id, { control: { prune } }) - } - } - - /** - * Emits gossip - Send IHAVE messages to a random set of gossip peers - */ - private emitGossip(peersToGossipByTopic: Map>): void { - const gossipIDsByTopic = this.mcache.getGossipIDs(new Set(peersToGossipByTopic.keys())) - for (const [topic, peersToGossip] of peersToGossipByTopic) { - this.doEmitGossip(topic, peersToGossip, gossipIDsByTopic.get(topic) ?? []) - } - } - - /** - * Send gossip messages to GossipFactor peers above threshold with a minimum of D_lazy - * Peers are randomly selected from the heartbeat which exclude mesh + fanout peers - * We also exclude direct peers, as there is no reason to emit gossip to them - * @param topic - * @param candidateToGossip - peers to gossip - * @param messageIDs - message ids to gossip - */ - private doEmitGossip(topic: string, candidateToGossip: Set, messageIDs: Uint8Array[]): void { - if (!messageIDs.length) { - return - } - - // shuffle to emit in random order - shuffle(messageIDs) - - // if we are emitting more than GossipsubMaxIHaveLength ids, truncate the list - if (messageIDs.length > constants.GossipsubMaxIHaveLength) { - // we do the truncation (with shuffling) per peer below - this.log('too many messages for gossip; will truncate IHAVE list (%d messages)', messageIDs.length) - } - - if (!candidateToGossip.size) return - let target = this.opts.Dlazy - const factor = constants.GossipsubGossipFactor * candidateToGossip.size - let peersToGossip: Set | PeerIdStr[] = candidateToGossip - if (factor > target) { - target = factor - } - if (target > peersToGossip.size) { - target = peersToGossip.size - } else { - // only shuffle if needed - peersToGossip = shuffle(Array.from(peersToGossip)).slice(0, target) - } - - // Emit the IHAVE gossip to the selected peers up to the target - peersToGossip.forEach((id) => { - let peerMessageIDs = messageIDs - if (messageIDs.length > constants.GossipsubMaxIHaveLength) { - // shuffle and slice message IDs per peer so that we emit a different set for each peer - // we have enough reduncancy in the system that this will significantly increase the message - // coverage when we do truncate - peerMessageIDs = shuffle(peerMessageIDs.slice()).slice(0, constants.GossipsubMaxIHaveLength) - } - this.pushGossip(id, { - topicID: topic, - messageIDs: peerMessageIDs - }) - }) - } - - /** - * Flush gossip and control messages - */ - private flush(): void { - // send gossip first, which will also piggyback control - for (const [peer, ihave] of this.gossip.entries()) { - this.gossip.delete(peer) - this.sendRpc(peer, { control: { ihave } }) - } - // send the remaining control messages - for (const [peer, control] of this.control.entries()) { - this.control.delete(peer) - this.sendRpc(peer, { control: { graft: control.graft, prune: control.prune } }) - } - } - - /** - * Adds new IHAVE messages to pending gossip - */ - private pushGossip(id: PeerIdStr, controlIHaveMsgs: RPC.IControlIHave): void { - this.log('Add gossip to %s', id) - const gossip = this.gossip.get(id) || [] - this.gossip.set(id, gossip.concat(controlIHaveMsgs)) - } - - /** - * Make a PRUNE control message for a peer in a topic - */ - private async makePrune(id: PeerIdStr, topic: string, doPX: boolean): Promise { - this.score.prune(id, topic) - if (this.streamsOutbound.get(id)!.protocol === constants.GossipsubIDv10) { - // Gossipsub v1.0 -- no backoff, the peer won't be able to parse it anyway - return { - topicID: topic, - peers: [] - } - } - // backoff is measured in seconds - // GossipsubPruneBackoff is measured in milliseconds - // The protobuf has it as a uint64 - const backoff = this.opts.pruneBackoff / 1000 - if (!doPX) { - return { - topicID: topic, - peers: [], - backoff: backoff - } - } - // select peers for Peer eXchange - const peers = this.getRandomGossipPeers(topic, this.opts.prunePeers, (xid) => { - return xid !== id && this.score.score(xid) >= 0 - }) - const px = await Promise.all( - Array.from(peers).map(async (peerId) => { - // see if we have a signed record to send back; if we don't, just send - // the peer ID and let the pruned peer find them in the DHT -- we can't trust - // unsigned address records through PX anyways - // Finding signed records in the DHT is not supported at the time of writing in js-libp2p - const id = peerIdFromString(peerId) - let peerInfo: Peer | undefined - - try { - peerInfo = await this.components.peerStore.get(id) - } catch (err: any) { - if (err.code !== 'ERR_NOT_FOUND') { - throw err - } - } - - return { - peerID: id.toBytes(), - signedPeerRecord: peerInfo?.peerRecordEnvelope - } - }) - ) - return { - topicID: topic, - peers: px, - backoff: backoff - } - } - - private readonly runHeartbeat = () => { - const timer = this.metrics?.heartbeatDuration.startTimer() - - this.heartbeat() - .catch((err) => { - this.log('Error running heartbeat', err) - }) - .finally(() => { - if (timer != null) { - timer() - } - - // Schedule the next run if still in started status - if (this.status.code === GossipStatusCode.started) { - // Clear previous timeout before overwriting `status.heartbeatTimeout`, it should be completed tho. - clearTimeout(this.status.heartbeatTimeout) - - // NodeJS setInterval function is innexact, calls drift by a few miliseconds on each call. - // To run the heartbeat precisely setTimeout() must be used recomputing the delay on every loop. - let msToNextHeartbeat = - this.opts.heartbeatInterval - ((Date.now() - this.status.hearbeatStartMs) % this.opts.heartbeatInterval) - - // If too close to next heartbeat, skip one - if (msToNextHeartbeat < this.opts.heartbeatInterval * 0.25) { - msToNextHeartbeat += this.opts.heartbeatInterval - this.metrics?.heartbeatSkipped.inc() - } - - this.status.heartbeatTimeout = setTimeout(this.runHeartbeat, msToNextHeartbeat) - } - }) - } - - /** - * Maintains the mesh and fanout maps in gossipsub. - */ - public async heartbeat(): Promise { - const { D, Dlo, Dhi, Dscore, Dout, fanoutTTL } = this.opts - - this.heartbeatTicks++ - - // cache scores throught the heartbeat - const scores = new Map() - const getScore = (id: string): number => { - let s = scores.get(id) - if (s === undefined) { - s = this.score.score(id) - scores.set(id, s) - } - return s - } - - // peer id => topic[] - const tograft = new Map() - // peer id => topic[] - const toprune = new Map() - // peer id => don't px - const noPX = new Map() - - // clean up expired backoffs - this.clearBackoff() - - // clean up peerhave/iasked counters - this.peerhave.clear() - this.metrics?.cacheSize.set({ cache: 'iasked' }, this.iasked.size) - this.iasked.clear() - - // apply IWANT request penalties - this.applyIwantPenalties() - - // ensure direct peers are connected - if (this.heartbeatTicks % this.opts.directConnectTicks === 0) { - // we only do this every few ticks to allow pending connections to complete and account for restarts/downtime - await this.directConnect() - } - - // EXTRA: Prune caches - this.fastMsgIdCache?.prune() - this.seenCache.prune() - this.gossipTracer.prune() - this.publishedMessageIds.prune() - - /** - * Instead of calling getRandomGossipPeers multiple times to: - * + get more mesh peers - * + more outbound peers - * + oppportunistic grafting - * + emitGossip - * - * We want to loop through the topic peers only a single time and prepare gossip peers for all topics to improve the performance - */ - - const peersToGossipByTopic = new Map>() - // maintain the mesh for topics we have joined - this.mesh.forEach((peers, topic) => { - const peersInTopic = this.topics.get(topic) - const candidateMeshPeers = new Set() - const peersToGossip = new Set() - peersToGossipByTopic.set(topic, peersToGossip) - - if (peersInTopic) { - const shuffledPeers = shuffle(Array.from(peersInTopic)) - const backoff = this.backoff.get(topic) - for (const id of shuffledPeers) { - const peerStreams = this.streamsOutbound.get(id) - if ( - peerStreams && - this.multicodecs.includes(peerStreams.protocol) && - !peers.has(id) && - !this.direct.has(id) - ) { - const score = getScore(id) - if ((!backoff || !backoff.has(id)) && score >= 0) candidateMeshPeers.add(id) - // instead of having to find gossip peers after heartbeat which require another loop - // we prepare peers to gossip in a topic within heartbeat to improve performance - if (score >= this.opts.scoreThresholds.gossipThreshold) peersToGossip.add(id) - } - } - } - - // prune/graft helper functions (defined per topic) - const prunePeer = (id: PeerIdStr, reason: ChurnReason): void => { - this.log('HEARTBEAT: Remove mesh link to %s in %s', id, topic) - // no need to update peer score here as we do it in makePrune - // add prune backoff record - this.addBackoff(id, topic) - // remove peer from mesh - peers.delete(id) - // after pruning a peer from mesh, we want to gossip topic to it if its score meet the gossip threshold - if (getScore(id) >= this.opts.scoreThresholds.gossipThreshold) peersToGossip.add(id) - this.metrics?.onRemoveFromMesh(topic, reason, 1) - // add to toprune - const topics = toprune.get(id) - if (!topics) { - toprune.set(id, [topic]) - } else { - topics.push(topic) - } - } - - const graftPeer = (id: PeerIdStr, reason: InclusionReason): void => { - this.log('HEARTBEAT: Add mesh link to %s in %s', id, topic) - // update peer score - this.score.graft(id, topic) - // add peer to mesh - peers.add(id) - // when we add a new mesh peer, we don't want to gossip messages to it - peersToGossip.delete(id) - this.metrics?.onAddToMesh(topic, reason, 1) - // add to tograft - const topics = tograft.get(id) - if (!topics) { - tograft.set(id, [topic]) - } else { - topics.push(topic) - } - } - - // drop all peers with negative score, without PX - peers.forEach((id) => { - const score = getScore(id) - - // Record the score - - if (score < 0) { - this.log('HEARTBEAT: Prune peer %s with negative score: score=%d, topic=%s', id, score, topic) - prunePeer(id, ChurnReason.BadScore) - noPX.set(id, true) - } - }) - - // do we have enough peers? - if (peers.size < Dlo) { - const ineed = D - peers.size - // slice up to first `ineed` items and remove them from candidateMeshPeers - // same to `const newMeshPeers = candidateMeshPeers.slice(0, ineed)` - const newMeshPeers = removeFirstNItemsFromSet(candidateMeshPeers, ineed) - - newMeshPeers.forEach((p) => { - graftPeer(p, InclusionReason.NotEnough) - }) - } - - // do we have to many peers? - if (peers.size > Dhi) { - let peersArray = Array.from(peers) - // sort by score - peersArray.sort((a, b) => getScore(b) - getScore(a)) - // We keep the first D_score peers by score and the remaining up to D randomly - // under the constraint that we keep D_out peers in the mesh (if we have that many) - peersArray = peersArray.slice(0, Dscore).concat(shuffle(peersArray.slice(Dscore))) - - // count the outbound peers we are keeping - let outbound = 0 - peersArray.slice(0, D).forEach((p) => { - if (this.outbound.get(p)) { - outbound++ - } - }) - - // if it's less than D_out, bubble up some outbound peers from the random selection - if (outbound < Dout) { - const rotate = (i: number): void => { - // rotate the peersArray to the right and put the ith peer in the front - const p = peersArray[i] - for (let j = i; j > 0; j--) { - peersArray[j] = peersArray[j - 1] - } - peersArray[0] = p - } - - // first bubble up all outbound peers already in the selection to the front - if (outbound > 0) { - let ihave = outbound - for (let i = 1; i < D && ihave > 0; i++) { - if (this.outbound.get(peersArray[i])) { - rotate(i) - ihave-- - } - } - } - - // now bubble up enough outbound peers outside the selection to the front - let ineed = D - outbound - for (let i = D; i < peersArray.length && ineed > 0; i++) { - if (this.outbound.get(peersArray[i])) { - rotate(i) - ineed-- - } - } - } - - // prune the excess peers - peersArray.slice(D).forEach((p) => { - prunePeer(p, ChurnReason.Excess) - }) - } - - // do we have enough outbound peers? - if (peers.size >= Dlo) { - // count the outbound peers we have - let outbound = 0 - peers.forEach((p) => { - if (this.outbound.get(p)) { - outbound++ - } - }) - - // if it's less than D_out, select some peers with outbound connections and graft them - if (outbound < Dout) { - const ineed = Dout - outbound - const newMeshPeers = removeItemsFromSet(candidateMeshPeers, ineed, (id) => this.outbound.get(id) === true) - - newMeshPeers.forEach((p) => { - graftPeer(p, InclusionReason.Outbound) - }) - } - } - - // should we try to improve the mesh with opportunistic grafting? - if (this.heartbeatTicks % this.opts.opportunisticGraftTicks === 0 && peers.size > 1) { - // Opportunistic grafting works as follows: we check the median score of peers in the - // mesh; if this score is below the opportunisticGraftThreshold, we select a few peers at - // random with score over the median. - // The intention is to (slowly) improve an underperforming mesh by introducing good - // scoring peers that may have been gossiping at us. This allows us to get out of sticky - // situations where we are stuck with poor peers and also recover from churn of good peers. - - // now compute the median peer score in the mesh - const peersList = Array.from(peers).sort((a, b) => getScore(a) - getScore(b)) - const medianIndex = Math.floor(peers.size / 2) - const medianScore = getScore(peersList[medianIndex]) - - // if the median score is below the threshold, select a better peer (if any) and GRAFT - if (medianScore < this.opts.scoreThresholds.opportunisticGraftThreshold) { - const ineed = this.opts.opportunisticGraftPeers - const newMeshPeers = removeItemsFromSet(candidateMeshPeers, ineed, (id) => getScore(id) > medianScore) - for (const id of newMeshPeers) { - this.log('HEARTBEAT: Opportunistically graft peer %s on topic %s', id, topic) - graftPeer(id, InclusionReason.Opportunistic) - } - } - } - }) - - // expire fanout for topics we haven't published to in a while - const now = Date.now() - this.fanoutLastpub.forEach((lastpb, topic) => { - if (lastpb + fanoutTTL < now) { - this.fanout.delete(topic) - this.fanoutLastpub.delete(topic) - } - }) - - // maintain our fanout for topics we are publishing but we have not joined - this.fanout.forEach((fanoutPeers, topic) => { - // checks whether our peers are still in the topic and have a score above the publish threshold - const topicPeers = this.topics.get(topic) - fanoutPeers.forEach((id) => { - if (!topicPeers!.has(id) || getScore(id) < this.opts.scoreThresholds.publishThreshold) { - fanoutPeers.delete(id) - } - }) - - const peersInTopic = this.topics.get(topic) - const candidateFanoutPeers = [] - // the fanout map contains topics to which we are not subscribed. - const peersToGossip = new Set() - peersToGossipByTopic.set(topic, peersToGossip) - - if (peersInTopic) { - const shuffledPeers = shuffle(Array.from(peersInTopic)) - for (const id of shuffledPeers) { - const peerStreams = this.streamsOutbound.get(id) - if ( - peerStreams && - this.multicodecs.includes(peerStreams.protocol) && - !fanoutPeers.has(id) && - !this.direct.has(id) - ) { - const score = getScore(id) - if (score >= this.opts.scoreThresholds.publishThreshold) candidateFanoutPeers.push(id) - // instead of having to find gossip peers after heartbeat which require another loop - // we prepare peers to gossip in a topic within heartbeat to improve performance - if (score >= this.opts.scoreThresholds.gossipThreshold) peersToGossip.add(id) - } - } - } - - // do we need more peers? - if (fanoutPeers.size < D) { - const ineed = D - fanoutPeers.size - candidateFanoutPeers.slice(0, ineed).forEach((id) => { - fanoutPeers.add(id) - peersToGossip?.delete(id) - }) - } - }) - - this.emitGossip(peersToGossipByTopic) - - // send coalesced GRAFT/PRUNE messages (will piggyback gossip) - await this.sendGraftPrune(tograft, toprune, noPX) - - // flush pending gossip that wasn't piggybacked above - this.flush() - - // advance the message history window - this.mcache.shift() - - this.dispatchEvent(new CustomEvent('gossipsub:heartbeat')) - } - - /** - * Given a topic, returns up to count peers subscribed to that topic - * that pass an optional filter function - * - * @param topic - * @param count - * @param filter - a function to filter acceptable peers - */ - private getRandomGossipPeers( - topic: string, - count: number, - filter: (id: string) => boolean = () => true - ): Set { - const peersInTopic = this.topics.get(topic) - - if (!peersInTopic) { - return new Set() - } - - // Adds all peers using our protocol - // that also pass the filter function - let peers: string[] = [] - peersInTopic.forEach((id) => { - const peerStreams = this.streamsOutbound.get(id) - if (!peerStreams) { - return - } - if (this.multicodecs.includes(peerStreams.protocol) && filter(id)) { - peers.push(id) - } - }) - - // Pseudo-randomly shuffles peers - peers = shuffle(peers) - if (count > 0 && peers.length > count) { - peers = peers.slice(0, count) - } - - return new Set(peers) - } - - private onScrapeMetrics(metrics: Metrics): void { - /* Data structure sizes */ - metrics.mcacheSize.set(this.mcache.size) - metrics.mcacheNotValidatedCount.set(this.mcache.notValidatedCount) - // Arbitrary size - metrics.cacheSize.set({ cache: 'direct' }, this.direct.size) - metrics.cacheSize.set({ cache: 'seenCache' }, this.seenCache.size) - metrics.cacheSize.set({ cache: 'fastMsgIdCache' }, this.fastMsgIdCache?.size ?? 0) - metrics.cacheSize.set({ cache: 'publishedMessageIds' }, this.publishedMessageIds.size) - metrics.cacheSize.set({ cache: 'mcache' }, this.mcache.size) - metrics.cacheSize.set({ cache: 'score' }, this.score.size) - metrics.cacheSize.set({ cache: 'gossipTracer.promises' }, this.gossipTracer.size) - metrics.cacheSize.set({ cache: 'gossipTracer.requests' }, this.gossipTracer.requestMsByMsgSize) - // Bounded by topic - metrics.cacheSize.set({ cache: 'topics' }, this.topics.size) - metrics.cacheSize.set({ cache: 'subscriptions' }, this.subscriptions.size) - metrics.cacheSize.set({ cache: 'mesh' }, this.mesh.size) - metrics.cacheSize.set({ cache: 'fanout' }, this.fanout.size) - // Bounded by peer - metrics.cacheSize.set({ cache: 'peers' }, this.peers.size) - metrics.cacheSize.set({ cache: 'streamsOutbound' }, this.streamsOutbound.size) - metrics.cacheSize.set({ cache: 'streamsInbound' }, this.streamsInbound.size) - metrics.cacheSize.set({ cache: 'acceptFromWhitelist' }, this.acceptFromWhitelist.size) - metrics.cacheSize.set({ cache: 'gossip' }, this.gossip.size) - metrics.cacheSize.set({ cache: 'control' }, this.control.size) - metrics.cacheSize.set({ cache: 'peerhave' }, this.peerhave.size) - metrics.cacheSize.set({ cache: 'outbound' }, this.outbound.size) - // 2D nested data structure - let backoffSize = 0 - for (const backoff of this.backoff.values()) { - backoffSize += backoff.size - } - metrics.cacheSize.set({ cache: 'backoff' }, backoffSize) - - // Peer counts - - for (const [topicStr, peers] of this.topics) { - metrics.topicPeersCount.set({ topicStr }, peers.size) - } - - for (const [topicStr, peers] of this.mesh) { - metrics.meshPeerCounts.set({ topicStr }, peers.size) - } - - // Peer scores - - const scores: number[] = [] - const scoreByPeer = new Map() - metrics.behaviourPenalty.reset() - - for (const peerIdStr of this.peers.keys()) { - const score = this.score.score(peerIdStr) - scores.push(score) - scoreByPeer.set(peerIdStr, score) - metrics.behaviourPenalty.observe(this.score.peerStats.get(peerIdStr)?.behaviourPenalty ?? 0) - } - - metrics.registerScores(scores, this.opts.scoreThresholds) - - // Breakdown score per mesh topicLabel - - metrics.registerScorePerMesh(this.mesh, scoreByPeer) - - // Breakdown on each score weight - - const sw = computeAllPeersScoreWeights( - this.peers.keys(), - this.score.peerStats, - this.score.params, - this.score.peerIPs, - metrics.topicStrToLabel - ) - - metrics.registerScoreWeights(sw) - } -} - -export function gossipsub( - init: Partial = {} -): (components: GossipSubComponents) => PubSub { - return (components: GossipSubComponents) => new GossipSub(components, init) -} diff --git a/packages/pubsub-gossipsub/src/message-cache.ts b/packages/pubsub-gossipsub/src/message-cache.ts deleted file mode 100644 index 3544a51925..0000000000 --- a/packages/pubsub-gossipsub/src/message-cache.ts +++ /dev/null @@ -1,196 +0,0 @@ -import type { RPC } from './message/rpc.js' -import type { MessageId, MsgIdStr, PeerIdStr, TopicStr, MsgIdToStrFn } from './types.js' - -export type CacheEntry = MessageId & { - topic: TopicStr -} - -export type MessageCacheRecord = Pick - -interface MessageCacheEntry { - message: RPC.IMessage - /** - * Tracks if the message has been validated by the app layer and thus forwarded - */ - validated: boolean - /** - * Tracks peers that sent this message before it has been validated by the app layer - */ - originatingPeers: Set - /** - * For every message and peer the number of times this peer asked for the message - */ - iwantCounts: Map -} - -export class MessageCache { - msgs = new Map() - - msgIdToStrFn: MsgIdToStrFn - - history: CacheEntry[][] = [] - - /** Track with accounting of messages in the mcache that are not yet validated */ - notValidatedCount = 0 - - /** - * Holds history of messages in timebounded history arrays - */ - constructor( - /** - * The number of indices in the cache history used for gossiping. That means that a message - * won't get gossiped anymore when shift got called `gossip` many times after inserting the - * message in the cache. - */ - private readonly gossip: number, - historyCapacity: number, - msgIdToStrFn: MsgIdToStrFn - ) { - this.msgIdToStrFn = msgIdToStrFn - for (let i = 0; i < historyCapacity; i++) { - this.history[i] = [] - } - } - - get size(): number { - return this.msgs.size - } - - /** - * Adds a message to the current window and the cache - * Returns true if the message is not known and is inserted in the cache - */ - put(messageId: MessageId, msg: RPC.IMessage, validated = false): boolean { - const { msgIdStr } = messageId - // Don't add duplicate entries to the cache. - if (this.msgs.has(msgIdStr)) { - return false - } - - this.msgs.set(msgIdStr, { - message: msg, - validated, - originatingPeers: new Set(), - iwantCounts: new Map() - }) - - this.history[0].push({ ...messageId, topic: msg.topic }) - - if (!validated) { - this.notValidatedCount++ - } - - return true - } - - observeDuplicate(msgId: MsgIdStr, fromPeerIdStr: PeerIdStr): void { - const entry = this.msgs.get(msgId) - - if ( - entry && - // if the message is already validated, we don't need to store extra peers sending us - // duplicates as the message has already been forwarded - !entry.validated - ) { - entry.originatingPeers.add(fromPeerIdStr) - } - } - - /** - * Retrieves a message from the cache by its ID, if it is still present - */ - get(msgId: Uint8Array): RPC.IMessage | undefined { - return this.msgs.get(this.msgIdToStrFn(msgId))?.message - } - - /** - * Increases the iwant count for the given message by one and returns the message together - * with the iwant if the message exists. - */ - getWithIWantCount(msgIdStr: string, p: string): { msg: RPC.IMessage; count: number } | null { - const msg = this.msgs.get(msgIdStr) - if (!msg) { - return null - } - - const count = (msg.iwantCounts.get(p) ?? 0) + 1 - msg.iwantCounts.set(p, count) - - return { msg: msg.message, count } - } - - /** - * Retrieves a list of message IDs for a set of topics - */ - getGossipIDs(topics: Set): Map { - const msgIdsByTopic = new Map() - for (let i = 0; i < this.gossip; i++) { - this.history[i].forEach((entry) => { - const msg = this.msgs.get(entry.msgIdStr) - if (msg && msg.validated && topics.has(entry.topic)) { - let msgIds = msgIdsByTopic.get(entry.topic) - if (!msgIds) { - msgIds = [] - msgIdsByTopic.set(entry.topic, msgIds) - } - msgIds.push(entry.msgId) - } - }) - } - - return msgIdsByTopic - } - - /** - * Gets a message with msgId and tags it as validated. - * This function also returns the known peers that have sent us this message. This is used to - * prevent us sending redundant messages to peers who have already propagated it. - */ - validate(msgId: MsgIdStr): MessageCacheRecord | null { - const entry = this.msgs.get(msgId) - if (!entry) { - return null - } - - if (!entry.validated) { - this.notValidatedCount-- - } - - const { message, originatingPeers } = entry - entry.validated = true - // Clear the known peers list (after a message is validated, it is forwarded and we no - // longer need to store the originating peers). - entry.originatingPeers = new Set() - return { message, originatingPeers } - } - - /** - * Shifts the current window, discarding messages older than this.history.length of the cache - */ - shift(): void { - const lastCacheEntries = this.history[this.history.length - 1] - lastCacheEntries.forEach((cacheEntry) => { - const entry = this.msgs.get(cacheEntry.msgIdStr) - if (entry) { - this.msgs.delete(cacheEntry.msgIdStr) - if (!entry.validated) { - this.notValidatedCount-- - } - } - }) - - this.history.pop() - this.history.unshift([]) - } - - remove(msgId: MsgIdStr): MessageCacheRecord | null { - const entry = this.msgs.get(msgId) - if (!entry) { - return null - } - - // Keep the message on the history vector, it will be dropped on a shift() - this.msgs.delete(msgId) - return entry - } -} diff --git a/packages/pubsub-gossipsub/src/message/decodeRpc.ts b/packages/pubsub-gossipsub/src/message/decodeRpc.ts deleted file mode 100644 index 11037930ad..0000000000 --- a/packages/pubsub-gossipsub/src/message/decodeRpc.ts +++ /dev/null @@ -1,247 +0,0 @@ -import type { IRPC, RPC } from './rpc.js' -import protobuf from 'protobufjs/minimal.js' - -export type DecodeRPCLimits = { - maxSubscriptions: number - maxMessages: number - maxIhaveMessageIDs: number - maxIwantMessageIDs: number - maxControlMessages: number - maxPeerInfos: number -} - -export const defaultDecodeRpcLimits: DecodeRPCLimits = { - maxSubscriptions: Infinity, - maxMessages: Infinity, - maxIhaveMessageIDs: Infinity, - maxIwantMessageIDs: Infinity, - maxControlMessages: Infinity, - maxPeerInfos: Infinity -} - -/** - * Copied code from src/message/rpc.cjs but with decode limits to prevent OOM attacks - */ -export function decodeRpc(bytes: Uint8Array, opts: DecodeRPCLimits): IRPC { - // Mutate to use the option as stateful counter. Must limit the total count of messageIDs across all IWANT, IHAVE - // else one count put 100 messageIDs into each 100 IWANT and "get around" the limit - opts = { ...opts } - - const r = protobuf.Reader.create(bytes) - const l = bytes.length - - const c = l === undefined ? r.len : r.pos + l - const m: IRPC = {} - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { - case 1: - if (!(m.subscriptions && m.subscriptions.length)) m.subscriptions = [] - if (m.subscriptions.length < opts.maxSubscriptions) m.subscriptions.push(decodeSubOpts(r, r.uint32())) - else r.skipType(t & 7) - break - case 2: - if (!(m.messages && m.messages.length)) m.messages = [] - if (m.messages.length < opts.maxMessages) m.messages.push(decodeMessage(r, r.uint32())) - else r.skipType(t & 7) - break - case 3: - m.control = decodeControlMessage(r, r.uint32(), opts) - break - default: - r.skipType(t & 7) - break - } - } - return m -} - -function decodeSubOpts(r: protobuf.Reader, l: number) { - const c = l === undefined ? r.len : r.pos + l - const m: RPC.ISubOpts = {} - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { - case 1: - m.subscribe = r.bool() - break - case 2: - m.topic = r.string() - break - default: - r.skipType(t & 7) - break - } - } - return m -} - -function decodeMessage(r: protobuf.Reader, l: number) { - const c = l === undefined ? r.len : r.pos + l - const m = {} as RPC.IMessage - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { - case 1: - m.from = r.bytes() - break - case 2: - m.data = r.bytes() - break - case 3: - m.seqno = r.bytes() - break - case 4: - m.topic = r.string() - break - case 5: - m.signature = r.bytes() - break - case 6: - m.key = r.bytes() - break - default: - r.skipType(t & 7) - break - } - } - if (!m.topic) throw Error("missing required 'topic'") - return m -} - -function decodeControlMessage(r: protobuf.Reader, l: number, opts: DecodeRPCLimits) { - const c = l === undefined ? r.len : r.pos + l - const m = {} as RPC.IControlMessage - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { - case 1: - if (!(m.ihave && m.ihave.length)) m.ihave = [] - if (m.ihave.length < opts.maxControlMessages) m.ihave.push(decodeControlIHave(r, r.uint32(), opts)) - else r.skipType(t & 7) - break - case 2: - if (!(m.iwant && m.iwant.length)) m.iwant = [] - if (m.iwant.length < opts.maxControlMessages) m.iwant.push(decodeControlIWant(r, r.uint32(), opts)) - else r.skipType(t & 7) - break - case 3: - if (!(m.graft && m.graft.length)) m.graft = [] - if (m.graft.length < opts.maxControlMessages) m.graft.push(decodeControlGraft(r, r.uint32())) - else r.skipType(t & 7) - break - case 4: - if (!(m.prune && m.prune.length)) m.prune = [] - if (m.prune.length < opts.maxControlMessages) m.prune.push(decodeControlPrune(r, r.uint32(), opts)) - else r.skipType(t & 7) - break - default: - r.skipType(t & 7) - break - } - } - return m -} - -function decodeControlIHave(r: protobuf.Reader, l: number, opts: DecodeRPCLimits) { - const c = l === undefined ? r.len : r.pos + l - const m = {} as RPC.IControlIHave - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { - case 1: - m.topicID = r.string() - break - case 2: - if (!(m.messageIDs && m.messageIDs.length)) m.messageIDs = [] - if (opts.maxIhaveMessageIDs-- > 0) m.messageIDs.push(r.bytes()) - else r.skipType(t & 7) - break - default: - r.skipType(t & 7) - break - } - } - return m -} - -function decodeControlIWant(r: protobuf.Reader, l: number, opts: DecodeRPCLimits) { - const c = l === undefined ? r.len : r.pos + l - const m = {} as RPC.IControlIWant - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { - case 1: - if (!(m.messageIDs && m.messageIDs.length)) m.messageIDs = [] - if (opts.maxIwantMessageIDs-- > 0) m.messageIDs.push(r.bytes()) - else r.skipType(t & 7) - break - default: - r.skipType(t & 7) - break - } - } - return m -} - -function decodeControlGraft(r: protobuf.Reader, l: number) { - const c = l === undefined ? r.len : r.pos + l - const m = {} as RPC.IControlGraft - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { - case 1: - m.topicID = r.string() - break - default: - r.skipType(t & 7) - break - } - } - return m -} - -function decodeControlPrune(r: protobuf.Reader, l: number, opts: DecodeRPCLimits) { - const c = l === undefined ? r.len : r.pos + l - const m = {} as RPC.IControlPrune - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { - case 1: - m.topicID = r.string() - break - case 2: - if (!(m.peers && m.peers.length)) m.peers = [] - if (opts.maxPeerInfos-- > 0) m.peers.push(decodePeerInfo(r, r.uint32())) - else r.skipType(t & 7) - break - case 3: - m.backoff = r.uint64() as unknown as number - break - default: - r.skipType(t & 7) - break - } - } - return m -} - -function decodePeerInfo(r: protobuf.Reader, l: number) { - const c = l === undefined ? r.len : r.pos + l - const m = {} as RPC.IPeerInfo - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { - case 1: - m.peerID = r.bytes() - break - case 2: - m.signedPeerRecord = r.bytes() - break - default: - r.skipType(t & 7) - break - } - } - return m -} diff --git a/packages/pubsub-gossipsub/src/message/index.ts b/packages/pubsub-gossipsub/src/message/index.ts deleted file mode 100644 index d1e0e3604b..0000000000 --- a/packages/pubsub-gossipsub/src/message/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './rpc.js' diff --git a/packages/pubsub-gossipsub/src/message/rpc.cjs b/packages/pubsub-gossipsub/src/message/rpc.cjs deleted file mode 100644 index 4f5f4ce666..0000000000 --- a/packages/pubsub-gossipsub/src/message/rpc.cjs +++ /dev/null @@ -1,1878 +0,0 @@ -// @ts-nocheck -/*eslint-disable*/ -(function(global, factory) { /* global define, require, module */ - - /* AMD */ if (typeof define === 'function' && define.amd) - define(["protobufjs/minimal"], factory); - - /* CommonJS */ else if (typeof require === 'function' && typeof module === 'object' && module && module.exports) - module.exports = factory(require("protobufjs/minimal")); - -})(this, function($protobuf) { - "use strict"; - - // Common aliases - var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - - // Exported root namespace - var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); - - $root.RPC = (function() { - - /** - * Properties of a RPC. - * @exports IRPC - * @interface IRPC - * @property {Array.|null} [subscriptions] RPC subscriptions - * @property {Array.|null} [messages] RPC messages - * @property {RPC.IControlMessage|null} [control] RPC control - */ - - /** - * Constructs a new RPC. - * @exports RPC - * @classdesc Represents a RPC. - * @implements IRPC - * @constructor - * @param {IRPC=} [p] Properties to set - */ - function RPC(p) { - this.subscriptions = []; - this.messages = []; - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * RPC subscriptions. - * @member {Array.} subscriptions - * @memberof RPC - * @instance - */ - RPC.prototype.subscriptions = $util.emptyArray; - - /** - * RPC messages. - * @member {Array.} messages - * @memberof RPC - * @instance - */ - RPC.prototype.messages = $util.emptyArray; - - /** - * RPC control. - * @member {RPC.IControlMessage|null|undefined} control - * @memberof RPC - * @instance - */ - RPC.prototype.control = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * RPC _control. - * @member {"control"|undefined} _control - * @memberof RPC - * @instance - */ - Object.defineProperty(RPC.prototype, "_control", { - get: $util.oneOfGetter($oneOfFields = ["control"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified RPC message. Does not implicitly {@link RPC.verify|verify} messages. - * @function encode - * @memberof RPC - * @static - * @param {IRPC} m RPC message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - RPC.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.subscriptions != null && m.subscriptions.length) { - for (var i = 0; i < m.subscriptions.length; ++i) - $root.RPC.SubOpts.encode(m.subscriptions[i], w.uint32(10).fork()).ldelim(); - } - if (m.messages != null && m.messages.length) { - for (var i = 0; i < m.messages.length; ++i) - $root.RPC.Message.encode(m.messages[i], w.uint32(18).fork()).ldelim(); - } - if (m.control != null && Object.hasOwnProperty.call(m, "control")) - $root.RPC.ControlMessage.encode(m.control, w.uint32(26).fork()).ldelim(); - return w; - }; - - /** - * Decodes a RPC message from the specified reader or buffer. - * @function decode - * @memberof RPC - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC} RPC - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - RPC.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.RPC(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - if (!(m.subscriptions && m.subscriptions.length)) - m.subscriptions = []; - m.subscriptions.push($root.RPC.SubOpts.decode(r, r.uint32())); - break; - case 2: - if (!(m.messages && m.messages.length)) - m.messages = []; - m.messages.push($root.RPC.Message.decode(r, r.uint32())); - break; - case 3: - m.control = $root.RPC.ControlMessage.decode(r, r.uint32()); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a RPC message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC - * @static - * @param {Object.} d Plain object - * @returns {RPC} RPC - */ - RPC.fromObject = function fromObject(d) { - if (d instanceof $root.RPC) - return d; - var m = new $root.RPC(); - if (d.subscriptions) { - if (!Array.isArray(d.subscriptions)) - throw TypeError(".RPC.subscriptions: array expected"); - m.subscriptions = []; - for (var i = 0; i < d.subscriptions.length; ++i) { - if (typeof d.subscriptions[i] !== "object") - throw TypeError(".RPC.subscriptions: object expected"); - m.subscriptions[i] = $root.RPC.SubOpts.fromObject(d.subscriptions[i]); - } - } - if (d.messages) { - if (!Array.isArray(d.messages)) - throw TypeError(".RPC.messages: array expected"); - m.messages = []; - for (var i = 0; i < d.messages.length; ++i) { - if (typeof d.messages[i] !== "object") - throw TypeError(".RPC.messages: object expected"); - m.messages[i] = $root.RPC.Message.fromObject(d.messages[i]); - } - } - if (d.control != null) { - if (typeof d.control !== "object") - throw TypeError(".RPC.control: object expected"); - m.control = $root.RPC.ControlMessage.fromObject(d.control); - } - return m; - }; - - /** - * Creates a plain object from a RPC message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC - * @static - * @param {RPC} m RPC - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - RPC.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.subscriptions = []; - d.messages = []; - } - if (m.subscriptions && m.subscriptions.length) { - d.subscriptions = []; - for (var j = 0; j < m.subscriptions.length; ++j) { - d.subscriptions[j] = $root.RPC.SubOpts.toObject(m.subscriptions[j], o); - } - } - if (m.messages && m.messages.length) { - d.messages = []; - for (var j = 0; j < m.messages.length; ++j) { - d.messages[j] = $root.RPC.Message.toObject(m.messages[j], o); - } - } - if (m.control != null && m.hasOwnProperty("control")) { - d.control = $root.RPC.ControlMessage.toObject(m.control, o); - if (o.oneofs) - d._control = "control"; - } - return d; - }; - - /** - * Converts this RPC to JSON. - * @function toJSON - * @memberof RPC - * @instance - * @returns {Object.} JSON object - */ - RPC.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - RPC.SubOpts = (function() { - - /** - * Properties of a SubOpts. - * @memberof RPC - * @interface ISubOpts - * @property {boolean|null} [subscribe] SubOpts subscribe - * @property {string|null} [topic] SubOpts topic - */ - - /** - * Constructs a new SubOpts. - * @memberof RPC - * @classdesc Represents a SubOpts. - * @implements ISubOpts - * @constructor - * @param {RPC.ISubOpts=} [p] Properties to set - */ - function SubOpts(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * SubOpts subscribe. - * @member {boolean|null|undefined} subscribe - * @memberof RPC.SubOpts - * @instance - */ - SubOpts.prototype.subscribe = null; - - /** - * SubOpts topic. - * @member {string|null|undefined} topic - * @memberof RPC.SubOpts - * @instance - */ - SubOpts.prototype.topic = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * SubOpts _subscribe. - * @member {"subscribe"|undefined} _subscribe - * @memberof RPC.SubOpts - * @instance - */ - Object.defineProperty(SubOpts.prototype, "_subscribe", { - get: $util.oneOfGetter($oneOfFields = ["subscribe"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * SubOpts _topic. - * @member {"topic"|undefined} _topic - * @memberof RPC.SubOpts - * @instance - */ - Object.defineProperty(SubOpts.prototype, "_topic", { - get: $util.oneOfGetter($oneOfFields = ["topic"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified SubOpts message. Does not implicitly {@link RPC.SubOpts.verify|verify} messages. - * @function encode - * @memberof RPC.SubOpts - * @static - * @param {RPC.ISubOpts} m SubOpts message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - SubOpts.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.subscribe != null && Object.hasOwnProperty.call(m, "subscribe")) - w.uint32(8).bool(m.subscribe); - if (m.topic != null && Object.hasOwnProperty.call(m, "topic")) - w.uint32(18).string(m.topic); - return w; - }; - - /** - * Decodes a SubOpts message from the specified reader or buffer. - * @function decode - * @memberof RPC.SubOpts - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.SubOpts} SubOpts - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - SubOpts.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.RPC.SubOpts(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.subscribe = r.bool(); - break; - case 2: - m.topic = r.string(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a SubOpts message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.SubOpts - * @static - * @param {Object.} d Plain object - * @returns {RPC.SubOpts} SubOpts - */ - SubOpts.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.SubOpts) - return d; - var m = new $root.RPC.SubOpts(); - if (d.subscribe != null) { - m.subscribe = Boolean(d.subscribe); - } - if (d.topic != null) { - m.topic = String(d.topic); - } - return m; - }; - - /** - * Creates a plain object from a SubOpts message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.SubOpts - * @static - * @param {RPC.SubOpts} m SubOpts - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - SubOpts.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (m.subscribe != null && m.hasOwnProperty("subscribe")) { - d.subscribe = m.subscribe; - if (o.oneofs) - d._subscribe = "subscribe"; - } - if (m.topic != null && m.hasOwnProperty("topic")) { - d.topic = m.topic; - if (o.oneofs) - d._topic = "topic"; - } - return d; - }; - - /** - * Converts this SubOpts to JSON. - * @function toJSON - * @memberof RPC.SubOpts - * @instance - * @returns {Object.} JSON object - */ - SubOpts.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return SubOpts; - })(); - - RPC.Message = (function() { - - /** - * Properties of a Message. - * @memberof RPC - * @interface IMessage - * @property {Uint8Array|null} [from] Message from - * @property {Uint8Array|null} [data] Message data - * @property {Uint8Array|null} [seqno] Message seqno - * @property {string} topic Message topic - * @property {Uint8Array|null} [signature] Message signature - * @property {Uint8Array|null} [key] Message key - */ - - /** - * Constructs a new Message. - * @memberof RPC - * @classdesc Represents a Message. - * @implements IMessage - * @constructor - * @param {RPC.IMessage=} [p] Properties to set - */ - function Message(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * Message from. - * @member {Uint8Array|null|undefined} from - * @memberof RPC.Message - * @instance - */ - Message.prototype.from = null; - - /** - * Message data. - * @member {Uint8Array|null|undefined} data - * @memberof RPC.Message - * @instance - */ - Message.prototype.data = null; - - /** - * Message seqno. - * @member {Uint8Array|null|undefined} seqno - * @memberof RPC.Message - * @instance - */ - Message.prototype.seqno = null; - - /** - * Message topic. - * @member {string} topic - * @memberof RPC.Message - * @instance - */ - Message.prototype.topic = ""; - - /** - * Message signature. - * @member {Uint8Array|null|undefined} signature - * @memberof RPC.Message - * @instance - */ - Message.prototype.signature = null; - - /** - * Message key. - * @member {Uint8Array|null|undefined} key - * @memberof RPC.Message - * @instance - */ - Message.prototype.key = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * Message _from. - * @member {"from"|undefined} _from - * @memberof RPC.Message - * @instance - */ - Object.defineProperty(Message.prototype, "_from", { - get: $util.oneOfGetter($oneOfFields = ["from"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Message _data. - * @member {"data"|undefined} _data - * @memberof RPC.Message - * @instance - */ - Object.defineProperty(Message.prototype, "_data", { - get: $util.oneOfGetter($oneOfFields = ["data"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Message _seqno. - * @member {"seqno"|undefined} _seqno - * @memberof RPC.Message - * @instance - */ - Object.defineProperty(Message.prototype, "_seqno", { - get: $util.oneOfGetter($oneOfFields = ["seqno"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Message _signature. - * @member {"signature"|undefined} _signature - * @memberof RPC.Message - * @instance - */ - Object.defineProperty(Message.prototype, "_signature", { - get: $util.oneOfGetter($oneOfFields = ["signature"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Message _key. - * @member {"key"|undefined} _key - * @memberof RPC.Message - * @instance - */ - Object.defineProperty(Message.prototype, "_key", { - get: $util.oneOfGetter($oneOfFields = ["key"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified Message message. Does not implicitly {@link RPC.Message.verify|verify} messages. - * @function encode - * @memberof RPC.Message - * @static - * @param {RPC.IMessage} m Message message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Message.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.from != null && Object.hasOwnProperty.call(m, "from")) - w.uint32(10).bytes(m.from); - if (m.data != null && Object.hasOwnProperty.call(m, "data")) - w.uint32(18).bytes(m.data); - if (m.seqno != null && Object.hasOwnProperty.call(m, "seqno")) - w.uint32(26).bytes(m.seqno); - w.uint32(34).string(m.topic); - if (m.signature != null && Object.hasOwnProperty.call(m, "signature")) - w.uint32(42).bytes(m.signature); - if (m.key != null && Object.hasOwnProperty.call(m, "key")) - w.uint32(50).bytes(m.key); - return w; - }; - - /** - * Decodes a Message message from the specified reader or buffer. - * @function decode - * @memberof RPC.Message - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.Message} Message - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Message.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.RPC.Message(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.from = r.bytes(); - break; - case 2: - m.data = r.bytes(); - break; - case 3: - m.seqno = r.bytes(); - break; - case 4: - m.topic = r.string(); - break; - case 5: - m.signature = r.bytes(); - break; - case 6: - m.key = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - if (!m.hasOwnProperty("topic")) - throw $util.ProtocolError("missing required 'topic'", { instance: m }); - return m; - }; - - /** - * Creates a Message message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.Message - * @static - * @param {Object.} d Plain object - * @returns {RPC.Message} Message - */ - Message.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.Message) - return d; - var m = new $root.RPC.Message(); - if (d.from != null) { - if (typeof d.from === "string") - $util.base64.decode(d.from, m.from = $util.newBuffer($util.base64.length(d.from)), 0); - else if (d.from.length) - m.from = d.from; - } - if (d.data != null) { - if (typeof d.data === "string") - $util.base64.decode(d.data, m.data = $util.newBuffer($util.base64.length(d.data)), 0); - else if (d.data.length) - m.data = d.data; - } - if (d.seqno != null) { - if (typeof d.seqno === "string") - $util.base64.decode(d.seqno, m.seqno = $util.newBuffer($util.base64.length(d.seqno)), 0); - else if (d.seqno.length) - m.seqno = d.seqno; - } - if (d.topic != null) { - m.topic = String(d.topic); - } - if (d.signature != null) { - if (typeof d.signature === "string") - $util.base64.decode(d.signature, m.signature = $util.newBuffer($util.base64.length(d.signature)), 0); - else if (d.signature.length) - m.signature = d.signature; - } - if (d.key != null) { - if (typeof d.key === "string") - $util.base64.decode(d.key, m.key = $util.newBuffer($util.base64.length(d.key)), 0); - else if (d.key.length) - m.key = d.key; - } - return m; - }; - - /** - * Creates a plain object from a Message message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.Message - * @static - * @param {RPC.Message} m Message - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Message.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - d.topic = ""; - } - if (m.from != null && m.hasOwnProperty("from")) { - d.from = o.bytes === String ? $util.base64.encode(m.from, 0, m.from.length) : o.bytes === Array ? Array.prototype.slice.call(m.from) : m.from; - if (o.oneofs) - d._from = "from"; - } - if (m.data != null && m.hasOwnProperty("data")) { - d.data = o.bytes === String ? $util.base64.encode(m.data, 0, m.data.length) : o.bytes === Array ? Array.prototype.slice.call(m.data) : m.data; - if (o.oneofs) - d._data = "data"; - } - if (m.seqno != null && m.hasOwnProperty("seqno")) { - d.seqno = o.bytes === String ? $util.base64.encode(m.seqno, 0, m.seqno.length) : o.bytes === Array ? Array.prototype.slice.call(m.seqno) : m.seqno; - if (o.oneofs) - d._seqno = "seqno"; - } - if (m.topic != null && m.hasOwnProperty("topic")) { - d.topic = m.topic; - } - if (m.signature != null && m.hasOwnProperty("signature")) { - d.signature = o.bytes === String ? $util.base64.encode(m.signature, 0, m.signature.length) : o.bytes === Array ? Array.prototype.slice.call(m.signature) : m.signature; - if (o.oneofs) - d._signature = "signature"; - } - if (m.key != null && m.hasOwnProperty("key")) { - d.key = o.bytes === String ? $util.base64.encode(m.key, 0, m.key.length) : o.bytes === Array ? Array.prototype.slice.call(m.key) : m.key; - if (o.oneofs) - d._key = "key"; - } - return d; - }; - - /** - * Converts this Message to JSON. - * @function toJSON - * @memberof RPC.Message - * @instance - * @returns {Object.} JSON object - */ - Message.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Message; - })(); - - RPC.ControlMessage = (function() { - - /** - * Properties of a ControlMessage. - * @memberof RPC - * @interface IControlMessage - * @property {Array.|null} [ihave] ControlMessage ihave - * @property {Array.|null} [iwant] ControlMessage iwant - * @property {Array.|null} [graft] ControlMessage graft - * @property {Array.|null} [prune] ControlMessage prune - */ - - /** - * Constructs a new ControlMessage. - * @memberof RPC - * @classdesc Represents a ControlMessage. - * @implements IControlMessage - * @constructor - * @param {RPC.IControlMessage=} [p] Properties to set - */ - function ControlMessage(p) { - this.ihave = []; - this.iwant = []; - this.graft = []; - this.prune = []; - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * ControlMessage ihave. - * @member {Array.} ihave - * @memberof RPC.ControlMessage - * @instance - */ - ControlMessage.prototype.ihave = $util.emptyArray; - - /** - * ControlMessage iwant. - * @member {Array.} iwant - * @memberof RPC.ControlMessage - * @instance - */ - ControlMessage.prototype.iwant = $util.emptyArray; - - /** - * ControlMessage graft. - * @member {Array.} graft - * @memberof RPC.ControlMessage - * @instance - */ - ControlMessage.prototype.graft = $util.emptyArray; - - /** - * ControlMessage prune. - * @member {Array.} prune - * @memberof RPC.ControlMessage - * @instance - */ - ControlMessage.prototype.prune = $util.emptyArray; - - /** - * Encodes the specified ControlMessage message. Does not implicitly {@link RPC.ControlMessage.verify|verify} messages. - * @function encode - * @memberof RPC.ControlMessage - * @static - * @param {RPC.IControlMessage} m ControlMessage message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - ControlMessage.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.ihave != null && m.ihave.length) { - for (var i = 0; i < m.ihave.length; ++i) - $root.RPC.ControlIHave.encode(m.ihave[i], w.uint32(10).fork()).ldelim(); - } - if (m.iwant != null && m.iwant.length) { - for (var i = 0; i < m.iwant.length; ++i) - $root.RPC.ControlIWant.encode(m.iwant[i], w.uint32(18).fork()).ldelim(); - } - if (m.graft != null && m.graft.length) { - for (var i = 0; i < m.graft.length; ++i) - $root.RPC.ControlGraft.encode(m.graft[i], w.uint32(26).fork()).ldelim(); - } - if (m.prune != null && m.prune.length) { - for (var i = 0; i < m.prune.length; ++i) - $root.RPC.ControlPrune.encode(m.prune[i], w.uint32(34).fork()).ldelim(); - } - return w; - }; - - /** - * Decodes a ControlMessage message from the specified reader or buffer. - * @function decode - * @memberof RPC.ControlMessage - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.ControlMessage} ControlMessage - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - ControlMessage.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.RPC.ControlMessage(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - if (!(m.ihave && m.ihave.length)) - m.ihave = []; - m.ihave.push($root.RPC.ControlIHave.decode(r, r.uint32())); - break; - case 2: - if (!(m.iwant && m.iwant.length)) - m.iwant = []; - m.iwant.push($root.RPC.ControlIWant.decode(r, r.uint32())); - break; - case 3: - if (!(m.graft && m.graft.length)) - m.graft = []; - m.graft.push($root.RPC.ControlGraft.decode(r, r.uint32())); - break; - case 4: - if (!(m.prune && m.prune.length)) - m.prune = []; - m.prune.push($root.RPC.ControlPrune.decode(r, r.uint32())); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a ControlMessage message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.ControlMessage - * @static - * @param {Object.} d Plain object - * @returns {RPC.ControlMessage} ControlMessage - */ - ControlMessage.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.ControlMessage) - return d; - var m = new $root.RPC.ControlMessage(); - if (d.ihave) { - if (!Array.isArray(d.ihave)) - throw TypeError(".RPC.ControlMessage.ihave: array expected"); - m.ihave = []; - for (var i = 0; i < d.ihave.length; ++i) { - if (typeof d.ihave[i] !== "object") - throw TypeError(".RPC.ControlMessage.ihave: object expected"); - m.ihave[i] = $root.RPC.ControlIHave.fromObject(d.ihave[i]); - } - } - if (d.iwant) { - if (!Array.isArray(d.iwant)) - throw TypeError(".RPC.ControlMessage.iwant: array expected"); - m.iwant = []; - for (var i = 0; i < d.iwant.length; ++i) { - if (typeof d.iwant[i] !== "object") - throw TypeError(".RPC.ControlMessage.iwant: object expected"); - m.iwant[i] = $root.RPC.ControlIWant.fromObject(d.iwant[i]); - } - } - if (d.graft) { - if (!Array.isArray(d.graft)) - throw TypeError(".RPC.ControlMessage.graft: array expected"); - m.graft = []; - for (var i = 0; i < d.graft.length; ++i) { - if (typeof d.graft[i] !== "object") - throw TypeError(".RPC.ControlMessage.graft: object expected"); - m.graft[i] = $root.RPC.ControlGraft.fromObject(d.graft[i]); - } - } - if (d.prune) { - if (!Array.isArray(d.prune)) - throw TypeError(".RPC.ControlMessage.prune: array expected"); - m.prune = []; - for (var i = 0; i < d.prune.length; ++i) { - if (typeof d.prune[i] !== "object") - throw TypeError(".RPC.ControlMessage.prune: object expected"); - m.prune[i] = $root.RPC.ControlPrune.fromObject(d.prune[i]); - } - } - return m; - }; - - /** - * Creates a plain object from a ControlMessage message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.ControlMessage - * @static - * @param {RPC.ControlMessage} m ControlMessage - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - ControlMessage.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.ihave = []; - d.iwant = []; - d.graft = []; - d.prune = []; - } - if (m.ihave && m.ihave.length) { - d.ihave = []; - for (var j = 0; j < m.ihave.length; ++j) { - d.ihave[j] = $root.RPC.ControlIHave.toObject(m.ihave[j], o); - } - } - if (m.iwant && m.iwant.length) { - d.iwant = []; - for (var j = 0; j < m.iwant.length; ++j) { - d.iwant[j] = $root.RPC.ControlIWant.toObject(m.iwant[j], o); - } - } - if (m.graft && m.graft.length) { - d.graft = []; - for (var j = 0; j < m.graft.length; ++j) { - d.graft[j] = $root.RPC.ControlGraft.toObject(m.graft[j], o); - } - } - if (m.prune && m.prune.length) { - d.prune = []; - for (var j = 0; j < m.prune.length; ++j) { - d.prune[j] = $root.RPC.ControlPrune.toObject(m.prune[j], o); - } - } - return d; - }; - - /** - * Converts this ControlMessage to JSON. - * @function toJSON - * @memberof RPC.ControlMessage - * @instance - * @returns {Object.} JSON object - */ - ControlMessage.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return ControlMessage; - })(); - - RPC.ControlIHave = (function() { - - /** - * Properties of a ControlIHave. - * @memberof RPC - * @interface IControlIHave - * @property {string|null} [topicID] ControlIHave topicID - * @property {Array.|null} [messageIDs] ControlIHave messageIDs - */ - - /** - * Constructs a new ControlIHave. - * @memberof RPC - * @classdesc Represents a ControlIHave. - * @implements IControlIHave - * @constructor - * @param {RPC.IControlIHave=} [p] Properties to set - */ - function ControlIHave(p) { - this.messageIDs = []; - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * ControlIHave topicID. - * @member {string|null|undefined} topicID - * @memberof RPC.ControlIHave - * @instance - */ - ControlIHave.prototype.topicID = null; - - /** - * ControlIHave messageIDs. - * @member {Array.} messageIDs - * @memberof RPC.ControlIHave - * @instance - */ - ControlIHave.prototype.messageIDs = $util.emptyArray; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * ControlIHave _topicID. - * @member {"topicID"|undefined} _topicID - * @memberof RPC.ControlIHave - * @instance - */ - Object.defineProperty(ControlIHave.prototype, "_topicID", { - get: $util.oneOfGetter($oneOfFields = ["topicID"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified ControlIHave message. Does not implicitly {@link RPC.ControlIHave.verify|verify} messages. - * @function encode - * @memberof RPC.ControlIHave - * @static - * @param {RPC.IControlIHave} m ControlIHave message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - ControlIHave.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.topicID != null && Object.hasOwnProperty.call(m, "topicID")) - w.uint32(10).string(m.topicID); - if (m.messageIDs != null && m.messageIDs.length) { - for (var i = 0; i < m.messageIDs.length; ++i) - w.uint32(18).bytes(m.messageIDs[i]); - } - return w; - }; - - /** - * Decodes a ControlIHave message from the specified reader or buffer. - * @function decode - * @memberof RPC.ControlIHave - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.ControlIHave} ControlIHave - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - ControlIHave.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.RPC.ControlIHave(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.topicID = r.string(); - break; - case 2: - if (!(m.messageIDs && m.messageIDs.length)) - m.messageIDs = []; - m.messageIDs.push(r.bytes()); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a ControlIHave message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.ControlIHave - * @static - * @param {Object.} d Plain object - * @returns {RPC.ControlIHave} ControlIHave - */ - ControlIHave.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.ControlIHave) - return d; - var m = new $root.RPC.ControlIHave(); - if (d.topicID != null) { - m.topicID = String(d.topicID); - } - if (d.messageIDs) { - if (!Array.isArray(d.messageIDs)) - throw TypeError(".RPC.ControlIHave.messageIDs: array expected"); - m.messageIDs = []; - for (var i = 0; i < d.messageIDs.length; ++i) { - if (typeof d.messageIDs[i] === "string") - $util.base64.decode(d.messageIDs[i], m.messageIDs[i] = $util.newBuffer($util.base64.length(d.messageIDs[i])), 0); - else if (d.messageIDs[i].length) - m.messageIDs[i] = d.messageIDs[i]; - } - } - return m; - }; - - /** - * Creates a plain object from a ControlIHave message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.ControlIHave - * @static - * @param {RPC.ControlIHave} m ControlIHave - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - ControlIHave.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.messageIDs = []; - } - if (m.topicID != null && m.hasOwnProperty("topicID")) { - d.topicID = m.topicID; - if (o.oneofs) - d._topicID = "topicID"; - } - if (m.messageIDs && m.messageIDs.length) { - d.messageIDs = []; - for (var j = 0; j < m.messageIDs.length; ++j) { - d.messageIDs[j] = o.bytes === String ? $util.base64.encode(m.messageIDs[j], 0, m.messageIDs[j].length) : o.bytes === Array ? Array.prototype.slice.call(m.messageIDs[j]) : m.messageIDs[j]; - } - } - return d; - }; - - /** - * Converts this ControlIHave to JSON. - * @function toJSON - * @memberof RPC.ControlIHave - * @instance - * @returns {Object.} JSON object - */ - ControlIHave.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return ControlIHave; - })(); - - RPC.ControlIWant = (function() { - - /** - * Properties of a ControlIWant. - * @memberof RPC - * @interface IControlIWant - * @property {Array.|null} [messageIDs] ControlIWant messageIDs - */ - - /** - * Constructs a new ControlIWant. - * @memberof RPC - * @classdesc Represents a ControlIWant. - * @implements IControlIWant - * @constructor - * @param {RPC.IControlIWant=} [p] Properties to set - */ - function ControlIWant(p) { - this.messageIDs = []; - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * ControlIWant messageIDs. - * @member {Array.} messageIDs - * @memberof RPC.ControlIWant - * @instance - */ - ControlIWant.prototype.messageIDs = $util.emptyArray; - - /** - * Encodes the specified ControlIWant message. Does not implicitly {@link RPC.ControlIWant.verify|verify} messages. - * @function encode - * @memberof RPC.ControlIWant - * @static - * @param {RPC.IControlIWant} m ControlIWant message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - ControlIWant.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.messageIDs != null && m.messageIDs.length) { - for (var i = 0; i < m.messageIDs.length; ++i) - w.uint32(10).bytes(m.messageIDs[i]); - } - return w; - }; - - /** - * Decodes a ControlIWant message from the specified reader or buffer. - * @function decode - * @memberof RPC.ControlIWant - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.ControlIWant} ControlIWant - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - ControlIWant.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.RPC.ControlIWant(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - if (!(m.messageIDs && m.messageIDs.length)) - m.messageIDs = []; - m.messageIDs.push(r.bytes()); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a ControlIWant message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.ControlIWant - * @static - * @param {Object.} d Plain object - * @returns {RPC.ControlIWant} ControlIWant - */ - ControlIWant.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.ControlIWant) - return d; - var m = new $root.RPC.ControlIWant(); - if (d.messageIDs) { - if (!Array.isArray(d.messageIDs)) - throw TypeError(".RPC.ControlIWant.messageIDs: array expected"); - m.messageIDs = []; - for (var i = 0; i < d.messageIDs.length; ++i) { - if (typeof d.messageIDs[i] === "string") - $util.base64.decode(d.messageIDs[i], m.messageIDs[i] = $util.newBuffer($util.base64.length(d.messageIDs[i])), 0); - else if (d.messageIDs[i].length) - m.messageIDs[i] = d.messageIDs[i]; - } - } - return m; - }; - - /** - * Creates a plain object from a ControlIWant message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.ControlIWant - * @static - * @param {RPC.ControlIWant} m ControlIWant - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - ControlIWant.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.messageIDs = []; - } - if (m.messageIDs && m.messageIDs.length) { - d.messageIDs = []; - for (var j = 0; j < m.messageIDs.length; ++j) { - d.messageIDs[j] = o.bytes === String ? $util.base64.encode(m.messageIDs[j], 0, m.messageIDs[j].length) : o.bytes === Array ? Array.prototype.slice.call(m.messageIDs[j]) : m.messageIDs[j]; - } - } - return d; - }; - - /** - * Converts this ControlIWant to JSON. - * @function toJSON - * @memberof RPC.ControlIWant - * @instance - * @returns {Object.} JSON object - */ - ControlIWant.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return ControlIWant; - })(); - - RPC.ControlGraft = (function() { - - /** - * Properties of a ControlGraft. - * @memberof RPC - * @interface IControlGraft - * @property {string|null} [topicID] ControlGraft topicID - */ - - /** - * Constructs a new ControlGraft. - * @memberof RPC - * @classdesc Represents a ControlGraft. - * @implements IControlGraft - * @constructor - * @param {RPC.IControlGraft=} [p] Properties to set - */ - function ControlGraft(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * ControlGraft topicID. - * @member {string|null|undefined} topicID - * @memberof RPC.ControlGraft - * @instance - */ - ControlGraft.prototype.topicID = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * ControlGraft _topicID. - * @member {"topicID"|undefined} _topicID - * @memberof RPC.ControlGraft - * @instance - */ - Object.defineProperty(ControlGraft.prototype, "_topicID", { - get: $util.oneOfGetter($oneOfFields = ["topicID"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified ControlGraft message. Does not implicitly {@link RPC.ControlGraft.verify|verify} messages. - * @function encode - * @memberof RPC.ControlGraft - * @static - * @param {RPC.IControlGraft} m ControlGraft message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - ControlGraft.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.topicID != null && Object.hasOwnProperty.call(m, "topicID")) - w.uint32(10).string(m.topicID); - return w; - }; - - /** - * Decodes a ControlGraft message from the specified reader or buffer. - * @function decode - * @memberof RPC.ControlGraft - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.ControlGraft} ControlGraft - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - ControlGraft.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.RPC.ControlGraft(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.topicID = r.string(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a ControlGraft message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.ControlGraft - * @static - * @param {Object.} d Plain object - * @returns {RPC.ControlGraft} ControlGraft - */ - ControlGraft.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.ControlGraft) - return d; - var m = new $root.RPC.ControlGraft(); - if (d.topicID != null) { - m.topicID = String(d.topicID); - } - return m; - }; - - /** - * Creates a plain object from a ControlGraft message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.ControlGraft - * @static - * @param {RPC.ControlGraft} m ControlGraft - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - ControlGraft.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (m.topicID != null && m.hasOwnProperty("topicID")) { - d.topicID = m.topicID; - if (o.oneofs) - d._topicID = "topicID"; - } - return d; - }; - - /** - * Converts this ControlGraft to JSON. - * @function toJSON - * @memberof RPC.ControlGraft - * @instance - * @returns {Object.} JSON object - */ - ControlGraft.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return ControlGraft; - })(); - - RPC.ControlPrune = (function() { - - /** - * Properties of a ControlPrune. - * @memberof RPC - * @interface IControlPrune - * @property {string|null} [topicID] ControlPrune topicID - * @property {Array.|null} [peers] ControlPrune peers - * @property {number|null} [backoff] ControlPrune backoff - */ - - /** - * Constructs a new ControlPrune. - * @memberof RPC - * @classdesc Represents a ControlPrune. - * @implements IControlPrune - * @constructor - * @param {RPC.IControlPrune=} [p] Properties to set - */ - function ControlPrune(p) { - this.peers = []; - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * ControlPrune topicID. - * @member {string|null|undefined} topicID - * @memberof RPC.ControlPrune - * @instance - */ - ControlPrune.prototype.topicID = null; - - /** - * ControlPrune peers. - * @member {Array.} peers - * @memberof RPC.ControlPrune - * @instance - */ - ControlPrune.prototype.peers = $util.emptyArray; - - /** - * ControlPrune backoff. - * @member {number|null|undefined} backoff - * @memberof RPC.ControlPrune - * @instance - */ - ControlPrune.prototype.backoff = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * ControlPrune _topicID. - * @member {"topicID"|undefined} _topicID - * @memberof RPC.ControlPrune - * @instance - */ - Object.defineProperty(ControlPrune.prototype, "_topicID", { - get: $util.oneOfGetter($oneOfFields = ["topicID"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * ControlPrune _backoff. - * @member {"backoff"|undefined} _backoff - * @memberof RPC.ControlPrune - * @instance - */ - Object.defineProperty(ControlPrune.prototype, "_backoff", { - get: $util.oneOfGetter($oneOfFields = ["backoff"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified ControlPrune message. Does not implicitly {@link RPC.ControlPrune.verify|verify} messages. - * @function encode - * @memberof RPC.ControlPrune - * @static - * @param {RPC.IControlPrune} m ControlPrune message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - ControlPrune.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.topicID != null && Object.hasOwnProperty.call(m, "topicID")) - w.uint32(10).string(m.topicID); - if (m.peers != null && m.peers.length) { - for (var i = 0; i < m.peers.length; ++i) - $root.RPC.PeerInfo.encode(m.peers[i], w.uint32(18).fork()).ldelim(); - } - if (m.backoff != null && Object.hasOwnProperty.call(m, "backoff")) - w.uint32(24).uint64(m.backoff); - return w; - }; - - /** - * Decodes a ControlPrune message from the specified reader or buffer. - * @function decode - * @memberof RPC.ControlPrune - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.ControlPrune} ControlPrune - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - ControlPrune.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.RPC.ControlPrune(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.topicID = r.string(); - break; - case 2: - if (!(m.peers && m.peers.length)) - m.peers = []; - m.peers.push($root.RPC.PeerInfo.decode(r, r.uint32())); - break; - case 3: - m.backoff = r.uint64(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a ControlPrune message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.ControlPrune - * @static - * @param {Object.} d Plain object - * @returns {RPC.ControlPrune} ControlPrune - */ - ControlPrune.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.ControlPrune) - return d; - var m = new $root.RPC.ControlPrune(); - if (d.topicID != null) { - m.topicID = String(d.topicID); - } - if (d.peers) { - if (!Array.isArray(d.peers)) - throw TypeError(".RPC.ControlPrune.peers: array expected"); - m.peers = []; - for (var i = 0; i < d.peers.length; ++i) { - if (typeof d.peers[i] !== "object") - throw TypeError(".RPC.ControlPrune.peers: object expected"); - m.peers[i] = $root.RPC.PeerInfo.fromObject(d.peers[i]); - } - } - if (d.backoff != null) { - if ($util.Long) - (m.backoff = $util.Long.fromValue(d.backoff)).unsigned = true; - else if (typeof d.backoff === "string") - m.backoff = parseInt(d.backoff, 10); - else if (typeof d.backoff === "number") - m.backoff = d.backoff; - else if (typeof d.backoff === "object") - m.backoff = new $util.LongBits(d.backoff.low >>> 0, d.backoff.high >>> 0).toNumber(true); - } - return m; - }; - - /** - * Creates a plain object from a ControlPrune message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.ControlPrune - * @static - * @param {RPC.ControlPrune} m ControlPrune - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - ControlPrune.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.peers = []; - } - if (m.topicID != null && m.hasOwnProperty("topicID")) { - d.topicID = m.topicID; - if (o.oneofs) - d._topicID = "topicID"; - } - if (m.peers && m.peers.length) { - d.peers = []; - for (var j = 0; j < m.peers.length; ++j) { - d.peers[j] = $root.RPC.PeerInfo.toObject(m.peers[j], o); - } - } - if (m.backoff != null && m.hasOwnProperty("backoff")) { - if (typeof m.backoff === "number") - d.backoff = o.longs === String ? String(m.backoff) : m.backoff; - else - d.backoff = o.longs === String ? $util.Long.prototype.toString.call(m.backoff) : o.longs === Number ? new $util.LongBits(m.backoff.low >>> 0, m.backoff.high >>> 0).toNumber(true) : m.backoff; - if (o.oneofs) - d._backoff = "backoff"; - } - return d; - }; - - /** - * Converts this ControlPrune to JSON. - * @function toJSON - * @memberof RPC.ControlPrune - * @instance - * @returns {Object.} JSON object - */ - ControlPrune.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return ControlPrune; - })(); - - RPC.PeerInfo = (function() { - - /** - * Properties of a PeerInfo. - * @memberof RPC - * @interface IPeerInfo - * @property {Uint8Array|null} [peerID] PeerInfo peerID - * @property {Uint8Array|null} [signedPeerRecord] PeerInfo signedPeerRecord - */ - - /** - * Constructs a new PeerInfo. - * @memberof RPC - * @classdesc Represents a PeerInfo. - * @implements IPeerInfo - * @constructor - * @param {RPC.IPeerInfo=} [p] Properties to set - */ - function PeerInfo(p) { - if (p) - for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) - if (p[ks[i]] != null) - this[ks[i]] = p[ks[i]]; - } - - /** - * PeerInfo peerID. - * @member {Uint8Array|null|undefined} peerID - * @memberof RPC.PeerInfo - * @instance - */ - PeerInfo.prototype.peerID = null; - - /** - * PeerInfo signedPeerRecord. - * @member {Uint8Array|null|undefined} signedPeerRecord - * @memberof RPC.PeerInfo - * @instance - */ - PeerInfo.prototype.signedPeerRecord = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * PeerInfo _peerID. - * @member {"peerID"|undefined} _peerID - * @memberof RPC.PeerInfo - * @instance - */ - Object.defineProperty(PeerInfo.prototype, "_peerID", { - get: $util.oneOfGetter($oneOfFields = ["peerID"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * PeerInfo _signedPeerRecord. - * @member {"signedPeerRecord"|undefined} _signedPeerRecord - * @memberof RPC.PeerInfo - * @instance - */ - Object.defineProperty(PeerInfo.prototype, "_signedPeerRecord", { - get: $util.oneOfGetter($oneOfFields = ["signedPeerRecord"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified PeerInfo message. Does not implicitly {@link RPC.PeerInfo.verify|verify} messages. - * @function encode - * @memberof RPC.PeerInfo - * @static - * @param {RPC.IPeerInfo} m PeerInfo message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - PeerInfo.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.peerID != null && Object.hasOwnProperty.call(m, "peerID")) - w.uint32(10).bytes(m.peerID); - if (m.signedPeerRecord != null && Object.hasOwnProperty.call(m, "signedPeerRecord")) - w.uint32(18).bytes(m.signedPeerRecord); - return w; - }; - - /** - * Decodes a PeerInfo message from the specified reader or buffer. - * @function decode - * @memberof RPC.PeerInfo - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.PeerInfo} PeerInfo - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - PeerInfo.decode = function decode(r, l) { - if (!(r instanceof $Reader)) - r = $Reader.create(r); - var c = l === undefined ? r.len : r.pos + l, m = new $root.RPC.PeerInfo(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.peerID = r.bytes(); - break; - case 2: - m.signedPeerRecord = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a PeerInfo message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.PeerInfo - * @static - * @param {Object.} d Plain object - * @returns {RPC.PeerInfo} PeerInfo - */ - PeerInfo.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.PeerInfo) - return d; - var m = new $root.RPC.PeerInfo(); - if (d.peerID != null) { - if (typeof d.peerID === "string") - $util.base64.decode(d.peerID, m.peerID = $util.newBuffer($util.base64.length(d.peerID)), 0); - else if (d.peerID.length) - m.peerID = d.peerID; - } - if (d.signedPeerRecord != null) { - if (typeof d.signedPeerRecord === "string") - $util.base64.decode(d.signedPeerRecord, m.signedPeerRecord = $util.newBuffer($util.base64.length(d.signedPeerRecord)), 0); - else if (d.signedPeerRecord.length) - m.signedPeerRecord = d.signedPeerRecord; - } - return m; - }; - - /** - * Creates a plain object from a PeerInfo message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.PeerInfo - * @static - * @param {RPC.PeerInfo} m PeerInfo - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - PeerInfo.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (m.peerID != null && m.hasOwnProperty("peerID")) { - d.peerID = o.bytes === String ? $util.base64.encode(m.peerID, 0, m.peerID.length) : o.bytes === Array ? Array.prototype.slice.call(m.peerID) : m.peerID; - if (o.oneofs) - d._peerID = "peerID"; - } - if (m.signedPeerRecord != null && m.hasOwnProperty("signedPeerRecord")) { - d.signedPeerRecord = o.bytes === String ? $util.base64.encode(m.signedPeerRecord, 0, m.signedPeerRecord.length) : o.bytes === Array ? Array.prototype.slice.call(m.signedPeerRecord) : m.signedPeerRecord; - if (o.oneofs) - d._signedPeerRecord = "signedPeerRecord"; - } - return d; - }; - - /** - * Converts this PeerInfo to JSON. - * @function toJSON - * @memberof RPC.PeerInfo - * @instance - * @returns {Object.} JSON object - */ - PeerInfo.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return PeerInfo; - })(); - - return RPC; - })(); - - return $root; -}); diff --git a/packages/pubsub-gossipsub/src/message/rpc.d.ts b/packages/pubsub-gossipsub/src/message/rpc.d.ts deleted file mode 100644 index 3716d3cc2b..0000000000 --- a/packages/pubsub-gossipsub/src/message/rpc.d.ts +++ /dev/null @@ -1,666 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of a RPC. */ -export interface IRPC { - - /** RPC subscriptions */ - subscriptions?: (RPC.ISubOpts[]|null); - - /** RPC messages */ - messages?: (RPC.IMessage[]|null); - - /** RPC control */ - control?: (RPC.IControlMessage|null); -} - -/** Represents a RPC. */ -export class RPC implements IRPC { - - /** - * Constructs a new RPC. - * @param [p] Properties to set - */ - constructor(p?: IRPC); - - /** RPC subscriptions. */ - public subscriptions: RPC.ISubOpts[]; - - /** RPC messages. */ - public messages: RPC.IMessage[]; - - /** RPC control. */ - public control?: (RPC.IControlMessage|null); - - /** RPC _control. */ - public _control?: "control"; - - /** - * Encodes the specified RPC message. Does not implicitly {@link RPC.verify|verify} messages. - * @param m RPC message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IRPC, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a RPC message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns RPC - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): RPC; - - /** - * Creates a RPC message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns RPC - */ - public static fromObject(d: { [k: string]: any }): RPC; - - /** - * Creates a plain object from a RPC message. Also converts values to other types if specified. - * @param m RPC - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this RPC to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} - -export namespace RPC { - - /** Properties of a SubOpts. */ - interface ISubOpts { - - /** SubOpts subscribe */ - subscribe?: (boolean|null); - - /** SubOpts topic */ - topic?: (string|null); - } - - /** Represents a SubOpts. */ - class SubOpts implements ISubOpts { - - /** - * Constructs a new SubOpts. - * @param [p] Properties to set - */ - constructor(p?: RPC.ISubOpts); - - /** SubOpts subscribe. */ - public subscribe?: (boolean|null); - - /** SubOpts topic. */ - public topic?: (string|null); - - /** SubOpts _subscribe. */ - public _subscribe?: "subscribe"; - - /** SubOpts _topic. */ - public _topic?: "topic"; - - /** - * Encodes the specified SubOpts message. Does not implicitly {@link RPC.SubOpts.verify|verify} messages. - * @param m SubOpts message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.ISubOpts, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a SubOpts message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns SubOpts - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): RPC.SubOpts; - - /** - * Creates a SubOpts message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns SubOpts - */ - public static fromObject(d: { [k: string]: any }): RPC.SubOpts; - - /** - * Creates a plain object from a SubOpts message. Also converts values to other types if specified. - * @param m SubOpts - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.SubOpts, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this SubOpts to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a Message. */ - interface IMessage { - - /** Message from */ - from?: (Uint8Array|null); - - /** Message data */ - data?: (Uint8Array|null); - - /** Message seqno */ - seqno?: (Uint8Array|null); - - /** Message topic */ - topic: string; - - /** Message signature */ - signature?: (Uint8Array|null); - - /** Message key */ - key?: (Uint8Array|null); - } - - /** Represents a Message. */ - class Message implements IMessage { - - /** - * Constructs a new Message. - * @param [p] Properties to set - */ - constructor(p?: RPC.IMessage); - - /** Message from. */ - public from?: (Uint8Array|null); - - /** Message data. */ - public data?: (Uint8Array|null); - - /** Message seqno. */ - public seqno?: (Uint8Array|null); - - /** Message topic. */ - public topic: string; - - /** Message signature. */ - public signature?: (Uint8Array|null); - - /** Message key. */ - public key?: (Uint8Array|null); - - /** Message _from. */ - public _from?: "from"; - - /** Message _data. */ - public _data?: "data"; - - /** Message _seqno. */ - public _seqno?: "seqno"; - - /** Message _signature. */ - public _signature?: "signature"; - - /** Message _key. */ - public _key?: "key"; - - /** - * Encodes the specified Message message. Does not implicitly {@link RPC.Message.verify|verify} messages. - * @param m Message message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IMessage, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a Message message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Message - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): RPC.Message; - - /** - * Creates a Message message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Message - */ - public static fromObject(d: { [k: string]: any }): RPC.Message; - - /** - * Creates a plain object from a Message message. Also converts values to other types if specified. - * @param m Message - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.Message, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Message to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a ControlMessage. */ - interface IControlMessage { - - /** ControlMessage ihave */ - ihave?: (RPC.IControlIHave[]|null); - - /** ControlMessage iwant */ - iwant?: (RPC.IControlIWant[]|null); - - /** ControlMessage graft */ - graft?: (RPC.IControlGraft[]|null); - - /** ControlMessage prune */ - prune?: (RPC.IControlPrune[]|null); - } - - /** Represents a ControlMessage. */ - class ControlMessage implements IControlMessage { - - /** - * Constructs a new ControlMessage. - * @param [p] Properties to set - */ - constructor(p?: RPC.IControlMessage); - - /** ControlMessage ihave. */ - public ihave: RPC.IControlIHave[]; - - /** ControlMessage iwant. */ - public iwant: RPC.IControlIWant[]; - - /** ControlMessage graft. */ - public graft: RPC.IControlGraft[]; - - /** ControlMessage prune. */ - public prune: RPC.IControlPrune[]; - - /** - * Encodes the specified ControlMessage message. Does not implicitly {@link RPC.ControlMessage.verify|verify} messages. - * @param m ControlMessage message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IControlMessage, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a ControlMessage message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns ControlMessage - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): RPC.ControlMessage; - - /** - * Creates a ControlMessage message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns ControlMessage - */ - public static fromObject(d: { [k: string]: any }): RPC.ControlMessage; - - /** - * Creates a plain object from a ControlMessage message. Also converts values to other types if specified. - * @param m ControlMessage - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.ControlMessage, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this ControlMessage to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a ControlIHave. */ - interface IControlIHave { - - /** ControlIHave topicID */ - topicID?: (string|null); - - /** ControlIHave messageIDs */ - messageIDs?: (Uint8Array[]|null); - } - - /** Represents a ControlIHave. */ - class ControlIHave implements IControlIHave { - - /** - * Constructs a new ControlIHave. - * @param [p] Properties to set - */ - constructor(p?: RPC.IControlIHave); - - /** ControlIHave topicID. */ - public topicID?: (string|null); - - /** ControlIHave messageIDs. */ - public messageIDs: Uint8Array[]; - - /** ControlIHave _topicID. */ - public _topicID?: "topicID"; - - /** - * Encodes the specified ControlIHave message. Does not implicitly {@link RPC.ControlIHave.verify|verify} messages. - * @param m ControlIHave message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IControlIHave, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a ControlIHave message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns ControlIHave - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): RPC.ControlIHave; - - /** - * Creates a ControlIHave message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns ControlIHave - */ - public static fromObject(d: { [k: string]: any }): RPC.ControlIHave; - - /** - * Creates a plain object from a ControlIHave message. Also converts values to other types if specified. - * @param m ControlIHave - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.ControlIHave, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this ControlIHave to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a ControlIWant. */ - interface IControlIWant { - - /** ControlIWant messageIDs */ - messageIDs?: (Uint8Array[]|null); - } - - /** Represents a ControlIWant. */ - class ControlIWant implements IControlIWant { - - /** - * Constructs a new ControlIWant. - * @param [p] Properties to set - */ - constructor(p?: RPC.IControlIWant); - - /** ControlIWant messageIDs. */ - public messageIDs: Uint8Array[]; - - /** - * Encodes the specified ControlIWant message. Does not implicitly {@link RPC.ControlIWant.verify|verify} messages. - * @param m ControlIWant message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IControlIWant, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a ControlIWant message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns ControlIWant - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): RPC.ControlIWant; - - /** - * Creates a ControlIWant message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns ControlIWant - */ - public static fromObject(d: { [k: string]: any }): RPC.ControlIWant; - - /** - * Creates a plain object from a ControlIWant message. Also converts values to other types if specified. - * @param m ControlIWant - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.ControlIWant, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this ControlIWant to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a ControlGraft. */ - interface IControlGraft { - - /** ControlGraft topicID */ - topicID?: (string|null); - } - - /** Represents a ControlGraft. */ - class ControlGraft implements IControlGraft { - - /** - * Constructs a new ControlGraft. - * @param [p] Properties to set - */ - constructor(p?: RPC.IControlGraft); - - /** ControlGraft topicID. */ - public topicID?: (string|null); - - /** ControlGraft _topicID. */ - public _topicID?: "topicID"; - - /** - * Encodes the specified ControlGraft message. Does not implicitly {@link RPC.ControlGraft.verify|verify} messages. - * @param m ControlGraft message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IControlGraft, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a ControlGraft message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns ControlGraft - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): RPC.ControlGraft; - - /** - * Creates a ControlGraft message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns ControlGraft - */ - public static fromObject(d: { [k: string]: any }): RPC.ControlGraft; - - /** - * Creates a plain object from a ControlGraft message. Also converts values to other types if specified. - * @param m ControlGraft - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.ControlGraft, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this ControlGraft to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a ControlPrune. */ - interface IControlPrune { - - /** ControlPrune topicID */ - topicID?: (string|null); - - /** ControlPrune peers */ - peers?: (RPC.IPeerInfo[]|null); - - /** ControlPrune backoff */ - backoff?: (number|null); - } - - /** Represents a ControlPrune. */ - class ControlPrune implements IControlPrune { - - /** - * Constructs a new ControlPrune. - * @param [p] Properties to set - */ - constructor(p?: RPC.IControlPrune); - - /** ControlPrune topicID. */ - public topicID?: (string|null); - - /** ControlPrune peers. */ - public peers: RPC.IPeerInfo[]; - - /** ControlPrune backoff. */ - public backoff?: (number|null); - - /** ControlPrune _topicID. */ - public _topicID?: "topicID"; - - /** ControlPrune _backoff. */ - public _backoff?: "backoff"; - - /** - * Encodes the specified ControlPrune message. Does not implicitly {@link RPC.ControlPrune.verify|verify} messages. - * @param m ControlPrune message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IControlPrune, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a ControlPrune message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns ControlPrune - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): RPC.ControlPrune; - - /** - * Creates a ControlPrune message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns ControlPrune - */ - public static fromObject(d: { [k: string]: any }): RPC.ControlPrune; - - /** - * Creates a plain object from a ControlPrune message. Also converts values to other types if specified. - * @param m ControlPrune - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.ControlPrune, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this ControlPrune to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a PeerInfo. */ - interface IPeerInfo { - - /** PeerInfo peerID */ - peerID?: (Uint8Array|null); - - /** PeerInfo signedPeerRecord */ - signedPeerRecord?: (Uint8Array|null); - } - - /** Represents a PeerInfo. */ - class PeerInfo implements IPeerInfo { - - /** - * Constructs a new PeerInfo. - * @param [p] Properties to set - */ - constructor(p?: RPC.IPeerInfo); - - /** PeerInfo peerID. */ - public peerID?: (Uint8Array|null); - - /** PeerInfo signedPeerRecord. */ - public signedPeerRecord?: (Uint8Array|null); - - /** PeerInfo _peerID. */ - public _peerID?: "peerID"; - - /** PeerInfo _signedPeerRecord. */ - public _signedPeerRecord?: "signedPeerRecord"; - - /** - * Encodes the specified PeerInfo message. Does not implicitly {@link RPC.PeerInfo.verify|verify} messages. - * @param m PeerInfo message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IPeerInfo, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a PeerInfo message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns PeerInfo - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): RPC.PeerInfo; - - /** - * Creates a PeerInfo message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns PeerInfo - */ - public static fromObject(d: { [k: string]: any }): RPC.PeerInfo; - - /** - * Creates a plain object from a PeerInfo message. Also converts values to other types if specified. - * @param m PeerInfo - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.PeerInfo, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this PeerInfo to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } -} diff --git a/packages/pubsub-gossipsub/src/message/rpc.js b/packages/pubsub-gossipsub/src/message/rpc.js deleted file mode 100644 index b569a782b9..0000000000 --- a/packages/pubsub-gossipsub/src/message/rpc.js +++ /dev/null @@ -1,3 +0,0 @@ -import cjs from "./rpc.cjs" - -export const {RPC} = cjs diff --git a/packages/pubsub-gossipsub/src/message/rpc.proto b/packages/pubsub-gossipsub/src/message/rpc.proto deleted file mode 100644 index 4e09b4ebef..0000000000 --- a/packages/pubsub-gossipsub/src/message/rpc.proto +++ /dev/null @@ -1,52 +0,0 @@ -syntax = "proto3"; - -message RPC { - repeated SubOpts subscriptions = 1; - repeated Message messages = 2; - optional ControlMessage control = 3; - - message SubOpts { - optional bool subscribe = 1; // subscribe or unsubcribe - optional string topic = 2; - } - - message Message { - optional bytes from = 1; - optional bytes data = 2; - optional bytes seqno = 3; - required string topic = 4; - optional bytes signature = 5; - optional bytes key = 6; - } - - message ControlMessage { - repeated ControlIHave ihave = 1; - repeated ControlIWant iwant = 2; - repeated ControlGraft graft = 3; - repeated ControlPrune prune = 4; - } - - message ControlIHave { - optional string topicID = 1; - repeated bytes messageIDs = 2; - } - - message ControlIWant { - repeated bytes messageIDs = 1; - } - - message ControlGraft { - optional string topicID = 1; - } - - message ControlPrune { - optional string topicID = 1; - repeated PeerInfo peers = 2; - optional uint64 backoff = 3; - } - - message PeerInfo { - optional bytes peerID = 1; - optional bytes signedPeerRecord = 2; - } -} \ No newline at end of file diff --git a/packages/pubsub-gossipsub/src/metrics.ts b/packages/pubsub-gossipsub/src/metrics.ts deleted file mode 100644 index 0f3501b2ca..0000000000 --- a/packages/pubsub-gossipsub/src/metrics.ts +++ /dev/null @@ -1,829 +0,0 @@ -import type { TopicValidatorResult } from '@libp2p/interface/pubsub' -import type { IRPC } from './message/rpc.js' -import type { PeerScoreThresholds } from './score/peer-score-thresholds.js' -import { MessageStatus, type PeerIdStr, RejectReason, type RejectReasonObj, type TopicStr, ValidateError } from './types.js' - -/** Topic label as provided in `topicStrToLabel` */ -export type TopicLabel = string -export type TopicStrToLabel = Map - -export enum MessageSource { - forward = 'forward', - publish = 'publish' -} - -type LabelsGeneric = Record -type CollectFn = (metric: Gauge) => void - -interface Gauge { - // Sorry for this mess, `prom-client` API choices are not great - // If the function signature was `inc(value: number, labels?: Labels)`, this would be simpler - inc(value?: number): void - inc(labels: Labels, value?: number): void - inc(arg1?: Labels | number, arg2?: number): void - - set(value: number): void - set(labels: Labels, value: number): void - set(arg1?: Labels | number, arg2?: number): void - - addCollect(collectFn: CollectFn): void -} - -interface Histogram { - startTimer(): () => void - - observe(value: number): void - observe(labels: Labels, values: number): void - observe(arg1: Labels | number, arg2?: number): void - - reset(): void -} - -interface AvgMinMax { - set(values: number[]): void - set(labels: Labels, values: number[]): void - set(arg1?: Labels | number[], arg2?: number[]): void -} - -type GaugeConfig = { - name: string - help: string - labelNames?: keyof Labels extends string ? (keyof Labels)[] : undefined -} - -type HistogramConfig = { - name: string - help: string - labelNames?: (keyof Labels)[] - buckets?: number[] -} - -type AvgMinMaxConfig = GaugeConfig - -export interface MetricsRegister { - gauge(config: GaugeConfig): Gauge - histogram(config: HistogramConfig): Histogram - avgMinMax(config: AvgMinMaxConfig): AvgMinMax -} - -export enum InclusionReason { - /** Peer was a fanaout peer. */ - Fanout = 'fanout', - /** Included from random selection. */ - Random = 'random', - /** Peer subscribed. */ - Subscribed = 'subscribed', - /** On heartbeat, peer was included to fill the outbound quota. */ - Outbound = 'outbound', - /** On heartbeat, not enough peers in mesh */ - NotEnough = 'not_enough', - /** On heartbeat opportunistic grafting due to low mesh score */ - Opportunistic = 'opportunistic' -} - -/// Reasons why a peer was removed from the mesh. -export enum ChurnReason { - /// Peer disconnected. - Dc = 'disconnected', - /// Peer had a bad score. - BadScore = 'bad_score', - /// Peer sent a PRUNE. - Prune = 'prune', - /// Peer unsubscribed. - Unsub = 'unsubscribed', - /// Too many peers. - Excess = 'excess' -} - -/// Kinds of reasons a peer's score has been penalized -export enum ScorePenalty { - /// A peer grafted before waiting the back-off time. - GraftBackoff = 'graft_backoff', - /// A Peer did not respond to an IWANT request in time. - BrokenPromise = 'broken_promise', - /// A Peer did not send enough messages as expected. - MessageDeficit = 'message_deficit', - /// Too many peers under one IP address. - IPColocation = 'IP_colocation' -} - -export enum IHaveIgnoreReason { - LowScore = 'low_score', - MaxIhave = 'max_ihave', - MaxIasked = 'max_iasked' -} - -export enum ScoreThreshold { - graylist = 'graylist', - publish = 'publish', - gossip = 'gossip', - mesh = 'mesh' -} - -export type PeersByScoreThreshold = Record - -export type ToSendGroupCount = { - direct: number - floodsub: number - mesh: number - fanout: number -} - -export type ToAddGroupCount = { - fanout: number - random: number -} - -export type PromiseDeliveredStats = - | { expired: false; requestedCount: number; maxDeliverMs: number } - | { expired: true; maxDeliverMs: number } - -export type TopicScoreWeights = { p1w: T; p2w: T; p3w: T; p3bw: T; p4w: T } -export type ScoreWeights = { - byTopic: Map> - p5w: T - p6w: T - p7w: T - score: T -} - -export type Metrics = ReturnType - -/** - * A collection of metrics used throughout the Gossipsub behaviour. - * NOTE: except for special reasons, do not add more than 1 label for frequent metrics, - * there's a performance penalty as of June 2023. - */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function getMetrics( - register: MetricsRegister, - topicStrToLabel: TopicStrToLabel, - opts: { gossipPromiseExpireSec: number; behaviourPenaltyThreshold: number; maxMeshMessageDeliveriesWindowSec: number } -) { - // Using function style instead of class to prevent having to re-declare all MetricsPrometheus types. - - return { - /* Metrics for static config */ - protocolsEnabled: register.gauge<{ protocol: string }>({ - name: 'gossipsub_protocol', - help: 'Status of enabled protocols', - labelNames: ['protocol'] - }), - - /* Metrics per known topic */ - /** Status of our subscription to this topic. This metric allows analyzing other topic metrics - * filtered by our current subscription status. - * = rust-libp2p `topic_subscription_status` */ - topicSubscriptionStatus: register.gauge<{ topicStr: TopicStr }>({ - name: 'gossipsub_topic_subscription_status', - help: 'Status of our subscription to this topic', - labelNames: ['topicStr'] - }), - /** Number of peers subscribed to each topic. This allows us to analyze a topic's behaviour - * regardless of our subscription status. */ - topicPeersCount: register.gauge<{ topicStr: TopicStr }>({ - name: 'gossipsub_topic_peer_count', - help: 'Number of peers subscribed to each topic', - labelNames: ['topicStr'] - }), - - /* Metrics regarding mesh state */ - /** Number of peers in our mesh. This metric should be updated with the count of peers for a - * topic in the mesh regardless of inclusion and churn events. - * = rust-libp2p `mesh_peer_counts` */ - meshPeerCounts: register.gauge<{ topicStr: TopicStr }>({ - name: 'gossipsub_mesh_peer_count', - help: 'Number of peers in our mesh', - labelNames: ['topicStr'] - }), - /** Number of times we include peers in a topic mesh for different reasons. - * = rust-libp2p `mesh_peer_inclusion_events` */ - meshPeerInclusionEvents: register.gauge<{ reason: InclusionReason }>({ - name: 'gossipsub_mesh_peer_inclusion_events_total', - help: 'Number of times we include peers in a topic mesh for different reasons', - labelNames: ['reason'] - }), - meshPeerInclusionEventsByTopic: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_mesh_peer_inclusion_events_by_topic_total', - help: 'Number of times we include peers in a topic', - labelNames: ['topic'] - }), - /** Number of times we remove peers in a topic mesh for different reasons. - * = rust-libp2p `mesh_peer_churn_events` */ - meshPeerChurnEvents: register.gauge<{ reason: ChurnReason }>({ - name: 'gossipsub_peer_churn_events_total', - help: 'Number of times we remove peers in a topic mesh for different reasons', - labelNames: ['reason'] - }), - meshPeerChurnEventsByTopic: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_peer_churn_events_by_topic_total', - help: 'Number of times we remove peers in a topic', - labelNames: ['topic'] - }), - - /* General Metrics */ - /** Gossipsub supports floodsub, gossipsub v1.0 and gossipsub v1.1. Peers are classified based - * on which protocol they support. This metric keeps track of the number of peers that are - * connected of each type. */ - peersPerProtocol: register.gauge<{ protocol: string }>({ - name: 'gossipsub_peers_per_protocol_count', - help: 'Peers connected for each topic', - labelNames: ['protocol'] - }), - /** The time it takes to complete one iteration of the heartbeat. */ - heartbeatDuration: register.histogram({ - name: 'gossipsub_heartbeat_duration_seconds', - help: 'The time it takes to complete one iteration of the heartbeat', - // Should take <10ms, over 1s it's a huge issue that needs debugging, since a heartbeat will be cancelled - buckets: [0.01, 0.1, 1] - }), - /** Heartbeat run took longer than heartbeat interval so next is skipped */ - heartbeatSkipped: register.gauge({ - name: 'gossipsub_heartbeat_skipped', - help: 'Heartbeat run took longer than heartbeat interval so next is skipped' - }), - - /** Message validation results for each topic. - * Invalid == Reject? - * = rust-libp2p `invalid_messages`, `accepted_messages`, `ignored_messages`, `rejected_messages` */ - asyncValidationResult: register.gauge<{ acceptance: TopicValidatorResult }>({ - name: 'gossipsub_async_validation_result_total', - help: 'Message validation result', - labelNames: ['acceptance'] - }), - asyncValidationResultByTopic: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_async_validation_result_by_topic_total', - help: 'Message validation result for each topic', - labelNames: ['topic'] - }), - /** When the user validates a message, it tries to re propagate it to its mesh peers. If the - * message expires from the memcache before it can be validated, we count this a cache miss - * and it is an indicator that the memcache size should be increased. - * = rust-libp2p `mcache_misses` */ - asyncValidationMcacheHit: register.gauge<{ hit: 'hit' | 'miss' }>({ - name: 'gossipsub_async_validation_mcache_hit_total', - help: 'Async validation result reported by the user layer', - labelNames: ['hit'] - }), - - asyncValidationDelayFromFirstSeenSec: register.histogram<{ topic: TopicLabel }>({ - name: 'gossipsub_async_validation_delay_from_first_seen', - help: 'Async validation report delay from first seen in second', - labelNames: ['topic'], - buckets: [0.01, 0.03, 0.1, 0.3, 1, 3, 10] - }), - - asyncValidationUnknownFirstSeen: register.gauge({ - name: 'gossipsub_async_validation_unknown_first_seen_count_total', - help: 'Async validation report unknown first seen value for message' - }), - - // peer stream - peerReadStreamError: register.gauge({ - name: 'gossipsub_peer_read_stream_err_count_total', - help: 'Peer read stream error' - }), - - // RPC outgoing. Track byte length + data structure sizes - rpcRecvBytes: register.gauge({ name: 'gossipsub_rpc_recv_bytes_total', help: 'RPC recv' }), - rpcRecvCount: register.gauge({ name: 'gossipsub_rpc_recv_count_total', help: 'RPC recv' }), - rpcRecvSubscription: register.gauge({ name: 'gossipsub_rpc_recv_subscription_total', help: 'RPC recv' }), - rpcRecvMessage: register.gauge({ name: 'gossipsub_rpc_recv_message_total', help: 'RPC recv' }), - rpcRecvControl: register.gauge({ name: 'gossipsub_rpc_recv_control_total', help: 'RPC recv' }), - rpcRecvIHave: register.gauge({ name: 'gossipsub_rpc_recv_ihave_total', help: 'RPC recv' }), - rpcRecvIWant: register.gauge({ name: 'gossipsub_rpc_recv_iwant_total', help: 'RPC recv' }), - rpcRecvGraft: register.gauge({ name: 'gossipsub_rpc_recv_graft_total', help: 'RPC recv' }), - rpcRecvPrune: register.gauge({ name: 'gossipsub_rpc_recv_prune_total', help: 'RPC recv' }), - rpcDataError: register.gauge({ name: 'gossipsub_rpc_data_err_count_total', help: 'RPC data error' }), - rpcRecvError: register.gauge({ name: 'gossipsub_rpc_recv_err_count_total', help: 'RPC recv error' }), - - /** Total count of RPC dropped because acceptFrom() == false */ - rpcRecvNotAccepted: register.gauge({ - name: 'gossipsub_rpc_rcv_not_accepted_total', - help: 'Total count of RPC dropped because acceptFrom() == false' - }), - - // RPC incoming. Track byte length + data structure sizes - rpcSentBytes: register.gauge({ name: 'gossipsub_rpc_sent_bytes_total', help: 'RPC sent' }), - rpcSentCount: register.gauge({ name: 'gossipsub_rpc_sent_count_total', help: 'RPC sent' }), - rpcSentSubscription: register.gauge({ name: 'gossipsub_rpc_sent_subscription_total', help: 'RPC sent' }), - rpcSentMessage: register.gauge({ name: 'gossipsub_rpc_sent_message_total', help: 'RPC sent' }), - rpcSentControl: register.gauge({ name: 'gossipsub_rpc_sent_control_total', help: 'RPC sent' }), - rpcSentIHave: register.gauge({ name: 'gossipsub_rpc_sent_ihave_total', help: 'RPC sent' }), - rpcSentIWant: register.gauge({ name: 'gossipsub_rpc_sent_iwant_total', help: 'RPC sent' }), - rpcSentGraft: register.gauge({ name: 'gossipsub_rpc_sent_graft_total', help: 'RPC sent' }), - rpcSentPrune: register.gauge({ name: 'gossipsub_rpc_sent_prune_total', help: 'RPC sent' }), - - // publish message. Track peers sent to and bytes - /** Total count of msg published by topic */ - msgPublishCount: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_msg_publish_count_total', - help: 'Total count of msg published by topic', - labelNames: ['topic'] - }), - /** Total count of peers that we publish a msg to */ - msgPublishPeersByTopic: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_msg_publish_peers_total', - help: 'Total count of peers that we publish a msg to', - labelNames: ['topic'] - }), - /** Total count of peers (by group) that we publish a msg to */ - // NOTE: Do not use 'group' label since it's a generic already used by Prometheus to group instances - msgPublishPeersByGroup: register.gauge<{ peerGroup: keyof ToSendGroupCount }>({ - name: 'gossipsub_msg_publish_peers_by_group', - help: 'Total count of peers (by group) that we publish a msg to', - labelNames: ['peerGroup'] - }), - /** Total count of msg publish data.length bytes */ - msgPublishBytes: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_msg_publish_bytes_total', - help: 'Total count of msg publish data.length bytes', - labelNames: ['topic'] - }), - - /** Total count of msg forwarded by topic */ - msgForwardCount: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_msg_forward_count_total', - help: 'Total count of msg forwarded by topic', - labelNames: ['topic'] - }), - /** Total count of peers that we forward a msg to */ - msgForwardPeers: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_msg_forward_peers_total', - help: 'Total count of peers that we forward a msg to', - labelNames: ['topic'] - }), - - /** Total count of recv msgs before any validation */ - msgReceivedPreValidation: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_msg_received_prevalidation_total', - help: 'Total count of recv msgs before any validation', - labelNames: ['topic'] - }), - /** Total count of recv msgs error */ - msgReceivedError: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_msg_received_error_total', - help: 'Total count of recv msgs error', - labelNames: ['topic'] - }), - /** Tracks distribution of recv msgs by duplicate, invalid, valid */ - msgReceivedStatus: register.gauge<{ status: MessageStatus }>({ - name: 'gossipsub_msg_received_status_total', - help: 'Tracks distribution of recv msgs by duplicate, invalid, valid', - labelNames: ['status'] - }), - msgReceivedTopic: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_msg_received_topic_total', - help: 'Tracks distribution of recv msgs by topic label', - labelNames: ['topic'] - }), - /** Tracks specific reason of invalid */ - msgReceivedInvalid: register.gauge<{ error: RejectReason | ValidateError }>({ - name: 'gossipsub_msg_received_invalid_total', - help: 'Tracks specific reason of invalid', - labelNames: ['error'] - }), - msgReceivedInvalidByTopic: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_msg_received_invalid_by_topic_total', - help: 'Tracks specific invalid message by topic', - labelNames: ['topic'] - }), - /** Track duplicate message delivery time */ - duplicateMsgDeliveryDelay: register.histogram({ - name: 'gossisub_duplicate_msg_delivery_delay_seconds', - help: 'Time since the 1st duplicated message validated', - labelNames: ['topic'], - buckets: [ - 0.25 * opts.maxMeshMessageDeliveriesWindowSec, - 0.5 * opts.maxMeshMessageDeliveriesWindowSec, - 1 * opts.maxMeshMessageDeliveriesWindowSec, - 2 * opts.maxMeshMessageDeliveriesWindowSec, - 4 * opts.maxMeshMessageDeliveriesWindowSec - ] - }), - /** Total count of late msg delivery total by topic */ - duplicateMsgLateDelivery: register.gauge<{ topic: TopicLabel }>({ - name: 'gossisub_duplicate_msg_late_delivery_total', - help: 'Total count of late duplicate message delivery by topic, which triggers P3 penalty', - labelNames: ['topic'] - }), - - duplicateMsgIgnored: register.gauge<{ topic: TopicLabel }>({ - name: 'gossisub_ignored_published_duplicate_msgs_total', - help: 'Total count of published duplicate message ignored by topic', - labelNames: ['topic'] - }), - - /* Metrics related to scoring */ - /** Total times score() is called */ - scoreFnCalls: register.gauge({ - name: 'gossipsub_score_fn_calls_total', - help: 'Total times score() is called' - }), - /** Total times score() call actually computed computeScore(), no cache */ - scoreFnRuns: register.gauge({ - name: 'gossipsub_score_fn_runs_total', - help: 'Total times score() call actually computed computeScore(), no cache' - }), - scoreCachedDelta: register.histogram({ - name: 'gossipsub_score_cache_delta', - help: 'Delta of score between cached values that expired', - buckets: [10, 100, 1000] - }), - /** Current count of peers by score threshold */ - peersByScoreThreshold: register.gauge<{ threshold: ScoreThreshold }>({ - name: 'gossipsub_peers_by_score_threshold_count', - help: 'Current count of peers by score threshold', - labelNames: ['threshold'] - }), - score: register.avgMinMax({ - name: 'gossipsub_score', - help: 'Avg min max of gossip scores' - }), - /** - * Separate score weights - * Need to use 2-label metrics in this case to debug the score weights - **/ - scoreWeights: register.avgMinMax<{ topic?: TopicLabel; p: string }>({ - name: 'gossipsub_score_weights', - help: 'Separate score weights', - labelNames: ['topic', 'p'] - }), - /** Histogram of the scores for each mesh topic. */ - // TODO: Not implemented - scorePerMesh: register.avgMinMax<{ topic: TopicLabel }>({ - name: 'gossipsub_score_per_mesh', - help: 'Histogram of the scores for each mesh topic', - labelNames: ['topic'] - }), - /** A counter of the kind of penalties being applied to peers. */ - // TODO: Not fully implemented - scoringPenalties: register.gauge<{ penalty: ScorePenalty }>({ - name: 'gossipsub_scoring_penalties_total', - help: 'A counter of the kind of penalties being applied to peers', - labelNames: ['penalty'] - }), - behaviourPenalty: register.histogram({ - name: 'gossipsub_peer_stat_behaviour_penalty', - help: 'Current peer stat behaviour_penalty at each scrape', - buckets: [ - 0.25 * opts.behaviourPenaltyThreshold, - 0.5 * opts.behaviourPenaltyThreshold, - 1 * opts.behaviourPenaltyThreshold, - 2 * opts.behaviourPenaltyThreshold, - 4 * opts.behaviourPenaltyThreshold - ] - }), - - // TODO: - // - iasked per peer (on heartbeat) - // - when promise is resolved, track messages from promises - - /** Total received IHAVE messages that we ignore for some reason */ - ihaveRcvIgnored: register.gauge<{ reason: IHaveIgnoreReason }>({ - name: 'gossipsub_ihave_rcv_ignored_total', - help: 'Total received IHAVE messages that we ignore for some reason', - labelNames: ['reason'] - }), - /** Total received IHAVE messages by topic */ - ihaveRcvMsgids: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_ihave_rcv_msgids_total', - help: 'Total received IHAVE messages by topic', - labelNames: ['topic'] - }), - /** Total messages per topic we don't have. Not actual requests. - * The number of times we have decided that an IWANT control message is required for this - * topic. A very high metric might indicate an underperforming network. - * = rust-libp2p `topic_iwant_msgs` */ - ihaveRcvNotSeenMsgids: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_ihave_rcv_not_seen_msgids_total', - help: 'Total messages per topic we do not have, not actual requests', - labelNames: ['topic'] - }), - - /** Total received IWANT messages by topic */ - iwantRcvMsgids: register.gauge<{ topic: TopicLabel }>({ - name: 'gossipsub_iwant_rcv_msgids_total', - help: 'Total received IWANT messages by topic', - labelNames: ['topic'] - }), - /** Total requested messageIDs that we don't have */ - iwantRcvDonthaveMsgids: register.gauge({ - name: 'gossipsub_iwant_rcv_dont_have_msgids_total', - help: 'Total requested messageIDs that we do not have' - }), - iwantPromiseStarted: register.gauge({ - name: 'gossipsub_iwant_promise_sent_total', - help: 'Total count of started IWANT promises' - }), - /** Total count of resolved IWANT promises */ - iwantPromiseResolved: register.gauge({ - name: 'gossipsub_iwant_promise_resolved_total', - help: 'Total count of resolved IWANT promises' - }), - /** Total count of resolved IWANT promises from duplicate messages */ - iwantPromiseResolvedFromDuplicate: register.gauge({ - name: 'gossipsub_iwant_promise_resolved_from_duplicate_total', - help: 'Total count of resolved IWANT promises from duplicate messages' - }), - /** Total count of peers we have asked IWANT promises that are resolved */ - iwantPromiseResolvedPeers: register.gauge({ - name: 'gossipsub_iwant_promise_resolved_peers', - help: 'Total count of peers we have asked IWANT promises that are resolved' - }), - iwantPromiseBroken: register.gauge({ - name: 'gossipsub_iwant_promise_broken', - help: 'Total count of broken IWANT promises' - }), - iwantMessagePruned: register.gauge({ - name: 'gossipsub_iwant_message_pruned', - help: 'Total count of pruned IWANT messages' - }), - /** Histogram of delivery time of resolved IWANT promises */ - iwantPromiseDeliveryTime: register.histogram({ - name: 'gossipsub_iwant_promise_delivery_seconds', - help: 'Histogram of delivery time of resolved IWANT promises', - buckets: [ - 0.5 * opts.gossipPromiseExpireSec, - 1 * opts.gossipPromiseExpireSec, - 2 * opts.gossipPromiseExpireSec, - 4 * opts.gossipPromiseExpireSec - ] - }), - iwantPromiseUntracked: register.gauge({ - name: 'gossip_iwant_promise_untracked', - help: 'Total count of untracked IWANT promise' - }), - - /* Data structure sizes */ - /** Unbounded cache sizes */ - cacheSize: register.gauge<{ cache: string }>({ - name: 'gossipsub_cache_size', - help: 'Unbounded cache sizes', - labelNames: ['cache'] - }), - /** Current mcache msg count */ - mcacheSize: register.gauge({ - name: 'gossipsub_mcache_size', - help: 'Current mcache msg count' - }), - mcacheNotValidatedCount: register.gauge({ - name: 'gossipsub_mcache_not_validated_count', - help: 'Current mcache msg count not validated' - }), - - fastMsgIdCacheCollision: register.gauge({ - name: 'gossipsub_fastmsgid_cache_collision_total', - help: 'Total count of key collisions on fastmsgid cache put' - }), - - newConnectionCount: register.gauge<{ status: string }>({ - name: 'gossipsub_new_connection_total', - help: 'Total new connection by status', - labelNames: ['status'] - }), - - topicStrToLabel: topicStrToLabel, - - toTopic(topicStr: TopicStr): TopicLabel { - return this.topicStrToLabel.get(topicStr) ?? topicStr - }, - - /** We joined a topic */ - onJoin(topicStr: TopicStr): void { - this.topicSubscriptionStatus.set({ topicStr }, 1) - this.meshPeerCounts.set({ topicStr }, 0) // Reset count - }, - - /** We left a topic */ - onLeave(topicStr: TopicStr): void { - this.topicSubscriptionStatus.set({ topicStr }, 0) - this.meshPeerCounts.set({ topicStr }, 0) // Reset count - }, - - /** Register the inclusion of peers in our mesh due to some reason. */ - onAddToMesh(topicStr: TopicStr, reason: InclusionReason, count: number): void { - const topic = this.toTopic(topicStr) - this.meshPeerInclusionEvents.inc({ reason }, count) - this.meshPeerInclusionEventsByTopic.inc({ topic }, count) - }, - - /** Register the removal of peers in our mesh due to some reason */ - // - remove_peer_from_mesh() - // - heartbeat() Churn::BadScore - // - heartbeat() Churn::Excess - // - on_disconnect() Churn::Ds - onRemoveFromMesh(topicStr: TopicStr, reason: ChurnReason, count: number): void { - const topic = this.toTopic(topicStr) - this.meshPeerChurnEvents.inc({ reason }, count) - this.meshPeerChurnEventsByTopic.inc({ topic }, count) - }, - - /** - * Update validation result to metrics - * @param messageRecord null means the message's mcache record was not known at the time of acceptance report - */ - onReportValidation( - messageRecord: { message: { topic: TopicStr } } | null, - acceptance: TopicValidatorResult, - firstSeenTimestampMs: number | null - ): void { - this.asyncValidationMcacheHit.inc({ hit: messageRecord != null ? 'hit' : 'miss' }) - - if (messageRecord != null) { - const topic = this.toTopic(messageRecord.message.topic) - this.asyncValidationResult.inc({ acceptance }) - this.asyncValidationResultByTopic.inc({ topic }) - } - - if (firstSeenTimestampMs != null) { - this.asyncValidationDelayFromFirstSeenSec.observe((Date.now() - firstSeenTimestampMs) / 1000) - } else { - this.asyncValidationUnknownFirstSeen.inc() - } - }, - - /** - * - in handle_graft() Penalty::GraftBackoff - * - in apply_iwant_penalties() Penalty::BrokenPromise - * - in metric_score() P3 Penalty::MessageDeficit - * - in metric_score() P6 Penalty::IPColocation - */ - onScorePenalty(penalty: ScorePenalty): void { - // Can this be labeled by topic too? - this.scoringPenalties.inc({ penalty }, 1) - }, - - onIhaveRcv(topicStr: TopicStr, ihave: number, idonthave: number): void { - const topic = this.toTopic(topicStr) - this.ihaveRcvMsgids.inc({ topic }, ihave) - this.ihaveRcvNotSeenMsgids.inc({ topic }, idonthave) - }, - - onIwantRcv(iwantByTopic: Map, iwantDonthave: number): void { - for (const [topicStr, iwant] of iwantByTopic) { - const topic = this.toTopic(topicStr) - this.iwantRcvMsgids.inc({ topic }, iwant) - } - - this.iwantRcvDonthaveMsgids.inc(iwantDonthave) - }, - - onForwardMsg(topicStr: TopicStr, tosendCount: number): void { - const topic = this.toTopic(topicStr) - this.msgForwardCount.inc({ topic }, 1) - this.msgForwardPeers.inc({ topic }, tosendCount) - }, - - onPublishMsg(topicStr: TopicStr, tosendGroupCount: ToSendGroupCount, tosendCount: number, dataLen: number): void { - const topic = this.toTopic(topicStr) - this.msgPublishCount.inc({ topic }, 1) - this.msgPublishBytes.inc({ topic }, tosendCount * dataLen) - this.msgPublishPeersByTopic.inc({ topic }, tosendCount) - this.msgPublishPeersByGroup.inc({ peerGroup: 'direct' }, tosendGroupCount.direct) - this.msgPublishPeersByGroup.inc({ peerGroup: 'floodsub' }, tosendGroupCount.floodsub) - this.msgPublishPeersByGroup.inc({ peerGroup: 'mesh' }, tosendGroupCount.mesh) - this.msgPublishPeersByGroup.inc({ peerGroup: 'fanout' }, tosendGroupCount.fanout) - }, - - onMsgRecvPreValidation(topicStr: TopicStr): void { - const topic = this.toTopic(topicStr) - this.msgReceivedPreValidation.inc({ topic }, 1) - }, - - onMsgRecvError(topicStr: TopicStr): void { - const topic = this.toTopic(topicStr) - this.msgReceivedError.inc({ topic }, 1) - }, - - onMsgRecvResult(topicStr: TopicStr, status: MessageStatus): void { - const topic = this.toTopic(topicStr) - this.msgReceivedTopic.inc({ topic }) - this.msgReceivedStatus.inc({ status }) - }, - - onMsgRecvInvalid(topicStr: TopicStr, reason: RejectReasonObj): void { - const topic = this.toTopic(topicStr) - - const error = reason.reason === RejectReason.Error ? reason.error : reason.reason - this.msgReceivedInvalid.inc({ error }, 1) - this.msgReceivedInvalidByTopic.inc({ topic }, 1) - }, - - onDuplicateMsgDelivery(topicStr: TopicStr, deliveryDelayMs: number, isLateDelivery: boolean): void { - this.duplicateMsgDeliveryDelay.observe(deliveryDelayMs / 1000) - if (isLateDelivery) { - const topic = this.toTopic(topicStr) - this.duplicateMsgLateDelivery.inc({ topic }, 1) - } - }, - - onPublishDuplicateMsg(topicStr: TopicStr): void { - const topic = this.toTopic(topicStr) - this.duplicateMsgIgnored.inc({ topic }, 1) - }, - - onPeerReadStreamError(): void { - this.peerReadStreamError.inc(1) - }, - - onRpcRecvError(): void { - this.rpcRecvError.inc(1) - }, - - onRpcDataError(): void { - this.rpcDataError.inc(1) - }, - - onRpcRecv(rpc: IRPC, rpcBytes: number): void { - this.rpcRecvBytes.inc(rpcBytes) - this.rpcRecvCount.inc(1) - if (rpc.subscriptions) this.rpcRecvSubscription.inc(rpc.subscriptions.length) - if (rpc.messages) this.rpcRecvMessage.inc(rpc.messages.length) - if (rpc.control) { - this.rpcRecvControl.inc(1) - if (rpc.control.ihave) this.rpcRecvIHave.inc(rpc.control.ihave.length) - if (rpc.control.iwant) this.rpcRecvIWant.inc(rpc.control.iwant.length) - if (rpc.control.graft) this.rpcRecvGraft.inc(rpc.control.graft.length) - if (rpc.control.prune) this.rpcRecvPrune.inc(rpc.control.prune.length) - } - }, - - onRpcSent(rpc: IRPC, rpcBytes: number): void { - this.rpcSentBytes.inc(rpcBytes) - this.rpcSentCount.inc(1) - if (rpc.subscriptions) this.rpcSentSubscription.inc(rpc.subscriptions.length) - if (rpc.messages) this.rpcSentMessage.inc(rpc.messages.length) - if (rpc.control) { - const ihave = rpc.control.ihave?.length ?? 0 - const iwant = rpc.control.iwant?.length ?? 0 - const graft = rpc.control.graft?.length ?? 0 - const prune = rpc.control.prune?.length ?? 0 - if (ihave > 0) this.rpcSentIHave.inc(ihave) - if (iwant > 0) this.rpcSentIWant.inc(iwant) - if (graft > 0) this.rpcSentGraft.inc(graft) - if (prune > 0) this.rpcSentPrune.inc(prune) - if (ihave > 0 || iwant > 0 || graft > 0 || prune > 0) this.rpcSentControl.inc(1) - } - }, - - registerScores(scores: number[], scoreThresholds: PeerScoreThresholds): void { - let graylist = 0 - let publish = 0 - let gossip = 0 - let mesh = 0 - - for (const score of scores) { - if (score >= scoreThresholds.graylistThreshold) graylist++ - if (score >= scoreThresholds.publishThreshold) publish++ - if (score >= scoreThresholds.gossipThreshold) gossip++ - if (score >= 0) mesh++ - } - - this.peersByScoreThreshold.set({ threshold: ScoreThreshold.graylist }, graylist) - this.peersByScoreThreshold.set({ threshold: ScoreThreshold.publish }, publish) - this.peersByScoreThreshold.set({ threshold: ScoreThreshold.gossip }, gossip) - this.peersByScoreThreshold.set({ threshold: ScoreThreshold.mesh }, mesh) - - // Register full score too - this.score.set(scores) - }, - - registerScoreWeights(sw: ScoreWeights): void { - for (const [topic, wsTopic] of sw.byTopic) { - this.scoreWeights.set({ topic, p: 'p1' }, wsTopic.p1w) - this.scoreWeights.set({ topic, p: 'p2' }, wsTopic.p2w) - this.scoreWeights.set({ topic, p: 'p3' }, wsTopic.p3w) - this.scoreWeights.set({ topic, p: 'p3b' }, wsTopic.p3bw) - this.scoreWeights.set({ topic, p: 'p4' }, wsTopic.p4w) - } - - this.scoreWeights.set({ p: 'p5' }, sw.p5w) - this.scoreWeights.set({ p: 'p6' }, sw.p6w) - this.scoreWeights.set({ p: 'p7' }, sw.p7w) - }, - - registerScorePerMesh(mesh: Map>, scoreByPeer: Map): void { - const peersPerTopicLabel = new Map>() - - mesh.forEach((peers, topicStr) => { - // Aggregate by known topicLabel or throw to 'unknown'. This prevent too high cardinality - const topicLabel = this.topicStrToLabel.get(topicStr) ?? 'unknown' - let peersInMesh = peersPerTopicLabel.get(topicLabel) - if (!peersInMesh) { - peersInMesh = new Set() - peersPerTopicLabel.set(topicLabel, peersInMesh) - } - peers.forEach((p) => peersInMesh?.add(p)) - }) - - for (const [topic, peers] of peersPerTopicLabel) { - const meshScores: number[] = [] - peers.forEach((peer) => { - meshScores.push(scoreByPeer.get(peer) ?? 0) - }) - this.scorePerMesh.set({ topic }, meshScores) - } - } - } -} diff --git a/packages/pubsub-gossipsub/src/score/compute-score.ts b/packages/pubsub-gossipsub/src/score/compute-score.ts deleted file mode 100644 index f2c6f1a77f..0000000000 --- a/packages/pubsub-gossipsub/src/score/compute-score.ts +++ /dev/null @@ -1,98 +0,0 @@ -import type { PeerStats } from './peer-stats.js' -import type { PeerScoreParams } from './peer-score-params.js' - -export function computeScore( - peer: string, - pstats: PeerStats, - params: PeerScoreParams, - peerIPs: Map> -): number { - let score = 0 - - // topic stores - Object.entries(pstats.topics).forEach(([topic, tstats]) => { - // the topic parameters - const topicParams = params.topics[topic] - if (topicParams === undefined) { - // we are not scoring this topic - return - } - - let topicScore = 0 - - // P1: time in Mesh - if (tstats.inMesh) { - let p1 = tstats.meshTime / topicParams.timeInMeshQuantum - if (p1 > topicParams.timeInMeshCap) { - p1 = topicParams.timeInMeshCap - } - topicScore += p1 * topicParams.timeInMeshWeight - } - - // P2: first message deliveries - let p2 = tstats.firstMessageDeliveries - if (p2 > topicParams.firstMessageDeliveriesCap) { - p2 = topicParams.firstMessageDeliveriesCap - } - topicScore += p2 * topicParams.firstMessageDeliveriesWeight - - // P3: mesh message deliveries - if ( - tstats.meshMessageDeliveriesActive && - tstats.meshMessageDeliveries < topicParams.meshMessageDeliveriesThreshold - ) { - const deficit = topicParams.meshMessageDeliveriesThreshold - tstats.meshMessageDeliveries - const p3 = deficit * deficit - topicScore += p3 * topicParams.meshMessageDeliveriesWeight - } - - // P3b: - // NOTE: the weight of P3b is negative (validated in validateTopicScoreParams) so this detracts - const p3b = tstats.meshFailurePenalty - topicScore += p3b * topicParams.meshFailurePenaltyWeight - - // P4: invalid messages - // NOTE: the weight of P4 is negative (validated in validateTopicScoreParams) so this detracts - const p4 = tstats.invalidMessageDeliveries * tstats.invalidMessageDeliveries - topicScore += p4 * topicParams.invalidMessageDeliveriesWeight - - // update score, mixing with topic weight - score += topicScore * topicParams.topicWeight - }) - - // apply the topic score cap, if any - if (params.topicScoreCap > 0 && score > params.topicScoreCap) { - score = params.topicScoreCap - } - - // P5: application-specific score - const p5 = params.appSpecificScore(peer) - score += p5 * params.appSpecificWeight - - // P6: IP colocation factor - pstats.knownIPs.forEach((ip) => { - if (params.IPColocationFactorWhitelist.has(ip)) { - return - } - - // P6 has a cliff (IPColocationFactorThreshold) - // It's only applied if at least that many peers are connected to us from that source IP addr. - // It is quadratic, and the weight is negative (validated in validatePeerScoreParams) - const peersInIP = peerIPs.get(ip) - const numPeersInIP = peersInIP ? peersInIP.size : 0 - if (numPeersInIP > params.IPColocationFactorThreshold) { - const surplus = numPeersInIP - params.IPColocationFactorThreshold - const p6 = surplus * surplus - score += p6 * params.IPColocationFactorWeight - } - }) - - // P7: behavioural pattern penalty - if (pstats.behaviourPenalty > params.behaviourPenaltyThreshold) { - const excess = pstats.behaviourPenalty - params.behaviourPenaltyThreshold - const p7 = excess * excess - score += p7 * params.behaviourPenaltyWeight - } - - return score -} diff --git a/packages/pubsub-gossipsub/src/score/constants.ts b/packages/pubsub-gossipsub/src/score/constants.ts deleted file mode 100644 index 664742889b..0000000000 --- a/packages/pubsub-gossipsub/src/score/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const ERR_INVALID_PEER_SCORE_PARAMS = 'ERR_INVALID_PEER_SCORE_PARAMS' -export const ERR_INVALID_PEER_SCORE_THRESHOLDS = 'ERR_INVALID_PEER_SCORE_THRESHOLDS' diff --git a/packages/pubsub-gossipsub/src/score/index.ts b/packages/pubsub-gossipsub/src/score/index.ts deleted file mode 100644 index 4aa268e445..0000000000 --- a/packages/pubsub-gossipsub/src/score/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './peer-score-params.js' -export * from './peer-score-thresholds.js' -export * from './peer-score.js' diff --git a/packages/pubsub-gossipsub/src/score/message-deliveries.ts b/packages/pubsub-gossipsub/src/score/message-deliveries.ts deleted file mode 100644 index 364abb137f..0000000000 --- a/packages/pubsub-gossipsub/src/score/message-deliveries.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { TimeCacheDuration } from '../constants.js' -import Denque from 'denque' - -export enum DeliveryRecordStatus { - /** - * we don't know (yet) if the message is valid - */ - unknown, - /** - * we know the message is valid - */ - valid, - /** - * we know the message is invalid - */ - invalid, - /** - * we were instructed by the validator to ignore the message - */ - ignored -} - -export interface DeliveryRecord { - status: DeliveryRecordStatus - firstSeenTsMs: number - validated: number - peers: Set -} - -interface DeliveryQueueEntry { - msgId: string - expire: number -} - -/** - * Map of canonical message ID to DeliveryRecord - * - * Maintains an internal queue for efficient gc of old messages - */ -export class MessageDeliveries { - private records: Map - public queue: Denque - - constructor() { - this.records = new Map() - this.queue = new Denque() - } - - getRecord(msgIdStr: string): DeliveryRecord | undefined { - return this.records.get(msgIdStr) - } - - ensureRecord(msgIdStr: string): DeliveryRecord { - let drec = this.records.get(msgIdStr) - if (drec) { - return drec - } - - // record doesn't exist yet - // create record - drec = { - status: DeliveryRecordStatus.unknown, - firstSeenTsMs: Date.now(), - validated: 0, - peers: new Set() - } - this.records.set(msgIdStr, drec) - - // and add msgId to the queue - const entry: DeliveryQueueEntry = { - msgId: msgIdStr, - expire: Date.now() + TimeCacheDuration - } - this.queue.push(entry) - - return drec - } - - gc(): void { - const now = Date.now() - // queue is sorted by expiry time - // remove expired messages, remove from queue until first un-expired message found - let head = this.queue.peekFront() - while (head && head.expire < now) { - this.records.delete(head.msgId) - this.queue.shift() - head = this.queue.peekFront() - } - } - - clear(): void { - this.records.clear() - this.queue.clear() - } -} diff --git a/packages/pubsub-gossipsub/src/score/peer-score-params.ts b/packages/pubsub-gossipsub/src/score/peer-score-params.ts deleted file mode 100644 index 774b563b97..0000000000 --- a/packages/pubsub-gossipsub/src/score/peer-score-params.ts +++ /dev/null @@ -1,337 +0,0 @@ -import { ERR_INVALID_PEER_SCORE_PARAMS } from './constants.js' -import { CodeError } from '@libp2p/interface/errors' - -// This file defines PeerScoreParams and TopicScoreParams interfaces -// as well as constructors, default constructors, and validation functions -// for these interfaces - -export interface PeerScoreParams { - /** - * Score parameters per topic. - */ - topics: Record - - /** - * Aggregate topic score cap; this limits the total contribution of topics towards a positive - * score. It must be positive (or 0 for no cap). - */ - topicScoreCap: number - - /** - * P5: Application-specific peer scoring - */ - appSpecificScore: (p: string) => number - appSpecificWeight: number - - /** - * P6: IP-colocation factor. - * The parameter has an associated counter which counts the number of peers with the same IP. - * If the number of peers in the same IP exceeds IPColocationFactorThreshold, then the value - * is the square of the difference, ie (PeersInSameIP - IPColocationThreshold)^2. - * If the number of peers in the same IP is less than the threshold, then the value is 0. - * The weight of the parameter MUST be negative, unless you want to disable for testing. - * Note: In order to simulate many IPs in a managable manner when testing, you can set the weight to 0 - * thus disabling the IP colocation penalty. - */ - IPColocationFactorWeight: number - IPColocationFactorThreshold: number - IPColocationFactorWhitelist: Set - - /** - * P7: behavioural pattern penalties. - * This parameter has an associated counter which tracks misbehaviour as detected by the - * router. The router currently applies penalties for the following behaviors: - * - attempting to re-graft before the prune backoff time has elapsed. - * - not following up in IWANT requests for messages advertised with IHAVE. - * - * The value of the parameter is the square of the counter, which decays with BehaviourPenaltyDecay. - * The weight of the parameter MUST be negative (or zero to disable). - */ - behaviourPenaltyWeight: number - behaviourPenaltyThreshold: number - behaviourPenaltyDecay: number - - /** - * the decay interval for parameter counters. - */ - decayInterval: number - - /** - * counter value below which it is considered 0. - */ - decayToZero: number - - /** - * time to remember counters for a disconnected peer. - */ - retainScore: number -} - -export interface TopicScoreParams { - /** - * The weight of the topic. - */ - topicWeight: number - - /** - * P1: time in the mesh - * This is the time the peer has ben grafted in the mesh. - * The value of the parameter is the time/TimeInMeshQuantum, capped by TimeInMeshCap - * The weight of the parameter MUST be positive (or zero to disable). - */ - timeInMeshWeight: number - timeInMeshQuantum: number - timeInMeshCap: number - - /** - * P2: first message deliveries - * This is the number of message deliveries in the topic. - * The value of the parameter is a counter, decaying with FirstMessageDeliveriesDecay, and capped - * by FirstMessageDeliveriesCap. - * The weight of the parameter MUST be positive (or zero to disable). - */ - firstMessageDeliveriesWeight: number - firstMessageDeliveriesDecay: number - firstMessageDeliveriesCap: number - - /** - * P3: mesh message deliveries - * This is the number of message deliveries in the mesh, within the MeshMessageDeliveriesWindow of - * message validation; deliveries during validation also count and are retroactively applied - * when validation succeeds. - * This window accounts for the minimum time before a hostile mesh peer trying to game the score - * could replay back a valid message we just sent them. - * It effectively tracks first and near-first deliveries, ie a message seen from a mesh peer - * before we have forwarded it to them. - * The parameter has an associated counter, decaying with MeshMessageDeliveriesDecay. - * If the counter exceeds the threshold, its value is 0. - * If the counter is below the MeshMessageDeliveriesThreshold, the value is the square of - * the deficit, ie (MessageDeliveriesThreshold - counter)^2 - * The penalty is only activated after MeshMessageDeliveriesActivation time in the mesh. - * The weight of the parameter MUST be negative (or zero to disable). - */ - meshMessageDeliveriesWeight: number - meshMessageDeliveriesDecay: number - meshMessageDeliveriesCap: number - meshMessageDeliveriesThreshold: number - meshMessageDeliveriesWindow: number - meshMessageDeliveriesActivation: number - - /** - * P3b: sticky mesh propagation failures - * This is a sticky penalty that applies when a peer gets pruned from the mesh with an active - * mesh message delivery penalty. - * The weight of the parameter MUST be negative (or zero to disable) - */ - meshFailurePenaltyWeight: number - meshFailurePenaltyDecay: number - - /** - * P4: invalid messages - * This is the number of invalid messages in the topic. - * The value of the parameter is the square of the counter, decaying with - * InvalidMessageDeliveriesDecay. - * The weight of the parameter MUST be negative (or zero to disable). - */ - invalidMessageDeliveriesWeight: number - invalidMessageDeliveriesDecay: number -} - -export const defaultPeerScoreParams: PeerScoreParams = { - topics: {}, - topicScoreCap: 10.0, - appSpecificScore: () => 0.0, - appSpecificWeight: 10.0, - IPColocationFactorWeight: -5.0, - IPColocationFactorThreshold: 10.0, - IPColocationFactorWhitelist: new Set(), - behaviourPenaltyWeight: -10.0, - behaviourPenaltyThreshold: 0.0, - behaviourPenaltyDecay: 0.2, - decayInterval: 1000.0, - decayToZero: 0.1, - retainScore: 3600 * 1000 -} - -export const defaultTopicScoreParams: TopicScoreParams = { - topicWeight: 0.5, - timeInMeshWeight: 1, - timeInMeshQuantum: 1, - timeInMeshCap: 3600, - - firstMessageDeliveriesWeight: 1, - firstMessageDeliveriesDecay: 0.5, - firstMessageDeliveriesCap: 2000, - - meshMessageDeliveriesWeight: -1, - meshMessageDeliveriesDecay: 0.5, - meshMessageDeliveriesCap: 100, - meshMessageDeliveriesThreshold: 20, - meshMessageDeliveriesWindow: 10, - meshMessageDeliveriesActivation: 5000, - - meshFailurePenaltyWeight: -1, - meshFailurePenaltyDecay: 0.5, - - invalidMessageDeliveriesWeight: -1, - invalidMessageDeliveriesDecay: 0.3 -} - -export function createPeerScoreParams(p: Partial = {}): PeerScoreParams { - return { - ...defaultPeerScoreParams, - ...p, - topics: p.topics - ? Object.entries(p.topics).reduce((topics, [topic, topicScoreParams]) => { - topics[topic] = createTopicScoreParams(topicScoreParams) - return topics - }, {} as Record) - : {} - } -} - -export function createTopicScoreParams(p: Partial = {}): TopicScoreParams { - return { - ...defaultTopicScoreParams, - ...p - } -} - -// peer score parameter validation -export function validatePeerScoreParams(p: PeerScoreParams): void { - for (const [topic, params] of Object.entries(p.topics)) { - try { - validateTopicScoreParams(params) - } catch (e) { - throw new CodeError( - `invalid score parameters for topic ${topic}: ${(e as Error).message}`, - ERR_INVALID_PEER_SCORE_PARAMS - ) - } - } - - // check that the topic score is 0 or something positive - if (p.topicScoreCap < 0) { - throw new CodeError('invalid topic score cap; must be positive (or 0 for no cap)', ERR_INVALID_PEER_SCORE_PARAMS) - } - - // check that we have an app specific score; the weight can be anything (but expected positive) - if (p.appSpecificScore === null || p.appSpecificScore === undefined) { - throw new CodeError('missing application specific score function', ERR_INVALID_PEER_SCORE_PARAMS) - } - - // check the IP colocation factor - if (p.IPColocationFactorWeight > 0) { - throw new CodeError( - 'invalid IPColocationFactorWeight; must be negative (or 0 to disable)', - ERR_INVALID_PEER_SCORE_PARAMS - ) - } - if (p.IPColocationFactorWeight !== 0 && p.IPColocationFactorThreshold < 1) { - throw new CodeError('invalid IPColocationFactorThreshold; must be at least 1', ERR_INVALID_PEER_SCORE_PARAMS) - } - - // check the behaviour penalty - if (p.behaviourPenaltyWeight > 0) { - throw new CodeError( - 'invalid BehaviourPenaltyWeight; must be negative (or 0 to disable)', - ERR_INVALID_PEER_SCORE_PARAMS - ) - } - if (p.behaviourPenaltyWeight !== 0 && (p.behaviourPenaltyDecay <= 0 || p.behaviourPenaltyDecay >= 1)) { - throw new CodeError('invalid BehaviourPenaltyDecay; must be between 0 and 1', ERR_INVALID_PEER_SCORE_PARAMS) - } - - // check the decay parameters - if (p.decayInterval < 1000) { - throw new CodeError('invalid DecayInterval; must be at least 1s', ERR_INVALID_PEER_SCORE_PARAMS) - } - if (p.decayToZero <= 0 || p.decayToZero >= 1) { - throw new CodeError('invalid DecayToZero; must be between 0 and 1', ERR_INVALID_PEER_SCORE_PARAMS) - } - - // no need to check the score retention; a value of 0 means that we don't retain scores -} - -export function validateTopicScoreParams(p: TopicScoreParams): void { - // make sure we have a sane topic weight - if (p.topicWeight < 0) { - throw new CodeError('invalid topic weight; must be >= 0', ERR_INVALID_PEER_SCORE_PARAMS) - } - - // check P1 - if (p.timeInMeshQuantum === 0) { - throw new CodeError('invalid TimeInMeshQuantum; must be non zero', ERR_INVALID_PEER_SCORE_PARAMS) - } - if (p.timeInMeshWeight < 0) { - throw new CodeError('invalid TimeInMeshWeight; must be positive (or 0 to disable)', ERR_INVALID_PEER_SCORE_PARAMS) - } - if (p.timeInMeshWeight !== 0 && p.timeInMeshQuantum <= 0) { - throw new CodeError('invalid TimeInMeshQuantum; must be positive', ERR_INVALID_PEER_SCORE_PARAMS) - } - if (p.timeInMeshWeight !== 0 && p.timeInMeshCap <= 0) { - throw new CodeError('invalid TimeInMeshCap; must be positive', ERR_INVALID_PEER_SCORE_PARAMS) - } - - // check P2 - if (p.firstMessageDeliveriesWeight < 0) { - throw new CodeError( - 'invallid FirstMessageDeliveriesWeight; must be positive (or 0 to disable)', - ERR_INVALID_PEER_SCORE_PARAMS - ) - } - if ( - p.firstMessageDeliveriesWeight !== 0 && - (p.firstMessageDeliveriesDecay <= 0 || p.firstMessageDeliveriesDecay >= 1) - ) { - throw new CodeError('invalid FirstMessageDeliveriesDecay; must be between 0 and 1', ERR_INVALID_PEER_SCORE_PARAMS) - } - if (p.firstMessageDeliveriesWeight !== 0 && p.firstMessageDeliveriesCap <= 0) { - throw new CodeError('invalid FirstMessageDeliveriesCap; must be positive', ERR_INVALID_PEER_SCORE_PARAMS) - } - - // check P3 - if (p.meshMessageDeliveriesWeight > 0) { - throw new CodeError( - 'invalid MeshMessageDeliveriesWeight; must be negative (or 0 to disable)', - ERR_INVALID_PEER_SCORE_PARAMS - ) - } - if (p.meshMessageDeliveriesWeight !== 0 && (p.meshMessageDeliveriesDecay <= 0 || p.meshMessageDeliveriesDecay >= 1)) { - throw new CodeError('invalid MeshMessageDeliveriesDecay; must be between 0 and 1', ERR_INVALID_PEER_SCORE_PARAMS) - } - if (p.meshMessageDeliveriesWeight !== 0 && p.meshMessageDeliveriesCap <= 0) { - throw new CodeError('invalid MeshMessageDeliveriesCap; must be positive', ERR_INVALID_PEER_SCORE_PARAMS) - } - if (p.meshMessageDeliveriesWeight !== 0 && p.meshMessageDeliveriesThreshold <= 0) { - throw new CodeError('invalid MeshMessageDeliveriesThreshold; must be positive', ERR_INVALID_PEER_SCORE_PARAMS) - } - if (p.meshMessageDeliveriesWindow < 0) { - throw new CodeError('invalid MeshMessageDeliveriesWindow; must be non-negative', ERR_INVALID_PEER_SCORE_PARAMS) - } - if (p.meshMessageDeliveriesWeight !== 0 && p.meshMessageDeliveriesActivation < 1000) { - throw new CodeError('invalid MeshMessageDeliveriesActivation; must be at least 1s', ERR_INVALID_PEER_SCORE_PARAMS) - } - - // check P3b - if (p.meshFailurePenaltyWeight > 0) { - throw new CodeError( - 'invalid MeshFailurePenaltyWeight; must be negative (or 0 to disable)', - ERR_INVALID_PEER_SCORE_PARAMS - ) - } - if (p.meshFailurePenaltyWeight !== 0 && (p.meshFailurePenaltyDecay <= 0 || p.meshFailurePenaltyDecay >= 1)) { - throw new CodeError('invalid MeshFailurePenaltyDecay; must be between 0 and 1', ERR_INVALID_PEER_SCORE_PARAMS) - } - - // check P4 - if (p.invalidMessageDeliveriesWeight > 0) { - throw new CodeError( - 'invalid InvalidMessageDeliveriesWeight; must be negative (or 0 to disable)', - ERR_INVALID_PEER_SCORE_PARAMS - ) - } - if (p.invalidMessageDeliveriesDecay <= 0 || p.invalidMessageDeliveriesDecay >= 1) { - throw new CodeError('invalid InvalidMessageDeliveriesDecay; must be between 0 and 1', ERR_INVALID_PEER_SCORE_PARAMS) - } -} diff --git a/packages/pubsub-gossipsub/src/score/peer-score-thresholds.ts b/packages/pubsub-gossipsub/src/score/peer-score-thresholds.ts deleted file mode 100644 index 853211eb3e..0000000000 --- a/packages/pubsub-gossipsub/src/score/peer-score-thresholds.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { ERR_INVALID_PEER_SCORE_THRESHOLDS } from './constants.js' -import { CodeError } from '@libp2p/interface/errors' - -// This file defines PeerScoreThresholds interface -// as well as a constructor, default constructor, and validation function -// for this interface - -export interface PeerScoreThresholds { - /** - * gossipThreshold is the score threshold below which gossip propagation is supressed; - * should be negative. - */ - gossipThreshold: number - - /** - * publishThreshold is the score threshold below which we shouldn't publish when using flood - * publishing (also applies to fanout and floodsub peers); should be negative and <= GossipThreshold. - */ - publishThreshold: number - - /** - * graylistThreshold is the score threshold below which message processing is supressed altogether, - * implementing an effective graylist according to peer score; should be negative and <= PublisThreshold. - */ - graylistThreshold: number - - /** - * acceptPXThreshold is the score threshold below which PX will be ignored; this should be positive - * and limited to scores attainable by bootstrappers and other trusted nodes. - */ - acceptPXThreshold: number - - /** - * opportunisticGraftThreshold is the median mesh score threshold before triggering opportunistic - * grafting; this should have a small positive value. - */ - opportunisticGraftThreshold: number -} - -export const defaultPeerScoreThresholds: PeerScoreThresholds = { - gossipThreshold: -10, - publishThreshold: -50, - graylistThreshold: -80, - acceptPXThreshold: 10, - opportunisticGraftThreshold: 20 -} - -export function createPeerScoreThresholds(p: Partial = {}): PeerScoreThresholds { - return { - ...defaultPeerScoreThresholds, - ...p - } -} - -export function validatePeerScoreThresholds(p: PeerScoreThresholds): void { - if (p.gossipThreshold > 0) { - throw new CodeError('invalid gossip threshold; it must be <= 0', ERR_INVALID_PEER_SCORE_THRESHOLDS) - } - if (p.publishThreshold > 0 || p.publishThreshold > p.gossipThreshold) { - throw new CodeError( - 'invalid publish threshold; it must be <= 0 and <= gossip threshold', - ERR_INVALID_PEER_SCORE_THRESHOLDS - ) - } - if (p.graylistThreshold > 0 || p.graylistThreshold > p.publishThreshold) { - throw new CodeError( - 'invalid graylist threshold; it must be <= 0 and <= publish threshold', - ERR_INVALID_PEER_SCORE_THRESHOLDS - ) - } - if (p.acceptPXThreshold < 0) { - throw new CodeError('invalid accept PX threshold; it must be >= 0', ERR_INVALID_PEER_SCORE_THRESHOLDS) - } - if (p.opportunisticGraftThreshold < 0) { - throw new CodeError('invalid opportunistic grafting threshold; it must be >= 0', ERR_INVALID_PEER_SCORE_THRESHOLDS) - } -} diff --git a/packages/pubsub-gossipsub/src/score/peer-score.ts b/packages/pubsub-gossipsub/src/score/peer-score.ts deleted file mode 100644 index d4ea021a5c..0000000000 --- a/packages/pubsub-gossipsub/src/score/peer-score.ts +++ /dev/null @@ -1,560 +0,0 @@ -import { type PeerScoreParams, validatePeerScoreParams } from './peer-score-params.js' -import type { PeerStats, TopicStats } from './peer-stats.js' -import { computeScore } from './compute-score.js' -import { MessageDeliveries, DeliveryRecordStatus } from './message-deliveries.js' -import { logger } from '@libp2p/logger' -import { type MsgIdStr, type PeerIdStr, RejectReason, type TopicStr, type IPStr } from '../types.js' -import type { Metrics, ScorePenalty } from '../metrics.js' -import { MapDef } from '../utils/set.js' - -const log = logger('libp2p:gossipsub:score') - -interface PeerScoreOpts { - /** - * Miliseconds to cache computed score per peer - */ - scoreCacheValidityMs: number - - computeScore?: typeof computeScore -} - -interface ScoreCacheEntry { - /** The cached score */ - score: number - /** Unix timestamp in miliseconds, the time after which the cached score for a peer is no longer valid */ - cacheUntil: number -} - -export type PeerScoreStatsDump = Record - -export class PeerScore { - /** - * Per-peer stats for score calculation - */ - readonly peerStats = new Map() - /** - * IP colocation tracking; maps IP => set of peers. - */ - readonly peerIPs = new MapDef>(() => new Set()) - /** - * Cache score up to decayInterval if topic stats are unchanged. - */ - readonly scoreCache = new Map() - /** - * Recent message delivery timing/participants - */ - readonly deliveryRecords = new MessageDeliveries() - - _backgroundInterval?: ReturnType - - private readonly scoreCacheValidityMs: number - private readonly computeScore: typeof computeScore - - constructor(readonly params: PeerScoreParams, private readonly metrics: Metrics | null, opts: PeerScoreOpts) { - validatePeerScoreParams(params) - this.scoreCacheValidityMs = opts.scoreCacheValidityMs - this.computeScore = opts.computeScore ?? computeScore - } - - get size(): number { - return this.peerStats.size - } - - /** - * Start PeerScore instance - */ - start(): void { - if (this._backgroundInterval) { - log('Peer score already running') - return - } - this._backgroundInterval = setInterval(() => this.background(), this.params.decayInterval) - log('started') - } - - /** - * Stop PeerScore instance - */ - stop(): void { - if (!this._backgroundInterval) { - log('Peer score already stopped') - return - } - clearInterval(this._backgroundInterval) - delete this._backgroundInterval - this.peerIPs.clear() - this.peerStats.clear() - this.deliveryRecords.clear() - log('stopped') - } - - /** - * Periodic maintenance - */ - background(): void { - this.refreshScores() - this.deliveryRecords.gc() - } - - dumpPeerScoreStats(): PeerScoreStatsDump { - return Object.fromEntries(Array.from(this.peerStats.entries()).map(([peer, stats]) => [peer, stats])) - } - - messageFirstSeenTimestampMs(msgIdStr: MsgIdStr): number | null { - const drec = this.deliveryRecords.getRecord(msgIdStr) - return drec ? drec.firstSeenTsMs : null - } - - /** - * Decays scores, and purges score records for disconnected peers once their expiry has elapsed. - */ - public refreshScores(): void { - const now = Date.now() - const decayToZero = this.params.decayToZero - - this.peerStats.forEach((pstats, id) => { - if (!pstats.connected) { - // has the retention period expired? - if (now > pstats.expire) { - // yes, throw it away (but clean up the IP tracking first) - this.removeIPsForPeer(id, pstats.knownIPs) - this.peerStats.delete(id) - this.scoreCache.delete(id) - } - - // we don't decay retained scores, as the peer is not active. - // this way the peer cannot reset a negative score by simply disconnecting and reconnecting, - // unless the retention period has elapsed. - // similarly, a well behaved peer does not lose its score by getting disconnected. - return - } - - Object.entries(pstats.topics).forEach(([topic, tstats]) => { - const tparams = this.params.topics[topic] - if (tparams === undefined) { - // we are not scoring this topic - // should be unreachable, we only add scored topics to pstats - return - } - - // decay counters - tstats.firstMessageDeliveries *= tparams.firstMessageDeliveriesDecay - if (tstats.firstMessageDeliveries < decayToZero) { - tstats.firstMessageDeliveries = 0 - } - - tstats.meshMessageDeliveries *= tparams.meshMessageDeliveriesDecay - if (tstats.meshMessageDeliveries < decayToZero) { - tstats.meshMessageDeliveries = 0 - } - - tstats.meshFailurePenalty *= tparams.meshFailurePenaltyDecay - if (tstats.meshFailurePenalty < decayToZero) { - tstats.meshFailurePenalty = 0 - } - - tstats.invalidMessageDeliveries *= tparams.invalidMessageDeliveriesDecay - if (tstats.invalidMessageDeliveries < decayToZero) { - tstats.invalidMessageDeliveries = 0 - } - - // update mesh time and activate mesh message delivery parameter if need be - if (tstats.inMesh) { - tstats.meshTime = now - tstats.graftTime - if (tstats.meshTime > tparams.meshMessageDeliveriesActivation) { - tstats.meshMessageDeliveriesActive = true - } - } - }) - - // decay P7 counter - pstats.behaviourPenalty *= this.params.behaviourPenaltyDecay - if (pstats.behaviourPenalty < decayToZero) { - pstats.behaviourPenalty = 0 - } - }) - } - - /** - * Return the score for a peer - */ - score(id: PeerIdStr): number { - this.metrics?.scoreFnCalls.inc() - - const pstats = this.peerStats.get(id) - if (!pstats) { - return 0 - } - - const now = Date.now() - const cacheEntry = this.scoreCache.get(id) - - // Found cached score within validity period - if (cacheEntry && cacheEntry.cacheUntil > now) { - return cacheEntry.score - } - - this.metrics?.scoreFnRuns.inc() - - const score = this.computeScore(id, pstats, this.params, this.peerIPs) - const cacheUntil = now + this.scoreCacheValidityMs - - if (cacheEntry) { - this.metrics?.scoreCachedDelta.observe(Math.abs(score - cacheEntry.score)) - cacheEntry.score = score - cacheEntry.cacheUntil = cacheUntil - } else { - this.scoreCache.set(id, { score, cacheUntil }) - } - - return score - } - - /** - * Apply a behavioural penalty to a peer - */ - addPenalty(id: PeerIdStr, penalty: number, penaltyLabel: ScorePenalty): void { - const pstats = this.peerStats.get(id) - if (pstats) { - pstats.behaviourPenalty += penalty - this.metrics?.onScorePenalty(penaltyLabel) - } - } - - addPeer(id: PeerIdStr): void { - // create peer stats (not including topic stats for each topic to be scored) - // topic stats will be added as needed - const pstats: PeerStats = { - connected: true, - expire: 0, - topics: {}, - knownIPs: new Set(), - behaviourPenalty: 0 - } - this.peerStats.set(id, pstats) - } - - /** Adds a new IP to a peer, if the peer is not known the update is ignored */ - addIP(id: PeerIdStr, ip: string): void { - const pstats = this.peerStats.get(id) - if (pstats) { - pstats.knownIPs.add(ip) - } - - this.peerIPs.getOrDefault(ip).add(id) - } - - /** Remove peer association with IP */ - removeIP(id: PeerIdStr, ip: string): void { - const pstats = this.peerStats.get(id) - if (pstats) { - pstats.knownIPs.delete(ip) - } - - const peersWithIP = this.peerIPs.get(ip) - if (peersWithIP) { - peersWithIP.delete(id) - if (peersWithIP.size === 0) { - this.peerIPs.delete(ip) - } - } - } - - removePeer(id: PeerIdStr): void { - const pstats = this.peerStats.get(id) - if (!pstats) { - return - } - - // decide whether to retain the score; this currently only retains non-positive scores - // to dissuade attacks on the score function. - if (this.score(id) > 0) { - this.removeIPsForPeer(id, pstats.knownIPs) - this.peerStats.delete(id) - return - } - - // furthermore, when we decide to retain the score, the firstMessageDelivery counters are - // reset to 0 and mesh delivery penalties applied. - Object.entries(pstats.topics).forEach(([topic, tstats]) => { - tstats.firstMessageDeliveries = 0 - - const threshold = this.params.topics[topic].meshMessageDeliveriesThreshold - if (tstats.inMesh && tstats.meshMessageDeliveriesActive && tstats.meshMessageDeliveries < threshold) { - const deficit = threshold - tstats.meshMessageDeliveries - tstats.meshFailurePenalty += deficit * deficit - } - - tstats.inMesh = false - tstats.meshMessageDeliveriesActive = false - }) - - pstats.connected = false - pstats.expire = Date.now() + this.params.retainScore - } - - /** Handles scoring functionality as a peer GRAFTs to a topic. */ - graft(id: PeerIdStr, topic: TopicStr): void { - const pstats = this.peerStats.get(id) - if (pstats) { - const tstats = this.getPtopicStats(pstats, topic) - if (tstats) { - // if we are scoring the topic, update the mesh status. - tstats.inMesh = true - tstats.graftTime = Date.now() - tstats.meshTime = 0 - tstats.meshMessageDeliveriesActive = false - } - } - } - - /** Handles scoring functionality as a peer PRUNEs from a topic. */ - prune(id: PeerIdStr, topic: TopicStr): void { - const pstats = this.peerStats.get(id) - if (pstats) { - const tstats = this.getPtopicStats(pstats, topic) - if (tstats) { - // sticky mesh delivery rate failure penalty - const threshold = this.params.topics[topic].meshMessageDeliveriesThreshold - if (tstats.meshMessageDeliveriesActive && tstats.meshMessageDeliveries < threshold) { - const deficit = threshold - tstats.meshMessageDeliveries - tstats.meshFailurePenalty += deficit * deficit - } - tstats.meshMessageDeliveriesActive = false - tstats.inMesh = false - - // TODO: Consider clearing score cache on important penalties - // this.scoreCache.delete(id) - } - } - } - - validateMessage(msgIdStr: MsgIdStr): void { - this.deliveryRecords.ensureRecord(msgIdStr) - } - - deliverMessage(from: PeerIdStr, msgIdStr: MsgIdStr, topic: TopicStr): void { - this.markFirstMessageDelivery(from, topic) - - const drec = this.deliveryRecords.ensureRecord(msgIdStr) - const now = Date.now() - - // defensive check that this is the first delivery trace -- delivery status should be unknown - if (drec.status !== DeliveryRecordStatus.unknown) { - log( - 'unexpected delivery: message from %s was first seen %s ago and has delivery status %s', - from, - now - drec.firstSeenTsMs, - DeliveryRecordStatus[drec.status] - ) - return - } - - // mark the message as valid and reward mesh peers that have already forwarded it to us - drec.status = DeliveryRecordStatus.valid - drec.validated = now - drec.peers.forEach((p) => { - // this check is to make sure a peer can't send us a message twice and get a double count - // if it is a first delivery. - if (p !== from.toString()) { - this.markDuplicateMessageDelivery(p, topic) - } - }) - } - - /** - * Similar to `rejectMessage` except does not require the message id or reason for an invalid message. - */ - rejectInvalidMessage(from: PeerIdStr, topic: TopicStr): void { - this.markInvalidMessageDelivery(from, topic) - } - - rejectMessage(from: PeerIdStr, msgIdStr: MsgIdStr, topic: TopicStr, reason: RejectReason): void { - switch (reason) { - // these messages are not tracked, but the peer is penalized as they are invalid - case RejectReason.Error: - this.markInvalidMessageDelivery(from, topic) - return - - // we ignore those messages, so do nothing. - case RejectReason.Blacklisted: - return - - // the rest are handled after record creation - } - - const drec = this.deliveryRecords.ensureRecord(msgIdStr) - - // defensive check that this is the first rejection -- delivery status should be unknown - if (drec.status !== DeliveryRecordStatus.unknown) { - log( - 'unexpected rejection: message from %s was first seen %s ago and has delivery status %d', - from, - Date.now() - drec.firstSeenTsMs, - DeliveryRecordStatus[drec.status] - ) - return - } - - if (reason === RejectReason.Ignore) { - // we were explicitly instructed by the validator to ignore the message but not penalize the peer - drec.status = DeliveryRecordStatus.ignored - drec.peers.clear() - return - } - - // mark the message as invalid and penalize peers that have already forwarded it. - drec.status = DeliveryRecordStatus.invalid - - this.markInvalidMessageDelivery(from, topic) - drec.peers.forEach((p) => { - this.markInvalidMessageDelivery(p, topic) - }) - - // release the delivery time tracking map to free some memory early - drec.peers.clear() - } - - duplicateMessage(from: PeerIdStr, msgIdStr: MsgIdStr, topic: TopicStr): void { - const drec = this.deliveryRecords.ensureRecord(msgIdStr) - - if (drec.peers.has(from)) { - // we have already seen this duplicate - return - } - - switch (drec.status) { - case DeliveryRecordStatus.unknown: - // the message is being validated; track the peer delivery and wait for - // the Deliver/Reject/Ignore notification. - drec.peers.add(from) - break - - case DeliveryRecordStatus.valid: - // mark the peer delivery time to only count a duplicate delivery once. - drec.peers.add(from) - this.markDuplicateMessageDelivery(from, topic, drec.validated) - break - - case DeliveryRecordStatus.invalid: - // we no longer track delivery time - this.markInvalidMessageDelivery(from, topic) - break - - case DeliveryRecordStatus.ignored: - // the message was ignored; do nothing (we don't know if it was valid) - break - } - } - - /** - * Increments the "invalid message deliveries" counter for all scored topics the message is published in. - */ - public markInvalidMessageDelivery(from: PeerIdStr, topic: TopicStr): void { - const pstats = this.peerStats.get(from) - if (pstats) { - const tstats = this.getPtopicStats(pstats, topic) - if (tstats) { - tstats.invalidMessageDeliveries += 1 - } - } - } - - /** - * Increments the "first message deliveries" counter for all scored topics the message is published in, - * as well as the "mesh message deliveries" counter, if the peer is in the mesh for the topic. - * Messages already known (with the seenCache) are counted with markDuplicateMessageDelivery() - */ - public markFirstMessageDelivery(from: PeerIdStr, topic: TopicStr): void { - const pstats = this.peerStats.get(from) - if (pstats) { - const tstats = this.getPtopicStats(pstats, topic) - if (tstats) { - let cap = this.params.topics[topic].firstMessageDeliveriesCap - tstats.firstMessageDeliveries = Math.min(cap, tstats.firstMessageDeliveries + 1) - - if (tstats.inMesh) { - cap = this.params.topics[topic].meshMessageDeliveriesCap - tstats.meshMessageDeliveries = Math.min(cap, tstats.meshMessageDeliveries + 1) - } - } - } - } - - /** - * Increments the "mesh message deliveries" counter for messages we've seen before, - * as long the message was received within the P3 window. - */ - public markDuplicateMessageDelivery(from: PeerIdStr, topic: TopicStr, validatedTime?: number): void { - const pstats = this.peerStats.get(from) - if (pstats) { - const now = validatedTime !== undefined ? Date.now() : 0 - - const tstats = this.getPtopicStats(pstats, topic) - if (tstats && tstats.inMesh) { - const tparams = this.params.topics[topic] - - // check against the mesh delivery window -- if the validated time is passed as 0, then - // the message was received before we finished validation and thus falls within the mesh - // delivery window. - if (validatedTime !== undefined) { - const deliveryDelayMs = now - validatedTime - const isLateDelivery = deliveryDelayMs > tparams.meshMessageDeliveriesWindow - this.metrics?.onDuplicateMsgDelivery(topic, deliveryDelayMs, isLateDelivery) - - if (isLateDelivery) { - return - } - } - - const cap = tparams.meshMessageDeliveriesCap - tstats.meshMessageDeliveries = Math.min(cap, tstats.meshMessageDeliveries + 1) - } - } - } - - /** - * Removes an IP list from the tracking list for a peer. - */ - private removeIPsForPeer(id: PeerIdStr, ipsToRemove: Set): void { - for (const ipToRemove of ipsToRemove) { - const peerSet = this.peerIPs.get(ipToRemove) - if (peerSet) { - peerSet.delete(id) - if (peerSet.size === 0) { - this.peerIPs.delete(ipToRemove) - } - } - } - } - - /** - * Returns topic stats if they exist, otherwise if the supplied parameters score the - * topic, inserts the default stats and returns a reference to those. If neither apply, returns None. - */ - private getPtopicStats(pstats: PeerStats, topic: TopicStr): TopicStats | null { - let topicStats: TopicStats | undefined = pstats.topics[topic] - - if (topicStats !== undefined) { - return topicStats - } - - if (this.params.topics[topic] !== undefined) { - topicStats = { - inMesh: false, - graftTime: 0, - meshTime: 0, - firstMessageDeliveries: 0, - meshMessageDeliveries: 0, - meshMessageDeliveriesActive: false, - meshFailurePenalty: 0, - invalidMessageDeliveries: 0 - } - pstats.topics[topic] = topicStats - - return topicStats - } - - return null - } -} diff --git a/packages/pubsub-gossipsub/src/score/peer-stats.ts b/packages/pubsub-gossipsub/src/score/peer-stats.ts deleted file mode 100644 index 3401168561..0000000000 --- a/packages/pubsub-gossipsub/src/score/peer-stats.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { TopicStr } from '../types.js' - -export interface PeerStats { - /** true if the peer is currently connected */ - connected: boolean - /** expiration time of the score stats for disconnected peers */ - expire: number - /** per topic stats */ - topics: Record - /** IP tracking; store as set for easy processing */ - knownIPs: Set - /** behavioural pattern penalties (applied by the router) */ - behaviourPenalty: number -} - -export interface TopicStats { - /** true if the peer is in the mesh */ - inMesh: boolean - /** time when the peer was (last) GRAFTed; valid only when in mesh */ - graftTime: number - /** time in mesh (updated during refresh/decay to avoid calling gettimeofday on every score invocation) */ - meshTime: number - /** first message deliveries */ - firstMessageDeliveries: number - /** mesh message deliveries */ - meshMessageDeliveries: number - /** true if the peer has been enough time in the mesh to activate mess message deliveries */ - meshMessageDeliveriesActive: boolean - /** sticky mesh rate failure penalty counter */ - meshFailurePenalty: number - /** invalid message counter */ - invalidMessageDeliveries: number -} diff --git a/packages/pubsub-gossipsub/src/score/scoreMetrics.ts b/packages/pubsub-gossipsub/src/score/scoreMetrics.ts deleted file mode 100644 index 299e232cf5..0000000000 --- a/packages/pubsub-gossipsub/src/score/scoreMetrics.ts +++ /dev/null @@ -1,215 +0,0 @@ -import type { PeerScoreParams } from './peer-score-params.js' -import type { PeerStats } from './peer-stats.js' - -type TopicLabel = string -type TopicStr = string -type TopicStrToLabel = Map - -export interface TopicScoreWeights { - p1w: T - p2w: T - p3w: T - p3bw: T - p4w: T -} -export interface ScoreWeights { - byTopic: Map> - p5w: T - p6w: T - p7w: T - score: T -} - -export function computeScoreWeights( - peer: string, - pstats: PeerStats, - params: PeerScoreParams, - peerIPs: Map>, - topicStrToLabel: TopicStrToLabel -): ScoreWeights { - let score = 0 - - const byTopic = new Map>() - - // topic stores - Object.entries(pstats.topics).forEach(([topic, tstats]) => { - // the topic parameters - // Aggregate by known topicLabel or throw to 'unknown'. This prevent too high cardinality - const topicLabel = topicStrToLabel.get(topic) ?? 'unknown' - const topicParams = params.topics[topic] - if (topicParams === undefined) { - // we are not scoring this topic - return - } - - let topicScores = byTopic.get(topicLabel) - if (!topicScores) { - topicScores = { - p1w: 0, - p2w: 0, - p3w: 0, - p3bw: 0, - p4w: 0 - } - byTopic.set(topicLabel, topicScores) - } - - let p1w = 0 - let p2w = 0 - let p3w = 0 - let p3bw = 0 - let p4w = 0 - - // P1: time in Mesh - if (tstats.inMesh) { - const p1 = Math.max(tstats.meshTime / topicParams.timeInMeshQuantum, topicParams.timeInMeshCap) - p1w += p1 * topicParams.timeInMeshWeight - } - - // P2: first message deliveries - let p2 = tstats.firstMessageDeliveries - if (p2 > topicParams.firstMessageDeliveriesCap) { - p2 = topicParams.firstMessageDeliveriesCap - } - p2w += p2 * topicParams.firstMessageDeliveriesWeight - - // P3: mesh message deliveries - if ( - tstats.meshMessageDeliveriesActive && - tstats.meshMessageDeliveries < topicParams.meshMessageDeliveriesThreshold - ) { - const deficit = topicParams.meshMessageDeliveriesThreshold - tstats.meshMessageDeliveries - const p3 = deficit * deficit - p3w += p3 * topicParams.meshMessageDeliveriesWeight - } - - // P3b: - // NOTE: the weight of P3b is negative (validated in validateTopicScoreParams) so this detracts - const p3b = tstats.meshFailurePenalty - p3bw += p3b * topicParams.meshFailurePenaltyWeight - - // P4: invalid messages - // NOTE: the weight of P4 is negative (validated in validateTopicScoreParams) so this detracts - const p4 = tstats.invalidMessageDeliveries * tstats.invalidMessageDeliveries - p4w += p4 * topicParams.invalidMessageDeliveriesWeight - - // update score, mixing with topic weight - score += (p1w + p2w + p3w + p3bw + p4w) * topicParams.topicWeight - - topicScores.p1w += p1w - topicScores.p2w += p2w - topicScores.p3w += p3w - topicScores.p3bw += p3bw - topicScores.p4w += p4w - }) - - // apply the topic score cap, if any - if (params.topicScoreCap > 0 && score > params.topicScoreCap) { - score = params.topicScoreCap - - // Proportionally apply cap to all individual contributions - const capF = params.topicScoreCap / score - for (const ws of byTopic.values()) { - ws.p1w *= capF - ws.p2w *= capF - ws.p3w *= capF - ws.p3bw *= capF - ws.p4w *= capF - } - } - - let p5w = 0 - let p6w = 0 - let p7w = 0 - - // P5: application-specific score - const p5 = params.appSpecificScore(peer) - p5w += p5 * params.appSpecificWeight - - // P6: IP colocation factor - pstats.knownIPs.forEach((ip) => { - if (params.IPColocationFactorWhitelist.has(ip)) { - return - } - - // P6 has a cliff (IPColocationFactorThreshold) - // It's only applied if at least that many peers are connected to us from that source IP addr. - // It is quadratic, and the weight is negative (validated in validatePeerScoreParams) - const peersInIP = peerIPs.get(ip) - const numPeersInIP = peersInIP ? peersInIP.size : 0 - if (numPeersInIP > params.IPColocationFactorThreshold) { - const surplus = numPeersInIP - params.IPColocationFactorThreshold - const p6 = surplus * surplus - p6w += p6 * params.IPColocationFactorWeight - } - }) - - // P7: behavioural pattern penalty - const p7 = pstats.behaviourPenalty * pstats.behaviourPenalty - p7w += p7 * params.behaviourPenaltyWeight - - score += p5w + p6w + p7w - - return { - byTopic, - p5w, - p6w, - p7w, - score - } -} - -export function computeAllPeersScoreWeights( - peerIdStrs: Iterable, - peerStats: Map, - params: PeerScoreParams, - peerIPs: Map>, - topicStrToLabel: TopicStrToLabel -): ScoreWeights { - const sw: ScoreWeights = { - byTopic: new Map(), - p5w: [], - p6w: [], - p7w: [], - score: [] - } - - for (const peerIdStr of peerIdStrs) { - const pstats = peerStats.get(peerIdStr) - if (pstats) { - const swPeer = computeScoreWeights(peerIdStr, pstats, params, peerIPs, topicStrToLabel) - - for (const [topic, swPeerTopic] of swPeer.byTopic) { - let swTopic = sw.byTopic.get(topic) - if (!swTopic) { - swTopic = { - p1w: [], - p2w: [], - p3w: [], - p3bw: [], - p4w: [] - } - sw.byTopic.set(topic, swTopic) - } - - swTopic.p1w.push(swPeerTopic.p1w) - swTopic.p2w.push(swPeerTopic.p2w) - swTopic.p3w.push(swPeerTopic.p3w) - swTopic.p3bw.push(swPeerTopic.p3bw) - swTopic.p4w.push(swPeerTopic.p4w) - } - - sw.p5w.push(swPeer.p5w) - sw.p6w.push(swPeer.p6w) - sw.p7w.push(swPeer.p7w) - sw.score.push(swPeer.score) - } else { - sw.p5w.push(0) - sw.p6w.push(0) - sw.p7w.push(0) - sw.score.push(0) - } - } - - return sw -} diff --git a/packages/pubsub-gossipsub/src/stream.ts b/packages/pubsub-gossipsub/src/stream.ts deleted file mode 100644 index d4b954a136..0000000000 --- a/packages/pubsub-gossipsub/src/stream.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { Stream } from '@libp2p/interface/connection' -import { abortableSource } from 'abortable-iterator' -import { pipe } from 'it-pipe' -import { pushable, type Pushable } from 'it-pushable' -import { encode, decode } from 'it-length-prefixed' -import type { Uint8ArrayList } from 'uint8arraylist' - -type OutboundStreamOpts = { - /** Max size in bytes for pushable buffer. If full, will throw on .push */ - maxBufferSize?: number -} - -type InboundStreamOpts = { - /** Max size in bytes for reading messages from the stream */ - maxDataLength?: number -} - -export class OutboundStream { - private readonly pushable: Pushable - private readonly closeController: AbortController - private readonly maxBufferSize: number - - constructor(private readonly rawStream: Stream, errCallback: (e: Error) => void, opts: OutboundStreamOpts) { - this.pushable = pushable({ objectMode: false }) - this.closeController = new AbortController() - this.maxBufferSize = opts.maxBufferSize ?? Infinity - - pipe( - abortableSource(this.pushable, this.closeController.signal, { returnOnAbort: true }), - (source) => encode(source), - this.rawStream - ).catch(errCallback) - } - - get protocol(): string { - // TODO remove this non-nullish assertion after https://github.com/libp2p/js-libp2p-interfaces/pull/265 is incorporated - return this.rawStream.protocol! - } - - push(data: Uint8Array): void { - if (this.pushable.readableLength > this.maxBufferSize) { - throw Error(`OutboundStream buffer full, size > ${this.maxBufferSize}`) - } - - this.pushable.push(data) - } - - close(): void { - this.closeController.abort() - // similar to pushable.end() but clear the internal buffer - this.pushable.return() - this.rawStream.close() - } -} - -export class InboundStream { - public readonly source: AsyncIterable - - private readonly rawStream: Stream - private readonly closeController: AbortController - - constructor(rawStream: Stream, opts: InboundStreamOpts = {}) { - this.rawStream = rawStream - this.closeController = new AbortController() - - this.source = abortableSource( - pipe(this.rawStream, (source) => decode(source, opts)), - this.closeController.signal, - { - returnOnAbort: true - } - ) - } - - close(): void { - this.closeController.abort() - this.rawStream.close() - } -} diff --git a/packages/pubsub-gossipsub/src/tracer.ts b/packages/pubsub-gossipsub/src/tracer.ts deleted file mode 100644 index 9471129df4..0000000000 --- a/packages/pubsub-gossipsub/src/tracer.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { type MsgIdStr, type MsgIdToStrFn, type PeerIdStr, RejectReason } from './types.js' -import type { Metrics } from './metrics.js' - -/** - * IWantTracer is an internal tracer that tracks IWANT requests in order to penalize - * peers who don't follow up on IWANT requests after an IHAVE advertisement. - * The tracking of promises is probabilistic to avoid using too much memory. - * - * Note: Do not confuse these 'promises' with JS Promise objects. - * These 'promises' are merely expectations of a peer's behavior. - */ -export class IWantTracer { - /** - * Promises to deliver a message - * Map per message id, per peer, promise expiration time - */ - private readonly promises = new Map>() - /** - * First request time by msgId. Used for metrics to track expire times. - * Necessary to know if peers are actually breaking promises or simply sending them a bit later - */ - private readonly requestMsByMsg = new Map() - private readonly requestMsByMsgExpire: number - - constructor( - private readonly gossipsubIWantFollowupMs: number, - private readonly msgIdToStrFn: MsgIdToStrFn, - private readonly metrics: Metrics | null - ) { - this.requestMsByMsgExpire = 10 * gossipsubIWantFollowupMs - } - - get size(): number { - return this.promises.size - } - - get requestMsByMsgSize(): number { - return this.requestMsByMsg.size - } - - /** - * Track a promise to deliver a message from a list of msgIds we are requesting - */ - addPromise(from: PeerIdStr, msgIds: Uint8Array[]): void { - // pick msgId randomly from the list - const ix = Math.floor(Math.random() * msgIds.length) - const msgId = msgIds[ix] - const msgIdStr = this.msgIdToStrFn(msgId) - - let expireByPeer = this.promises.get(msgIdStr) - if (!expireByPeer) { - expireByPeer = new Map() - this.promises.set(msgIdStr, expireByPeer) - } - - const now = Date.now() - - // If a promise for this message id and peer already exists we don't update the expiry - if (!expireByPeer.has(from)) { - expireByPeer.set(from, now + this.gossipsubIWantFollowupMs) - - if (this.metrics) { - this.metrics.iwantPromiseStarted.inc(1) - if (!this.requestMsByMsg.has(msgIdStr)) { - this.requestMsByMsg.set(msgIdStr, now) - } - } - } - } - - /** - * Returns the number of broken promises for each peer who didn't follow up on an IWANT request. - * - * This should be called not too often relative to the expire times, since it iterates over the whole data. - */ - getBrokenPromises(): Map { - const now = Date.now() - const result = new Map() - - let brokenPromises = 0 - - this.promises.forEach((expireByPeer, msgId) => { - expireByPeer.forEach((expire, p) => { - // the promise has been broken - if (expire < now) { - // add 1 to result - result.set(p, (result.get(p) ?? 0) + 1) - // delete from tracked promises - expireByPeer.delete(p) - // for metrics - brokenPromises++ - } - }) - // clean up empty promises for a msgId - if (!expireByPeer.size) { - this.promises.delete(msgId) - } - }) - - this.metrics?.iwantPromiseBroken.inc(brokenPromises) - - return result - } - - /** - * Someone delivered a message, stop tracking promises for it - */ - deliverMessage(msgIdStr: MsgIdStr, isDuplicate = false): void { - this.trackMessage(msgIdStr) - - const expireByPeer = this.promises.get(msgIdStr) - - // Expired promise, check requestMsByMsg - if (expireByPeer) { - this.promises.delete(msgIdStr) - - if (this.metrics) { - this.metrics.iwantPromiseResolved.inc(1) - if (isDuplicate) this.metrics.iwantPromiseResolvedFromDuplicate.inc(1) - this.metrics.iwantPromiseResolvedPeers.inc(expireByPeer.size) - } - } - } - - /** - * A message got rejected, so we can stop tracking promises and let the score penalty apply from invalid message delivery, - * unless its an obviously invalid message. - */ - rejectMessage(msgIdStr: MsgIdStr, reason: RejectReason): void { - this.trackMessage(msgIdStr) - - // A message got rejected, so we can stop tracking promises and let the score penalty apply. - // With the expection of obvious invalid messages - switch (reason) { - case RejectReason.Error: - return - } - - this.promises.delete(msgIdStr) - } - - clear(): void { - this.promises.clear() - } - - prune(): void { - const maxMs = Date.now() - this.requestMsByMsgExpire - let count = 0 - - for (const [k, v] of this.requestMsByMsg.entries()) { - if (v < maxMs) { - // messages that stay too long in the requestMsByMsg map, delete - this.requestMsByMsg.delete(k) - count++ - } else { - // recent messages, keep them - // sort by insertion order - break - } - } - - this.metrics?.iwantMessagePruned.inc(count) - } - - private trackMessage(msgIdStr: MsgIdStr): void { - if (this.metrics) { - const requestMs = this.requestMsByMsg.get(msgIdStr) - if (requestMs !== undefined) { - this.metrics.iwantPromiseDeliveryTime.observe((Date.now() - requestMs) / 1000) - this.requestMsByMsg.delete(msgIdStr) - } - } - } -} diff --git a/packages/pubsub-gossipsub/src/types.ts b/packages/pubsub-gossipsub/src/types.ts deleted file mode 100644 index 9fb512d7cc..0000000000 --- a/packages/pubsub-gossipsub/src/types.ts +++ /dev/null @@ -1,166 +0,0 @@ -import type { PeerId } from '@libp2p/interface/peer-id' -import type { PrivateKey } from '@libp2p/interface/keys' -import type { Multiaddr } from '@multiformats/multiaddr' -import type { RPC } from './message/rpc.js' -import { type Message, TopicValidatorResult } from '@libp2p/interface/pubsub' - -export type MsgIdStr = string -export type PeerIdStr = string -export type TopicStr = string -export type IPStr = string - -export interface AddrInfo { - id: PeerId - addrs: Multiaddr[] -} - -/** - * Compute a local non-spec'ed msg-id for faster de-duplication of seen messages. - * Used exclusively for a local seen_cache - */ -export type FastMsgIdFn = (msg: RPC.IMessage) => string | number - -/** - * By default, gossipsub only provide a browser friendly function to convert Uint8Array message id to string. - * Application could use this option to provide a more efficient function. - */ -export type MsgIdToStrFn = (msgId: Uint8Array) => string - -/** - * Compute spec'ed msg-id. Used for IHAVE / IWANT messages - */ -export interface MsgIdFn { - (msg: Message): Promise | Uint8Array -} - -export interface DataTransform { - /** - * Takes the data published by peers on a topic and transforms the data. - * Should be the reverse of outboundTransform(). Example: - * - `inboundTransform()`: decompress snappy payload - * - `outboundTransform()`: compress snappy payload - */ - inboundTransform(topic: TopicStr, data: Uint8Array): Uint8Array - - /** - * Takes the data to be published (a topic and associated data) transforms the data. The - * transformed data will then be used to create a `RawGossipsubMessage` to be sent to peers. - */ - outboundTransform(topic: TopicStr, data: Uint8Array): Uint8Array -} - -export enum SignaturePolicy { - /** - * On the producing side: - * - Build messages with the signature, key (from may be enough for certain inlineable public key types), from and seqno fields. - * - * On the consuming side: - * - Enforce the fields to be present, reject otherwise. - * - Propagate only if the fields are valid and signature can be verified, reject otherwise. - */ - StrictSign = 'StrictSign', - /** - * On the producing side: - * - Build messages without the signature, key, from and seqno fields. - * - The corresponding protobuf key-value pairs are absent from the marshalled message, not just empty. - * - * On the consuming side: - * - Enforce the fields to be absent, reject otherwise. - * - Propagate only if the fields are absent, reject otherwise. - * - A message_id function will not be able to use the above fields, and should instead rely on the data field. A commonplace strategy is to calculate a hash. - */ - StrictNoSign = 'StrictNoSign' -} - -export type PublishOpts = { - allowPublishToZeroPeers?: boolean - ignoreDuplicatePublishError?: boolean -} - -export enum PublishConfigType { - Signing, - Anonymous -} - -export type PublishConfig = - | { - type: PublishConfigType.Signing - author: PeerId - key: Uint8Array - privateKey: PrivateKey - } - | { type: PublishConfigType.Anonymous } - -export type RejectReasonObj = - | { reason: RejectReason.Error; error: ValidateError } - | { reason: Exclude } - -export enum RejectReason { - /** - * The message failed the configured validation during decoding. - * SelfOrigin is considered a ValidationError - */ - Error = 'error', - /** - * Custom validator fn reported status IGNORE. - */ - Ignore = 'ignore', - /** - * Custom validator fn reported status REJECT. - */ - Reject = 'reject', - /** - * The peer that sent the message OR the source from field is blacklisted. - * Causes messages to be ignored, not penalized, neither do score record creation. - */ - Blacklisted = 'blacklisted' -} - -export enum ValidateError { - /// The message has an invalid signature, - InvalidSignature = 'invalid_signature', - /// The sequence number was the incorrect size - InvalidSeqno = 'invalid_seqno', - /// The PeerId was invalid - InvalidPeerId = 'invalid_peerid', - /// Signature existed when validation has been sent to - /// [`crate::behaviour::MessageAuthenticity::Anonymous`]. - SignaturePresent = 'signature_present', - /// Sequence number existed when validation has been sent to - /// [`crate::behaviour::MessageAuthenticity::Anonymous`]. - SeqnoPresent = 'seqno_present', - /// Message source existed when validation has been sent to - /// [`crate::behaviour::MessageAuthenticity::Anonymous`]. - FromPresent = 'from_present', - /// The data transformation failed. - TransformFailed = 'transform_failed' -} - -export enum MessageStatus { - duplicate = 'duplicate', - invalid = 'invalid', - valid = 'valid' -} - -/** - * Store both Uint8Array and string message id so that we don't have to convert data between the two. - * See https://github.com/ChainSafe/js-libp2p-gossipsub/pull/274 - */ -export type MessageId = { - msgId: Uint8Array - msgIdStr: MsgIdStr -} - -/** - * Typesafe conversion of MessageAcceptance -> RejectReason. TS ensures all values covered - */ -export function rejectReasonFromAcceptance( - acceptance: Exclude -): RejectReason.Ignore | RejectReason.Reject { - switch (acceptance) { - case TopicValidatorResult.Ignore: - return RejectReason.Ignore - case TopicValidatorResult.Reject: - return RejectReason.Reject - } -} diff --git a/packages/pubsub-gossipsub/src/utils/buildRawMessage.ts b/packages/pubsub-gossipsub/src/utils/buildRawMessage.ts deleted file mode 100644 index 407de6581b..0000000000 --- a/packages/pubsub-gossipsub/src/utils/buildRawMessage.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { concat as uint8ArrayConcat } from 'uint8arrays/concat' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { marshalPublicKey, unmarshalPublicKey } from '@libp2p/crypto/keys' -import { randomBytes } from '@libp2p/crypto' -import { peerIdFromBytes } from '@libp2p/peer-id' -import type { PublicKey } from '@libp2p/interface/keys' -import type { PeerId } from '@libp2p/interface/peer-id' -import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { RPC } from '../message/rpc.js' -import { type PublishConfig, PublishConfigType, type TopicStr, ValidateError } from '../types.js' -import { StrictSign, StrictNoSign, type Message } from '@libp2p/interface/pubsub' -import { toString as uint8ArrayToString } from 'uint8arrays/to-string' - -export const SignPrefix = uint8ArrayFromString('libp2p-pubsub:') - -export type RawMessageAndMessage = { - raw: RPC.IMessage - msg: Message -} - -export async function buildRawMessage( - publishConfig: PublishConfig, - topic: TopicStr, - originalData: Uint8Array, - transformedData: Uint8Array -): Promise { - switch (publishConfig.type) { - case PublishConfigType.Signing: { - const rpcMsg: RPC.IMessage = { - from: publishConfig.author.toBytes(), - data: transformedData, - seqno: randomBytes(8), - topic, - signature: undefined, // Exclude signature field for signing - key: undefined // Exclude key field for signing - } - - // Get the message in bytes, and prepend with the pubsub prefix - // the signature is over the bytes "libp2p-pubsub:" - const bytes = uint8ArrayConcat([SignPrefix, RPC.Message.encode(rpcMsg).finish()]) - - rpcMsg.signature = await publishConfig.privateKey.sign(bytes) - rpcMsg.key = publishConfig.key - - const msg: Message = { - type: 'signed', - from: publishConfig.author, - data: originalData, - sequenceNumber: BigInt(`0x${uint8ArrayToString(rpcMsg.seqno as Uint8Array, 'base16')}`), - topic, - signature: rpcMsg.signature, - key: rpcMsg.key - } - return { - raw: rpcMsg, - msg: msg - } - } - - case PublishConfigType.Anonymous: { - return { - raw: { - from: undefined, - data: transformedData, - seqno: undefined, - topic, - signature: undefined, - key: undefined - }, - msg: { - type: 'unsigned', - data: originalData, - topic - } - } - } - } -} - -export type ValidationResult = { valid: true; message: Message } | { valid: false; error: ValidateError } - -export async function validateToRawMessage( - signaturePolicy: typeof StrictNoSign | typeof StrictSign, - msg: RPC.IMessage -): Promise { - // If strict-sign, verify all - // If anonymous (no-sign), ensure no preven - - switch (signaturePolicy) { - case StrictNoSign: - if (msg.signature != null) return { valid: false, error: ValidateError.SignaturePresent } - if (msg.seqno != null) return { valid: false, error: ValidateError.SeqnoPresent } - if (msg.key != null) return { valid: false, error: ValidateError.FromPresent } - - return { valid: true, message: { type: 'unsigned', topic: msg.topic, data: msg.data ?? new Uint8Array(0) } } - - case StrictSign: { - // Verify seqno - if (msg.seqno == null) return { valid: false, error: ValidateError.InvalidSeqno } - if (msg.seqno.length !== 8) { - return { valid: false, error: ValidateError.InvalidSeqno } - } - - if (msg.signature == null) return { valid: false, error: ValidateError.InvalidSignature } - if (msg.from == null) return { valid: false, error: ValidateError.InvalidPeerId } - - let fromPeerId: PeerId - try { - // TODO: Fix PeerId types - fromPeerId = peerIdFromBytes(msg.from) - } catch (e) { - return { valid: false, error: ValidateError.InvalidPeerId } - } - - // - check from defined - // - transform source to PeerId - // - parse signature - // - get .key, else from source - // - check key == source if present - // - verify sig - - let publicKey: PublicKey - if (msg.key) { - publicKey = unmarshalPublicKey(msg.key) - // TODO: Should `fromPeerId.pubKey` be optional? - if (fromPeerId.publicKey !== undefined && !uint8ArrayEquals(publicKey.bytes, fromPeerId.publicKey)) { - return { valid: false, error: ValidateError.InvalidPeerId } - } - } else { - if (fromPeerId.publicKey == null) { - return { valid: false, error: ValidateError.InvalidPeerId } - } - publicKey = unmarshalPublicKey(fromPeerId.publicKey) - } - - const rpcMsgPreSign: RPC.IMessage = { - from: msg.from, - data: msg.data, - seqno: msg.seqno, - topic: msg.topic, - signature: undefined, // Exclude signature field for signing - key: undefined // Exclude key field for signing - } - - // Get the message in bytes, and prepend with the pubsub prefix - // the signature is over the bytes "libp2p-pubsub:" - const bytes = uint8ArrayConcat([SignPrefix, RPC.Message.encode(rpcMsgPreSign).finish()]) - - if (!(await publicKey.verify(bytes, msg.signature))) { - return { valid: false, error: ValidateError.InvalidSignature } - } - - return { - valid: true, - message: { - type: 'signed', - from: fromPeerId, - data: msg.data ?? new Uint8Array(0), - sequenceNumber: BigInt(`0x${uint8ArrayToString(msg.seqno, 'base16')}`), - topic: msg.topic, - signature: msg.signature, - key: msg.key ?? marshalPublicKey(publicKey) - } - } - } - } -} diff --git a/packages/pubsub-gossipsub/src/utils/index.ts b/packages/pubsub-gossipsub/src/utils/index.ts deleted file mode 100644 index 40c1ad035b..0000000000 --- a/packages/pubsub-gossipsub/src/utils/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './shuffle.js' -export * from './messageIdToString.js' -export { getPublishConfigFromPeerId } from './publishConfig.js' diff --git a/packages/pubsub-gossipsub/src/utils/messageIdToString.ts b/packages/pubsub-gossipsub/src/utils/messageIdToString.ts deleted file mode 100644 index 289c5c9cad..0000000000 --- a/packages/pubsub-gossipsub/src/utils/messageIdToString.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { toString } from 'uint8arrays/to-string' - -/** - * Browser friendly function to convert Uint8Array message id to base64 string. - */ -export function messageIdToString(msgId: Uint8Array): string { - return toString(msgId, 'base64') -} diff --git a/packages/pubsub-gossipsub/src/utils/msgIdFn.ts b/packages/pubsub-gossipsub/src/utils/msgIdFn.ts deleted file mode 100644 index bd43d620d6..0000000000 --- a/packages/pubsub-gossipsub/src/utils/msgIdFn.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { sha256 } from 'multiformats/hashes/sha2' -import type { Message } from '@libp2p/interface/pubsub' -import { msgId } from '@libp2p/pubsub/utils' - -/** - * Generate a message id, based on the `key` and `seqno` - */ -export function msgIdFnStrictSign(msg: Message): Uint8Array { - if (msg.type !== 'signed') { - throw new Error('expected signed message type') - } - // Should never happen - if (msg.sequenceNumber == null) throw Error('missing seqno field') - - // TODO: Should use .from here or key? - return msgId(msg.from.toBytes(), msg.sequenceNumber) -} - -/** - * Generate a message id, based on message `data` - */ -export async function msgIdFnStrictNoSign(msg: Message): Promise { - return await sha256.encode(msg.data) -} diff --git a/packages/pubsub-gossipsub/src/utils/multiaddr.ts b/packages/pubsub-gossipsub/src/utils/multiaddr.ts deleted file mode 100644 index ba5deb57f7..0000000000 --- a/packages/pubsub-gossipsub/src/utils/multiaddr.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Multiaddr } from '@multiformats/multiaddr' -import { convertToString } from '@multiformats/multiaddr/convert' - -// Protocols https://github.com/multiformats/multiaddr/blob/master/protocols.csv -// code size name -// 4 32 ip4 -// 41 128 ip6 -enum Protocol { - ip4 = 4, - ip6 = 41 -} - -export function multiaddrToIPStr(multiaddr: Multiaddr): string | null { - for (const tuple of multiaddr.tuples()) { - switch (tuple[0]) { - case Protocol.ip4: - case Protocol.ip6: - return convertToString(tuple[0], tuple[1]!) - } - } - - return null -} diff --git a/packages/pubsub-gossipsub/src/utils/publishConfig.ts b/packages/pubsub-gossipsub/src/utils/publishConfig.ts deleted file mode 100644 index 892c5ed0e8..0000000000 --- a/packages/pubsub-gossipsub/src/utils/publishConfig.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { unmarshalPrivateKey } from '@libp2p/crypto/keys' -import { StrictSign, StrictNoSign } from '@libp2p/interface/pubsub' -import type { PeerId } from '@libp2p/interface/peer-id' -import { type PublishConfig, PublishConfigType } from '../types.js' - -/** - * Prepare a PublishConfig object from a PeerId. - */ -export async function getPublishConfigFromPeerId( - signaturePolicy: typeof StrictSign | typeof StrictNoSign, - peerId?: PeerId -): Promise { - switch (signaturePolicy) { - case StrictSign: { - if (!peerId) { - throw Error('Must provide PeerId') - } - - if (peerId.privateKey == null) { - throw Error('Cannot sign message, no private key present') - } - - if (peerId.publicKey == null) { - throw Error('Cannot sign message, no public key present') - } - - // Transform privateKey once at initialization time instead of once per message - const privateKey = await unmarshalPrivateKey(peerId.privateKey) - - return { - type: PublishConfigType.Signing, - author: peerId, - key: peerId.publicKey, - privateKey - } - } - - case StrictNoSign: - return { - type: PublishConfigType.Anonymous - } - - default: - throw new Error(`Unknown signature policy "${signaturePolicy}"`) - } -} diff --git a/packages/pubsub-gossipsub/src/utils/set.ts b/packages/pubsub-gossipsub/src/utils/set.ts deleted file mode 100644 index 9687f21098..0000000000 --- a/packages/pubsub-gossipsub/src/utils/set.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Exclude up to `ineed` items from a set if item meets condition `cond` - */ -export function removeItemsFromSet( - superSet: Set, - ineed: number, - cond: (peer: T) => boolean = () => true -): Set { - const subset = new Set() - if (ineed <= 0) return subset - - for (const id of superSet) { - if (subset.size >= ineed) break - if (cond(id)) { - subset.add(id) - superSet.delete(id) - } - } - - return subset -} - -/** - * Exclude up to `ineed` items from a set - */ -export function removeFirstNItemsFromSet(superSet: Set, ineed: number): Set { - return removeItemsFromSet(superSet, ineed, () => true) -} - -export class MapDef extends Map { - constructor(private readonly getDefault: () => V) { - super() - } - - getOrDefault(key: K): V { - let value = super.get(key) - if (value === undefined) { - value = this.getDefault() - this.set(key, value) - } - return value - } -} diff --git a/packages/pubsub-gossipsub/src/utils/shuffle.ts b/packages/pubsub-gossipsub/src/utils/shuffle.ts deleted file mode 100644 index 0601a29968..0000000000 --- a/packages/pubsub-gossipsub/src/utils/shuffle.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Pseudo-randomly shuffles an array - * - * Mutates the input array - */ -export function shuffle(arr: T[]): T[] { - if (arr.length <= 1) { - return arr - } - const randInt = () => { - return Math.floor(Math.random() * Math.floor(arr.length)) - } - - for (let i = 0; i < arr.length; i++) { - const j = randInt() - const tmp = arr[i] - arr[i] = arr[j] - arr[j] = tmp - } - return arr -} diff --git a/packages/pubsub-gossipsub/src/utils/time-cache.ts b/packages/pubsub-gossipsub/src/utils/time-cache.ts deleted file mode 100644 index 006dade43b..0000000000 --- a/packages/pubsub-gossipsub/src/utils/time-cache.ts +++ /dev/null @@ -1,71 +0,0 @@ -type SimpleTimeCacheOpts = { - validityMs: number -} - -type CacheValue = { - value: T - validUntilMs: number -} - -/** - * This is similar to https://github.com/daviddias/time-cache/blob/master/src/index.js - * for our own need, we don't use lodash throttle to improve performance. - * This gives 4x - 5x performance gain compared to npm TimeCache - */ -export class SimpleTimeCache { - private readonly entries = new Map>() - private readonly validityMs: number - - constructor(opts: SimpleTimeCacheOpts) { - this.validityMs = opts.validityMs - - // allow negative validityMs so that this does not cache anything, spec test compliance.spec.js - // sends duplicate messages and expect peer to receive all. Application likely uses positive validityMs - } - - get size(): number { - return this.entries.size - } - - /** Returns true if there was a key collision and the entry is dropped */ - put(key: string | number, value: T): boolean { - if (this.entries.has(key)) { - // Key collisions break insertion order in the entries cache, which break prune logic. - // prune relies on each iterated entry to have strictly ascending validUntilMs, else it - // won't prune expired entries and SimpleTimeCache will grow unexpectedly. - // As of Oct 2022 NodeJS v16, inserting the same key twice with different value does not - // change the key position in the iterator stream. A unit test asserts this behaviour. - return true - } - - this.entries.set(key, { value, validUntilMs: Date.now() + this.validityMs }) - return false - } - - prune(): void { - const now = Date.now() - - for (const [k, v] of this.entries.entries()) { - if (v.validUntilMs < now) { - this.entries.delete(k) - } else { - // Entries are inserted with strictly ascending validUntilMs. - // Stop early to save iterations - break - } - } - } - - has(key: string): boolean { - return this.entries.has(key) - } - - get(key: string | number): T | undefined { - const value = this.entries.get(key) - return value && value.validUntilMs >= Date.now() ? value.value : undefined - } - - clear(): void { - this.entries.clear() - } -} diff --git a/packages/pubsub-gossipsub/test/2-nodes.spec.ts b/packages/pubsub-gossipsub/test/2-nodes.spec.ts deleted file mode 100644 index 919fd4330c..0000000000 --- a/packages/pubsub-gossipsub/test/2-nodes.spec.ts +++ /dev/null @@ -1,389 +0,0 @@ -import { expect } from 'aegir/chai' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import type { GossipSub } from '../src/index.js' -import type { Message, SubscriptionChangeData } from '@libp2p/interface/pubsub' -import { pEvent } from 'p-event' -import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import defer from 'p-defer' -import pWaitFor from 'p-wait-for' -import { - connectAllPubSubNodes, - connectPubsubNodes, - createComponentsArray, - type GossipSubAndComponents -} from './utils/create-pubsub.js' -import { stop } from '@libp2p/interface/startable' -import { mockNetwork } from '@libp2p/interface-compliance-tests/mocks' - -const shouldNotHappen = () => expect.fail() - -async function nodesArePubSubPeers(node0: GossipSubAndComponents, node1: GossipSubAndComponents, timeout = 60000) { - await pWaitFor( - () => { - const node0SeesNode1 = node0.pubsub - .getPeers() - .map((p) => p.toString()) - .includes(node1.components.peerId.toString()) - const node1SeesNode0 = node1.pubsub - .getPeers() - .map((p) => p.toString()) - .includes(node0.components.peerId.toString()) - return node0SeesNode1 && node1SeesNode0 - }, - { - timeout - } - ) -} - -describe('2 nodes', () => { - describe('Pubsub dial', () => { - let nodes: GossipSubAndComponents[] - - // Create pubsub nodes - beforeEach(async () => { - mockNetwork.reset() - nodes = await createComponentsArray({ number: 2 }) - }) - - afterEach(async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it('Dial from nodeA to nodeB happened with FloodsubID', async () => { - await connectPubsubNodes(nodes[0], nodes[1]) - await nodesArePubSubPeers(nodes[0], nodes[1]) - }) - }) - - describe('basics', () => { - let nodes: GossipSubAndComponents[] - - // Create pubsub nodes - beforeEach(async () => { - mockNetwork.reset() - nodes = await createComponentsArray({ number: 2 }) - }) - - afterEach(async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it('Dial from nodeA to nodeB happened with GossipsubIDv11', async () => { - await connectPubsubNodes(nodes[0], nodes[1]) - await nodesArePubSubPeers(nodes[0], nodes[1]) - }) - }) - - describe('subscription functionality', () => { - let nodes: GossipSubAndComponents[] - - // Create pubsub nodes - beforeEach(async () => { - mockNetwork.reset() - nodes = await createComponentsArray({ - number: 2, - connected: true - }) - await nodesArePubSubPeers(nodes[0], nodes[1]) - }) - - afterEach(async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it('Subscribe to a topic', async () => { - const topic = 'test_topic' - - nodes[0].pubsub.subscribe(topic) - nodes[1].pubsub.subscribe(topic) - - // await subscription change - const [evt0] = await Promise.all([ - pEvent<'subscription-change', CustomEvent>(nodes[0].pubsub, 'subscription-change'), - pEvent<'subscription-change', CustomEvent>(nodes[1].pubsub, 'subscription-change') - ]) - - const { peerId: changedPeerId, subscriptions: changedSubs } = evt0.detail - - expect(nodes[0].pubsub.getTopics()).to.include(topic) - expect(nodes[1].pubsub.getTopics()).to.include(topic) - expect(nodes[0].pubsub.getSubscribers(topic).map((p) => p.toString())).to.include( - nodes[1].components.peerId.toString() - ) - expect(nodes[1].pubsub.getSubscribers(topic).map((p) => p.toString())).to.include( - nodes[0].components.peerId.toString() - ) - - expect(changedPeerId.toString()).to.equal(nodes[1].components.peerId.toString()) - expect(changedSubs).to.have.lengthOf(1) - expect(changedSubs[0].topic).to.equal(topic) - expect(changedSubs[0].subscribe).to.equal(true) - - // await heartbeats - await Promise.all([ - pEvent(nodes[0].pubsub, 'gossipsub:heartbeat'), - pEvent(nodes[1].pubsub, 'gossipsub:heartbeat') - ]) - - expect((nodes[0].pubsub as GossipSub).mesh.get(topic)?.has(nodes[1].components.peerId.toString())).to.be.true() - expect((nodes[1].pubsub as GossipSub).mesh.get(topic)?.has(nodes[0].components.peerId.toString())).to.be.true() - }) - }) - - describe('publish functionality', () => { - const topic = 'Z' - let nodes: GossipSubAndComponents[] - - // Create pubsub nodes - beforeEach(async () => { - mockNetwork.reset() - nodes = await createComponentsArray({ - number: 2, - connected: true - }) - - // Create subscriptions - nodes[0].pubsub.subscribe(topic) - nodes[1].pubsub.subscribe(topic) - - // await subscription change and heartbeat - await Promise.all([ - pEvent(nodes[0].pubsub, 'subscription-change'), - pEvent(nodes[1].pubsub, 'subscription-change'), - pEvent(nodes[0].pubsub, 'gossipsub:heartbeat'), - pEvent(nodes[1].pubsub, 'gossipsub:heartbeat') - ]) - }) - - afterEach(async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it('Publish to a topic - nodeA', async () => { - const promise = pEvent<'message', CustomEvent>(nodes[1].pubsub, 'message') - nodes[0].pubsub.addEventListener('message', shouldNotHappen) - const data = uint8ArrayFromString('hey') - - await nodes[0].pubsub.publish(topic, data) - - const evt = await promise - - if (evt.detail.type !== 'signed') { - throw new Error('unexpected msg type') - } - expect(evt.detail.data).to.equalBytes(data) - expect(evt.detail.from.toString()).to.equal(nodes[0].components.peerId.toString()) - - nodes[0].pubsub.removeEventListener('message', shouldNotHappen) - }) - - it('Publish to a topic - nodeB', async () => { - const promise = pEvent<'message', CustomEvent>(nodes[0].pubsub, 'message') - nodes[1].pubsub.addEventListener('message', shouldNotHappen) - const data = uint8ArrayFromString('banana') - - await nodes[1].pubsub.publish(topic, data) - - const evt = await promise - - if (evt.detail.type !== 'signed') { - throw new Error('unexpected msg type') - } - expect(evt.detail.data).to.equalBytes(data) - expect(evt.detail.from.toString()).to.equal(nodes[1].components.peerId.toString()) - - nodes[1].pubsub.removeEventListener('message', shouldNotHappen) - }) - - it('Publish 10 msg to a topic', async () => { - let counter = 0 - - nodes[1].pubsub.addEventListener('message', shouldNotHappen) - nodes[0].pubsub.addEventListener('message', receivedMsg) - - const done = defer() - - function receivedMsg(evt: CustomEvent) { - const msg = evt.detail - - expect(uint8ArrayToString(msg.data)).to.startWith('banana') - - if (msg.type !== 'signed') { - throw new Error('unexpected msg type') - } - expect(msg.from.toString()).to.equal(nodes[1].components.peerId.toString()) - expect(msg.sequenceNumber).to.be.a('BigInt') - expect(msg.topic).to.equal(topic) - - if (++counter === 10) { - nodes[0].pubsub.removeEventListener('message', receivedMsg) - nodes[1].pubsub.removeEventListener('message', shouldNotHappen) - done.resolve() - } - } - - await Promise.all( - Array.from({ length: 10 }).map(async (_, i) => { - await nodes[1].pubsub.publish(topic, uint8ArrayFromString(`banana${i}`)) - }) - ) - - await done.promise - }) - }) - - describe('publish after unsubscribe', () => { - const topic = 'Z' - let nodes: GossipSubAndComponents[] - - // Create pubsub nodes - beforeEach(async () => { - mockNetwork.reset() - nodes = await createComponentsArray({ number: 2, init: { allowPublishToZeroPeers: true } }) - await connectAllPubSubNodes(nodes) - - // Create subscriptions - nodes[0].pubsub.subscribe(topic) - nodes[1].pubsub.subscribe(topic) - - // await subscription change and heartbeat - await Promise.all([ - pEvent(nodes[0].pubsub, 'subscription-change'), - pEvent(nodes[1].pubsub, 'subscription-change') - ]) - await Promise.all([ - pEvent(nodes[0].pubsub, 'gossipsub:heartbeat'), - pEvent(nodes[1].pubsub, 'gossipsub:heartbeat') - ]) - }) - - afterEach(async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it('Unsubscribe from a topic', async () => { - nodes[0].pubsub.unsubscribe(topic) - expect(nodes[0].pubsub.getTopics()).to.be.empty() - - const evt = await pEvent<'subscription-change', CustomEvent>( - nodes[1].pubsub, - 'subscription-change' - ) - const { peerId: changedPeerId, subscriptions: changedSubs } = evt.detail - - await pEvent(nodes[1].pubsub, 'gossipsub:heartbeat') - - expect(nodes[1].pubsub.getPeers()).to.have.lengthOf(1) - expect(nodes[1].pubsub.getSubscribers(topic)).to.be.empty() - - expect(changedPeerId.toString()).to.equal(nodes[0].components.peerId.toString()) - expect(changedSubs).to.have.lengthOf(1) - expect(changedSubs[0].topic).to.equal(topic) - expect(changedSubs[0].subscribe).to.equal(false) - }) - - it('Publish to a topic after unsubscribe', async () => { - const promises = [pEvent(nodes[1].pubsub, 'subscription-change'), pEvent(nodes[1].pubsub, 'gossipsub:heartbeat')] - - nodes[0].pubsub.unsubscribe(topic) - - await Promise.all(promises) - - const promise = new Promise((resolve, reject) => { - nodes[0].pubsub.addEventListener('message', reject) - - setTimeout(() => { - nodes[0].pubsub.removeEventListener('message', reject) - resolve() - }, 100) - }) - - await nodes[1].pubsub.publish('Z', uint8ArrayFromString('banana')) - await nodes[0].pubsub.publish('Z', uint8ArrayFromString('banana')) - - try { - await promise - } catch (e) { - expect.fail('message should not be received') - } - }) - }) - - describe('nodes send state on connection', () => { - let nodes: GossipSubAndComponents[] - - // Create pubsub nodes - beforeEach(async () => { - mockNetwork.reset() - nodes = await createComponentsArray({ - number: 2 - }) - - // Make subscriptions prior to new nodes - nodes[0].pubsub.subscribe('Za') - nodes[1].pubsub.subscribe('Zb') - - expect(nodes[0].pubsub.getPeers()).to.be.empty() - expect(nodes[0].pubsub.getTopics()).to.include('Za') - expect(nodes[1].pubsub.getPeers()).to.be.empty() - expect(nodes[1].pubsub.getTopics()).to.include('Zb') - }) - - afterEach(async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it('existing subscriptions are sent upon peer connection', async function () { - this.timeout(5000) - - await Promise.all([ - connectPubsubNodes(nodes[0], nodes[1]), - pEvent(nodes[0].pubsub, 'subscription-change'), - pEvent(nodes[1].pubsub, 'subscription-change') - ]) - - expect(nodes[0].pubsub.getTopics()).to.include('Za') - expect(nodes[1].pubsub.getPeers()).to.have.lengthOf(1) - expect(nodes[1].pubsub.getSubscribers('Za').map((p) => p.toString())).to.include( - nodes[0].components.peerId.toString() - ) - - expect(nodes[1].pubsub.getTopics()).to.include('Zb') - expect(nodes[0].pubsub.getPeers()).to.have.lengthOf(1) - expect(nodes[0].pubsub.getSubscribers('Zb').map((p) => p.toString())).to.include( - nodes[1].components.peerId.toString() - ) - }) - }) - - describe('nodes handle stopping', () => { - let nodes: GossipSubAndComponents[] - - // Create pubsub nodes - beforeEach(async () => { - mockNetwork.reset() - nodes = await createComponentsArray({ - number: 2, - connected: true - }) - }) - - afterEach(async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it("nodes don't have peers after stopped", async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - expect(nodes[0].pubsub.getPeers()).to.be.empty() - expect(nodes[1].pubsub.getPeers()).to.be.empty() - }) - }) -}) diff --git a/packages/pubsub-gossipsub/test/accept-from.spec.ts b/packages/pubsub-gossipsub/test/accept-from.spec.ts deleted file mode 100644 index e1465ecd03..0000000000 --- a/packages/pubsub-gossipsub/test/accept-from.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager' -import type { PeerStore } from '@libp2p/interface/peer-store' -import type { Registrar } from '@libp2p/interface-internal/registrar' -import { expect } from 'aegir/chai' -import sinon from 'sinon' -import { stubInterface } from 'ts-sinon' -import { GossipSub } from '../src/index.js' -import { createPeerId } from './utils/index.js' -import { fastMsgIdFn } from './utils/msgId.js' - -const peerA = '16Uiu2HAmMkH6ZLen2tbhiuNCTZLLvrZaDgufNdT5MPjtC9Hr9YNA' - -describe('Gossipsub acceptFrom', () => { - let gossipsub: GossipSub - let sandbox: sinon.SinonSandbox - let scoreSpy: sinon.SinonSpy<[id: string], number> - - beforeEach(async () => { - sandbox = sinon.createSandbox() - // not able to use fake timers or tests in browser are suspended - // sandbox.useFakeTimers(Date.now()) - - const peerId = await createPeerId() - gossipsub = new GossipSub( - { - peerId, - registrar: stubInterface(), - peerStore: stubInterface(), - connectionManager: stubInterface() - }, - { emitSelf: false, fastMsgIdFn } - ) - - // stubbing PeerScore causes some pending issue in firefox browser environment - // we can only spy it - // using scoreSpy.withArgs("peerA").calledOnce causes the pending issue in firefox - // while spy.getCall() is fine - scoreSpy = sandbox.spy(gossipsub.score, 'score') - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should only white list peer with positive score', () => { - // by default the score is 0 - gossipsub.acceptFrom(peerA) - // 1st time, we have to compute score - expect(scoreSpy.getCall(0).args[0]).to.be.equal(peerA) - expect(scoreSpy.getCall(0).returnValue).to.be.equal(0) - expect(scoreSpy.getCall(1)).to.not.be.ok() - // 2nd time, use a cached score since it's white listed - gossipsub.acceptFrom(peerA) - expect(scoreSpy.getCall(1)).to.not.be.ok() - }) - - it('should recompute score after 1s', async () => { - // by default the score is 0 - gossipsub.acceptFrom(peerA) - // 1st time, we have to compute score - expect(scoreSpy.getCall(0).args[0]).to.be.equal(peerA) - expect(scoreSpy.getCall(1)).to.not.be.ok() - gossipsub.acceptFrom(peerA) - // score is cached - expect(scoreSpy.getCall(1)).to.not.be.ok() - - // after 1s - await new Promise((resolve) => setTimeout(resolve, 1001)) - - gossipsub.acceptFrom(peerA) - expect(scoreSpy.getCall(1).args[0]).to.be.equal(peerA) - expect(scoreSpy.getCall(2)).to.not.be.ok() - }) - - it('should recompute score after max messages accepted', () => { - // by default the score is 0 - gossipsub.acceptFrom(peerA) - // 1st time, we have to compute score - expect(scoreSpy.getCall(0).args[0]).to.be.equal(peerA) - expect(scoreSpy.getCall(1)).to.not.be.ok() - - for (let i = 0; i < 128; i++) { - gossipsub.acceptFrom(peerA) - } - expect(scoreSpy.getCall(1)).to.not.be.ok() - - // max messages reached - gossipsub.acceptFrom(peerA) - expect(scoreSpy.getCall(1).args[0]).to.be.equal(peerA) - expect(scoreSpy.getCall(2)).to.not.be.ok() - }) - - // TODO: run this in a unit test setup - // this causes the test to not finish in firefox environment - // it.skip('should NOT white list peer with negative score', () => { - // // peerB is not white listed since score is negative - // scoreStub.score.withArgs('peerB').returns(-1) - // gossipsub["acceptFrom"]('peerB') - // // 1st time, we have to compute score - // expect(scoreStub.score.withArgs('peerB').calledOnce).to.be.true() - // // 2nd time, still have to compute score since it's NOT white listed - // gossipsub["acceptFrom"]('peerB') - // expect(scoreStub.score.withArgs('peerB').calledTwice).to.be.true() - // }) -}) diff --git a/packages/pubsub-gossipsub/test/allowedTopics.spec.ts b/packages/pubsub-gossipsub/test/allowedTopics.spec.ts deleted file mode 100644 index e1820c23a3..0000000000 --- a/packages/pubsub-gossipsub/test/allowedTopics.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { expect } from 'aegir/chai' -import type { GossipSub } from '../src/index.js' -import { pEvent } from 'p-event' -import { connectAllPubSubNodes, createComponentsArray, type GossipSubAndComponents } from './utils/create-pubsub.js' -import { stop } from '@libp2p/interface/startable' -import { mockNetwork } from '@libp2p/interface-compliance-tests/mocks' - -/* eslint-disable dot-notation */ -describe('gossip / allowedTopics', () => { - let nodes: GossipSubAndComponents[] - - const allowedTopic = 'topic_allowed' - const notAllowedTopic = 'topic_not_allowed' - const allowedTopics = [allowedTopic] - const allTopics = [allowedTopic, notAllowedTopic] - - // Create pubsub nodes - beforeEach(async () => { - mockNetwork.reset() - nodes = await createComponentsArray({ - number: 2, - connected: false, - init: { - allowedTopics - } - }) - }) - - afterEach(async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it('should send gossip to non-mesh peers in topic', async function () { - this.timeout(10 * 1000) - const [nodeA, nodeB] = nodes - - // add subscriptions to each node - for (const topic of allTopics) { - nodeA.pubsub.subscribe(topic) - } - - // every node connected to every other - await Promise.all([ - connectAllPubSubNodes(nodes), - // nodeA should send nodeB all its subscriptions on connection - pEvent(nodeB.pubsub, 'subscription-change') - ]) - - const nodeASubscriptions = Array.from((nodeA.pubsub as GossipSub)['subscriptions'].keys()) - expect(nodeASubscriptions).deep.equals(allTopics, 'nodeA.subscriptions should be subcribed to all') - - const nodeBTopics = Array.from((nodeB.pubsub as GossipSub)['topics'].keys()) - expect(nodeBTopics).deep.equals(allowedTopics, 'nodeB.topics should only contain allowedTopics') - }) -}) diff --git a/packages/pubsub-gossipsub/test/benchmark/index.test.ts b/packages/pubsub-gossipsub/test/benchmark/index.test.ts deleted file mode 100644 index b1440e3047..0000000000 --- a/packages/pubsub-gossipsub/test/benchmark/index.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { itBench, setBenchOpts } from '@dapplion/benchmark' -import type { GossipSub } from '../../src/index.js' -import { - connectPubsubNodes, - createComponentsArray, - denseConnect, - type GossipSubAndComponents -} from '../utils/create-pubsub.js' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { awaitEvents, checkReceivedSubscriptions, checkReceivedSubscription } from '../utils/events.js' -import { expect } from 'aegir/chai' - -describe.only('heartbeat', function () { - this.timeout(0) - setBenchOpts({ - maxMs: 200 * 1000, - minMs: 120 * 1000, - minRuns: 200 - }) - - const topic = 'foobar' - const numTopic = 70 - const numPeers = 50 - const numPeersPerTopic = 30 - let numLoop = 0 - - const getTopic = (i: number): string => { - return topic + i - } - - const getTopicPeerIndices = (topic: number): number[] => { - // peer 0 join all topics - const peers = [0] - // topic 0 starts from index 1 - // topic 1 starts from index 2... - for (let i = 0; i < numPeersPerTopic - 1; i++) { - const peerIndex = (i + topic + 1) % numPeers - if (peerIndex !== 0) peers.push(peerIndex) - } - return peers - } - - /** - * Star topology - * peer 1 - * / - * peer 0 - peer 2 - * \ - * peer 3 - * - * A topic contains peer 0 and some other peers, with numPeersPerTopic = 4 - * - * |Topic| Peers | - * |-----|-----------| - * | 0 | 0, 1, 2, 3| - * | 1 | 0, 2, 3, 4| - */ - itBench({ - id: 'heartbeat', - before: async () => { - const psubs = await createComponentsArray({ - number: numPeers, - init: { - scoreParams: { - IPColocationFactorWeight: 0 - }, - floodPublish: true, - // TODO: why we need to configure this low score - // probably we should tweak topic score params - // is that why we don't have mesh peers? - scoreThresholds: { - gossipThreshold: -10, - publishThreshold: -100, - graylistThreshold: -1000 - } - } - }) - - // build the star - await Promise.all(psubs.slice(1).map((ps) => connectPubsubNodes(psubs[0], ps))) - await Promise.all(psubs.map((ps) => awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - - await denseConnect(psubs) - - // make sure psub 0 has `numPeers - 1` peers - expect(psubs[0].pubsub.getPeers().length).to.be.gte( - numPeers - 1, - `peer 0 should have at least ${numPeers - 1} peers` - ) - - const peerIds = psubs.map((psub) => psub.components.peerId.toString()) - for (let topicIndex = 0; topicIndex < numTopic; topicIndex++) { - const topic = getTopic(topicIndex) - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - const peerIndices = getTopicPeerIndices(topicIndex) - const peerIdsOnTopic = peerIndices.map((peerIndex) => peerIds[peerIndex]) - // peer 0 see all subscriptions from other - const subscription = checkReceivedSubscriptions(psubs[0], peerIdsOnTopic, topic) - // other peers should see the subsription from peer 0 to prevent PublishError.InsufficientPeers error - const otherSubscriptions = peerIndices - .slice(1) - .map((peerIndex) => psubs[peerIndex]) - .map((psub) => checkReceivedSubscription(psub, peerIds[0], topic, 0)) - peerIndices.map((peerIndex) => psubs[peerIndex].pubsub.subscribe(topic)) - await Promise.all([subscription, ...otherSubscriptions]) - } - - // wait for heartbeats to build mesh - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 3))) - - // make sure psubs 0 have at least 10 topic peers and 4 mesh peers for each topic - for (let i = 0; i < numTopic; i++) { - expect((psubs[0].pubsub as GossipSub).getSubscribers(getTopic(i)).length).to.be.gte( - 10, - `psub 0: topic ${i} does not have enough topic peers` - ) - - expect((psubs[0].pubsub as GossipSub).getMeshPeers(getTopic(i)).length).to.be.gte( - 4, - `psub 0: topic ${i} does not have enough mesh peers` - ) - } - - return psubs - }, - beforeEach: async (psubs) => { - numLoop++ - const msg = `its not a flooooood ${numLoop}` - const promises = [] - for (let topicIndex = 0; topicIndex < numTopic; topicIndex++) { - for (const peerIndex of getTopicPeerIndices(topicIndex)) { - promises.push( - psubs[peerIndex].pubsub.publish( - getTopic(topicIndex), - uint8ArrayFromString(psubs[peerIndex].components.peerId.toString() + msg) - ) - ) - } - } - await Promise.all(promises) - - return psubs[0] - }, - fn: (firstPsub: GossipSubAndComponents) => { - ;(firstPsub.pubsub as GossipSub).heartbeat() - } - }) -}) diff --git a/packages/pubsub-gossipsub/test/benchmark/protobuf.test.ts b/packages/pubsub-gossipsub/test/benchmark/protobuf.test.ts deleted file mode 100644 index f9017d354b..0000000000 --- a/packages/pubsub-gossipsub/test/benchmark/protobuf.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { itBench, setBenchOpts } from '@dapplion/benchmark' -import { type IRPC, RPC } from '../../src/message/rpc.js' - -describe('protobuf', function () { - this.timeout(0) - setBenchOpts({ - maxMs: 200 * 1000, - minMs: 120 * 1000, - minRuns: 200 - }) - - const rpc: IRPC = { - subscriptions: [], - messages: [ - { - topic: 'topic1', - // typical Attestation - data: Buffer.from( - 'e40000000a000000000000000a00000000000000a45c8daa336e17a150300afd4c717313c84f291754c51a378f20958083c5fa070a00000000000000a45c8daa336e17a150300afd4c717313c84f291754c51a378f20958083c5fa070a00000000000000a45c8daa336e17a150300afd4c717313c84f291754c51a378f20958083c5fa0795d2ef8ae4e2b4d1e5b3d5ce47b518e3db2c8c4d082e4498805ac2a686c69f248761b78437db2927470c1e77ede9c18606110faacbcbe4f13052bde7f7eff6aab09edf7bc4929fda2230f943aba2c47b6f940d350cb20c76fad4a8d40e2f3f1f01', - 'hex' - ), - signature: Uint8Array.from(Array.from({ length: 96 }, () => 100)) - } - ], - control: undefined - } - - const bytes = RPC.encode(rpc).finish() - - // console.log('@@@ encoded to', Buffer.from(bytes.slice()).toString('hex'), 'length', bytes.length) - - itBench({ - id: 'decode Attestation message using protobufjs', - fn: () => { - RPC.decode(bytes) - }, - runsFactor: 100 - }) - - itBench({ - id: 'encode Attestation message using protobufjs', - fn: () => { - RPC.encode(rpc).finish() - }, - runsFactor: 100 - }) -}) diff --git a/packages/pubsub-gossipsub/test/benchmark/time-cache.test.ts b/packages/pubsub-gossipsub/test/benchmark/time-cache.test.ts deleted file mode 100644 index 1e3291e8b9..0000000000 --- a/packages/pubsub-gossipsub/test/benchmark/time-cache.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { itBench, setBenchOpts } from '@dapplion/benchmark' -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-expect-error no types -import TimeCache from 'time-cache' -import { SimpleTimeCache } from '../../src/utils/time-cache.js' - -// TODO: errors with "Error: root suite not found" -describe('npm TimeCache vs SimpleTimeCache', () => { - setBenchOpts({ - maxMs: 100 * 1000, - minMs: 60 * 1000, - minRuns: 512 - }) - - const iterations = [1_000_000, 4_000_000, 8_000_000, 16_000_000] - const timeCache = new TimeCache({ validity: 1 }) - const simpleTimeCache = new SimpleTimeCache({ validityMs: 1000 }) - - for (const iteration of iterations) { - itBench(`npm TimeCache.put x${iteration}`, () => { - for (let j = 0; j < iteration; j++) timeCache.put(String(j)) - }) - - itBench(`SimpleTimeCache.put x${iteration}`, () => { - for (let j = 0; j < iteration; j++) simpleTimeCache.put(String(j), true) - }) - } -}) diff --git a/packages/pubsub-gossipsub/test/compliance.spec.ts b/packages/pubsub-gossipsub/test/compliance.spec.ts deleted file mode 100644 index 92621df9bf..0000000000 --- a/packages/pubsub-gossipsub/test/compliance.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Libp2pEvents } from '@libp2p/interface' -import tests from '@libp2p/interface-compliance-tests/pubsub' -import { EventEmitter } from '@libp2p/interface/events' -import { PersistentPeerStore } from '@libp2p/peer-store' -import { MemoryDatastore } from 'datastore-core' -import { GossipSub } from '../src/index.js' - -describe.skip('interface compliance', function () { - this.timeout(3000) - - tests({ - async setup(args) { - if (args == null) { - throw new Error('PubSubOptions is required') - } - - const pubsub = new GossipSub( - { - ...args.components, - peerStore: new PersistentPeerStore({ - peerId: args.components.peerId, - datastore: new MemoryDatastore(), - events: new EventEmitter() - }) - }, - { - ...args.init, - // libp2p-interfaces-compliance-tests in test 'can subscribe and unsubscribe correctly' publishes to no peers - // Disable check to allow passing tests - allowPublishToZeroPeers: true - } - ) - - return pubsub - }, - - async teardown() { - // - } - }) -}) diff --git a/packages/pubsub-gossipsub/test/decodeRpc.spec.ts b/packages/pubsub-gossipsub/test/decodeRpc.spec.ts deleted file mode 100644 index 745d0c4739..0000000000 --- a/packages/pubsub-gossipsub/test/decodeRpc.spec.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { expect } from 'aegir/chai' -import { decodeRpc, type DecodeRPCLimits, defaultDecodeRpcLimits } from '../src/message/decodeRpc.js' -import { RPC, type IRPC } from '../src/message/index.js' - -describe('decodeRpc', () => { - const topicID = 'topic' - const msgID = new Uint8Array(8) - - const subscription: RPC.ISubOpts = { subscribe: true, topic: topicID } - const message: RPC.IMessage = { topic: topicID, data: new Uint8Array(100) } - const peerInfo: RPC.IPeerInfo = { peerID: msgID, signedPeerRecord: msgID } - const ihave: RPC.IControlIHave = { topicID, messageIDs: [msgID] } - const iwant: RPC.IControlIWant = { messageIDs: [msgID] } - const graft: RPC.IControlGraft = { topicID } - const prune: RPC.IControlPrune = { topicID, peers: [peerInfo] } - - describe('decode correctness', () => { - it('Should decode full RPC', () => { - const rpc: IRPC = { - subscriptions: [subscription, subscription], - messages: [message, message], - control: { - ihave: [ihave, ihave], - iwant: [iwant, iwant], - graft: [graft, graft], - prune: [prune, prune] - } - } - - const bytes = RPC.encode(rpc).finish() - - // Compare as JSON - expect(RPC.fromObject(decodeRpc(bytes, defaultDecodeRpcLimits)).toJSON()).deep.equals(RPC.decode(bytes).toJSON()) - }) - }) - - describe('decode limits', () => { - const decodeRpcLimits: DecodeRPCLimits = { - maxSubscriptions: 2, - maxMessages: 2, - maxControlMessages: 2, - maxIhaveMessageIDs: 3, - maxIwantMessageIDs: 3, - maxPeerInfos: 3 - } - - // Check no mutations on limits - const limitsAfter = { ...decodeRpcLimits } - - after('decodeRpcLimits has not been mutated', () => { - expect(limitsAfter).deep.equals(decodeRpcLimits) - }) - - const rpcEmpty: IRPC = { - subscriptions: [], - messages: [], - control: { - ihave: [], - iwant: [], - graft: [], - prune: [] - } - } - - const rpcEmptyBytes = RPC.encode(rpcEmpty).finish() - - it('limit subscriptions.length', () => { - // Decode a fresh instance to allow safe mutations - const rpc = RPC.decode(rpcEmptyBytes) - rpc.subscriptions = [subscription, subscription, subscription] - expect(endecode(rpc).subscriptions).length(decodeRpcLimits.maxSubscriptions) - }) - - it('limit messages.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - rpc.messages = [message, message, message] - expect(endecode(rpc).messages).length(decodeRpcLimits.maxMessages) - }) - - it('limit control.ihave.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - rpc.control = { ihave: [ihave, ihave, ihave] } - expect(endecode(rpc).control?.ihave).length(decodeRpcLimits.maxControlMessages) - }) - - it('limit control.iwant.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - rpc.control = { iwant: [iwant, iwant, iwant] } - expect(endecode(rpc).control?.iwant).length(decodeRpcLimits.maxControlMessages) - }) - - it('limit control.graft.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - rpc.control = { graft: [graft, graft, graft] } - expect(endecode(rpc).control?.graft).length(decodeRpcLimits.maxControlMessages) - }) - - it('limit control.prune.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - rpc.control = { prune: [prune, prune, prune] } - expect(endecode(rpc).control?.prune).length(decodeRpcLimits.maxControlMessages) - }) - - it('limit ihave.messageIDs.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - // Limit to 3 items total, 2 (all) on the first one, 1 on the second one - rpc.control = { ihave: [{ messageIDs: [msgID, msgID] }, { messageIDs: [msgID, msgID] }] } - expect(decodeRpcLimits.maxIhaveMessageIDs).equals(3, 'Wrong maxIhaveMessageIDs') - expect(endecode(rpc).control?.ihave?.[0].messageIDs).length(2, 'Wrong ihave?.[0].messageIDs len') - expect(endecode(rpc).control?.ihave?.[1].messageIDs).length(1, 'Wrong ihave?.[1].messageIDs len') - }) - - it('limit iwant.messageIDs.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - // Limit to 3 items total, 2 (all) on the first one, 1 on the second one - rpc.control = { iwant: [{ messageIDs: [msgID, msgID] }, { messageIDs: [msgID, msgID] }] } - expect(decodeRpcLimits.maxIwantMessageIDs).equals(3, 'Wrong maxIwantMessageIDs') - expect(endecode(rpc).control?.iwant?.[0].messageIDs).length(2, 'Wrong iwant?.[0].messageIDs len') - expect(endecode(rpc).control?.iwant?.[1].messageIDs).length(1, 'Wrong iwant?.[1].messageIDs len') - }) - - it('limit prune.peers.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - // Limit to 3 items total, 2 (all) on the first one, 1 on the second one - rpc.control = { prune: [{ peers: [peerInfo, peerInfo] }, { peers: [peerInfo, peerInfo] }] } - expect(decodeRpcLimits.maxPeerInfos).equals(3, 'Wrong maxPeerInfos') - expect(endecode(rpc).control?.prune?.[0].peers).length(2, 'Wrong prune?.[0].peers len') - expect(endecode(rpc).control?.prune?.[1].peers).length(1, 'Wrong prune?.[1].peers len') - }) - - function endecode(rpc: IRPC): IRPC { - return decodeRpc(RPC.encode(rpc).finish(), decodeRpcLimits) - } - }) -}) diff --git a/packages/pubsub-gossipsub/test/e2e/go-gossipsub.spec.ts b/packages/pubsub-gossipsub/test/e2e/go-gossipsub.spec.ts deleted file mode 100644 index 643f554335..0000000000 --- a/packages/pubsub-gossipsub/test/e2e/go-gossipsub.spec.ts +++ /dev/null @@ -1,1324 +0,0 @@ -import { expect } from 'aegir/chai' -import delay from 'delay' -import pRetry from 'p-retry' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import type { GossipSub } from '../../src/index.js' -import { GossipsubD } from '../../src/constants.js' -import { fastMsgIdFn } from '../utils/index.js' -import { type Message, TopicValidatorResult } from '@libp2p/interface/pubsub' -import type { IRPC, RPC } from '../../src/message/rpc.js' -import type { Libp2pEvents } from '@libp2p/interface' -import pWaitFor from 'p-wait-for' -import { - sparseConnect, - denseConnect, - connectSome, - createComponentsArray, - createComponents, - connectPubsubNodes, - type GossipSubAndComponents -} from '../utils/create-pubsub.js' -import { FloodSub } from '@libp2p/floodsub' -import { mockNetwork } from '@libp2p/interface-compliance-tests/mocks' -import { stop } from '@libp2p/interface/startable' -import type { TopicScoreParams } from '../../src/score/peer-score-params.js' -import { awaitEvents, checkReceivedSubscription, checkReceivedSubscriptions } from '../utils/events.js' - -/** - * These tests were translated from: - * https://github.com/libp2p/go-libp2p-pubsub/blob/master/gossipsub_test.go - */ - -/** - * Given a topic and data (and debug metadata -- sender index and msg index) - * Return a function (takes a gossipsub (and receiver index)) - * that returns a Promise that awaits the message being received - * and checks that the received message equals the given message - */ -const checkReceivedMessage = - (topic: string, data: Uint8Array, senderIx: number, msgIx: number) => - async (node: GossipSubAndComponents, receiverIx: number) => - await new Promise((resolve, reject) => { - const t = setTimeout(() => { - node.pubsub.removeEventListener('message', cb) - reject(new Error(`Message never received, sender ${senderIx}, receiver ${receiverIx}, index ${msgIx}`)) - }, 60000) - const cb = (evt: CustomEvent) => { - const msg = evt.detail - - if (msg.topic !== topic) { - return - } - - if (uint8ArrayEquals(data, msg.data)) { - clearTimeout(t) - node.pubsub.removeEventListener('message', cb) - resolve() - } - } - node.pubsub.addEventListener('message', cb) - }) - -describe('go-libp2p-pubsub gossipsub tests', function () { - // In Github runners it takes ~10sec the longest test - this.timeout(120 * 1000) - this.retries(3) - - let psubs: GossipSubAndComponents[] - - beforeEach(() => { - mockNetwork.reset() - }) - - afterEach(async () => { - await stop(...psubs.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it('test sparse gossipsub', async function () { - // Create 20 gossipsub nodes - // Subscribe to the topic, all nodes - // Sparsely connect the nodes - // Publish 100 messages, each from a random node - // Assert that subscribed nodes receive the message - psubs = await createComponentsArray({ - number: 20, - init: { - floodPublish: false, - scoreParams: { - IPColocationFactorThreshold: 20, - behaviourPenaltyWeight: 0 - } - } - }) - const topic = 'foobar' - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - - await sparseConnect(psubs) - - // wait for heartbeats to build mesh - await Promise.all(psubs.map(async (ps) => awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - - const sendRecv = [] - for (let i = 0; i < 100; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - - const owner = Math.floor(Math.random() * psubs.length) - const results = Promise.all( - psubs.filter((psub, j) => j !== owner).map(checkReceivedMessage(topic, msg, owner, i)) - ) - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - }) - - it('test dense gossipsub', async function () { - // Create 20 gossipsub nodes - // Subscribe to the topic, all nodes - // Densely connect the nodes - // Publish 100 messages, each from a random node - // Assert that subscribed nodes receive the message - psubs = await createComponentsArray({ - number: 20, - init: { - floodPublish: false, - scoreParams: { - IPColocationFactorThreshold: 20, - behaviourPenaltyWeight: 0 - } - } - }) - const topic = 'foobar' - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - - await denseConnect(psubs) - - // wait for heartbeats to build mesh - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - - const sendRecv = [] - for (let i = 0; i < 100; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = Math.floor(Math.random() * psubs.length) - const results = Promise.all( - psubs.filter((psub, j) => j !== owner).map(checkReceivedMessage(topic, msg, owner, i)) - ) - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - }) - - it('test gossipsub fanout', async function () { - // Create 20 gossipsub nodes - // Subscribe to the topic, all nodes except the first - // Densely connect the nodes - // Publish 100 messages, each from the first node - // Assert that subscribed nodes receive the message - // Subscribe to the topic, first node - // Publish 100 messages, each from the first node - // Assert that subscribed nodes receive the message - psubs = await createComponentsArray({ - number: 20, - init: { - floodPublish: false, - scoreParams: { - IPColocationFactorThreshold: 20, - behaviourPenaltyWeight: 0 - } - } - }) - const topic = 'foobar' - const promises = psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2)) - psubs.slice(1).forEach((ps) => ps.pubsub.subscribe(topic)) - - await denseConnect(psubs) - - // wait for heartbeats to build mesh - await Promise.all(promises) - - let sendRecv = [] - for (let i = 0; i < 100; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - - const owner = 0 - - const results = Promise.all(psubs.slice(1).map(checkReceivedMessage(topic, msg, owner, i))) - await psubs[owner].pubsub.publish(topic, msg) - await results - } - // await Promise.all(sendRecv) - - psubs[0].pubsub.subscribe(topic) - - // wait for a heartbeat - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 1))) - - sendRecv = [] - for (let i = 0; i < 100; i++) { - const msg = uint8ArrayFromString(`2nd - ${i} its not a flooooood ${i}`) - - const owner = 0 - - const results = Promise.all( - psubs - .slice(1) - .filter((psub, j) => j !== owner) - .map(checkReceivedMessage(topic, msg, owner, i)) - ) - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - }) - - it('test gossipsub fanout maintenance', async function () { - // Create 20 gossipsub nodes - // Subscribe to the topic, all nodes except the first - // Densely connect the nodes - // Publish 100 messages, each from the first node - // Assert that subscribed nodes receive the message - // Unsubscribe to the topic, all nodes except the first - // Resubscribe to the topic, all nodes except the first - // Publish 100 messages, each from the first node - // Assert that the subscribed nodes receive the message - psubs = await createComponentsArray({ - number: 20, - init: { - floodPublish: false, - scoreParams: { - IPColocationFactorThreshold: 20, - behaviourPenaltyWeight: 0 - } - } - }) - const promises = psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2)) - const topic = 'foobar' - psubs.slice(1).forEach((ps) => ps.pubsub.subscribe(topic)) - - await denseConnect(psubs) - - // wait for heartbeats to build mesh - await Promise.all(promises) - let sendRecv: Array> = [] - const sendMessages = async (time: number) => { - for (let i = 0; i < 100; i++) { - const msg = uint8ArrayFromString(`${time} ${i} its not a flooooood ${i}`) - - const owner = 0 - - const results = Promise.all( - psubs - .slice(1) - .filter((psub, j) => j !== owner) - .map(checkReceivedMessage(topic, msg, owner, i)) - ) - await psubs[owner].pubsub.publish(topic, msg) - sendRecv.push(results) - } - } - await sendMessages(1) - await Promise.all(sendRecv) - - psubs.slice(1).forEach((ps) => ps.pubsub.unsubscribe(topic)) - - // wait for heartbeats - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - psubs.slice(1).forEach((ps) => ps.pubsub.subscribe(topic)) - - // wait for heartbeats - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - sendRecv = [] - await sendMessages(2) - await Promise.all(sendRecv) - }) - - it('test gossipsub fanout expiry', async function () { - // Create 10 gossipsub nodes - // Subscribe to the topic, all nodes except the first - // Densely connect the nodes - // Publish 5 messages, each from the first node - // Assert that the subscribed nodes receive every message - // Assert that the first node has fanout peers - // Wait until fanout expiry - // Assert that the first node has no fanout - psubs = await createComponentsArray({ - number: 10, - init: { - scoreParams: { - IPColocationFactorThreshold: 20, - behaviourPenaltyWeight: 0 - }, - floodPublish: false, - fanoutTTL: 1000 - } - }) - const promises = psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2)) - const topic = 'foobar' - psubs.slice(1).forEach((ps) => ps.pubsub.subscribe(topic)) - - await denseConnect(psubs) - - // wait for heartbeats to build mesh - await Promise.all(promises) - - const sendRecv = [] - for (let i = 0; i < 5; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - - const owner = 0 - - const results = Promise.all( - psubs.filter((psub, j) => j !== owner).map(checkReceivedMessage(topic, msg, owner, i)) - ) - await psubs[owner].pubsub.publish(topic, msg) - sendRecv.push(results) - } - await Promise.all(sendRecv) - - expect((psubs[0].pubsub as GossipSub).fanout).to.not.be.empty() - - await pWaitFor(async () => { - return (psubs[0].pubsub as GossipSub).fanout.size === 0 - }) - }) - - it('test gossipsub gossip', async function () { - // Create 20 gossipsub nodes - // Subscribe to the topic, all nodes - // Densely connect the nodes - // Publish 100 messages, each from a random node - // Assert that the subscribed nodes receive the message - // Wait a bit between each message so gossip can be interleaved - psubs = await createComponentsArray({ - number: 20, - init: { - scoreParams: { - IPColocationFactorThreshold: 20, - behaviourPenaltyWeight: 0 - } - } - }) - const promises = psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2)) - const topic = 'foobar' - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - - await denseConnect(psubs) - - // wait for heartbeats to build mesh - await Promise.all(promises) - - for (let i = 0; i < 100; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = Math.floor(Math.random() * psubs.length) - const results = Promise.all( - psubs.filter((psub, j) => j !== owner).map(checkReceivedMessage(topic, msg, owner, i)) - ) - await psubs[owner].pubsub.publish(topic, msg) - await results - // wait a bit to have some gossip interleaved - await delay(100) - } - // and wait for some gossip flushing - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - }) - - it('test gossipsub gossip propagation', async function () { - // Create 20 gossipsub nodes - // Split into two groups, just a single node shared between - // Densely connect each group to itself - // Subscribe to the topic, first group minus the shared node - // Publish 10 messages, each from the shared node - // Assert that the first group receives the messages - // Subscribe to the topic, second group minus the shared node - // Assert that the second group receives the messages (via gossip) - psubs = await createComponentsArray({ - number: 20, - init: { - floodPublish: false, - scoreParams: { - IPColocationFactorThreshold: 20, - behaviourPenaltyWeight: 0 - } - } - }) - const topic = 'foobar' - const group1 = psubs.slice(0, GossipsubD + 1) - const group2 = psubs.slice(GossipsubD + 1) - group2.unshift(psubs[0]) - - await denseConnect(group1) - await denseConnect(group2) - - group1.slice(1).forEach((ps) => ps.pubsub.subscribe(topic)) - - // wait for heartbeats to build mesh - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 3))) - - const sendRecv: Array> = [] - for (let i = 0; i < 10; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = 0 - const results = Promise.all(group1.slice(1).map(checkReceivedMessage(topic, msg, owner, i))) - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - - await delay(100) - - psubs.slice(GossipsubD + 1).forEach((ps) => ps.pubsub.subscribe(topic)) - - const received: Message[][] = Array.from({ length: psubs.length - (GossipsubD + 1) }, () => []) - const results = Promise.all( - group2.slice(1).map( - async (ps, ix) => - new Promise((resolve, reject) => { - const t = setTimeout(() => reject(new Error('Timed out')), 10000) - ps.pubsub.addEventListener('message', (e: CustomEvent) => { - if (e.detail.topic !== topic) { - return - } - - received[ix].push(e.detail) - if (received[ix].length >= 10) { - clearTimeout(t) - resolve() - } - }) - }) - ) - ) - - await results - }) - - it('test gossipsub prune', async function () { - // Create 20 gossipsub nodes - // Subscribe to the topic, all nodes - // Densely connect nodes - // Unsubscribe to the topic, first 5 nodes - // Publish 100 messages, each from a random node - // Assert that the subscribed nodes receive every message - psubs = await createComponentsArray({ - number: 20, - init: { - scoreParams: { - IPColocationFactorThreshold: 20 - } - } - }) - const topic = 'foobar' - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - - await denseConnect(psubs) - - // wait for heartbeats to build mesh - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - - // disconnect some peers from the mesh to get some PRUNEs - psubs.slice(0, 5).forEach((ps) => ps.pubsub.unsubscribe(topic)) - - // wait a bit to take effect - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - - const sendRecv: Array> = [] - for (let i = 0; i < 100; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = Math.floor(Math.random() * psubs.length) - const results = Promise.all( - psubs - .slice(5) - .filter((psub, j) => j + 5 !== owner) - .map(checkReceivedMessage(topic, msg, owner, i)) - ) - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - }) - - it('test gossipsub graft', async function () { - // Create 20 gossipsub nodes - // Sparsely connect nodes - // Subscribe to the topic, all nodes, waiting for each subscription to propagate first - // Publish 100 messages, each from a random node - // Assert that the subscribed nodes receive every message - psubs = await createComponentsArray({ - number: 20, - init: { - scoreParams: { - IPColocationFactorThreshold: 20, - behaviourPenaltyWeight: 0 - } - } - }) - const topic = 'foobar' - - await sparseConnect(psubs) - - for (const ps of psubs) { - ps.pubsub.subscribe(topic) - // wait for announce to propagate - await delay(100) - } - - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - - const sendRecv = [] - for (let i = 0; i < 100; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = Math.floor(Math.random() * psubs.length) - const results = Promise.all( - psubs.filter((psub, j) => j !== owner).map(checkReceivedMessage(topic, msg, owner, i)) - ) - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - }) - - it('test gossipsub remove peer', async function () { - // Create 20 gossipsub nodes - // Subscribe to the topic, all nodes - // Densely connect nodes - // Stop 5 nodes - // Publish 100 messages, each from a random still-started node - // Assert that the subscribed nodes receive every message - psubs = await createComponentsArray({ - number: 20, - init: { - scoreParams: { - IPColocationFactorThreshold: 20 - } - } - }) - const topic = 'foobar' - - await denseConnect(psubs) - - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - - // wait for heartbeats to build mesh - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - - // disconnect some peers to exercise _removePeer paths - afterEach(async () => { - await stop( - ...psubs - .slice(0, 5) - .reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), []) - ) - }) - - const sendRecv = [] - for (let i = 0; i < 100; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = Math.floor(Math.random() * (psubs.length - 5)) - const results = Promise.all( - psubs - .slice(5) - .filter((psub, j) => j !== owner) - .map(checkReceivedMessage(topic, msg, owner, i)) - ) - sendRecv.push(psubs.slice(5)[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - }) - - it('test gossipsub graft prune retry', async function () { - // Create 10 gossipsub nodes - // Densely connect nodes - // Subscribe to 35 topics, all nodes - // Publish a message from each topic, each from a random node - // Assert that the subscribed nodes receive every message - psubs = await createComponentsArray({ - number: 10, - init: { - scoreParams: { - IPColocationFactorThreshold: 20 - } - } - }) - const topic = 'foobar' - - await denseConnect(psubs) - - for (let i = 0; i < 35; i++) { - psubs.forEach((ps) => ps.pubsub.subscribe(`${topic}${i}`)) - } - - // wait for heartbeats to build mesh - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 9))) - - for (let i = 0; i < 35; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = Math.floor(Math.random() * psubs.length) - const results = Promise.all( - psubs.filter((psub, j) => j !== owner).map(checkReceivedMessage(`${topic}${i}`, msg, owner, i)) - ) - await psubs[owner].pubsub.publish(`${topic}${i}`, msg) - await delay(20) - await results - } - }) - - it.skip('test gossipsub control piggyback', async function () { - // Create 10 gossipsub nodes - // Densely connect nodes - // Subscribe to a 'flood' topic, all nodes - // Publish 10k messages on the flood topic, each from a random node, in the background - // Subscribe to 5 topics, all nodes - // Wait for the flood to stop - // Publish a message to each topic, each from a random node - // Assert that subscribed nodes receive each message - // Publish a message from each topic, each from a random node - // Assert that the subscribed nodes receive every message - psubs = await createComponentsArray({ - number: 10, - init: { - scoreParams: { - IPColocationFactorThreshold: 20, - behaviourPenaltyWeight: 0 - } - } - }) - const topic = 'foobar' - - await denseConnect(psubs) - - const floodTopic = 'flood' - psubs.forEach((ps) => ps.pubsub.subscribe(floodTopic)) - - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 1))) - - // create a background flood of messages that overloads the queues - const floodOwner = Math.floor(Math.random() * psubs.length) - const floodMsg = uint8ArrayFromString('background flooooood') - const backgroundFlood = Promise.resolve().then(async () => { - for (let i = 0; i < 10000; i++) { - await psubs[floodOwner].pubsub.publish(floodTopic, floodMsg) - } - }) - - await delay(20) - - // and subscribe to a bunch of topics in the meantime -- this should - // result in some dropped control messages, with subsequent piggybacking - // in the background flood - for (let i = 0; i < 5; i++) { - psubs.forEach((ps) => ps.pubsub.subscribe(`${topic}${i}`)) - } - - // wait for the flood to stop - await backgroundFlood - - // and test that we have functional overlays - const sendRecv: Array> = [] - for (let i = 0; i < 5; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = Math.floor(Math.random() * psubs.length) - const results = Promise.all( - psubs.filter((psub, j) => j !== owner).map(checkReceivedMessage(`${topic}${i}`, msg, owner, i)) - ) - await psubs[owner].pubsub.publish(`${topic}${i}`, msg) - sendRecv.push(results) - } - await Promise.all(sendRecv) - }) - - it('test mixed gossipsub', async function () { - // Create 20 gossipsub nodes - // Create 10 floodsub nodes - // Subscribe to the topic, all nodes - // Sparsely connect nodes - // Publish 100 messages, each from a random node - // Assert that the subscribed nodes receive every message - const gsubs: GossipSubAndComponents[] = await createComponentsArray({ - number: 20, - init: { - scoreParams: { - IPColocationFactorThreshold: 20, - behaviourPenaltyWeight: 0 - }, - fastMsgIdFn - } - }) - const fsubs = await createComponentsArray({ - number: 10, - pubsub: FloodSub - }) - psubs = gsubs.concat(fsubs) - - const topic = 'foobar' - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - - await sparseConnect(psubs) - - // wait for heartbeats to build mesh - await Promise.all(gsubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - - const sendRecv = [] - for (let i = 0; i < 100; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = Math.floor(Math.random() * psubs.length) - const results = Promise.all( - psubs.filter((psub, j) => j !== owner).map(checkReceivedMessage(topic, msg, owner, i)) - ) - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - }) - - it('test gossipsub multihops', async function () { - // Create 6 gossipsub nodes - // Connect nodes in a line (eg: 0 -> 1 -> 2 -> 3 ...) - // Subscribe to the topic, all nodes - // Publish a message from node 0 - // Assert that the last node receives the message - const numPeers = 6 - psubs = await createComponentsArray({ - number: numPeers, - init: { - scoreParams: { - IPColocationFactorThreshold: 20, - behaviourPenaltyWeight: 0 - } - } - }) - const topic = 'foobar' - - for (let i = 0; i < numPeers - 1; i++) { - await connectPubsubNodes(psubs[i], psubs[i + 1]) - } - const peerIdStrsByIdx: string[][] = [] - for (let i = 0; i < numPeers; i++) { - if (i === 0) { - // first - peerIdStrsByIdx[i] = [psubs[i + 1].components.peerId.toString()] - } else if (i > 0 && i < numPeers - 1) { - // middle - peerIdStrsByIdx[i] = [psubs[i + 1].components.peerId.toString(), psubs[i - 1].components.peerId.toString()] - } else if (i === numPeers - 1) { - // last - peerIdStrsByIdx[i] = [psubs[i - 1].components.peerId.toString()] - } - } - - const subscriptionPromises = psubs.map( - async (psub, i) => await checkReceivedSubscriptions(psub, peerIdStrsByIdx[i], topic) - ) - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - - // wait for heartbeats to build mesh - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - await Promise.all(subscriptionPromises) - - const msg = uint8ArrayFromString(`${0} its not a flooooood ${0}`) - const owner = 0 - const results = checkReceivedMessage(topic, msg, owner, 0)(psubs[5], 5) - await psubs[owner].pubsub.publish(topic, msg) - await results - }) - - it('test gossipsub tree topology', async function () { - // Create 10 gossipsub nodes - // Connect nodes in a tree, diagram below - // Subscribe to the topic, all nodes - // Assert that the nodes are peered appropriately - // Publish two messages, one from either end of the tree - // Assert that the subscribed nodes receive every message - psubs = await createComponentsArray({ - number: 10, - init: { - scoreParams: { - IPColocationFactorThreshold: 20 - } - } - }) - const topic = 'foobar' - - /* - [0] -> [1] -> [2] -> [3] - | L->[4] - v - [5] -> [6] -> [7] - | - v - [8] -> [9] - */ - const treeTopology = [ - [1, 5], // 0 - [2, 4], // 1 - [3], // 2 - [], // 3 leaf - [], // 4 leaf - [6, 8], // 5 - [7], // 6 - [], // 7 leaf - [9], // 8 - [] // 9 leaf - ] - for (let from = 0; from < treeTopology.length; from++) { - for (const to of treeTopology[from]) { - await connectPubsubNodes(psubs[from], psubs[to]) - } - } - - const getPeerIdStrs = (idx: number): string[] => { - const outbounds = treeTopology[idx] - const inbounds = [] - for (let i = 0; i < treeTopology.length; i++) { - if (treeTopology[i].includes(idx)) inbounds.push(i) - } - return Array.from(new Set([...inbounds, ...outbounds])).map((i) => psubs[i].components.peerId.toString()) - } - - const subscriptionPromises = psubs.map( - async (psub, i) => await checkReceivedSubscriptions(psub, getPeerIdStrs(i), topic) - ) - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - - // wait for heartbeats to build mesh - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - await Promise.all(subscriptionPromises) - - expect(psubs[0].pubsub.getPeers().map((s) => s.toString())).to.have.members([ - psubs[1].components.peerId.toString(), - psubs[5].components.peerId.toString() - ]) - expect(psubs[1].pubsub.getPeers().map((s) => s.toString())).to.have.members([ - psubs[0].components.peerId.toString(), - psubs[2].components.peerId.toString(), - psubs[4].components.peerId.toString() - ]) - expect(psubs[2].pubsub.getPeers().map((s) => s.toString())).to.have.members([ - psubs[1].components.peerId.toString(), - psubs[3].components.peerId.toString() - ]) - - const sendRecv = [] - for (const owner of [9, 3]) { - const msg = uint8ArrayFromString(`${owner} its not a flooooood ${owner}`) - const results = Promise.all( - psubs.filter((psub, j) => j !== owner).map(checkReceivedMessage(topic, msg, owner, owner)) - ) - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - }) - - it('test gossipsub star topology with signed peer records', async function () { - // Create 20 gossipsub nodes with lower degrees - // Connect nodes to a center node, with the center having very low degree - // Subscribe to the topic, all nodes - // Assert that all nodes have > 1 connection - // Publish one message per node - // Assert that the subscribed nodes receive every message - psubs = await createComponentsArray({ - number: 20, - init: { - scoreThresholds: { - acceptPXThreshold: 0 - }, - scoreParams: { - IPColocationFactorThreshold: 20 - }, - doPX: true, - D: 4, - Dhi: 5, - Dlo: 3, - Dscore: 3, - prunePeers: 5 - } - }) - - // configure the center of the star with very low D - ;(psubs[0].pubsub as GossipSub).opts.D = 0 - ;(psubs[0].pubsub as GossipSub).opts.Dhi = 0 - ;(psubs[0].pubsub as GossipSub).opts.Dlo = 0 - ;(psubs[0].pubsub as GossipSub).opts.Dscore = 0 - - // build the star - await Promise.all(psubs.slice(1).map((ps) => connectPubsubNodes(psubs[0], ps))) - await Promise.all(psubs.map((ps) => awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - - // build the mesh - const topic = 'foobar' - const peerIdStrs = psubs.map((psub) => psub.components.peerId.toString()) - const subscriptionPromise = checkReceivedSubscriptions(psubs[0], peerIdStrs, topic) - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - - // wait a bit for the mesh to build - await Promise.all(psubs.map((ps) => awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 15, 25000))) - await subscriptionPromise - - // check that all peers have > 1 connection - psubs.forEach((ps) => { - expect(ps.components.connectionManager.getConnections().length).to.be.gt(1) - }) - - // send a message from each peer and assert it was propagated - const sendRecv = [] - for (let i = 0; i < psubs.length; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = i - const results = Promise.all( - psubs.filter((psub, j) => j !== owner).map(checkReceivedMessage(topic, msg, owner, i)) - ) - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - }) - - it('test gossipsub direct peers', async function () { - // Create 3 gossipsub nodes - // 2 and 3 with direct peer connections with each other - // Connect nodes: 2 <- 1 -> 3 - // Assert that the nodes are connected - // Subscribe to the topic, all nodes - // Publish a message from each node - // Assert that all nodes receive the messages - // Disconnect peers - // Assert peers reconnect - // Publish a message from each node - // Assert that all nodes receive the messages - psubs = await Promise.all([ - createComponents({ - init: { - scoreParams: { - IPColocationFactorThreshold: 20 - }, - fastMsgIdFn, - directConnectTicks: 2 - } - }), - createComponents({ - init: { - scoreParams: { - IPColocationFactorThreshold: 20 - }, - fastMsgIdFn, - directConnectTicks: 2 - } - }), - createComponents({ - init: { - scoreParams: { - IPColocationFactorThreshold: 20 - }, - fastMsgIdFn - } - }) - ]) - ;(psubs[1].pubsub as GossipSub).direct.add(psubs[2].components.peerId.toString()) - await connectPubsubNodes(psubs[1], psubs[2]) - ;(psubs[2].pubsub as GossipSub).direct.add(psubs[1].components.peerId.toString()) - await connectPubsubNodes(psubs[2], psubs[1]) - - // each peer connects to 2 other peers - await connectPubsubNodes(psubs[0], psubs[1]) - await connectPubsubNodes(psubs[0], psubs[2]) - - const topic = 'foobar' - const peerIdStrs = psubs.map((libp2p) => libp2p.components.peerId.toString()) - let subscriptionPromises = psubs.map((libp2ps) => checkReceivedSubscriptions(libp2ps, peerIdStrs, topic)) - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - await Promise.all(psubs.map((ps) => awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 1))) - await Promise.all(subscriptionPromises) - - let sendRecv = [] - for (let i = 0; i < 3; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = i - const results = Promise.all(psubs.filter((_, j) => j !== owner).map(checkReceivedMessage(topic, msg, owner, i))) - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - - const connectPromises = [1, 2].map((i) => awaitEvents(psubs[i].components.events, 'peer:connect', 1)) - // disconnect the direct peers to test reconnection - // need more time to disconnect/connect/send subscriptions again - subscriptionPromises = [ - checkReceivedSubscription(psubs[1], peerIdStrs[2], topic, 2, 10000), - checkReceivedSubscription(psubs[2], peerIdStrs[1], topic, 1, 10000) - ] - await psubs[1].components.connectionManager.closeConnections(psubs[2].components.peerId) - // TODO remove when https://github.com/libp2p/js-libp2p-interfaces/pull/268 is merged - await psubs[2].components.connectionManager.closeConnections(psubs[1].components.peerId) - - await Promise.all(psubs.map((ps) => awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 5))) - await Promise.all(connectPromises) - await Promise.all(subscriptionPromises) - expect(psubs[1].components.connectionManager.getConnections(psubs[2].components.peerId)).to.not.be.empty() - - sendRecv = [] - for (let i = 0; i < 3; i++) { - const msg = uint8ArrayFromString(`2nd - ${i} its not a flooooood ${i}`) - const owner = i - const results = Promise.all( - psubs.filter((psub, j) => j !== owner).map(checkReceivedMessage(topic, msg, owner, i)) - ) - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - }) - - it('test gossipsub flood publish', async function () { - // Create 30 gossipsub nodes - // Connect in star topology - // Subscribe to the topic, all nodes - // Publish 20 messages, each from the center node - // Assert that the other nodes receive the message - const numPeers = 30 - psubs = await createComponentsArray({ - number: numPeers, - init: { - scoreParams: { - IPColocationFactorThreshold: 30 - } - } - }) - - await Promise.all( - psubs.slice(1).map(async (ps) => { - return await connectPubsubNodes(psubs[0], ps) - }) - ) - - const owner = 0 - const psub0 = psubs[owner] - const peerIdStrs = psubs.filter((_, j) => j !== owner).map((psub) => psub.components.peerId.toString()) - // build the (partial, unstable) mesh - const topic = 'foobar' - const subscriptionPromise = checkReceivedSubscriptions(psub0, peerIdStrs, topic) - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 1))) - await subscriptionPromise - - // send messages from the star and assert they were received - const sendRecv = [] - for (let i = 0; i < 20; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const results = Promise.all( - psubs.filter((psub, j) => j !== owner).map(checkReceivedMessage(topic, msg, owner, i)) - ) - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - sendRecv.push(results) - } - await Promise.all(sendRecv) - }) - - it('test gossipsub negative score', async function () { - // Create 20 gossipsub nodes, with scoring params to quickly lower node 0's score - // Connect densely - // Subscribe to the topic, all nodes - // Publish 20 messages, each from a different node, collecting all received messages - // Assert that nodes other than 0 should not receive any messages from node 0 - psubs = await createComponentsArray({ - number: 20, - init: { - scoreParams: { - IPColocationFactorThreshold: 30, - appSpecificScore: (p) => (p === psubs[0].components.peerId.toString() ? -1000 : 0), - decayInterval: 1000, - decayToZero: 0.01 - }, - scoreThresholds: { - gossipThreshold: -10, - publishThreshold: -100, - graylistThreshold: -1000 - }, - fastMsgIdFn - } - }) - - await denseConnect(psubs) - - const topic = 'foobar' - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 3))) - - psubs.slice(1).forEach((ps) => - ps.pubsub.addEventListener('message', (evt) => { - if (evt.detail.type !== 'signed') { - throw new Error('unexpected message type') - } - expect(evt.detail.from.equals(psubs[0].components.peerId)).to.be.false() - }) - ) - - const sendRecv = [] - for (let i = 0; i < 20; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = i - sendRecv.push(psubs[owner].pubsub.publish(topic, msg)) - } - await Promise.all(sendRecv) - - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - }) - - it('test gossipsub score validator ex', async function () { - // Create 3 gossipsub nodes - // Connect fully - // Register a topic validator on node 0: ignore 1, reject 2 - // Subscribe to the topic, node 0 - // Publish 2 messages, from 1 and 2 - // Assert that 0 received neither message - // Assert that 1's score is 0, 2's score is negative - const topic = 'foobar' - psubs = await createComponentsArray({ - number: 3, - init: { - scoreParams: { - topics: { - [topic]: { - topicWeight: 1, - timeInMeshQuantum: 1000, - invalidMessageDeliveriesWeight: -1, - invalidMessageDeliveriesDecay: 0.9999, - timeInMeshWeight: 0, - timeInMeshCap: 0, - firstMessageDeliveriesWeight: 0, - firstMessageDeliveriesDecay: 0, - firstMessageDeliveriesCap: 0, - meshMessageDeliveriesWeight: 0, - meshMessageDeliveriesDecay: 0, - meshMessageDeliveriesCap: 0, - meshMessageDeliveriesThreshold: 0, - meshMessageDeliveriesWindow: 0, - meshMessageDeliveriesActivation: 0, - meshFailurePenaltyWeight: 0, - meshFailurePenaltyDecay: 0 - } - } - } - } - }) - - await connectPubsubNodes(psubs[0], psubs[1]) - await connectPubsubNodes(psubs[1], psubs[2]) - await connectPubsubNodes(psubs[0], psubs[2]) - ;(psubs[0].pubsub as GossipSub).topicValidators.set(topic, async (propagationSource, m) => { - if (propagationSource.equals(psubs[1].components.peerId)) return TopicValidatorResult.Ignore - if (propagationSource.equals(psubs[2].components.peerId)) return TopicValidatorResult.Reject - throw Error('Unknown PeerId') - }) - - psubs[0].pubsub.subscribe(topic) - - await delay(200) - - psubs[0].pubsub.addEventListener('message', () => expect.fail('node 0 should not receive any messages')) - - const msg = uint8ArrayFromString('its not a flooooood') - await psubs[1].pubsub.publish(topic, msg) - const msg2 = uint8ArrayFromString('2nd - its not a flooooood') - await psubs[2].pubsub.publish(topic, msg2) - - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 2))) - - expect((psubs[0].pubsub as GossipSub).score.score(psubs[1].components.peerId.toString())).to.be.eql(0) - expect((psubs[0].pubsub as GossipSub).score.score(psubs[2].components.peerId.toString())).to.be.lt(0) - }) - - it('test gossipsub piggyback control', async function () { - psubs = await createComponentsArray({ number: 2 }) - const otherId = psubs[1].components.peerId.toString() - const psub = psubs[0].pubsub as GossipSub - - const topic1 = 'topic_1' - const topic2 = 'topic_2' - const topic3 = 'topic_3' - psub.mesh.set(topic1, new Set([otherId])) - psub.mesh.set(topic2, new Set()) - - const rpc: IRPC = { - subscriptions: [], - messages: [] - } - - const toGraft = (topicID: string): RPC.IControlGraft => ({ topicID }) - const toPrune = (topicID: string): RPC.IControlPrune => ({ topicID, peers: [] }) - - psub.piggybackControl(otherId, rpc, { - graft: [toGraft(topic1), toGraft(topic2), toGraft(topic3)], - prune: [toPrune(topic1), toPrune(topic2), toPrune(topic3)], - ihave: [], - iwant: [] - }) - - const expectedRpc: IRPC = { - subscriptions: [], - messages: [], - control: { - graft: [toGraft(topic1)], - prune: [toPrune(topic2), toPrune(topic3)] - } - } - - expect(rpc).deep.equals(expectedRpc) - - await psub.stop() - }) - - it('test gossipsub opportunistic grafting', async function () { - // Create 20 nodes - // 6 real gossip nodes, 14 'sybil' nodes, unresponsive nodes - // Connect some of the real nodes - // Connect every sybil to every real node - // Subscribe to the topic, all real nodes - // Publish 300 messages from the real nodes - // Wait for opgraft - // Assert the real peer meshes have at least 2 honest peers - const topic = 'test' - psubs = await createComponentsArray({ - number: 20, - init: { - scoreParams: { - IPColocationFactorThreshold: 50, - decayToZero: 0.01, - topics: { - [topic]: { - topicWeight: 1, - timeInMeshWeight: 0.00002777, - timeInMeshQuantum: 1000, - timeInMeshCap: 3600, - firstMessageDeliveriesWeight: 100, - firstMessageDeliveriesDecay: 0.99997, - firstMessageDeliveriesCap: 1000, - meshMessageDeliveriesWeight: 0, - invalidMessageDeliveriesDecay: 0.99997 - } as TopicScoreParams - } - }, - scoreThresholds: { - gossipThreshold: -10, - publishThreshold: -100, - graylistThreshold: -10000, - opportunisticGraftThreshold: 1 - } - } - }) - const promises = psubs.map((ps) => awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 1)) - const real = psubs.slice(0, 6) - const sybils = psubs.slice(6) - - const connectPromises = real.map( - async (psub) => await awaitEvents(psub.components.events, 'peer:connect', 3) - ) - await connectSome(real, 5) - await Promise.all(connectPromises) - sybils.forEach((s) => { - ;(s.pubsub as GossipSub).handleReceivedRpc = async function () { - // - } - }) - - for (let i = 0; i < sybils.length; i++) { - for (let j = 0; j < real.length; j++) { - await connectPubsubNodes(sybils[i], real[j]) - } - } - - await Promise.all(promises) - - const realPeerIdStrs = real.map((psub) => psub.components.peerId.toString()) - const subscriptionPromises = real.map((psub) => { - const waitingPeerIdStrs = Array.from(psub.pubsub.getPeers().values()) - .map((p) => p.toString()) - .filter((peerId) => realPeerIdStrs.includes(peerId.toString())) - return checkReceivedSubscriptions(psub, waitingPeerIdStrs, topic) - }) - psubs.forEach((ps) => ps.pubsub.subscribe(topic)) - await Promise.all(subscriptionPromises) - - for (let i = 0; i < 300; i++) { - const msg = uint8ArrayFromString(`${i} its not a flooooood ${i}`) - const owner = i % real.length - await psubs[owner].pubsub.publish(topic, msg) - } - - // now wait for opgraft cycles - await Promise.all(psubs.map(async (ps) => await awaitEvents(ps.pubsub, 'gossipsub:heartbeat', 7))) - - // check the honest node meshes, they should have at least 3 honest peers each - const realPeerIds = real.map((r) => r.components.peerId.toString()) - - await pRetry( - async () => { - for (const r of real) { - const meshPeers = (r.pubsub as GossipSub).mesh.get(topic) - - if (meshPeers == null) { - throw new Error('meshPeers was null') - } - - let count = 0 - realPeerIds.forEach((p) => { - if (meshPeers.has(p)) { - count++ - } - }) - - if (count < 2) { - await delay(100) - throw new Error('Count was less than 3') - } - } - }, - { retries: 10 } - ) - }) -}) diff --git a/packages/pubsub-gossipsub/test/floodsub.spec.ts b/packages/pubsub-gossipsub/test/floodsub.spec.ts deleted file mode 100644 index d9225e8567..0000000000 --- a/packages/pubsub-gossipsub/test/floodsub.spec.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { expect } from 'aegir/chai' -import delay from 'delay' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { pEvent } from 'p-event' -import type { SubscriptionChangeData, Message } from '@libp2p/interface/pubsub' -import pRetry from 'p-retry' -import { connectPubsubNodes, createComponents, type GossipSubAndComponents } from './utils/create-pubsub.js' -import { FloodSub } from '@libp2p/floodsub' -import { stop } from '@libp2p/interface/startable' -import { mockNetwork } from '@libp2p/interface-compliance-tests/mocks' - -describe('gossipsub fallbacks to floodsub', () => { - describe('basics', () => { - let nodeGs: GossipSubAndComponents - let nodeFs: GossipSubAndComponents - - beforeEach(async () => { - mockNetwork.reset() - - nodeGs = await createComponents({ - init: { - fallbackToFloodsub: true - } - }) - nodeFs = await createComponents({ - pubsub: FloodSub - }) - }) - - afterEach(async () => { - await stop( - ...[nodeGs, nodeFs].reduce( - (acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), - [] - ) - ) - mockNetwork.reset() - }) - - it('Dial event happened from nodeGs to nodeFs', async () => { - await connectPubsubNodes(nodeGs, nodeFs) - - await pRetry(() => { - expect(nodeGs.pubsub.getPeers().map((s) => s.toString())).to.include(nodeFs.components.peerId.toString()) - expect(nodeFs.pubsub.getPeers().map((s) => s.toString())).to.include(nodeGs.components.peerId.toString()) - }) - }) - }) - - describe.skip('should not be added if fallback disabled', () => { - let nodeGs: GossipSubAndComponents - let nodeFs: GossipSubAndComponents - - beforeEach(async () => { - mockNetwork.reset() - nodeGs = await createComponents({ - init: { - fallbackToFloodsub: false - } - }) - nodeFs = await createComponents({ - pubsub: FloodSub - }) - }) - - afterEach(async () => { - await stop( - ...[nodeGs, nodeFs].reduce( - (acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), - [] - ) - ) - mockNetwork.reset() - }) - - it('Dial event happened from nodeGs to nodeFs, but nodeGs does not support floodsub', async () => { - try { - await connectPubsubNodes(nodeGs, nodeFs) - expect.fail('Dial should not have succeed') - } catch (err) { - expect((err as { code: string }).code).to.be.equal('ERR_UNSUPPORTED_PROTOCOL') - } - }) - }) - - describe('subscription functionality', () => { - let nodeGs: GossipSubAndComponents - let nodeFs: GossipSubAndComponents - - before(async () => { - mockNetwork.reset() - nodeGs = await createComponents({ - init: { - fallbackToFloodsub: true - } - }) - nodeFs = await createComponents({ - pubsub: FloodSub - }) - - await connectPubsubNodes(nodeGs, nodeFs) - }) - - afterEach(async () => { - await stop( - ...[nodeGs, nodeFs].reduce((acc, curr) => { - acc.push(curr.pubsub, ...Object.entries(curr.components)) - - return acc - }, []) - ) - mockNetwork.reset() - }) - - it('Subscribe to a topic', async function () { - this.timeout(10000) - const topic = 'Z' - nodeGs.pubsub.subscribe(topic) - nodeFs.pubsub.subscribe(topic) - - // await subscription change - const [evt] = await Promise.all([ - pEvent<'subscription-change', CustomEvent>(nodeGs.pubsub, 'subscription-change'), - pEvent<'subscription-change', CustomEvent>(nodeFs.pubsub, 'subscription-change') - ]) - const { peerId: changedPeerId, subscriptions: changedSubs } = evt.detail - - expect(nodeGs.pubsub.getTopics()).to.include(topic) - expect(nodeFs.pubsub.getTopics()).to.include(topic) - expect(nodeGs.pubsub.getPeers()).to.have.lengthOf(1) - expect(nodeFs.pubsub.getPeers()).to.have.lengthOf(1) - expect(nodeGs.pubsub.getSubscribers(topic).map((p) => p.toString())).to.include( - nodeFs.components.peerId.toString() - ) - expect(nodeFs.pubsub.getSubscribers(topic).map((p) => p.toString())).to.include( - nodeGs.components.peerId.toString() - ) - - expect(nodeGs.pubsub.getPeers().map((p) => p.toString())).to.include(changedPeerId.toString()) - expect(changedSubs).to.have.lengthOf(1) - expect(changedSubs[0].topic).to.equal(topic) - expect(changedSubs[0].subscribe).to.equal(true) - }) - }) - - describe('publish functionality', () => { - let nodeGs: GossipSubAndComponents - let nodeFs: GossipSubAndComponents - const topic = 'Z' - - beforeEach(async () => { - mockNetwork.reset() - nodeGs = await createComponents({ - init: { - fallbackToFloodsub: true - } - }) - nodeFs = await createComponents({ - pubsub: FloodSub - }) - - await connectPubsubNodes(nodeGs, nodeFs) - - nodeGs.pubsub.subscribe(topic) - nodeFs.pubsub.subscribe(topic) - - // await subscription change - await Promise.all([pEvent(nodeGs.pubsub, 'subscription-change'), pEvent(nodeFs.pubsub, 'subscription-change')]) - }) - - afterEach(async () => { - await stop( - ...[nodeGs, nodeFs].reduce( - (acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), - [] - ) - ) - mockNetwork.reset() - }) - - it('Publish to a topic - nodeGs', async () => { - const promise = pEvent<'message', CustomEvent>(nodeFs.pubsub, 'message') - const data = uint8ArrayFromString('hey') - - await nodeGs.pubsub.publish(topic, data) - - const evt = await promise - if (evt.detail.type !== 'signed') { - throw new Error('unexpected message type') - } - expect(evt.detail.data).to.equalBytes(data) - expect(evt.detail.from.toString()).to.be.eql(nodeGs.components.peerId.toString()) - }) - - it('Publish to a topic - nodeFs', async () => { - const promise = pEvent<'message', CustomEvent>(nodeGs.pubsub, 'message') - const data = uint8ArrayFromString('banana') - - await nodeFs.pubsub.publish(topic, data) - - const evt = await promise - if (evt.detail.type !== 'signed') { - throw new Error('unexpected message type') - } - expect(evt.detail.data).to.equalBytes(data) - expect(evt.detail.from.toString()).to.be.eql(nodeFs.components.peerId.toString()) - }) - }) - - describe('publish after unsubscribe', () => { - let nodeGs: GossipSubAndComponents - let nodeFs: GossipSubAndComponents - const topic = 'Z' - - beforeEach(async () => { - mockNetwork.reset() - nodeGs = await createComponents({ - init: { - fallbackToFloodsub: true - } - }) - nodeFs = await createComponents({ - pubsub: FloodSub - }) - - await connectPubsubNodes(nodeGs, nodeFs) - - nodeGs.pubsub.subscribe(topic) - nodeFs.pubsub.subscribe(topic) - - // await subscription change - await Promise.all([pEvent(nodeGs.pubsub, 'subscription-change'), pEvent(nodeFs.pubsub, 'subscription-change')]) - // allow subscriptions to propagate to the other peer - await delay(10) - }) - - afterEach(async () => { - await stop( - ...[nodeGs, nodeFs].reduce( - (acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), - [] - ) - ) - mockNetwork.reset() - }) - - it('Unsubscribe from a topic', async () => { - const promise = pEvent<'subscription-change', CustomEvent>( - nodeFs.pubsub, - 'subscription-change' - ) - - nodeGs.pubsub.unsubscribe(topic) - expect(nodeGs.pubsub.getTopics()).to.be.empty() - - const evt = await promise - const { peerId: changedPeerId, subscriptions: changedSubs } = evt.detail - - expect(nodeFs.pubsub.getPeers()).to.have.lengthOf(1) - expect(nodeFs.pubsub.getSubscribers(topic)).to.be.empty() - expect(nodeFs.pubsub.getPeers().map((p) => p.toString())).to.include(changedPeerId.toString()) - expect(changedSubs).to.have.lengthOf(1) - expect(changedSubs[0].topic).to.equal(topic) - expect(changedSubs[0].subscribe).to.equal(false) - }) - - it('Publish to a topic after unsubscribe', async () => { - nodeGs.pubsub.unsubscribe(topic) - await pEvent(nodeFs.pubsub, 'subscription-change') - - const promise = new Promise((resolve, reject) => { - nodeGs.pubsub.addEventListener('message', reject, { - once: true - }) - setTimeout(() => { - nodeGs.pubsub.removeEventListener('message', reject) - resolve() - }, 100) - }) - - await nodeFs.pubsub.publish(topic, uint8ArrayFromString('banana')) - await nodeGs.pubsub.publish(topic, uint8ArrayFromString('banana')) - - try { - await promise - } catch (e) { - expect.fail('message should not be received') - } - }) - }) -}) diff --git a/packages/pubsub-gossipsub/test/gossip.spec.ts b/packages/pubsub-gossipsub/test/gossip.spec.ts deleted file mode 100644 index 6e80ba8c1d..0000000000 --- a/packages/pubsub-gossipsub/test/gossip.spec.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { expect } from 'aegir/chai' -import sinon, { type SinonStubbedInstance } from 'sinon' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { GossipsubDhi } from '../src/constants.js' -import { GossipSub } from '../src/index.js' -import { pEvent } from 'p-event' -import { connectAllPubSubNodes, createComponentsArray, type GossipSubAndComponents } from './utils/create-pubsub.js' -import { stop } from '@libp2p/interface/startable' -import { mockNetwork } from '@libp2p/interface-compliance-tests/mocks' -import { stubInterface } from 'ts-sinon' -import type { Registrar } from '@libp2p/interface-internal/registrar' -import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import type { PeerStore } from '@libp2p/interface/peer-store' -import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager' - -describe('gossip', () => { - let nodes: GossipSubAndComponents[] - - // Create pubsub nodes - beforeEach(async () => { - mockNetwork.reset() - nodes = await createComponentsArray({ - number: GossipsubDhi + 2, - connected: false, - init: { - scoreParams: { - IPColocationFactorThreshold: GossipsubDhi + 3 - }, - maxInboundDataLength: 4000000, - allowPublishToZeroPeers: false - } - }) - }) - - afterEach(async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it('should send gossip to non-mesh peers in topic', async function () { - this.timeout(10e4) - const nodeA = nodes[0] - const topic = 'Z' - - const subscriptionPromises = nodes.map(async (n) => await pEvent(n.pubsub, 'subscription-change')) - // add subscriptions to each node - nodes.forEach((n) => n.pubsub.subscribe(topic)) - - // every node connected to every other - await connectAllPubSubNodes(nodes) - - // wait for subscriptions to be transmitted - await Promise.all(subscriptionPromises) - - // await mesh rebalancing - await Promise.all(nodes.map(async (n) => await pEvent(n.pubsub, 'gossipsub:heartbeat'))) - - // set spy. NOTE: Forcing private property to be public - const nodeASpy = nodeA.pubsub as Partial as SinonStubbedInstance<{ - pushGossip: GossipSub['pushGossip'] - }> - sinon.spy(nodeASpy, 'pushGossip') - - await nodeA.pubsub.publish(topic, uint8ArrayFromString('hey')) - - // gossip happens during the heartbeat - await pEvent(nodeA.pubsub, 'gossipsub:heartbeat') - - const mesh = (nodeA.pubsub as GossipSub).mesh.get(topic) - - if (mesh == null) { - throw new Error('No mesh for topic') - } - - nodeASpy.pushGossip - .getCalls() - .map((call) => call.args[0]) - .forEach((peerId) => { - expect(mesh).to.not.include(peerId) - }) - - // unset spy - nodeASpy.pushGossip.restore() - }) - - it('Should allow publishing to zero peers if flag is passed', async function () { - this.timeout(10e4) - const nodeA = nodes[0] - const topic = 'Z' - - const publishResult = await nodeA.pubsub.publish(topic, uint8ArrayFromString('hey'), { - allowPublishToZeroPeers: true - }) - - // gossip happens during the heartbeat - await pEvent(nodeA.pubsub, 'gossipsub:heartbeat') - - // should have sent message to peerB - expect(publishResult.recipients).to.deep.equal([]) - }) - - it('should reject incoming messages bigger than maxInboundDataLength limit', async function () { - this.timeout(10e4) - const nodeA = nodes[0] - const nodeB = nodes[1] - - const twoNodes = [nodeA, nodeB] - const topic = 'Z' - const subscriptionPromises = twoNodes.map(async (n) => await pEvent(n.pubsub, 'subscription-change')) - // add subscriptions to each node - twoNodes.forEach((n) => n.pubsub.subscribe(topic)) - - // every node connected to every other - await connectAllPubSubNodes(twoNodes) - - // wait for subscriptions to be transmitted - await Promise.all(subscriptionPromises) - - // await mesh rebalancing - await Promise.all(twoNodes.map(async (n) => await pEvent(n.pubsub, 'gossipsub:heartbeat'))) - - // set spy. NOTE: Forcing private property to be public - const nodeBSpy = nodeB.pubsub as Partial as SinonStubbedInstance<{ - handlePeerReadStreamError: GossipSub['handlePeerReadStreamError'] - }> - sinon.spy(nodeBSpy, 'handlePeerReadStreamError') - - // This should lead to handlePeerReadStreamError at nodeB - await nodeA.pubsub.publish(topic, new Uint8Array(5000000)) - await pEvent(nodeA.pubsub, 'gossipsub:heartbeat') - const expectedError = nodeBSpy.handlePeerReadStreamError.getCalls()[0]?.args[0] - expect(expectedError !== undefined && (expectedError as unknown as { code: string }).code, 'ERR_MSG_DATA_TOO_LONG') - - // unset spy - nodeBSpy.handlePeerReadStreamError.restore() - }) - - it('should send piggyback control into other sent messages', async function () { - this.timeout(10e4) - const nodeA = nodes[0] - const topic = 'Z' - - const promises = nodes.map(async (n) => await pEvent(n.pubsub, 'subscription-change')) - // add subscriptions to each node - nodes.forEach((n) => n.pubsub.subscribe(topic)) - - // every node connected to every other - await connectAllPubSubNodes(nodes) - - // wait for subscriptions to be transmitted - await Promise.all(promises) - - // await nodeA mesh rebalancing - await pEvent(nodeA.pubsub, 'gossipsub:heartbeat') - - const mesh = (nodeA.pubsub as GossipSub).mesh.get(topic) - - if (mesh == null) { - throw new Error('No mesh for topic') - } - - if (mesh.size === 0) { - throw new Error('Topic mesh was empty') - } - - const peerB = Array.from(mesh)[0] - - if (peerB == null) { - throw new Error('Could not get peer from mesh') - } - - // should have peerB as a subscriber to the topic - expect(nodeA.pubsub.getSubscribers(topic).map((p) => p.toString())).to.include( - peerB, - "did not know about peerB's subscription to topic" - ) - - // should be able to send them messages - expect((nodeA.pubsub as GossipSub).streamsOutbound.has(peerB)).to.be.true( - 'nodeA did not have connection open to peerB' - ) - - // set spy. NOTE: Forcing private property to be public - const nodeASpy = sinon.spy(nodeA.pubsub as GossipSub, 'piggybackControl') - // manually add control message to be sent to peerB - const graft = { ihave: [], iwant: [], graft: [{ topicID: topic }], prune: [] } - ;(nodeA.pubsub as GossipSub).control.set(peerB, graft) - ;(nodeA.pubsub as GossipSub).gossip.set(peerB, []) - - const publishResult = await nodeA.pubsub.publish(topic, uint8ArrayFromString('hey')) - - // should have sent message to peerB - expect(publishResult.recipients.map((p) => p.toString())).to.include(peerB, 'did not send pubsub message to peerB') - - // wait until spy is called - const startTime = Date.now() - while (Date.now() - startTime < 5000) { - if (nodeASpy.callCount > 0) break - } - - expect(nodeASpy.callCount).to.be.equal(1) - // expect control message to be sent alongside published message - const call = nodeASpy.getCalls()[0] - expect(call).to.have.deep.nested.property('args[1].control.graft', graft.graft) - - // unset spy - nodeASpy.restore() - }) - - it('should allow configuring stream limits', async () => { - const maxInboundStreams = 7 - const maxOutboundStreams = 5 - - const registrar = stubInterface() - - const pubsub = new GossipSub( - { - peerId: await createEd25519PeerId(), - registrar, - peerStore: stubInterface(), - connectionManager: stubInterface() - }, - { - maxInboundStreams, - maxOutboundStreams - } - ) - - await pubsub.start() - - expect(registrar.register.called).to.be.true() - expect(registrar.handle.getCall(0)).to.have.nested.property('args[2].maxInboundStreams', maxInboundStreams) - expect(registrar.handle.getCall(0)).to.have.nested.property('args[2].maxOutboundStreams', maxOutboundStreams) - - await pubsub.stop() - }) -}) diff --git a/packages/pubsub-gossipsub/test/heartbeat.spec.ts b/packages/pubsub-gossipsub/test/heartbeat.spec.ts deleted file mode 100644 index 2a044f830d..0000000000 --- a/packages/pubsub-gossipsub/test/heartbeat.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { expect } from 'aegir/chai' -import { GossipsubHeartbeatInterval } from '../src/constants.js' -import { pEvent } from 'p-event' -import { createComponents, type GossipSubAndComponents } from './utils/create-pubsub.js' -import { stop } from '@libp2p/interface/startable' -import { mockNetwork } from '@libp2p/interface-compliance-tests/mocks' - -describe('heartbeat', () => { - let node: GossipSubAndComponents - - before(async () => { - mockNetwork.reset() - node = await createComponents({ - init: { - emitSelf: true - } - }) - }) - - after(() => { - stop(node.pubsub, ...Object.entries(node.components)) - mockNetwork.reset() - }) - - it('should occur with regularity defined by a constant', async function () { - this.timeout(GossipsubHeartbeatInterval * 5) - - await pEvent(node.pubsub, 'gossipsub:heartbeat') - const t1 = Date.now() - - await pEvent(node.pubsub, 'gossipsub:heartbeat') - const t2 = Date.now() - - const safeFactor = 1.5 - expect(t2 - t1).to.be.lt(GossipsubHeartbeatInterval * safeFactor) - }) -}) diff --git a/packages/pubsub-gossipsub/test/mesh.spec.ts b/packages/pubsub-gossipsub/test/mesh.spec.ts deleted file mode 100644 index dd6bfbe5bf..0000000000 --- a/packages/pubsub-gossipsub/test/mesh.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { expect } from 'aegir/chai' -import delay from 'delay' -import { GossipsubDhi } from '../src/constants.js' -import type { GossipSub } from '../src/index.js' -import { connectAllPubSubNodes, createComponentsArray, type GossipSubAndComponents } from './utils/create-pubsub.js' -import { stop } from '@libp2p/interface/startable' -import { mockNetwork } from '@libp2p/interface-compliance-tests/mocks' -import { pEvent } from 'p-event' - -describe('mesh overlay', () => { - let nodes: GossipSubAndComponents[] - - // Create pubsub nodes - beforeEach(async () => { - mockNetwork.reset() - nodes = await createComponentsArray({ - number: GossipsubDhi + 2, - connected: false, - init: { - scoreParams: { - IPColocationFactorThreshold: GossipsubDhi + 3 - } - } - }) - }) - - afterEach(async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it('should add mesh peers below threshold', async function () { - this.timeout(10e3) - - // test against node0 - const node0 = nodes[0] - const topic = 'Z' - - // add subscriptions to each node - nodes.forEach((node) => node.pubsub.subscribe(topic)) - - // connect N (< GossipsubD) nodes to node0 - const N = 4 - await connectAllPubSubNodes(nodes.slice(0, N + 1)) - - await delay(50) - // await mesh rebalancing - await new Promise((resolve) => - (node0.pubsub as GossipSub).addEventListener('gossipsub:heartbeat', resolve, { - once: true - }) - ) - - const mesh = (node0.pubsub as GossipSub).mesh.get(topic) - expect(mesh).to.have.property('size', N) - }) - - it('should remove mesh peers once above threshold', async function () { - this.timeout(10e4) - // test against node0 - const node0 = nodes[0] - const topic = 'Z' - - // add subscriptions to each node - nodes.forEach((node) => node.pubsub.subscribe(topic)) - - await connectAllPubSubNodes(nodes) - - // await mesh rebalancing - await pEvent(node0.pubsub, 'gossipsub:heartbeat') - - const mesh = (node0.pubsub as GossipSub).mesh.get(topic) - expect(mesh).to.have.property('size').that.is.lte(GossipsubDhi) - }) -}) diff --git a/packages/pubsub-gossipsub/test/message-cache.spec.ts b/packages/pubsub-gossipsub/test/message-cache.spec.ts deleted file mode 100644 index 265f1a5961..0000000000 --- a/packages/pubsub-gossipsub/test/message-cache.spec.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { expect } from 'aegir/chai' -import { messageIdToString } from '../src/utils/messageIdToString.js' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { MessageCache } from '../src/message-cache.js' -import * as utils from '@libp2p/pubsub/utils' -import { getMsgId } from './utils/index.js' -import type { RPC } from '../src/message/rpc.js' -import type { MessageId } from '../src/types.js' - -const toMessageId = (msgId: Uint8Array): MessageId => { - return { - msgId, - msgIdStr: messageIdToString(msgId) - } -} - -describe('Testing Message Cache Operations', () => { - const messageCache = new MessageCache(3, 5, messageIdToString) - const testMessages: RPC.IMessage[] = [] - const topic = 'test' - const getGossipIDs = (mcache: MessageCache, topic: string): Uint8Array[] => { - const gossipIDsByTopic = mcache.getGossipIDs(new Set([topic])) - return gossipIDsByTopic.get(topic) ?? [] - } - - before(async () => { - const makeTestMessage = (n: number): RPC.IMessage => { - return { - from: new Uint8Array(0), - data: uint8ArrayFromString(n.toString()), - seqno: uint8ArrayFromString(utils.randomSeqno().toString(16).padStart(16, '0'), 'base16'), - topic - } - } - - for (let i = 0; i < 60; i++) { - testMessages.push(makeTestMessage(i)) - } - - for (let i = 0; i < 10; i++) { - messageCache.put(toMessageId(getMsgId(testMessages[i])), testMessages[i], true) - } - }) - - it('Should retrieve correct messages for each test message', () => { - for (let i = 0; i < 10; i++) { - const messageId = getMsgId(testMessages[i]) - const message = messageCache.get(messageId) - expect(message).to.equal(testMessages[i]) - } - }) - - it('Get GossipIDs', () => { - const gossipIDs = getGossipIDs(messageCache, topic) - expect(gossipIDs.length).to.equal(10) - - for (let i = 0; i < 10; i++) { - const messageID = getMsgId(testMessages[i]) - expect(messageID).to.deep.equal(gossipIDs![i]) - } - }) - - it('Shift message cache', async () => { - messageCache.shift() - for (let i = 10; i < 20; i++) { - messageCache.put(toMessageId(getMsgId(testMessages[i])), testMessages[i], true) - } - - for (let i = 0; i < 20; i++) { - const messageID = getMsgId(testMessages[i]) - const message = messageCache.get(messageID) - expect(message).to.equal(testMessages[i]) - } - - let gossipIDs = getGossipIDs(messageCache, topic) - expect(gossipIDs.length).to.equal(20) - - for (let i = 0; i < 10; i++) { - const messageID = getMsgId(testMessages[i]) - expect(messageID).to.deep.equal(gossipIDs[10 + i]) - } - - for (let i = 10; i < 20; i++) { - const messageID = getMsgId(testMessages[i]) - expect(messageID).to.deep.equal(gossipIDs[i - 10]) - } - - messageCache.shift() - for (let i = 20; i < 30; i++) { - messageCache.put(toMessageId(getMsgId(testMessages[i])), testMessages[i], true) - } - - messageCache.shift() - for (let i = 30; i < 40; i++) { - messageCache.put(toMessageId(getMsgId(testMessages[i])), testMessages[i], true) - } - - messageCache.shift() - for (let i = 40; i < 50; i++) { - messageCache.put(toMessageId(getMsgId(testMessages[i])), testMessages[i], true) - } - - messageCache.shift() - for (let i = 50; i < 60; i++) { - messageCache.put(toMessageId(getMsgId(testMessages[i])), testMessages[i], true) - } - - expect(messageCache.msgs.size).to.equal(50) - - for (let i = 0; i < 10; i++) { - const messageID = getMsgId(testMessages[i]) - const message = messageCache.get(messageID) - expect(message).to.be.an('undefined') - } - - for (let i = 10; i < 60; i++) { - const messageID = getMsgId(testMessages[i]) - const message = messageCache.get(messageID) - expect(message).to.equal(testMessages[i]) - } - - gossipIDs = getGossipIDs(messageCache, topic) - expect(gossipIDs.length).to.equal(30) - - for (let i = 0; i < 10; i++) { - const messageID = getMsgId(testMessages[50 + i]) - expect(messageID).to.deep.equal(gossipIDs[i]) - } - - for (let i = 10; i < 20; i++) { - const messageID = getMsgId(testMessages[30 + i]) - expect(messageID).to.deep.equal(gossipIDs[i]) - } - - for (let i = 20; i < 30; i++) { - const messageID = getMsgId(testMessages[10 + i]) - expect(messageID).to.deep.equal(gossipIDs[i]) - } - }) - - it('should not gossip not-validated message ids', () => { - let gossipIDs = getGossipIDs(messageCache, topic) - while (gossipIDs.length > 0) { - messageCache.shift() - gossipIDs = getGossipIDs(messageCache, topic) - } - expect(gossipIDs.length).to.be.equal(0) - - for (let i = 10; i < 20; i++) { - // 5 last messages are not validated - const validated = i < 15 - messageCache.put(toMessageId(getMsgId(testMessages[i])), testMessages[i], validated) - } - - gossipIDs = getGossipIDs(messageCache, topic) - expect(gossipIDs.length).to.be.equal(5) - // only validate the new gossip ids - for (let i = 0; i < 5; i++) { - expect(gossipIDs[i]).to.deep.equal(getMsgId(testMessages[i + 10]), 'incorrect gossip message id ' + i) - } - }) -}) diff --git a/packages/pubsub-gossipsub/test/peer-score-params.spec.ts b/packages/pubsub-gossipsub/test/peer-score-params.spec.ts deleted file mode 100644 index cdafa67299..0000000000 --- a/packages/pubsub-gossipsub/test/peer-score-params.spec.ts +++ /dev/null @@ -1,503 +0,0 @@ -import { expect } from 'aegir/chai' -import { - createTopicScoreParams, - validateTopicScoreParams, - createPeerScoreParams, - validatePeerScoreParams -} from '../src/score/index.js' -import * as constants from '../src/constants.js' - -describe('TopicScoreParams validation', () => { - it('should not throw on default TopicScoreParams', () => { - expect(() => validateTopicScoreParams(createTopicScoreParams({}))).to.not.throw() - }) - it('should throw on invalid TopicScoreParams', () => { - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - topicWeight: -1 - }) - ), - 'topicWeight must be >= 0' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshWeight: -1, - timeInMeshQuantum: 1000 - }) - ), - 'timeInMeshWeight must be positive (or 0 to disable)' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshWeight: 1, - timeInMeshQuantum: -1 - }) - ), - 'timeInMeshQuantum must be positive' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshWeight: 1, - timeInMeshQuantum: 1000, - timeInMeshCap: -1 - }) - ), - 'timeInMeshCap must be positive' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - firstMessageDeliveriesWeight: -1 - }) - ), - 'firstMessageDeliveriesWeight must be positive (or 0 to disable)' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - firstMessageDeliveriesWeight: 1, - firstMessageDeliveriesDecay: -1 - }) - ), - 'firstMessageDeliveriesDecay must be between 0 and 1' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - firstMessageDeliveriesWeight: 1, - firstMessageDeliveriesDecay: 2 - }) - ), - 'firstMessageDeliveriesDecay must be between 0 and 1' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - firstMessageDeliveriesWeight: 1, - firstMessageDeliveriesDecay: 0.5, - firstMessageDeliveriesCap: -1 - }) - ), - 'firstMessageDeliveriesCap must be positive' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - meshMessageDeliveriesWeight: 1 - }) - ), - 'meshMessageDeliveriesWeight must be negative (or 0 to disable)' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - meshMessageDeliveriesWeight: -1, - meshMessageDeliveriesDecay: -1 - }) - ), - 'meshMessageDeliveriesDecay must be between 0 and 1' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - meshMessageDeliveriesWeight: -1, - meshMessageDeliveriesDecay: 2 - }) - ), - 'meshMessageDeliveriesDecay must be between 0 and 1' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - meshMessageDeliveriesWeight: -1, - meshMessageDeliveriesDecay: 0.5, - meshMessageDeliveriesCap: -1 - }) - ), - 'meshMessageDeliveriesCap must be positive' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - meshMessageDeliveriesWeight: -1, - meshMessageDeliveriesDecay: 5, - meshMessageDeliveriesThreshold: -3 - }) - ), - 'meshMessageDeliveriesDecay must be between 0 and 1' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - meshMessageDeliveriesWeight: -1, - meshMessageDeliveriesDecay: 0.5, - meshMessageDeliveriesThreshold: -3, - meshMessageDeliveriesWindow: -1 - }) - ), - 'meshMessageDeliveriesThreshold must be positive' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - meshMessageDeliveriesWeight: -1, - meshMessageDeliveriesDecay: 0.5, - meshMessageDeliveriesThreshold: 3, - meshMessageDeliveriesWindow: -1, - meshMessageDeliveriesActivation: 1 - }) - ), - 'meshMessageDeliveriesWindow must be non-negative' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - meshFailurePenaltyWeight: 1 - }) - ), - 'meshFailurePenaltyWeight must be negative' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - meshFailurePenaltyWeight: -1, - meshFailurePenaltyDecay: -1 - }) - ), - 'meshFailurePenaltyDecay must be between 0 and 1' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - meshFailurePenaltyWeight: -1, - meshFailurePenaltyDecay: 2 - }) - ), - 'meshFailurePenaltyDecay must be between 0 and 1' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - invalidMessageDeliveriesWeight: 1 - }) - ), - 'invalidMessageDeliveriesWeight must be negative' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - invalidMessageDeliveriesWeight: -1, - invalidMessageDeliveriesDecay: -1 - }) - ), - 'invalidMessageDeliveriesDecay must be between 0 and 1' - ).to.throw() - expect( - () => - validateTopicScoreParams( - createTopicScoreParams({ - timeInMeshQuantum: 1000, - invalidMessageDeliveriesWeight: -1, - invalidMessageDeliveriesDecay: 2 - }) - ), - 'invalidMessageDeliveriesDecay must be between 0 and 1' - ).to.throw() - }) - it('should not throw on valid TopicScoreParams', () => { - expect(() => - validateTopicScoreParams( - createTopicScoreParams({ - topicWeight: 2, - timeInMeshWeight: 0.01, - timeInMeshQuantum: 1000, - timeInMeshCap: 10, - firstMessageDeliveriesWeight: 1, - firstMessageDeliveriesDecay: 0.5, - firstMessageDeliveriesCap: 10, - meshMessageDeliveriesWeight: -1, - meshMessageDeliveriesDecay: 0.5, - meshMessageDeliveriesCap: 10, - meshMessageDeliveriesThreshold: 5, - meshMessageDeliveriesWindow: 1, - meshMessageDeliveriesActivation: 1000, - meshFailurePenaltyWeight: -1, - meshFailurePenaltyDecay: 0.5, - invalidMessageDeliveriesWeight: -1, - invalidMessageDeliveriesDecay: 0.5 - }) - ) - ).to.not.throw() - }) -}) - -describe('PeerScoreParams validation', () => { - const appScore = () => 0 - - it('should throw on invalid PeerScoreParams', () => { - expect( - () => - validatePeerScoreParams( - createPeerScoreParams({ - topicScoreCap: -1, - appSpecificScore: appScore, - decayInterval: 1000, - decayToZero: 0.01 - }) - ), - 'topicScoreCap must be positive' - ).to.throw() - expect( - () => - validatePeerScoreParams( - createPeerScoreParams({ - topicScoreCap: 1, - decayInterval: 999, - decayToZero: 0.01 - }) - ), - 'decayInterval must be at least 1s' - ).to.throw() - expect( - () => - validatePeerScoreParams( - createPeerScoreParams({ - topicScoreCap: 1, - appSpecificScore: appScore, - decayInterval: 1000, - decayToZero: 0.01, - IPColocationFactorWeight: 1 - }) - ), - 'IPColocationFactorWeight should be negative' - ).to.throw() - expect( - () => - validatePeerScoreParams( - createPeerScoreParams({ - topicScoreCap: 1, - appSpecificScore: appScore, - decayInterval: 1000, - decayToZero: 0.01, - IPColocationFactorWeight: -1, - IPColocationFactorThreshold: -1 - }) - ), - 'IPColocationFactorThreshold should be at least 1' - ).to.throw() - /* - TODO: appears to be valid config? - expect(() => - validatePeerScoreParams( - createPeerScoreParams({ - topicScoreCap: 1, - appSpecificScore: appScore, - decayInterval: 1000, - decayToZero: 0.01, - IPColocationFactorWeight: -1, - IPColocationFactorThreshold: 0.99 - }) - ), "IPColocationFactorThreshold should be at least 1" - ).to.throw() - */ - expect( - () => - validatePeerScoreParams( - createPeerScoreParams({ - topicScoreCap: 1, - appSpecificScore: appScore, - decayInterval: 1000, - decayToZero: -1, - IPColocationFactorWeight: -1, - IPColocationFactorThreshold: 1 - }) - ), - 'decayToZero must be between 0 and 1' - ).to.throw() - expect( - () => - validatePeerScoreParams( - createPeerScoreParams({ - topicScoreCap: 1, - appSpecificScore: appScore, - decayInterval: 1000, - decayToZero: 2, - IPColocationFactorWeight: -1, - IPColocationFactorThreshold: 1 - }) - ), - 'decayToZero must be between 0 and 1' - ).to.throw() - expect(() => - validatePeerScoreParams( - createPeerScoreParams({ - appSpecificScore: appScore, - decayInterval: 1000, - decayToZero: 0.01, - behaviourPenaltyWeight: 1 - }) - ) - ).to.throw() - /* - TODO: appears to be valid config? - expect(() => - validatePeerScoreParams( - createPeerScoreParams({ - appSpecificScore: appScore, - decayInterval: 1000, - decayToZero: 0.01, - behaviourPenaltyWeight: -1 - }) - ), "behaviourPenaltyWeight MUST be negative (or zero to disable)" - ).to.throw() - */ - expect( - () => - validatePeerScoreParams( - createPeerScoreParams({ - appSpecificScore: appScore, - decayInterval: 1000, - decayToZero: 0.01, - behaviourPenaltyWeight: -1, - behaviourPenaltyDecay: 2 - }) - ), - 'behaviourPenaltyDecay must be between 0 and 1' - ).to.throw() - expect(() => - validatePeerScoreParams( - createPeerScoreParams({ - topicScoreCap: 1, - appSpecificScore: appScore, - decayInterval: 1000, - decayToZero: 0.01, - IPColocationFactorWeight: -1, - IPColocationFactorThreshold: 1, - topics: { - test: { - topicWeight: -1, - timeInMeshWeight: 0.01, - timeInMeshQuantum: Number(constants.second), - timeInMeshCap: 10, - firstMessageDeliveriesWeight: 1, - firstMessageDeliveriesDecay: 0.5, - firstMessageDeliveriesCap: 10, - meshMessageDeliveriesWeight: -1, - meshMessageDeliveriesDecay: 0.5, - meshMessageDeliveriesCap: 10, - meshMessageDeliveriesThreshold: 5, - meshMessageDeliveriesWindow: 1, - meshMessageDeliveriesActivation: 1000, - meshFailurePenaltyWeight: -1, - meshFailurePenaltyDecay: 0.5, - invalidMessageDeliveriesWeight: -1, - invalidMessageDeliveriesDecay: 0.5 - } - } - }) - ) - ).to.throw() - }) - it('should not throw on valid PeerScoreParams', () => { - expect(() => - validatePeerScoreParams( - createPeerScoreParams({ - appSpecificScore: appScore, - decayInterval: 1000, - decayToZero: 0.01, - IPColocationFactorWeight: -1, - IPColocationFactorThreshold: 1, - behaviourPenaltyWeight: -1, - behaviourPenaltyDecay: 0.999 - }) - ) - ).to.not.throw() - expect(() => - validatePeerScoreParams( - createPeerScoreParams({ - topicScoreCap: 1, - appSpecificScore: appScore, - decayInterval: 1000, - decayToZero: 0.01, - IPColocationFactorWeight: -1, - IPColocationFactorThreshold: 1, - behaviourPenaltyWeight: -1, - behaviourPenaltyDecay: 0.999 - }) - ) - ).to.not.throw() - expect(() => - validatePeerScoreParams( - createPeerScoreParams({ - topicScoreCap: 1, - appSpecificScore: appScore, - decayInterval: Number(constants.second), - decayToZero: 0.01, - IPColocationFactorWeight: -1, - IPColocationFactorThreshold: 1, - topics: { - test: { - topicWeight: 1, - timeInMeshWeight: 0.01, - timeInMeshQuantum: 1000, - timeInMeshCap: 10, - firstMessageDeliveriesWeight: 1, - firstMessageDeliveriesDecay: 0.5, - firstMessageDeliveriesCap: 10, - meshMessageDeliveriesWeight: -1, - meshMessageDeliveriesDecay: 0.5, - meshMessageDeliveriesCap: 10, - meshMessageDeliveriesThreshold: 5, - meshMessageDeliveriesWindow: 1, - meshMessageDeliveriesActivation: 1000, - meshFailurePenaltyWeight: -1, - meshFailurePenaltyDecay: 0.5, - invalidMessageDeliveriesWeight: -1, - invalidMessageDeliveriesDecay: 0.5 - } - } - }) - ) - ).to.not.throw() - }) -}) diff --git a/packages/pubsub-gossipsub/test/peer-score-thresholds.spec.ts b/packages/pubsub-gossipsub/test/peer-score-thresholds.spec.ts deleted file mode 100644 index 6b762d227f..0000000000 --- a/packages/pubsub-gossipsub/test/peer-score-thresholds.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { expect } from 'aegir/chai' -import { createPeerScoreThresholds, validatePeerScoreThresholds } from '../src/score/index.js' - -describe('PeerScoreThresholds validation', () => { - it('should throw on invalid PeerScoreThresholds', () => { - expect( - () => - validatePeerScoreThresholds( - createPeerScoreThresholds({ - gossipThreshold: 1 - }) - ), - 'gossipThreshold must be <= 0' - ).to.throw() - expect( - () => - validatePeerScoreThresholds( - createPeerScoreThresholds({ - publishThreshold: 1 - }) - ), - 'publishThreshold must be <= 0 and <= gossip threshold' - ).to.throw() - expect( - () => - validatePeerScoreThresholds( - createPeerScoreThresholds({ - gossipThreshold: -1, - publishThreshold: 0 - }) - ), - 'publishThreshold must be <= 0 and <= gossip threshold' - ).to.throw() - expect( - () => - validatePeerScoreThresholds( - createPeerScoreThresholds({ - graylistThreshold: 1 - }) - ), - 'graylistThreshold must be <= 0 and <= publish threshold' - ).to.throw() - expect( - () => - validatePeerScoreThresholds( - createPeerScoreThresholds({ - publishThreshold: -1, - graylistThreshold: -2 - }) - ), - 'graylistThreshold must be <= 0 and <= publish threshold' - ).to.throw() - expect( - () => - validatePeerScoreThresholds( - createPeerScoreThresholds({ - acceptPXThreshold: -1 - }) - ), - 'acceptPXThreshold must be >= 0' - ).to.throw() - expect( - () => - validatePeerScoreThresholds( - createPeerScoreThresholds({ - opportunisticGraftThreshold: -1 - }) - ), - 'opportunisticGraftThreshold must be >= 0' - ).to.throw() - }) - it('should not throw on valid PeerScoreThresholds', () => { - expect(() => - validatePeerScoreThresholds( - createPeerScoreThresholds({ - gossipThreshold: -1, - publishThreshold: -2, - graylistThreshold: -3, - acceptPXThreshold: 1, - opportunisticGraftThreshold: 2 - }) - ) - ).to.not.throw() - }) -}) diff --git a/packages/pubsub-gossipsub/test/peer-score.spec.ts b/packages/pubsub-gossipsub/test/peer-score.spec.ts deleted file mode 100644 index 91bc861695..0000000000 --- a/packages/pubsub-gossipsub/test/peer-score.spec.ts +++ /dev/null @@ -1,748 +0,0 @@ -import sinon from 'sinon' -import { expect } from 'aegir/chai' -import delay from 'delay' -import { PeerScore, createPeerScoreParams, createTopicScoreParams } from '../src/score/index.js' -import { getMsgIdStr, makeTestMessage } from './utils/index.js' -import { RejectReason } from '../src/types.js' -import { ScorePenalty } from '../src/metrics.js' -import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import type { PeerStats } from '../src/score/peer-stats.js' -import type { PeerScoreParams, TopicScoreParams } from '../src/score/peer-score-params.js' - -/** Placeholder for some ScorePenalty value, only used for metrics */ -const scorePenaltyAny = ScorePenalty.BrokenPromise - -describe('PeerScore', () => { - it('should score based on time in mesh', async () => { - // Create parameters with reasonable default values - const mytopic = 'mytopic' - const params = createPeerScoreParams({ - topicScoreCap: 1000 - }) - const tparams = (params.topics[mytopic] = createTopicScoreParams({ - topicWeight: 0.5, - timeInMeshWeight: 1, - timeInMeshQuantum: 1, - timeInMeshCap: 3600 - })) - const peerA = (await createEd25519PeerId()).toString() - // Peer score should start at 0 - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - ps.addPeer(peerA) - - let aScore = ps.score(peerA) - expect(aScore, 'expected score to start at zero').to.equal(0) - - // The time in mesh depends on how long the peer has been grafted - ps.graft(peerA, mytopic) - const elapsed = tparams.timeInMeshQuantum * 100 - await delay(elapsed + 10) - - ps.refreshScores() - aScore = ps.score(peerA) - expect(aScore).to.be.gte(((tparams.topicWeight * tparams.timeInMeshWeight) / tparams.timeInMeshQuantum) * elapsed) - }) - - it('should cap time in mesh score', async () => { - // Create parameters with reasonable default values - const mytopic = 'mytopic' - const params = createPeerScoreParams({}) - const tparams = (params.topics[mytopic] = createTopicScoreParams({ - topicWeight: 0.5, - timeInMeshWeight: 1, - timeInMeshQuantum: 1, - timeInMeshCap: 10, - invalidMessageDeliveriesDecay: 0.1 - })) - const peerA = (await createEd25519PeerId()).toString() - // Peer score should start at 0 - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - ps.addPeer(peerA) - - let aScore = ps.score(peerA) - expect(aScore, 'expected score to start at zero').to.equal(0) - - // The time in mesh depends on how long the peer has been grafted - ps.graft(peerA, mytopic) - const elapsed = tparams.timeInMeshQuantum * 40 - await delay(elapsed) - - ps.refreshScores() - aScore = ps.score(peerA) - expect(aScore).to.be.gt(tparams.topicWeight * tparams.timeInMeshWeight * tparams.timeInMeshCap * 0.5) - expect(aScore).to.be.lt(tparams.topicWeight * tparams.timeInMeshWeight * tparams.timeInMeshCap * 1.5) - }) - - it('should score first message deliveries', async () => { - // Create parameters with reasonable default values - const mytopic = 'mytopic' - const params = createPeerScoreParams({ - topicScoreCap: 1000 - }) - const tparams = (params.topics[mytopic] = createTopicScoreParams({ - topicWeight: 1, - firstMessageDeliveriesWeight: 1, - firstMessageDeliveriesDecay: 0.9, - firstMessageDeliveriesCap: 50000, - timeInMeshWeight: 0 - })) - const peerA = (await createEd25519PeerId()).toString() - // Peer score should start at 0 - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - ps.addPeer(peerA) - ps.graft(peerA, mytopic) - - // deliver a bunch of messages from peer A - const nMessages = 100 - for (let i = 0; i < nMessages; i++) { - const msg = makeTestMessage(i, mytopic) - ps.validateMessage(getMsgIdStr(msg)) - ps.deliverMessage(peerA, getMsgIdStr(msg), msg.topic) - } - - ps.refreshScores() - const aScore = ps.score(peerA) - expect(aScore).to.be.equal( - tparams.topicWeight * tparams.firstMessageDeliveriesWeight * nMessages * tparams.firstMessageDeliveriesDecay - ) - }) - - it('should cap first message deliveries score', async () => { - // Create parameters with reasonable default values - const mytopic = 'mytopic' - const params = createPeerScoreParams({ - topicScoreCap: 1000 - }) - const tparams = (params.topics[mytopic] = createTopicScoreParams({ - topicWeight: 1, - firstMessageDeliveriesWeight: 1, - firstMessageDeliveriesDecay: 0.9, - invalidMessageDeliveriesDecay: 0.9, - firstMessageDeliveriesCap: 50, - timeInMeshWeight: 0 - })) - const peerA = (await createEd25519PeerId()).toString() - // Peer score should start at 0 - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - ps.addPeer(peerA) - - let aScore = ps.score(peerA) - expect(aScore, 'expected score to start at zero').to.equal(0) - - // The time in mesh depends on how long the peer has been grafted - ps.graft(peerA, mytopic) - - // deliver a bunch of messages from peer A - const nMessages = 100 - for (let i = 0; i < nMessages; i++) { - const msg = makeTestMessage(i, mytopic) - ps.validateMessage(getMsgIdStr(msg)) - ps.deliverMessage(peerA, getMsgIdStr(msg), msg.topic) - } - - ps.refreshScores() - aScore = ps.score(peerA) - expect(aScore).to.be.equal( - tparams.topicWeight * - tparams.firstMessageDeliveriesWeight * - tparams.firstMessageDeliveriesCap * - tparams.firstMessageDeliveriesDecay - ) - }) - - it('should decay first message deliveries score', async () => { - // Create parameters with reasonable default values - const mytopic = 'mytopic' - const params = createPeerScoreParams({ - topicScoreCap: 1000 - }) - const tparams = (params.topics[mytopic] = createTopicScoreParams({ - topicWeight: 1, - firstMessageDeliveriesWeight: 1, - firstMessageDeliveriesDecay: 0.9, // decay 10% per decay interval - invalidMessageDeliveriesDecay: 0.9, - firstMessageDeliveriesCap: 50, - timeInMeshWeight: 0 - })) - const peerA = (await createEd25519PeerId()).toString() - // Peer score should start at 0 - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - ps.addPeer(peerA) - - let aScore = ps.score(peerA) - expect(aScore, 'expected score to start at zero').to.equal(0) - - // The time in mesh depends on how long the peer has been grafted - ps.graft(peerA, mytopic) - - // deliver a bunch of messages from peer A - const nMessages = 100 - for (let i = 0; i < nMessages; i++) { - const msg = makeTestMessage(i, mytopic) - ps.validateMessage(getMsgIdStr(msg)) - ps.deliverMessage(peerA, getMsgIdStr(msg), msg.topic) - } - - ps.refreshScores() - aScore = ps.score(peerA) - let expected = - tparams.topicWeight * - tparams.firstMessageDeliveriesWeight * - tparams.firstMessageDeliveriesCap * - tparams.firstMessageDeliveriesDecay - expect(aScore).to.be.equal(expected) - - // refreshing the scores applies the decay param - const decayInterals = 10 - for (let i = 0; i < decayInterals; i++) { - ps.refreshScores() - expected *= tparams.firstMessageDeliveriesDecay - } - aScore = ps.score(peerA) - expect(aScore).to.be.equal(expected) - }) - - it('should score mesh message deliveries', async function () { - this.timeout(10000) - // Create parameters with reasonable default values - const mytopic = 'mytopic' - const params = createPeerScoreParams({}) - const tparams = (params.topics[mytopic] = createTopicScoreParams({ - topicWeight: 1, - meshMessageDeliveriesWeight: -1, - meshMessageDeliveriesActivation: 1000, - meshMessageDeliveriesWindow: 10, - meshMessageDeliveriesThreshold: 20, - meshMessageDeliveriesCap: 100, - meshMessageDeliveriesDecay: 0.9, - invalidMessageDeliveriesDecay: 0.9, - firstMessageDeliveriesWeight: 0, - timeInMeshWeight: 0 - })) - // peer A always delivers the message first - // peer B delivers next (within the delivery window) - // peer C delivers outside the delivery window - // we expect peers A and B to have a score of zero, since all other param weights are zero - // peer C should have a negative score - const peerA = (await createEd25519PeerId()).toString() - const peerB = (await createEd25519PeerId()).toString() - const peerC = (await createEd25519PeerId()).toString() - const peers = [peerA, peerB, peerC] - // Peer score should start at 0 - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - peers.forEach((p) => { - ps.addPeer(p) - ps.graft(p, mytopic) - }) - - // assert that nobody has been penalized yet for not delivering messages before activation time - ps.refreshScores() - peers.forEach((p) => { - const score = ps.score(p) - expect(score, 'expected no mesh delivery penalty before activation time').to.equal(0) - }) - // wait for the activation time to kick in - await delay(tparams.meshMessageDeliveriesActivation) - - // deliver a bunch of messages from peers - const nMessages = 100 - for (let i = 0; i < nMessages; i++) { - const msg = makeTestMessage(i, mytopic) - ps.validateMessage(getMsgIdStr(msg)) - ps.deliverMessage(peerA, getMsgIdStr(msg), msg.topic) - - ps.duplicateMessage(peerB, getMsgIdStr(msg), msg.topic) - - // deliver duplicate from peer C after the window - await delay(tparams.meshMessageDeliveriesWindow + 5) - ps.duplicateMessage(peerC, getMsgIdStr(msg), msg.topic) - } - ps.refreshScores() - const aScore = ps.score(peerA) - const bScore = ps.score(peerB) - const cScore = ps.score(peerC) - expect(aScore).to.be.gte(0) - expect(bScore).to.be.gte(0) - - // the penalty is the difference between the threshold and the actual mesh deliveries, squared. - // since we didn't deliver anything, this is just the value of the threshold - const penalty = tparams.meshMessageDeliveriesThreshold * tparams.meshMessageDeliveriesThreshold - const expected = tparams.topicWeight * tparams.meshMessageDeliveriesWeight * penalty - expect(cScore).to.be.equal(expected) - }) - - it('should decay mesh message deliveries score', async function () { - this.timeout(10000) - // Create parameters with reasonable default values - const mytopic = 'mytopic' - const params = createPeerScoreParams({}) - const tparams = (params.topics[mytopic] = createTopicScoreParams({ - topicWeight: 1, - meshMessageDeliveriesWeight: -1, - meshMessageDeliveriesActivation: 1000, - meshMessageDeliveriesWindow: 10, - meshMessageDeliveriesThreshold: 20, - meshMessageDeliveriesCap: 100, - meshMessageDeliveriesDecay: 0.9, - invalidMessageDeliveriesDecay: 0.9, - firstMessageDeliveriesWeight: 0, - timeInMeshWeight: 0 - })) - const peerA = (await createEd25519PeerId()).toString() - // Peer score should start at 0 - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - ps.addPeer(peerA) - ps.graft(peerA, mytopic) - - // wait for the activation time to kick in - await delay(tparams.meshMessageDeliveriesActivation + 10) - - // deliver a bunch of messages from peer A - const nMessages = 40 - for (let i = 0; i < nMessages; i++) { - const msg = makeTestMessage(i, mytopic) - ps.validateMessage(getMsgIdStr(msg)) - ps.deliverMessage(peerA, getMsgIdStr(msg), msg.topic) - } - ps.refreshScores() - let aScore = ps.score(peerA) - expect(aScore).to.be.gte(0) - - // we need to refresh enough times for the decay to bring us below the threshold - let decayedDeliveryCount = nMessages * tparams.meshMessageDeliveriesDecay - for (let i = 0; i < 20; i++) { - ps.refreshScores() - decayedDeliveryCount *= tparams.meshMessageDeliveriesDecay - } - aScore = ps.score(peerA) - // the penalty is the difference between the threshold and the (decayed) mesh deliveries, squared. - const deficit = tparams.meshMessageDeliveriesThreshold - decayedDeliveryCount - const penalty = deficit * deficit - const expected = tparams.topicWeight * tparams.meshMessageDeliveriesWeight * penalty - expect(aScore).to.be.equal(expected) - }) - - it('should score mesh message failures', async function () { - this.timeout(10000) - // Create parameters with reasonable default values - const mytopic = 'mytopic' - const params = createPeerScoreParams({}) - // the mesh failure penalty is applied when a peer is pruned while their - // mesh deliveries are under the threshold. - // for this test, we set the mesh delivery threshold, but set - // meshMessageDeliveriesWeight to zero, so the only affect on the score - // is from the mesh failure penalty - const tparams = (params.topics[mytopic] = createTopicScoreParams({ - topicWeight: 1, - meshFailurePenaltyWeight: -1, - meshFailurePenaltyDecay: 0.9, - - meshMessageDeliveriesWeight: 0, - meshMessageDeliveriesActivation: 1000, - meshMessageDeliveriesWindow: 10, - meshMessageDeliveriesThreshold: 20, - meshMessageDeliveriesCap: 100, - meshMessageDeliveriesDecay: 0.9, - - firstMessageDeliveriesWeight: 0, - timeInMeshWeight: 0 - })) - const peerA = (await createEd25519PeerId()).toString() - const peerB = (await createEd25519PeerId()).toString() - const peers = [peerA, peerB] - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - peers.forEach((p) => { - ps.addPeer(p) - ps.graft(p, mytopic) - }) - - // wait for the activation time to kick in - await delay(tparams.meshMessageDeliveriesActivation + 10) - - // deliver a bunch of messages from peer A. peer B does nothing - const nMessages = 100 - for (let i = 0; i < nMessages; i++) { - const msg = makeTestMessage(i, mytopic) - ps.validateMessage(getMsgIdStr(msg)) - ps.deliverMessage(peerA, getMsgIdStr(msg), msg.topic) - } - // peers A and B should both have zero scores, since the failure penalty hasn't been applied yet - ps.refreshScores() - let aScore = ps.score(peerA) - let bScore = ps.score(peerB) - expect(aScore).to.be.equal(0) - expect(bScore).to.be.equal(0) - - // prune peer B to apply the penalty - ps.prune(peerB, mytopic) - ps.refreshScores() - aScore = ps.score(peerA) - bScore = ps.score(peerB) - expect(aScore).to.be.equal(0) - - // penalty calculation is the same as for meshMessageDeliveries, but multiplied by meshFailurePenaltyWeight - // instead of meshMessageDeliveriesWeight - const penalty = tparams.meshMessageDeliveriesThreshold * tparams.meshMessageDeliveriesThreshold - const expected = tparams.topicWeight * tparams.meshFailurePenaltyWeight * penalty * tparams.meshFailurePenaltyDecay - expect(bScore).to.be.equal(expected) - }) - - it('should score invalid message deliveries', async function () { - // Create parameters with reasonable default values - const mytopic = 'mytopic' - const params = createPeerScoreParams({}) - const tparams = (params.topics[mytopic] = createTopicScoreParams({ - topicWeight: 1, - invalidMessageDeliveriesWeight: -1, - invalidMessageDeliveriesDecay: 0.9, - timeInMeshWeight: 0 - })) - const peerA = (await createEd25519PeerId()).toString() - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - ps.addPeer(peerA) - ps.graft(peerA, mytopic) - - // deliver a bunch of messages from peer A - const nMessages = 100 - for (let i = 0; i < nMessages; i++) { - const msg = makeTestMessage(i, mytopic) - ps.rejectMessage(peerA, getMsgIdStr(msg), msg.topic, RejectReason.Reject) - } - ps.refreshScores() - const aScore = ps.score(peerA) - - const expected = - tparams.topicWeight * - tparams.invalidMessageDeliveriesWeight * - (nMessages * tparams.invalidMessageDeliveriesDecay) ** 2 - expect(aScore).to.be.equal(expected) - }) - - it('should decay invalid message deliveries score', async function () { - // Create parameters with reasonable default values - const mytopic = 'mytopic' - const params = createPeerScoreParams({}) - const tparams = (params.topics[mytopic] = createTopicScoreParams({ - topicWeight: 1, - invalidMessageDeliveriesWeight: -1, - invalidMessageDeliveriesDecay: 0.9, - timeInMeshWeight: 0 - })) - const peerA = (await createEd25519PeerId()).toString() - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - ps.addPeer(peerA) - ps.graft(peerA, mytopic) - - // deliver a bunch of messages from peer A - const nMessages = 100 - for (let i = 0; i < nMessages; i++) { - const msg = makeTestMessage(i, mytopic) - ps.rejectMessage(peerA, getMsgIdStr(msg), msg.topic, RejectReason.Reject) - } - ps.refreshScores() - let aScore = ps.score(peerA) - - let expected = - tparams.topicWeight * - tparams.invalidMessageDeliveriesWeight * - (nMessages * tparams.invalidMessageDeliveriesDecay) ** 2 - expect(aScore).to.be.equal(expected) - - // refresh scores a few times to apply decay - for (let i = 0; i < 10; i++) { - ps.refreshScores() - expected *= tparams.invalidMessageDeliveriesDecay ** 2 - } - aScore = ps.score(peerA) - expect(aScore).to.be.equal(expected) - }) - - it('should score invalid/ignored messages', async function () { - // this test adds coverage for the dark corners of message rejection - const mytopic = 'mytopic' - const params = createPeerScoreParams({}) - params.topics[mytopic] = createTopicScoreParams({ - topicWeight: 1, - invalidMessageDeliveriesWeight: -1, - invalidMessageDeliveriesDecay: 0.9, - timeInMeshQuantum: 1000 - }) - const peerA = (await createEd25519PeerId()).toString() - const peerB = (await createEd25519PeerId()).toString() - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - ps.addPeer(peerA) - ps.addPeer(peerB) - - const msg = makeTestMessage(0, mytopic) - - // insert a record - ps.validateMessage(getMsgIdStr(msg)) - - // this should have no effect in the score, and subsequent duplicate messages should have no effect either - ps.rejectMessage(peerA, getMsgIdStr(msg), msg.topic, RejectReason.Ignore) - ps.duplicateMessage(peerB, getMsgIdStr(msg), msg.topic) - - let aScore = ps.score(peerA) - let bScore = ps.score(peerB) - let expected = 0 - expect(aScore).to.equal(expected) - expect(bScore).to.equal(expected) - - // now clear the delivery record - let record = ps.deliveryRecords.queue.peekFront() - - if (record == null) { - throw new Error('No record found') - } - - record.expire = Date.now() - - await delay(5) - ps.deliveryRecords.gc() - - // insert a new record in the message deliveries - ps.validateMessage(getMsgIdStr(msg)) - - // and reject the message to make sure duplicates are also penalized - ps.rejectMessage(peerA, getMsgIdStr(msg), msg.topic, RejectReason.Reject) - ps.duplicateMessage(peerB, getMsgIdStr(msg), msg.topic) - - aScore = ps.score(peerA) - bScore = ps.score(peerB) - expected = -1 - expect(aScore).to.equal(expected) - expect(bScore).to.equal(expected) - - // now clear the delivery record again - record = ps.deliveryRecords.queue.peekFront() - - if (record == null) { - throw new Error('No record found') - } - - record.expire = Date.now() - - await delay(5) - ps.deliveryRecords.gc() - - // insert a new record in the message deliveries - ps.validateMessage(getMsgIdStr(msg)) - - // and reject the message after a duplicate has arrived - ps.duplicateMessage(peerB, getMsgIdStr(msg), msg.topic) - ps.rejectMessage(peerA, getMsgIdStr(msg), msg.topic, RejectReason.Reject) - - aScore = ps.score(peerA) - bScore = ps.score(peerB) - expected = -4 - expect(aScore).to.equal(expected) - expect(bScore).to.equal(expected) - }) - - it('should score w/ application score', async function () { - const mytopic = 'mytopic' - let appScoreValue = 0 - const params = createPeerScoreParams({ - appSpecificScore: () => appScoreValue, - appSpecificWeight: 0.5 - }) - const peerA = (await createEd25519PeerId()).toString() - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - ps.addPeer(peerA) - ps.graft(peerA, mytopic) - - for (let i = -100; i < 100; i++) { - appScoreValue = i - ps.refreshScores() - const aScore = ps.score(peerA) - const expected = i * params.appSpecificWeight - expect(aScore).to.equal(expected) - } - }) - - it('should score w/ IP colocation', async function () { - const mytopic = 'mytopic' - const params = createPeerScoreParams({ - IPColocationFactorThreshold: 1, - IPColocationFactorWeight: -1 - }) - const peerA = (await createEd25519PeerId()).toString() - const peerB = (await createEd25519PeerId()).toString() - const peerC = (await createEd25519PeerId()).toString() - const peerD = (await createEd25519PeerId()).toString() - const peers = [peerA, peerB, peerC, peerD] - - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - peers.forEach((p) => { - ps.addPeer(p) - ps.graft(p, mytopic) - }) - - const setIPsForPeer = (p: string, ips: string[]) => { - for (const ip of ips) { - ps.addIP(p, ip) - } - } - // peerA should have no penalty, but B, C, and D should be penalized for sharing an IP - setIPsForPeer(peerA, ['1.2.3.4']) - setIPsForPeer(peerB, ['2.3.4.5']) - setIPsForPeer(peerC, ['2.3.4.5', '3.4.5.6']) - setIPsForPeer(peerD, ['2.3.4.5']) - - ps.refreshScores() - const aScore = ps.score(peerA) - const bScore = ps.score(peerB) - const cScore = ps.score(peerC) - const dScore = ps.score(peerD) - - expect(aScore).to.equal(0) - - const nShared = 3 - const ipSurplus = nShared - params.IPColocationFactorThreshold - const penalty = ipSurplus ** 2 - const expected = params.IPColocationFactorWeight * penalty - expect(bScore).to.equal(expected) - expect(cScore).to.equal(expected) - expect(dScore).to.equal(expected) - }) - - it('should score w/ behavior penalty', async function () { - const params = createPeerScoreParams({ - behaviourPenaltyWeight: -1, - behaviourPenaltyDecay: 0.99 - }) - const peerA = (await createEd25519PeerId()).toString() - - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - - // add penalty on a non-existent peer - ps.addPenalty(peerA, 1, ScorePenalty.MessageDeficit) - let aScore = ps.score(peerA) - expect(aScore).to.equal(0) - - // add the peer and test penalties - ps.addPeer(peerA) - - aScore = ps.score(peerA) - expect(aScore).to.equal(0) - - ps.addPenalty(peerA, 1, scorePenaltyAny) - aScore = ps.score(peerA) - expect(aScore).to.equal(-1) - - ps.addPenalty(peerA, 1, scorePenaltyAny) - aScore = ps.score(peerA) - expect(aScore).to.equal(-4) - - ps.refreshScores() - - aScore = ps.score(peerA) - expect(aScore).to.equal(-3.9204) - }) - - it('should handle score retention', async function () { - const mytopic = 'mytopic' - const params = createPeerScoreParams({ - appSpecificScore: () => -1000, - appSpecificWeight: 1, - retainScore: 800 - }) - const peerA = (await createEd25519PeerId()).toString() - - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - ps.addPeer(peerA) - ps.graft(peerA, mytopic) - // score should equal -1000 (app-specific score) - const expected = -1000 - ps.refreshScores() - let aScore = ps.score(peerA) - expect(aScore).to.equal(expected) - - // disconnect & wait half of the retainScoreTime - // should still have negative score - ps.removePeer(peerA) - const _delay = params.retainScore / 2 - await delay(_delay) - ps.refreshScores() - aScore = ps.score(peerA) - expect(aScore).to.equal(expected) - - // wait remaining time (plus a little slop) and the score should reset to 0 - await delay(_delay + 5) - ps.refreshScores() - aScore = ps.score(peerA) - expect(aScore).to.equal(0) - }) -}) - -// TODO: https://github.com/ChainSafe/js-libp2p-gossipsub/issues/238 -describe.skip('PeerScore score cache', function () { - const peerA = '16Uiu2HAmMkH6ZLen2tbhiuNCTZLLvrZaDgufNdT5MPjtC9Hr9YNG' - let sandbox: sinon.SinonSandbox - let computeStoreStub: sinon.SinonStub<[string, PeerStats, PeerScoreParams, Map>], number> - const params = createPeerScoreParams({ - appSpecificScore: () => -1000, - appSpecificWeight: 1, - retainScore: 800, - decayInterval: 1000, - topics: { a: { topicWeight: 10 } as TopicScoreParams } - }) - let ps2: PeerScore - - beforeEach(() => { - sandbox = sinon.createSandbox() - const now = Date.now() - sandbox.useFakeTimers(now) - computeStoreStub = sinon.stub<[string, PeerStats, PeerScoreParams, Map>], number>() - - ps2 = new PeerScore(params, null, { - scoreCacheValidityMs: 10, - computeScore: computeStoreStub - }) - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should compute first time', function () { - computeStoreStub.returns(10) - ps2.addPeer(peerA) - expect(computeStoreStub.calledOnce).to.be.false() - ps2.score(peerA) - expect(computeStoreStub.calledOnce).to.be.true() - // this time peerA score is cached - ps2.score(peerA) - expect(computeStoreStub.calledOnce).to.be.true() - }) - - const testCases = [ - { name: 'decayInterval timeout', fun: () => sandbox.clock.tick(params.decayInterval) }, - { name: 'refreshScores', fun: () => ps2.refreshScores() }, - { name: 'addPenalty', fun: () => ps2.addPenalty(peerA, 10, scorePenaltyAny) }, - { name: 'graft', fun: () => ps2.graft(peerA, 'a') }, - { name: 'prune', fun: () => ps2.prune(peerA, 'a') }, - { name: 'markInvalidMessageDelivery', fun: () => ps2.markInvalidMessageDelivery(peerA, 'a') }, - { name: 'markFirstMessageDelivery', fun: () => ps2.markFirstMessageDelivery(peerA, 'a') }, - { name: 'markDuplicateMessageDelivery', fun: () => ps2.markDuplicateMessageDelivery(peerA, 'a') }, - { name: 'removeIPs', fun: () => ps2.removeIP(peerA, '127.0.0.1') } - ] - - for (const { name, fun } of testCases) { - it(`should invalidate the cache after ${name}`, function () { - // eslint-disable-line no-loop-func - computeStoreStub.returns(10) - ps2.addPeer(peerA) - ps2.score(peerA) - expect(computeStoreStub.calledOnce).to.be.true() - // the score is cached - ps2.score(peerA) - expect(computeStoreStub.calledOnce).to.be.true() - // invalidate the cache - fun() - // should not use the cache - ps2.score(peerA) - expect(computeStoreStub.calledTwice).to.be.true() - }) - } -}) diff --git a/packages/pubsub-gossipsub/test/scoreMetrics.spec.ts b/packages/pubsub-gossipsub/test/scoreMetrics.spec.ts deleted file mode 100644 index 197f3482dd..0000000000 --- a/packages/pubsub-gossipsub/test/scoreMetrics.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { computeAllPeersScoreWeights } from '../src/score/scoreMetrics.js' -import { createPeerScoreParams, createTopicScoreParams, PeerScore } from '../src/score/index.js' -import { ScorePenalty } from '../src/metrics.js' -import { expect } from 'aegir/chai' -import { createEd25519PeerId } from '@libp2p/peer-id-factory' - -describe('score / scoreMetrics', () => { - it('computeScoreWeights', async () => { - // Create parameters with reasonable default values - const topic = 'test_topic' - - const params = createPeerScoreParams({ - topicScoreCap: 1000 - }) - params.topics[topic] = createTopicScoreParams({ - topicWeight: 0.5, - timeInMeshWeight: 1, - timeInMeshQuantum: 1, - timeInMeshCap: 3600 - }) - - // Add Map for metrics - const topicStrToLabel = new Map() - topicStrToLabel.set(topic, topic) - - const peerA = (await createEd25519PeerId()).toString() - // Peer score should start at 0 - const ps = new PeerScore(params, null, { scoreCacheValidityMs: 0 }) - ps.addPeer(peerA) - - // Do some actions that penalize the peer - const msgId = 'aaaaaaaaaaaaaaaa' - ps.addPenalty(peerA, 1, ScorePenalty.BrokenPromise) - ps.validateMessage(msgId) - ps.deliverMessage(peerA, msgId, topic) - - const sw = computeAllPeersScoreWeights([peerA], ps.peerStats, ps.params, ps.peerIPs, topicStrToLabel) - - // Ensure score is the same - expect(sw.score).to.deep.equal([ps.score(peerA)], 'Score from metrics and actual score not equal') - expect(sw.byTopic.get(topic)).to.deep.equal( - { p1w: [0], p2w: [1], p3w: [0], p3bw: [0], p4w: [0] }, - 'Wrong score weights by topic' - ) - }) -}) diff --git a/packages/pubsub-gossipsub/test/signature-policy.spec.ts b/packages/pubsub-gossipsub/test/signature-policy.spec.ts deleted file mode 100644 index df1faa023c..0000000000 --- a/packages/pubsub-gossipsub/test/signature-policy.spec.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { expect } from 'aegir/chai' -import { pEvent } from 'p-event' -import { mockNetwork } from '@libp2p/interface-compliance-tests/mocks' -import { stop } from '@libp2p/interface/startable' -import { - connectAllPubSubNodes, - connectPubsubNodes, - createComponents, - createComponentsArray, - type GossipSubAndComponents -} from './utils/create-pubsub.js' - -describe('signature policy', () => { - describe('strict-sign', () => { - const numNodes = 3 - let nodes: GossipSubAndComponents[] - - beforeEach(async () => { - mockNetwork.reset() - nodes = await createComponentsArray({ - number: numNodes, - connected: false, - init: { - scoreParams: { - IPColocationFactorThreshold: 3 - }, - // crucial line - globalSignaturePolicy: 'StrictSign' - } - }) - }) - - afterEach(async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it('should publish a message', async () => { - const topic = 'foo' - - // add subscriptions to each node - nodes.forEach((n) => n.pubsub.subscribe(topic)) - - // connect all nodes - await connectAllPubSubNodes(nodes) - - // wait for subscriptions to be transmitted - await Promise.all(nodes.map(async (n) => await pEvent(n.pubsub, 'subscription-change'))) - - // await mesh rebalancing - await Promise.all(nodes.map(async (n) => await pEvent(n.pubsub, 'gossipsub:heartbeat'))) - - // publish a message on the topic - const result = await nodes[0].pubsub.publish(topic, new Uint8Array()) - expect(result.recipients).to.length(numNodes - 1) - }) - - it('should forward a valid message', async () => { - const topic = 'foo' - - // add subscriptions to each node - nodes.forEach((n) => n.pubsub.subscribe(topic)) - - // connect in a line - await Promise.all(Array.from({ length: numNodes - 1 }, (_, i) => connectPubsubNodes(nodes[i], nodes[i + 1]))) - - // wait for subscriptions to be transmitted - await Promise.all(nodes.map(async (n) => await pEvent(n.pubsub, 'subscription-change'))) - - // await mesh rebalancing - await Promise.all(nodes.map(async (n) => await pEvent(n.pubsub, 'gossipsub:heartbeat'))) - - // publish a message on the topic - const result = await nodes[0].pubsub.publish(topic, new Uint8Array()) - expect(result.recipients).to.length(1) - - // the last node should get the message - await pEvent(nodes[nodes.length - 1].pubsub, 'gossipsub:message') - }) - - it('should not forward an strict-no-sign message', async () => { - const topic = 'foo' - - // add a no-sign peer to nodes - nodes.unshift( - await createComponents({ - init: { - globalSignaturePolicy: 'StrictNoSign' - } - }) - ) - - // add subscriptions to each node - nodes.forEach((n) => n.pubsub.subscribe(topic)) - - // connect in a line - await Promise.all(Array.from({ length: numNodes - 1 }, (_, i) => connectPubsubNodes(nodes[i], nodes[i + 1]))) - - // await mesh rebalancing - await Promise.all(nodes.map(async (n) => await pEvent(n.pubsub, 'gossipsub:heartbeat'))) - - // publish a message on the topic - const result = await nodes[0].pubsub.publish(topic, new Uint8Array()) - expect(result.recipients).to.length(1) - - // the last node should NOT get the message - try { - await pEvent(nodes[nodes.length - 1].pubsub, 'gossipsub:message', { timeout: 200 }) - expect.fail('no-sign message should not be emitted from strict-sign peer') - } catch (e) {} - }) - }) - - describe('strict-no-sign', () => { - const numNodes = 3 - let nodes: GossipSubAndComponents[] - - beforeEach(async () => { - mockNetwork.reset() - nodes = await createComponentsArray({ - number: numNodes, - connected: false, - init: { - scoreParams: { - IPColocationFactorThreshold: 3 - }, - // crucial line - globalSignaturePolicy: 'StrictNoSign' - } - }) - }) - - afterEach(async () => { - await stop(...nodes.reduce((acc, curr) => acc.concat(curr.pubsub, ...Object.entries(curr.components)), [])) - mockNetwork.reset() - }) - - it('should publish a message', async () => { - const topic = 'foo' - - // add subscriptions to each node - nodes.forEach((n) => n.pubsub.subscribe(topic)) - - // connect all nodes - await connectAllPubSubNodes(nodes) - - // wait for subscriptions to be transmitted - await Promise.all(nodes.map(async (n) => await pEvent(n.pubsub, 'subscription-change'))) - - // await mesh rebalancing - await Promise.all(nodes.map(async (n) => await pEvent(n.pubsub, 'gossipsub:heartbeat'))) - - // publish a message on the topic - const result = await nodes[0].pubsub.publish(topic, new Uint8Array()) - expect(result.recipients).to.length(numNodes - 1) - }) - - it('should forward a valid message', async () => { - const topic = 'foo' - - // add subscriptions to each node - nodes.forEach((n) => n.pubsub.subscribe(topic)) - - // connect in a line - await Promise.all(Array.from({ length: numNodes - 1 }, (_, i) => connectPubsubNodes(nodes[i], nodes[i + 1]))) - - // wait for subscriptions to be transmitted - await Promise.all(nodes.map(async (n) => await pEvent(n.pubsub, 'subscription-change'))) - - // await mesh rebalancing - await Promise.all(nodes.map(async (n) => await pEvent(n.pubsub, 'gossipsub:heartbeat'))) - - // publish a message on the topic - const result = await nodes[0].pubsub.publish(topic, new Uint8Array()) - expect(result.recipients).to.length(1) - - // the last node should get the message - await pEvent(nodes[nodes.length - 1].pubsub, 'gossipsub:message') - }) - - it('should not forward an strict-sign message', async () => { - const topic = 'foo' - - // add a no-sign peer to nodes - nodes.unshift( - await createComponents({ - init: { - globalSignaturePolicy: 'StrictSign' - } - }) - ) - - // add subscriptions to each node - nodes.forEach((n) => n.pubsub.subscribe(topic)) - - // connect in a line - await Promise.all(Array.from({ length: numNodes - 1 }, (_, i) => connectPubsubNodes(nodes[i], nodes[i + 1]))) - - // await mesh rebalancing - await Promise.all(nodes.map(async (n) => await pEvent(n.pubsub, 'gossipsub:heartbeat'))) - - // publish a message on the topic - const result = await nodes[0].pubsub.publish(topic, new Uint8Array()) - expect(result.recipients).to.length(1) - - // the last node should NOT get the message - try { - await pEvent(nodes[nodes.length - 1].pubsub, 'gossipsub:message', { timeout: 200 }) - expect.fail('no-sign message should not be emitted from strict-sign peer') - } catch (e) {} - }) - }) -}) diff --git a/packages/pubsub-gossipsub/test/time-cache.spec.ts b/packages/pubsub-gossipsub/test/time-cache.spec.ts deleted file mode 100644 index 079d578f85..0000000000 --- a/packages/pubsub-gossipsub/test/time-cache.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { expect } from 'aegir/chai' -import { SimpleTimeCache } from '../src/utils/time-cache.js' -import sinon from 'sinon' - -describe('SimpleTimeCache', () => { - const validityMs = 1000 - const timeCache = new SimpleTimeCache({ validityMs }) - const sandbox = sinon.createSandbox() - - beforeEach(() => { - sandbox.useFakeTimers() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should delete items after 1sec', () => { - timeCache.put('aFirst') - timeCache.put('bFirst') - timeCache.put('cFirst') - - expect(timeCache.has('aFirst')).to.be.true() - expect(timeCache.has('bFirst')).to.be.true() - expect(timeCache.has('cFirst')).to.be.true() - - sandbox.clock.tick(validityMs + 1) - - // https://github.com/ChainSafe/js-libp2p-gossipsub/issues/232#issuecomment-1109589919 - timeCache.prune() - - timeCache.put('aSecond') - timeCache.put('bSecond') - timeCache.put('cSecond') - - expect(timeCache.has('aSecond')).to.be.true() - expect(timeCache.has('bSecond')).to.be.true() - expect(timeCache.has('cSecond')).to.be.true() - expect(timeCache.has('aFirst')).to.be.false() - expect(timeCache.has('bFirst')).to.be.false() - expect(timeCache.has('cFirst')).to.be.false() - }) - - it('Map insertion order', () => { - const key1 = 'key1' - const key2 = 'key2' - const key3 = 'key3' - - const map = new Map() - map.set(key1, Date.now()) - map.set(key2, Date.now()) - map.set(key3, Date.now()) - - expect(Array.from(map.keys())).deep.equals([key1, key2, key3], 'Map iterator order') - - // Does not change key position - map.set(key2, Date.now()) - - expect(Array.from(map.keys())).deep.equals([key1, key2, key3], 'Map iterator order after re-set') - - // Changes key position - map.delete(key2) - map.set(key2, Date.now()) - - expect(Array.from(map.keys())).deep.equals([key1, key3, key2], 'Map iterator order after delete set') - }) -}) diff --git a/packages/pubsub-gossipsub/test/tracer.spec.ts b/packages/pubsub-gossipsub/test/tracer.spec.ts deleted file mode 100644 index f4d2b530bf..0000000000 --- a/packages/pubsub-gossipsub/test/tracer.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { expect } from 'aegir/chai' -import delay from 'delay' -import { IWantTracer } from '../src/tracer.js' -import * as constants from '../src/constants.js' -import { makeTestMessage, getMsgId, getMsgIdStr } from './utils/index.js' -import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import { messageIdToString } from '../src/utils/messageIdToString.js' - -describe('IWantTracer', () => { - it('should track broken promises', async function () { - // tests that unfulfilled promises are tracked correctly - this.timeout(6000) - const t = new IWantTracer(constants.GossipsubIWantFollowupTime, messageIdToString, null) - const peerA = (await createEd25519PeerId()).toString() - const peerB = (await createEd25519PeerId()).toString() - - const msgIds: Uint8Array[] = [] - for (let i = 0; i < 100; i++) { - const m = makeTestMessage(i, 'test_topic') - msgIds.push(getMsgId(m)) - } - - t.addPromise(peerA, msgIds) - t.addPromise(peerB, msgIds) - - // no broken promises yet - let brokenPromises = t.getBrokenPromises() - expect(brokenPromises.size).to.be.equal(0) - - // make promises break - await delay(constants.GossipsubIWantFollowupTime + 10) - - brokenPromises = t.getBrokenPromises() - expect(brokenPromises.size).to.be.equal(2) - expect(brokenPromises.get(peerA)).to.be.equal(1) - expect(brokenPromises.get(peerB)).to.be.equal(1) - }) - it('should track unbroken promises', async function () { - // like above, but this time we deliver messages to fullfil the promises - this.timeout(6000) - const t = new IWantTracer(constants.GossipsubIWantFollowupTime, messageIdToString, null) - const peerA = (await createEd25519PeerId()).toString() - const peerB = (await createEd25519PeerId()).toString() - - const msgs = [] - const msgIds = [] - for (let i = 0; i < 100; i++) { - const m = makeTestMessage(i, 'test_topic') - msgs.push(m) - msgIds.push(getMsgId(m)) - } - - t.addPromise(peerA, msgIds) - t.addPromise(peerB, msgIds) - - msgs.forEach((msg) => t.deliverMessage(getMsgIdStr(msg))) - - await delay(constants.GossipsubIWantFollowupTime + 10) - - // there should be no broken promises - const brokenPromises = t.getBrokenPromises() - expect(brokenPromises.size).to.be.equal(0) - }) -}) diff --git a/packages/pubsub-gossipsub/test/unit/set.test.ts b/packages/pubsub-gossipsub/test/unit/set.test.ts deleted file mode 100644 index 9c7682bd69..0000000000 --- a/packages/pubsub-gossipsub/test/unit/set.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { expect } from 'aegir/chai' -import { removeFirstNItemsFromSet, removeItemsFromSet } from '../../src/utils/set.js' - -describe('Set util', function () { - describe('removeItemsFromSet', function () { - let s: Set - this.beforeEach(() => { - s = new Set([1, 2, 3, 4, 5]) - }) - - const testCases: { id: string; ineed: number; fn: (item: number) => boolean; result: Set }[] = [ - { id: 'remove even numbers - need 0', ineed: 0, fn: (item) => item % 2 === 0, result: new Set([]) }, - { id: 'remove even numbers - need 1', ineed: 1, fn: (item) => item % 2 === 0, result: new Set([2]) }, - { id: 'remove even numbers - need 2', ineed: 2, fn: (item) => item % 2 === 0, result: new Set([2, 4]) }, - { id: 'remove even numbers - need 10', ineed: 2, fn: (item) => item % 2 === 0, result: new Set([2, 4]) } - ] - - for (const { id, ineed, fn, result } of testCases) { - it(id, () => { - expect(removeItemsFromSet(s, ineed, fn)).to.deep.equal(result) - }) - } - }) - - describe('removeFirstNItemsFromSet', function () { - let s: Set - this.beforeEach(() => { - s = new Set([1, 2, 3, 4, 5]) - }) - - const testCases: { id: string; ineed: number; result: Set }[] = [ - { id: 'remove first 0 item', ineed: 0, result: new Set([]) }, - { id: 'remove first 1 item', ineed: 1, result: new Set([1]) }, - { id: 'remove first 2 item', ineed: 2, result: new Set([1, 2]) }, - { id: 'remove first 10 item', ineed: 10, result: new Set([1, 2, 3, 4, 5]) } - ] - - for (const { id, ineed, result } of testCases) { - it(id, () => { - expect(removeFirstNItemsFromSet(s, ineed)).to.deep.equal(result) - }) - } - }) -}) diff --git a/packages/pubsub-gossipsub/test/utils/create-pubsub.ts b/packages/pubsub-gossipsub/test/utils/create-pubsub.ts deleted file mode 100644 index 12bf99409e..0000000000 --- a/packages/pubsub-gossipsub/test/utils/create-pubsub.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { createRSAPeerId } from '@libp2p/peer-id-factory' -import { mockRegistrar, mockConnectionManager, mockNetwork } from '@libp2p/interface-compliance-tests/mocks' -import { MemoryDatastore } from 'datastore-core' -import { GossipSub, type GossipSubComponents, type GossipsubOpts } from '../../src/index.js' -import type { PubSub } from '@libp2p/interface/pubsub' -import { setMaxListeners } from 'events' -import { PersistentPeerStore } from '@libp2p/peer-store' -import { start } from '@libp2p/interface/startable' -import { stubInterface } from 'ts-sinon' -import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager' -import { EventEmitter } from '@libp2p/interface/events' -import type { Libp2pEvents } from '@libp2p/interface' - -export interface CreateComponentsOpts { - init?: Partial - pubsub?: { new (opts?: any): PubSub } -} - -export interface GossipSubTestComponents extends GossipSubComponents { - events: EventEmitter -} - -export interface GossipSubAndComponents { - pubsub: GossipSub - components: GossipSubTestComponents -} - -export const createComponents = async (opts: CreateComponentsOpts): Promise => { - const Ctor = opts.pubsub ?? GossipSub - const peerId = await createRSAPeerId({ bits: 512 }) - - const events = new EventEmitter() - - const components: GossipSubTestComponents = { - peerId, - registrar: mockRegistrar(), - connectionManager: stubInterface(), - peerStore: new PersistentPeerStore({ - peerId, - datastore: new MemoryDatastore(), - events - }), - events - } - components.connectionManager = mockConnectionManager(components) - - const pubsub = new Ctor(components, opts.init) as GossipSub - - await start(...Object.entries(components), pubsub) - - mockNetwork.addNode(components) - - try { - // not available everywhere - setMaxListeners(Infinity, pubsub) - } catch {} - - return { pubsub, components } -} - -export const createComponentsArray = async ( - opts: CreateComponentsOpts & { number: number; connected?: boolean } = { number: 1, connected: true } -): Promise => { - const output = await Promise.all( - Array.from({ length: opts.number }).map(async (_, i) => - createComponents({ ...opts, init: { ...opts.init, debugName: `libp2p:gossipsub:${i}` } }) - ) - ) - - if (opts.connected) { - await connectAllPubSubNodes(output) - } - - return output -} - -export const connectPubsubNodes = async (a: GossipSubAndComponents, b: GossipSubAndComponents): Promise => { - const multicodecs = new Set([...a.pubsub.multicodecs, ...b.pubsub.multicodecs]) - - const connection = await a.components.connectionManager.openConnection(b.components.peerId) - - for (const multicodec of multicodecs) { - for (const topology of a.components.registrar.getTopologies(multicodec)) { - topology.onConnect?.(b.components.peerId, connection) - } - } -} - -export const connectAllPubSubNodes = async (components: GossipSubAndComponents[]): Promise => { - for (let i = 0; i < components.length; i++) { - for (let j = i + 1; j < components.length; j++) { - await connectPubsubNodes(components[i], components[j]) - } - } -} - -/** - * Connect some gossipsub nodes to others, ensure each has num peers - * @param {GossipSubAndComponents[]} gss - * @param {number} num number of peers to connect - */ -export async function connectSome(gss: GossipSubAndComponents[], num: number): Promise { - for (let i = 0; i < gss.length; i++) { - let count = 0 - // merely do a Math.random() and check for duplicate may take a lot of time to run a test - // so we make an array of candidate peers - // initially, don't populate i as a candidate to connect: candidatePeers[i] = i + 1 - const candidatePeers = Array.from({ length: gss.length - 1 }, (_, j) => (j >= i ? j + 1 : j)) - while (count < num) { - const n = Math.floor(Math.random() * candidatePeers.length) - const peer = candidatePeers[n] - await connectPubsubNodes(gss[i], gss[peer]) - // after connecting to a peer, update candidatePeers so that we don't connect to it again - for (let j = n; j < candidatePeers.length - 1; j++) { - candidatePeers[j] = candidatePeers[j + 1] - } - // remove the last item - candidatePeers.splice(candidatePeers.length - 1, 1) - count++ - } - } -} - -export async function sparseConnect(gss: GossipSubAndComponents[]): Promise { - await connectSome(gss, 3) -} - -export async function denseConnect(gss: GossipSubAndComponents[]): Promise { - await connectSome(gss, Math.min(gss.length - 1, 10)) -} diff --git a/packages/pubsub-gossipsub/test/utils/events.ts b/packages/pubsub-gossipsub/test/utils/events.ts deleted file mode 100644 index a410eb7534..0000000000 --- a/packages/pubsub-gossipsub/test/utils/events.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { SubscriptionChangeData } from '@libp2p/interface/pubsub' -import type { EventEmitter } from '@libp2p/interface/events' -import { expect } from 'aegir/chai' -import pWaitFor from 'p-wait-for' -import type { GossipSub, GossipsubEvents } from '../../src/index.js' -import type { GossipSubAndComponents } from './create-pubsub.js' - -export const checkReceivedSubscription = ( - node: GossipSubAndComponents, - peerIdStr: string, - topic: string, - peerIdx: number, - timeout = 1000 -): Promise => - new Promise((resolve, reject) => { - const event = 'subscription-change' - const t = setTimeout( - () => reject(new Error(`Not received subscriptions of psub ${peerIdx}, topic ${topic}`)), - timeout - ) - const cb = (evt: CustomEvent) => { - const { peerId, subscriptions } = evt.detail - - // console.log('@@@ in test received subscriptions from peer id', peerId.toString()) - if (peerId.toString() === peerIdStr && subscriptions[0].topic === topic && subscriptions[0].subscribe === true) { - clearTimeout(t) - node.pubsub.removeEventListener(event, cb) - if ( - Array.from(node.pubsub.getSubscribers(topic)) - .map((p) => p.toString()) - .includes(peerIdStr) - ) { - resolve() - } else { - reject(Error('topics should include the peerId')) - } - } - } - node.pubsub.addEventListener(event, cb) - }) - -export const checkReceivedSubscriptions = async ( - node: GossipSubAndComponents, - peerIdStrs: string[], - topic: string, - timeout = 5000 -): Promise => { - const recvPeerIdStrs = peerIdStrs.filter((peerIdStr) => peerIdStr !== node.components.peerId.toString()) - const promises = recvPeerIdStrs.map( - async (peerIdStr, idx) => await checkReceivedSubscription(node, peerIdStr, topic, idx, timeout) - ) - await Promise.all(promises) - for (const str of recvPeerIdStrs) { - expect(Array.from(node.pubsub.getSubscribers(topic)).map((p) => p.toString())).to.include(str) - } - await pWaitFor(() => { - return recvPeerIdStrs.every((peerIdStr) => { - return (node.pubsub as GossipSub).streamsOutbound.has(peerIdStr) - }) - }) -} - -export const awaitEvents = async ( - emitter: EventEmitter, - event: keyof Events, - number: number, - timeout = 30000 -): Promise => { - return new Promise((resolve, reject) => { - let counter = 0 - const t = setTimeout(() => { - emitter.removeEventListener(event, cb) - reject(new Error(`${counter} of ${number} '${String(event)}' events received after ${timeout}ms`)) - }, timeout) - const cb = () => { - counter++ - if (counter >= number) { - clearTimeout(t) - emitter.removeEventListener(event, cb) - resolve() - } - } - emitter.addEventListener(event, cb) - }) -} diff --git a/packages/pubsub-gossipsub/test/utils/index.ts b/packages/pubsub-gossipsub/test/utils/index.ts deleted file mode 100644 index 850aa15dc4..0000000000 --- a/packages/pubsub-gossipsub/test/utils/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { TopicStr } from '../../src/types.js' -import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import type { PeerId } from '@libp2p/interface/peer-id' -import type { RPC } from '../../src/message/rpc.js' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' - -export * from './msgId.js' - -export const createPeerId = async (): Promise => { - const peerId = await createEd25519PeerId() - - return peerId -} - -let seq = 0n -const defaultPeer = uint8ArrayFromString('12D3KooWBsYhazxNL7aeisdwttzc6DejNaM48889t5ifiS6tTrBf', 'base58btc') - -export function makeTestMessage(i: number, topic: TopicStr, from?: PeerId): RPC.IMessage { - return { - seqno: uint8ArrayFromString((seq++).toString(16).padStart(16, '0'), 'base16'), - data: Uint8Array.from([i]), - from: from?.toBytes() ?? defaultPeer, - topic - } -} diff --git a/packages/pubsub-gossipsub/test/utils/msgId.ts b/packages/pubsub-gossipsub/test/utils/msgId.ts deleted file mode 100644 index b3d445b020..0000000000 --- a/packages/pubsub-gossipsub/test/utils/msgId.ts +++ /dev/null @@ -1,20 +0,0 @@ -import SHA256 from '@chainsafe/as-sha256' -import type { RPC } from '../../src/message/rpc.js' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { messageIdToString } from '../../src/utils/messageIdToString.js' - -export const getMsgId = (msg: RPC.IMessage): Uint8Array => { - const from = msg.from != null ? msg.from : new Uint8Array(0) - const seqno = msg.seqno instanceof Uint8Array ? msg.seqno : uint8ArrayFromString(msg.seqno ?? '') - const result = new Uint8Array(from.length + seqno.length) - result.set(from, 0) - result.set(seqno, from.length) - return result -} - -export const getMsgIdStr = (msg: RPC.IMessage): string => messageIdToString(getMsgId(msg)) - -export const fastMsgIdFn = (msg: RPC.IMessage): string => - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error @chainsafe/as-sha256 types are wrong - msg.data != null ? messageIdToString(SHA256.default.digest(msg.data)) : '0' diff --git a/packages/pubsub-gossipsub/tsconfig.json b/packages/pubsub-gossipsub/tsconfig.json deleted file mode 100644 index 7b54bcf41f..0000000000 --- a/packages/pubsub-gossipsub/tsconfig.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "extends": "aegir/src/config/tsconfig.aegir.json", - "compilerOptions": { - "outDir": "dist", - "allowJs": false, - "checkJs": false - }, - "include": [ - "src", - "test" - ], - "references": [ - { - "path": "../crypto" - }, - { - "path": "../interface" - }, - { - "path": "../interface-compliance-tests" - }, - { - "path": "../interface-internal" - }, - { - "path": "../logger" - }, - { - "path": "../peer-id" - }, - { - "path": "../peer-id-factory" - }, - { - "path": "../peer-store" - }, - { - "path": "../pubsub" - }, - { - "path": "../pubsub-floodsub" - } - ] -} diff --git a/packages/pubsub-gossipsub/typedoc.json b/packages/pubsub-gossipsub/typedoc.json deleted file mode 100644 index 1a59579750..0000000000 --- a/packages/pubsub-gossipsub/typedoc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "entryPoints": [ - "./src/index.ts", - "./src/message/index.ts", - "./src/metrics.ts", - "./src/score/index.ts", - "./src/types.ts" - ] -} diff --git a/packages/pubsub/CHANGELOG.md b/packages/pubsub/CHANGELOG.md index a8b736085a..a44be7e971 100644 --- a/packages/pubsub/CHANGELOG.md +++ b/packages/pubsub/CHANGELOG.md @@ -5,6 +5,13 @@ * **dev:** bump delay from 5.0.0 to 6.0.0 ([#144](https://github.com/libp2p/js-libp2p-pubsub/issues/144)) ([1364ce4](https://github.com/libp2p/js-libp2p-pubsub/commit/1364ce41815d3392cfca61169e113cc5414ac2d9)) +### [8.0.1](https://www.github.com/libp2p/js-libp2p/compare/pubsub-v8.0.0...pubsub-v8.0.1) (2023-08-01) + + +### Bug Fixes + +* update package config ([#1919](https://www.github.com/libp2p/js-libp2p/issues/1919)) ([8d49602](https://www.github.com/libp2p/js-libp2p/commit/8d49602fb6f0c906f1920d397ff28705bb0bc845)) + ## [7.0.2](https://github.com/libp2p/js-libp2p-pubsub/compare/v7.0.1...v7.0.2) (2023-06-27) diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json index d234929d82..2e58e3259f 100644 --- a/packages/pubsub/package.json +++ b/packages/pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/pubsub", - "version": "7.0.3", + "version": "8.0.1", "description": "libp2p pubsub base class", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/pubsub#readme", @@ -61,11 +61,7 @@ "extends": "ipfs", "parserOptions": { "sourceType": "module" - }, - "ignorePatterns": [ - "test/message/*.d.ts", - "test/message/*.js" - ] + } }, "scripts": { "clean": "aegir clean", @@ -82,12 +78,12 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/crypto": "^1.0.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/interface-internal": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/peer-collections": "^3.0.0", - "@libp2p/peer-id": "^2.0.0", + "@libp2p/crypto": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/interface-internal": "^0.1.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/peer-collections": "^4.0.0", + "@libp2p/peer-id": "^3.0.0", "abortable-iterator": "^5.0.1", "it-length-prefixed": "^9.0.1", "it-pipe": "^3.0.1", @@ -98,7 +94,7 @@ "uint8arrays": "^4.0.4" }, "devDependencies": { - "@libp2p/peer-id-factory": "^2.0.0", + "@libp2p/peer-id-factory": "^3.0.0", "@types/sinon": "^10.0.15", "aegir": "^40.0.1", "delay": "^6.0.0", diff --git a/packages/stream-multiplexer-mplex/CHANGELOG.md b/packages/stream-multiplexer-mplex/CHANGELOG.md index 9018e4926a..77e59ed521 100644 --- a/packages/stream-multiplexer-mplex/CHANGELOG.md +++ b/packages/stream-multiplexer-mplex/CHANGELOG.md @@ -12,6 +12,35 @@ * **dev:** bump cborg from 1.10.2 to 2.0.1 ([#282](https://github.com/libp2p/js-libp2p-mplex/issues/282)) ([4dbc590](https://github.com/libp2p/js-libp2p-mplex/commit/4dbc590d1ac92581fe2e937757567eef3854acf4)) * **dev:** bump delay from 5.0.0 to 6.0.0 ([#281](https://github.com/libp2p/js-libp2p-mplex/issues/281)) ([1e03e75](https://github.com/libp2p/js-libp2p-mplex/commit/1e03e75369722be9872f747cd83f555bc08d49fe)) +## [9.0.0](https://www.github.com/libp2p/js-libp2p/compare/mplex-v8.0.4...mplex-v9.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* the `.close`, `closeRead` and `closeWrite` methods on the `Stream` interface are now asynchronous +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* close streams gracefully ([#1864](https://www.github.com/libp2p/js-libp2p/issues/1864)) ([b36ec7f](https://www.github.com/libp2p/js-libp2p/commit/b36ec7f24e477af21cec31effc086a6c611bf271)), closes [#1793](https://www.github.com/libp2p/js-libp2p/issues/1793) [#656](https://www.github.com/libp2p/js-libp2p/issues/656) +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^3.0.0 to ^4.0.0 + ## [8.0.3](https://github.com/libp2p/js-libp2p-mplex/compare/v8.0.2...v8.0.3) (2023-05-17) @@ -762,4 +791,4 @@ Signed-off-by: Alan Shaw -# 0.1.0 (2016-03-07) +# 0.1.0 (2016-03-07) \ No newline at end of file diff --git a/packages/stream-multiplexer-mplex/package.json b/packages/stream-multiplexer-mplex/package.json index c24ee90fcf..52b168077d 100644 --- a/packages/stream-multiplexer-mplex/package.json +++ b/packages/stream-multiplexer-mplex/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/mplex", - "version": "8.0.4", + "version": "9.0.0", "description": "JavaScript implementation of https://github.com/libp2p/mplex", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/stream-multiplexer-mplex#readme", @@ -56,8 +56,8 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/logger": "^3.0.0", "abortable-iterator": "^5.0.1", "benchmark": "^2.1.4", "it-batched-bytes": "^2.0.2", @@ -69,7 +69,7 @@ "varint": "^6.0.0" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^3.0.0", + "@libp2p/interface-compliance-tests": "^4.0.0", "@types/varint": "^6.0.0", "aegir": "^40.0.1", "cborg": "^2.0.1", diff --git a/packages/stream-multiplexer-yamux/package.json b/packages/stream-multiplexer-yamux/package.json deleted file mode 100644 index 131b7e3f4b..0000000000 --- a/packages/stream-multiplexer-yamux/package.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "name": "@chainsafe/libp2p-yamux", - "version": "4.0.2", - "description": "Yamux stream multiplexer for libp2p", - "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/stream-multiplexer-yamux#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/libp2p/js-libp2p.git" - }, - "bugs": { - "url": "https://github.com/libp2p/js-libp2p/issues" - }, - "keywords": [ - "IPFS", - "libp2p", - "multiplexer", - "muxer", - "stream" - ], - "type": "module", - "types": "./dist/src/index.d.ts", - "typesVersions": { - "*": { - "*": [ - "*", - "dist/*", - "dist/src/*", - "dist/src/*/index" - ], - "src/*": [ - "*", - "dist/*", - "dist/src/*", - "dist/src/*/index" - ] - } - }, - "files": [ - "src", - "dist", - "!dist/test", - "!**/*.tsbuildinfo" - ], - "exports": { - ".": { - "types": "./dist/src/index.d.ts", - "import": "./dist/src/index.js" - }, - "./config": { - "types": "./dist/src/config.d.ts", - "import": "./dist/src/config.js" - }, - "./stream": { - "types": "./dist/src/stream.d.ts", - "import": "./dist/src/stream.js" - } - }, - "eslintConfig": { - "extends": "ipfs", - "parserOptions": { - "sourceType": "module" - }, - "ignorePatterns": [ - "src/*.d.ts" - ] - }, - "release": { - "branches": [ - "master" - ], - "plugins": [ - [ - "@semantic-release/commit-analyzer", - { - "preset": "conventionalcommits", - "releaseRules": [ - { - "breaking": true, - "release": "major" - }, - { - "revert": true, - "release": "patch" - }, - { - "type": "feat", - "release": "minor" - }, - { - "type": "fix", - "release": "patch" - }, - { - "type": "docs", - "release": "patch" - }, - { - "type": "test", - "release": "patch" - }, - { - "type": "deps", - "release": "patch" - }, - { - "scope": "no-release", - "release": false - } - ] - } - ], - [ - "@semantic-release/release-notes-generator", - { - "preset": "conventionalcommits", - "presetConfig": { - "types": [ - { - "type": "feat", - "section": "Features" - }, - { - "type": "fix", - "section": "Bug Fixes" - }, - { - "type": "chore", - "section": "Trivial Changes" - }, - { - "type": "docs", - "section": "Documentation" - }, - { - "type": "deps", - "section": "Dependencies" - }, - { - "type": "test", - "section": "Tests" - } - ] - } - } - ], - "@semantic-release/changelog", - "@semantic-release/npm", - "@semantic-release/github", - "@semantic-release/git" - ] - }, - "scripts": { - "clean": "aegir clean", - "lint": "aegir lint", - "dep-check": "aegir dep-check", - "benchmark": "benchmark dist/test/bench/*.bench.js --timeout 400000", - "build": "aegir build", - "test": "aegir test", - "test:chrome": "aegir test -t browser", - "test:chrome-webworker": "aegir test -t webworker", - "test:node": "aegir test -t node --cov", - "test:electron-main": "aegir test -t electron-main", - "release": "aegir release", - "docs": "aegir docs" - }, - "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "abortable-iterator": "^5.0.1", - "it-foreach": "^2.0.3", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.0", - "uint8arraylist": "^2.4.3" - }, - "devDependencies": { - "@dapplion/benchmark": "^0.2.4", - "@libp2p/interface-compliance-tests": "^3.0.0", - "@libp2p/mplex": "^8.0.0", - "aegir": "^40.0.1", - "it-drain": "^3.0.2", - "it-pair": "^2.0.6", - "it-stream-types": "^2.0.1" - }, - "browser": {}, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - }, - "private": true -} diff --git a/packages/stream-multiplexer-yamux/src/config.ts b/packages/stream-multiplexer-yamux/src/config.ts deleted file mode 100644 index 887e461f5e..0000000000 --- a/packages/stream-multiplexer-yamux/src/config.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { CodeError } from '@libp2p/interface/errors' -import { logger, type Logger } from '@libp2p/logger' -import { ERR_INVALID_CONFIG, INITIAL_STREAM_WINDOW, MAX_STREAM_WINDOW } from './constants.js' - -// TOOD use config items or delete them -export interface Config { - /** - * Used to control the log destination - * - * It can be disabled by explicitly setting to `undefined` - */ - log?: Logger - - /** - * Used to do periodic keep alive messages using a ping. - */ - enableKeepAlive: boolean - - /** - * How often to perform the keep alive - * - * measured in milliseconds - */ - keepAliveInterval: number - - /** - * Maximum number of concurrent inbound streams that we accept. - * If the peer tries to open more streams, those will be reset immediately. - */ - maxInboundStreams: number - - /** - * Maximum number of concurrent outbound streams that we accept. - * If the application tries to open more streams, the call to `newStream` will throw - */ - maxOutboundStreams: number - - /** - * Used to control the initial window size that we allow for a stream. - * - * measured in bytes - */ - initialStreamWindowSize: number - - /** - * Used to control the maximum window size that we allow for a stream. - */ - maxStreamWindowSize: number - - /** - * Maximum size of a message that we'll send on a stream. - * This ensures that a single stream doesn't hog a connection. - */ - maxMessageSize: number -} - -export const defaultConfig: Config = { - log: logger('libp2p:yamux'), - enableKeepAlive: true, - keepAliveInterval: 30_000, - maxInboundStreams: 1_000, - maxOutboundStreams: 1_000, - initialStreamWindowSize: INITIAL_STREAM_WINDOW, - maxStreamWindowSize: MAX_STREAM_WINDOW, - maxMessageSize: 64 * 1024 -} - -export function verifyConfig (config: Config): void { - if (config.keepAliveInterval <= 0) { - throw new CodeError('keep-alive interval must be positive', ERR_INVALID_CONFIG) - } - if (config.maxInboundStreams < 0) { - throw new CodeError('max inbound streams must be larger or equal 0', ERR_INVALID_CONFIG) - } - if (config.maxOutboundStreams < 0) { - throw new CodeError('max outbound streams must be larger or equal 0', ERR_INVALID_CONFIG) - } - if (config.initialStreamWindowSize < INITIAL_STREAM_WINDOW) { - throw new CodeError('InitialStreamWindowSize must be larger or equal 256 kB', ERR_INVALID_CONFIG) - } - if (config.maxStreamWindowSize < config.initialStreamWindowSize) { - throw new CodeError('MaxStreamWindowSize must be larger than the InitialStreamWindowSize', ERR_INVALID_CONFIG) - } - if (config.maxStreamWindowSize > 2 ** 32 - 1) { - throw new CodeError('MaxStreamWindowSize must be less than equal MAX_UINT32', ERR_INVALID_CONFIG) - } - if (config.maxMessageSize < 1024) { - throw new CodeError('MaxMessageSize must be greater than a kilobyte', ERR_INVALID_CONFIG) - } -} diff --git a/packages/stream-multiplexer-yamux/src/constants.ts b/packages/stream-multiplexer-yamux/src/constants.ts deleted file mode 100644 index d288300081..0000000000 --- a/packages/stream-multiplexer-yamux/src/constants.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Protocol violation errors - -export const ERR_INVALID_FRAME = 'ERR_INVALID_FRAME' -export const ERR_UNREQUESTED_PING = 'ERR_UNREQUESTED_PING' -export const ERR_NOT_MATCHING_PING = 'ERR_NOT_MATCHING_PING' -export const ERR_STREAM_ALREADY_EXISTS = 'ERR_STREAM_ALREADY_EXISTS' -export const ERR_DECODE_INVALID_VERSION = 'ERR_DECODE_INVALID_VERSION' -export const ERR_BOTH_CLIENTS = 'ERR_BOTH_CLIENTS' -export const ERR_RECV_WINDOW_EXCEEDED = 'ERR_RECV_WINDOW_EXCEEDED' - -export const PROTOCOL_ERRORS = new Set([ - ERR_INVALID_FRAME, - ERR_UNREQUESTED_PING, - ERR_NOT_MATCHING_PING, - ERR_STREAM_ALREADY_EXISTS, - ERR_DECODE_INVALID_VERSION, - ERR_BOTH_CLIENTS, - ERR_RECV_WINDOW_EXCEEDED -]) - -// local errors - -export const ERR_INVALID_CONFIG = 'ERR_INVALID_CONFIG' -export const ERR_MUXER_LOCAL_CLOSED = 'ERR_MUXER_LOCAL_CLOSED' -export const ERR_MUXER_REMOTE_CLOSED = 'ERR_MUXER_REMOTE_CLOSED' -export const ERR_STREAM_RESET = 'ERR_STREAM_RESET' -export const ERR_STREAM_ABORT = 'ERR_STREAM_ABORT' -export const ERR_MAX_OUTBOUND_STREAMS_EXCEEDED = 'ERROR_MAX_OUTBOUND_STREAMS_EXCEEDED' -export const ERR_DECODE_IN_PROGRESS = 'ERR_DECODE_IN_PROGRESS' - -/** - * INITIAL_STREAM_WINDOW is the initial stream window size. - * - * Not an implementation choice, this is defined in the specification - */ -export const INITIAL_STREAM_WINDOW = 256 * 1024 - -/** - * Default max stream window - */ -export const MAX_STREAM_WINDOW = 16 * 1024 * 1024 diff --git a/packages/stream-multiplexer-yamux/src/decode.ts b/packages/stream-multiplexer-yamux/src/decode.ts deleted file mode 100644 index 8433f8f2c6..0000000000 --- a/packages/stream-multiplexer-yamux/src/decode.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { CodeError } from '@libp2p/interface/errors' -import { Uint8ArrayList } from 'uint8arraylist' -import { ERR_DECODE_INVALID_VERSION, ERR_DECODE_IN_PROGRESS } from './constants.js' -import { type FrameHeader, FrameType, HEADER_LENGTH, YAMUX_VERSION } from './frame.js' -import type { Source } from 'it-stream-types' - -// used to bitshift in decoding -// native bitshift can overflow into a negative number, so we bitshift by multiplying by a power of 2 -const twoPow24 = 2 ** 24 - -/** - * Decode a header from the front of a buffer - * - * @param data - Assumed to have enough bytes for a header - */ -export function decodeHeader (data: Uint8Array): FrameHeader { - if (data[0] !== YAMUX_VERSION) { - throw new CodeError('Invalid frame version', ERR_DECODE_INVALID_VERSION) - } - return { - type: data[1], - flag: (data[2] << 8) + data[3], - streamID: (data[4] * twoPow24) + (data[5] << 16) + (data[6] << 8) + data[7], - length: (data[8] * twoPow24) + (data[9] << 16) + (data[10] << 8) + data[11] - } -} - -/** - * Decodes yamux frames from a source - */ -export class Decoder { - private readonly source: Source - /** Buffer for in-progress frames */ - private readonly buffer: Uint8ArrayList - /** Used to sanity check against decoding while in an inconsistent state */ - private frameInProgress: boolean - - constructor (source: Source) { - // Normally, when entering a for-await loop with an iterable/async iterable, the only ways to exit the loop are: - // 1. exhaust the iterable - // 2. throw an error - slow, undesirable if there's not actually an error - // 3. break or return - calls the iterable's `return` method, finalizing the iterable, no more iteration possible - // - // In this case, we want to enter (and exit) a for-await loop per chunked data frame and continue processing the iterable. - // To do this, we strip the `return` method from the iterator and can now `break` early and continue iterating. - // Exiting the main for-await is still possible via 1. and 2. - this.source = returnlessSource(source) - this.buffer = new Uint8ArrayList() - this.frameInProgress = false - } - - /** - * Emits frames from the decoder source. - * - * Note: If `readData` is emitted, it _must_ be called before the next iteration - * Otherwise an error is thrown - */ - async * emitFrames (): AsyncGenerator<{ header: FrameHeader, readData?: () => Promise }> { - for await (const chunk of this.source) { - this.buffer.append(chunk) - - // Loop to consume as many bytes from the buffer as possible - // Eg: when a single chunk contains several frames - while (true) { - const header = this.readHeader() - if (header === undefined) { - break - } - - const { type, length } = header - if (type === FrameType.Data) { - // This is a data frame, the frame body must still be read - // `readData` must be called before the next iteration here - this.frameInProgress = true - yield { - header, - readData: this.readBytes.bind(this, length) - } - } else { - yield { header } - } - } - } - } - - private readHeader (): FrameHeader | undefined { - // Sanity check to ensure a header isn't read when another frame is partially decoded - // In practice this shouldn't happen - if (this.frameInProgress) { - throw new CodeError('decoding frame already in progress', ERR_DECODE_IN_PROGRESS) - } - - if (this.buffer.length < HEADER_LENGTH) { - // not enough data yet - return - } - - const header = decodeHeader(this.buffer.subarray(0, HEADER_LENGTH)) - this.buffer.consume(HEADER_LENGTH) - return header - } - - private async readBytes (length: number): Promise { - if (this.buffer.length < length) { - for await (const chunk of this.source) { - this.buffer.append(chunk) - - if (this.buffer.length >= length) { - // see note above, the iterator is not `return`ed here - break - } - } - } - - const out = this.buffer.sublist(0, length) - this.buffer.consume(length) - - // The next frame can now be decoded - this.frameInProgress = false - - return out - } -} - -/** - * Strip the `return` method from a `Source` - */ -export function returnlessSource (source: Source): Source { - if ((source as Iterable)[Symbol.iterator] !== undefined) { - const iterator = (source as Iterable)[Symbol.iterator]() - iterator.return = undefined - return { - [Symbol.iterator] () { return iterator } - } - } else if ((source as AsyncIterable)[Symbol.asyncIterator] !== undefined) { - const iterator = (source as AsyncIterable)[Symbol.asyncIterator]() - iterator.return = undefined - return { - [Symbol.asyncIterator] () { return iterator } - } - } else { - throw new Error('a source must be either an iterable or an async iterable') - } -} diff --git a/packages/stream-multiplexer-yamux/src/encode.ts b/packages/stream-multiplexer-yamux/src/encode.ts deleted file mode 100644 index 6353c00916..0000000000 --- a/packages/stream-multiplexer-yamux/src/encode.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { HEADER_LENGTH } from './frame.js' -import type { FrameHeader } from './frame.js' - -export function encodeHeader (header: FrameHeader): Uint8Array { - const frame = new Uint8Array(HEADER_LENGTH) - - // always assume version 0 - // frameView.setUint8(0, header.version) - - frame[1] = header.type - - frame[2] = header.flag >>> 8 - frame[3] = header.flag - - frame[4] = header.streamID >>> 24 - frame[5] = header.streamID >>> 16 - frame[6] = header.streamID >>> 8 - frame[7] = header.streamID - - frame[8] = header.length >>> 24 - frame[9] = header.length >>> 16 - frame[10] = header.length >>> 8 - frame[11] = header.length - - return frame -} diff --git a/packages/stream-multiplexer-yamux/src/frame.ts b/packages/stream-multiplexer-yamux/src/frame.ts deleted file mode 100644 index b9f41289e2..0000000000 --- a/packages/stream-multiplexer-yamux/src/frame.ts +++ /dev/null @@ -1,64 +0,0 @@ -export enum FrameType { - /** Used to transmit data. May transmit zero length payloads depending on the flags. */ - Data = 0x0, - /** Used to updated the senders receive window size. This is used to implement per-session flow control. */ - WindowUpdate = 0x1, - /** Used to measure RTT. It can also be used to heart-beat and do keep-alives over TCP. */ - Ping = 0x2, - /** Used to close a session. */ - GoAway = 0x3, -} - -export enum Flag { - /** Signals the start of a new stream. May be sent with a data or window update message. Also sent with a ping to indicate outbound. */ - SYN = 0x1, - /** Acknowledges the start of a new stream. May be sent with a data or window update message. Also sent with a ping to indicate response. */ - ACK = 0x2, - /** Performs a half-close of a stream. May be sent with a data message or window update. */ - FIN = 0x4, - /** Reset a stream immediately. May be sent with a data or window update message. */ - RST = 0x8, -} - -const flagCodes = Object.values(Flag).filter((x) => typeof x !== 'string') as Flag[] - -export const YAMUX_VERSION = 0 - -export enum GoAwayCode { - NormalTermination = 0x0, - ProtocolError = 0x1, - InternalError = 0x2, -} - -export const HEADER_LENGTH = 12 - -export interface FrameHeader { - /** - * The version field is used for future backward compatibility. - * At the current time, the field is always set to 0, to indicate the initial version. - */ - version?: number - /** The type field is used to switch the frame message type. */ - type: FrameType - /** The flags field is used to provide additional information related to the message type. */ - flag: number - /** - * The StreamID field is used to identify the logical stream the frame is addressing. - * The client side should use odd ID's, and the server even. - * This prevents any collisions. Additionally, the 0 ID is reserved to represent the session. - */ - streamID: number - /** - * The meaning of the length field depends on the message type: - * * Data - provides the length of bytes following the header - * * Window update - provides a delta update to the window size - * * Ping - Contains an opaque value, echoed back - * * Go Away - Contains an error code - */ - length: number -} - -export function stringifyHeader (header: FrameHeader): string { - const flags = flagCodes.filter(f => (header.flag & f) === f).map(f => Flag[f]).join('|') - return `streamID=${header.streamID} type=${FrameType[header.type]} flag=${flags} length=${header.length}` -} diff --git a/packages/stream-multiplexer-yamux/src/index.ts b/packages/stream-multiplexer-yamux/src/index.ts deleted file mode 100644 index ade3ad883a..0000000000 --- a/packages/stream-multiplexer-yamux/src/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Yamux } from './muxer.js' -import type { YamuxMuxerInit } from './muxer.js' -import type { StreamMuxerFactory } from '@libp2p/interface/stream-muxer' -export { GoAwayCode } from './frame.js' - -export function yamux (init: YamuxMuxerInit = {}): () => StreamMuxerFactory { - return () => new Yamux(init) -} diff --git a/packages/stream-multiplexer-yamux/src/muxer.ts b/packages/stream-multiplexer-yamux/src/muxer.ts deleted file mode 100644 index 83009366b0..0000000000 --- a/packages/stream-multiplexer-yamux/src/muxer.ts +++ /dev/null @@ -1,573 +0,0 @@ -import { CodeError } from '@libp2p/interface/errors' -import { logger, type Logger } from '@libp2p/logger' -import { abortableSource } from 'abortable-iterator' -import { pipe } from 'it-pipe' -import { pushable, type Pushable } from 'it-pushable' -import { type Config, defaultConfig, verifyConfig } from './config.js' -import { ERR_BOTH_CLIENTS, ERR_INVALID_FRAME, ERR_MAX_OUTBOUND_STREAMS_EXCEEDED, ERR_MUXER_LOCAL_CLOSED, ERR_MUXER_REMOTE_CLOSED, ERR_NOT_MATCHING_PING, ERR_STREAM_ALREADY_EXISTS, ERR_UNREQUESTED_PING, PROTOCOL_ERRORS } from './constants.js' -import { Decoder } from './decode.js' -import { encodeHeader } from './encode.js' -import { Flag, type FrameHeader, FrameType, GoAwayCode, stringifyHeader } from './frame.js' -import { StreamState, YamuxStream } from './stream.js' -import type { AbortOptions } from '@libp2p/interface' -import type { Stream } from '@libp2p/interface/connection' -import type { StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interface/stream-muxer' -import type { Sink, Source } from 'it-stream-types' -import type { Uint8ArrayList } from 'uint8arraylist' - -const YAMUX_PROTOCOL_ID = '/yamux/1.0.0' -const CLOSE_TIMEOUT = 500 - -export interface YamuxMuxerInit extends StreamMuxerInit, Partial { -} - -export class Yamux implements StreamMuxerFactory { - protocol = YAMUX_PROTOCOL_ID - private readonly _init: YamuxMuxerInit - - constructor (init: YamuxMuxerInit = {}) { - this._init = init - } - - createStreamMuxer (init?: YamuxMuxerInit): YamuxMuxer { - return new YamuxMuxer({ - ...this._init, - ...init - }) - } -} - -export interface CloseOptions extends AbortOptions { - reason?: GoAwayCode -} - -export class YamuxMuxer implements StreamMuxer { - protocol = YAMUX_PROTOCOL_ID - source: Pushable - sink: Sink, Promise> - - private readonly config: Config - private readonly log?: Logger - - /** Used to close the muxer from either the sink or source */ - private readonly closeController: AbortController - - /** The next stream id to be used when initiating a new stream */ - private nextStreamID: number - /** Primary stream mapping, streamID => stream */ - private readonly _streams: Map - - /** The next ping id to be used when pinging */ - private nextPingID: number - /** Tracking info for the currently active ping */ - private activePing?: { id: number, promise: Promise, resolve: () => void } - /** Round trip time */ - private rtt: number - - /** True if client, false if server */ - private readonly client: boolean - - private localGoAway?: GoAwayCode - private remoteGoAway?: GoAwayCode - - /** Number of tracked inbound streams */ - private numInboundStreams: number - /** Number of tracked outbound streams */ - private numOutboundStreams: number - - private readonly onIncomingStream?: (stream: Stream) => void - private readonly onStreamEnd?: (stream: Stream) => void - - constructor (init: YamuxMuxerInit) { - this.client = init.direction === 'outbound' - this.config = { ...defaultConfig, ...init } - this.log = this.config.log - verifyConfig(this.config) - - this.closeController = new AbortController() - - this.onIncomingStream = init.onIncomingStream - this.onStreamEnd = init.onStreamEnd - - this._streams = new Map() - - this.source = pushable({ - onEnd: (): void => { - this.log?.trace('muxer source ended') - - this._streams.forEach(stream => { - stream.destroy() - }) - } - }) - - this.sink = async (source: Source): Promise => { - source = abortableSource( - source, - this.closeController.signal, - { returnOnAbort: true } - ) - - let reason, error - try { - const decoder = new Decoder(source) - await pipe( - decoder.emitFrames.bind(decoder), - async source => { - for await (const { header, readData } of source) { - await this.handleFrame(header, readData) - } - } - ) - - reason = GoAwayCode.NormalTermination - } catch (err: unknown) { - // either a protocol or internal error - const errCode = (err as { code: string }).code - if (PROTOCOL_ERRORS.has(errCode)) { - this.log?.error('protocol error in sink', err) - reason = GoAwayCode.ProtocolError - } else { - this.log?.error('internal error in sink', err) - reason = GoAwayCode.InternalError - } - - error = err as Error - } - - this.log?.trace('muxer sink ended') - - if (error != null) { - this.abort(error, reason) - } else { - await this.close({ reason }) - } - } - - this.numInboundStreams = 0 - this.numOutboundStreams = 0 - - // client uses odd streamIDs, server uses even streamIDs - this.nextStreamID = this.client ? 1 : 2 - - this.nextPingID = 0 - this.rtt = 0 - - this.log?.trace('muxer created') - - if (this.config.enableKeepAlive) { - this.keepAliveLoop().catch(e => this.log?.error('keepalive error: %s', e)) - } - } - - get streams (): YamuxStream[] { - return Array.from(this._streams.values()) - } - - newStream (name?: string | undefined): YamuxStream { - if (this.remoteGoAway !== undefined) { - throw new CodeError('muxer closed remotely', ERR_MUXER_REMOTE_CLOSED) - } - if (this.localGoAway !== undefined) { - throw new CodeError('muxer closed locally', ERR_MUXER_LOCAL_CLOSED) - } - - const id = this.nextStreamID - this.nextStreamID += 2 - - // check against our configured maximum number of outbound streams - if (this.numOutboundStreams >= this.config.maxOutboundStreams) { - throw new CodeError('max outbound streams exceeded', ERR_MAX_OUTBOUND_STREAMS_EXCEEDED) - } - - this.log?.trace('new outgoing stream id=%s', id) - - const stream = this._newStream(id, name, StreamState.Init, 'outbound') - this._streams.set(id, stream) - - this.numOutboundStreams++ - - // send a window update to open the stream on the receiver end - stream.sendWindowUpdate() - - return stream - } - - /** - * Initiate a ping and wait for a response - * - * Note: only a single ping will be initiated at a time. - * If a ping is already in progress, a new ping will not be initiated. - * - * @returns the round-trip-time in milliseconds - */ - async ping (): Promise { - if (this.remoteGoAway !== undefined) { - throw new CodeError('muxer closed remotely', ERR_MUXER_REMOTE_CLOSED) - } - if (this.localGoAway !== undefined) { - throw new CodeError('muxer closed locally', ERR_MUXER_LOCAL_CLOSED) - } - - // An active ping does not yet exist, handle the process here - if (this.activePing === undefined) { - // create active ping - let _resolve = (): void => {} - this.activePing = { - id: this.nextPingID++, - // this promise awaits resolution or the close controller aborting - promise: new Promise((resolve, reject) => { - const closed = (): void => { - reject(new CodeError('muxer closed locally', ERR_MUXER_LOCAL_CLOSED)) - } - this.closeController.signal.addEventListener('abort', closed, { once: true }) - _resolve = (): void => { - this.closeController.signal.removeEventListener('abort', closed) - resolve() - } - }), - resolve: _resolve - } - // send ping - const start = Date.now() - this.sendPing(this.activePing.id) - // await pong - try { - await this.activePing.promise - } finally { - // clean-up active ping - delete this.activePing - } - // update rtt - const end = Date.now() - this.rtt = end - start - } else { - // an active ping is already in progress, piggyback off that - await this.activePing.promise - } - return this.rtt - } - - /** - * Get the ping round trip time - * - * Note: Will return 0 if no successful ping has yet been completed - * - * @returns the round-trip-time in milliseconds - */ - getRTT (): number { - return this.rtt - } - - /** - * Close the muxer - */ - async close (options: CloseOptions = {}): Promise { - if (this.closeController.signal.aborted) { - // already closed - return - } - - const reason = options?.reason ?? GoAwayCode.NormalTermination - - this.log?.trace('muxer close reason=%s', reason) - - options.signal = options.signal ?? AbortSignal.timeout(CLOSE_TIMEOUT) - - try { - // If err is provided, abort all underlying streams, else close all underlying streams - await Promise.all( - [...this._streams.values()].map(async s => s.close(options)) - ) - - // send reason to the other side, allow the other side to close gracefully - this.sendGoAway(reason) - - this._closeMuxer() - } catch (err: any) { - this.abort(err) - } - } - - abort (err: Error, reason?: GoAwayCode): void { - if (this.closeController.signal.aborted) { - // already closed - return - } - - reason = reason ?? GoAwayCode.InternalError - - // If reason was provided, use that, otherwise use the presence of `err` to determine the reason - this.log?.error('muxer abort reason=%s error=%s', reason, err) - - // Abort all underlying streams - for (const stream of this._streams.values()) { - stream.abort(err) - } - - // send reason to the other side, allow the other side to close gracefully - this.sendGoAway(reason) - - this._closeMuxer() - } - - isClosed (): boolean { - return this.closeController.signal.aborted - } - - /** - * Called when either the local or remote shuts down the muxer - */ - private _closeMuxer (): void { - // stop the sink and any other processes - this.closeController.abort() - - // stop the source - this.source.end() - } - - /** Create a new stream */ - private _newStream (id: number, name: string | undefined, state: StreamState, direction: 'inbound' | 'outbound'): YamuxStream { - if (this._streams.get(id) != null) { - throw new CodeError('Stream already exists', ERR_STREAM_ALREADY_EXISTS, { id }) - } - - const stream = new YamuxStream({ - id: id.toString(), - name, - state, - direction, - sendFrame: this.sendFrame.bind(this), - onEnd: () => { - this.closeStream(id) - this.onStreamEnd?.(stream) - }, - log: logger(`libp2p:yamux:${direction}:${id}`), - config: this.config, - getRTT: this.getRTT.bind(this) - }) - - return stream - } - - /** - * closeStream is used to close a stream once both sides have - * issued a close. - */ - private closeStream (id: number): void { - if (this.client === (id % 2 === 0)) { - this.numInboundStreams-- - } else { - this.numOutboundStreams-- - } - this._streams.delete(id) - } - - private async keepAliveLoop (): Promise { - const abortPromise = new Promise((_resolve, reject) => { this.closeController.signal.addEventListener('abort', reject, { once: true }) }) - this.log?.trace('muxer keepalive enabled interval=%s', this.config.keepAliveInterval) - while (true) { - let timeoutId - try { - await Promise.race([ - abortPromise, - new Promise((resolve) => { - timeoutId = setTimeout(resolve, this.config.keepAliveInterval) - }) - ]) - this.ping().catch(e => this.log?.error('ping error: %s', e)) - } catch (e) { - // closed - clearInterval(timeoutId) - return - } - } - } - - private async handleFrame (header: FrameHeader, readData?: () => Promise): Promise { - const { - streamID, - type, - length - } = header - this.log?.trace('received frame %s', stringifyHeader(header)) - - if (streamID === 0) { - switch (type) { - case FrameType.Ping: - { this.handlePing(header); return } - case FrameType.GoAway: - { this.handleGoAway(length); return } - default: - // Invalid state - throw new CodeError('Invalid frame type', ERR_INVALID_FRAME, { header }) - } - } else { - switch (header.type) { - case FrameType.Data: - case FrameType.WindowUpdate: - { await this.handleStreamMessage(header, readData); return } - default: - // Invalid state - throw new CodeError('Invalid frame type', ERR_INVALID_FRAME, { header }) - } - } - } - - private handlePing (header: FrameHeader): void { - // If the ping is initiated by the sender, send a response - if (header.flag === Flag.SYN) { - this.log?.trace('received ping request pingId=%s', header.length) - this.sendPing(header.length, Flag.ACK) - } else if (header.flag === Flag.ACK) { - this.log?.trace('received ping response pingId=%s', header.length) - this.handlePingResponse(header.length) - } else { - // Invalid state - throw new CodeError('Invalid frame flag', ERR_INVALID_FRAME, { header }) - } - } - - private handlePingResponse (pingId: number): void { - if (this.activePing === undefined) { - // this ping was not requested - throw new CodeError('ping not requested', ERR_UNREQUESTED_PING) - } - if (this.activePing.id !== pingId) { - // this ping doesn't match our active ping request - throw new CodeError('ping doesn\'t match our id', ERR_NOT_MATCHING_PING) - } - - // valid ping response - this.activePing.resolve() - } - - private handleGoAway (reason: GoAwayCode): void { - this.log?.trace('received GoAway reason=%s', GoAwayCode[reason] ?? 'unknown') - this.remoteGoAway = reason - - // If the other side is friendly, they would have already closed all streams before sending a GoAway - // In case they weren't, reset all streams - for (const stream of this._streams.values()) { - stream.reset() - } - - this._closeMuxer() - } - - private async handleStreamMessage (header: FrameHeader, readData?: () => Promise): Promise { - const { streamID, flag, type } = header - - if ((flag & Flag.SYN) === Flag.SYN) { - this.incomingStream(streamID) - } - - const stream = this._streams.get(streamID) - if (stream === undefined) { - if (type === FrameType.Data) { - this.log?.('discarding data for stream id=%s', streamID) - if (readData === undefined) { - throw new Error('unreachable') - } - await readData() - } else { - this.log?.('frame for missing stream id=%s', streamID) - } - return - } - - switch (type) { - case FrameType.WindowUpdate: { - stream.handleWindowUpdate(header); return - } - case FrameType.Data: { - if (readData === undefined) { - throw new Error('unreachable') - } - - await stream.handleData(header, readData); return - } - default: - throw new Error('unreachable') - } - } - - private incomingStream (id: number): void { - if (this.client !== (id % 2 === 0)) { - throw new CodeError('both endpoints are clients', ERR_BOTH_CLIENTS) - } - if (this._streams.has(id)) { - return - } - - this.log?.trace('new incoming stream id=%s', id) - - if (this.localGoAway !== undefined) { - // reject (reset) immediately if we are doing a go away - this.sendFrame({ - type: FrameType.WindowUpdate, - flag: Flag.RST, - streamID: id, - length: 0 - }); return - } - - // check against our configured maximum number of inbound streams - if (this.numInboundStreams >= this.config.maxInboundStreams) { - this.log?.('maxIncomingStreams exceeded, forcing stream reset') - this.sendFrame({ - type: FrameType.WindowUpdate, - flag: Flag.RST, - streamID: id, - length: 0 - }); return - } - - // allocate a new stream - const stream = this._newStream(id, undefined, StreamState.SYNReceived, 'inbound') - - this.numInboundStreams++ - // the stream should now be tracked - this._streams.set(id, stream) - - this.onIncomingStream?.(stream) - } - - private sendFrame (header: FrameHeader, data?: Uint8Array): void { - this.log?.trace('sending frame %s', stringifyHeader(header)) - if (header.type === FrameType.Data) { - if (data === undefined) { - throw new CodeError('invalid frame', ERR_INVALID_FRAME) - } - this.source.push(encodeHeader(header)) - this.source.push(data) - } else { - this.source.push(encodeHeader(header)) - } - } - - private sendPing (pingId: number, flag: Flag = Flag.SYN): void { - if (flag === Flag.SYN) { - this.log?.trace('sending ping request pingId=%s', pingId) - } else { - this.log?.trace('sending ping response pingId=%s', pingId) - } - this.sendFrame({ - type: FrameType.Ping, - flag, - streamID: 0, - length: pingId - }) - } - - private sendGoAway (reason: GoAwayCode = GoAwayCode.NormalTermination): void { - this.log?.('sending GoAway reason=%s', GoAwayCode[reason]) - this.localGoAway = reason - this.sendFrame({ - type: FrameType.GoAway, - flag: 0, - streamID: 0, - length: reason - }) - } -} diff --git a/packages/stream-multiplexer-yamux/src/stream.ts b/packages/stream-multiplexer-yamux/src/stream.ts deleted file mode 100644 index 20e89094de..0000000000 --- a/packages/stream-multiplexer-yamux/src/stream.ts +++ /dev/null @@ -1,328 +0,0 @@ -import { CodeError } from '@libp2p/interface/errors' -import { AbstractStream, type AbstractStreamInit } from '@libp2p/interface/stream-muxer/stream' -import each from 'it-foreach' -import { ERR_RECV_WINDOW_EXCEEDED, ERR_STREAM_ABORT, INITIAL_STREAM_WINDOW } from './constants.js' -import { Flag, type FrameHeader, FrameType, HEADER_LENGTH } from './frame.js' -import type { Config } from './config.js' -import type { AbortOptions } from '@libp2p/interface' -import type { Uint8ArrayList } from 'uint8arraylist' - -export enum StreamState { - Init, - SYNSent, - SYNReceived, - Established, - Finished, -} - -export enum HalfStreamState { - Open, - Closed, - Reset, -} - -export interface YamuxStreamInit extends AbstractStreamInit { - name?: string - sendFrame: (header: FrameHeader, body?: Uint8Array) => void - getRTT: () => number - config: Config - state: StreamState -} - -/** YamuxStream is used to represent a logical stream within a session */ -export class YamuxStream extends AbstractStream { - name?: string - - state: StreamState - /** Used to track received FIN/RST */ - readState: HalfStreamState - /** Used to track sent FIN/RST */ - writeState: HalfStreamState - - private readonly config: Config - private readonly _id: number - - /** The number of available bytes to send */ - private sendWindowCapacity: number - /** Callback to notify that the sendWindowCapacity has been updated */ - private sendWindowCapacityUpdate?: () => void - - /** The number of bytes available to receive in a full window */ - private recvWindow: number - /** The number of available bytes to receive */ - private recvWindowCapacity: number - - /** - * An 'epoch' is the time it takes to process and read data - * - * Used in conjunction with RTT to determine whether to increase the recvWindow - */ - private epochStart: number - private readonly getRTT: () => number - - private readonly sendFrame: (header: FrameHeader, body?: Uint8Array) => void - - constructor (init: YamuxStreamInit) { - super({ - ...init, - onEnd: (err?: Error) => { - this.state = StreamState.Finished - init.onEnd?.(err) - }, - onCloseRead: () => { - this.readState = HalfStreamState.Closed - }, - onCloseWrite: () => { - this.writeState = HalfStreamState.Closed - }, - onReset: () => { - this.readState = HalfStreamState.Reset - this.writeState = HalfStreamState.Reset - }, - onAbort: () => { - this.readState = HalfStreamState.Reset - this.writeState = HalfStreamState.Reset - } - }) - - this.config = init.config - this._id = parseInt(init.id, 10) - this.name = init.name - - this.state = init.state - this.readState = HalfStreamState.Open - this.writeState = HalfStreamState.Open - - this.sendWindowCapacity = INITIAL_STREAM_WINDOW - this.recvWindow = this.config.initialStreamWindowSize - this.recvWindowCapacity = this.recvWindow - this.epochStart = Date.now() - this.getRTT = init.getRTT - - this.sendFrame = init.sendFrame - - this.source = each(this.source, () => { - this.sendWindowUpdate() - }) - } - - /** - * Send a message to the remote muxer informing them a new stream is being - * opened - */ - async sendNewStream (): Promise { - - } - - /** - * Send a data message to the remote muxer - */ - async sendData (buf: Uint8ArrayList, options: AbortOptions = {}): Promise { - buf = buf.sublist() - - // send in chunks, waiting for window updates - while (buf.byteLength !== 0) { - // wait for the send window to refill - if (this.sendWindowCapacity === 0) { - await this.waitForSendWindowCapacity(options) - } - - // check we didn't close while waiting for send window capacity - if (this.status !== 'open') { - return - } - - // send as much as we can - const toSend = Math.min(this.sendWindowCapacity, this.config.maxMessageSize - HEADER_LENGTH, buf.length) - const flags = this.getSendFlags() - - this.sendFrame({ - type: FrameType.Data, - flag: flags, - streamID: this._id, - length: toSend - }, buf.subarray(0, toSend)) - - this.sendWindowCapacity -= toSend - - buf.consume(toSend) - } - } - - /** - * Send a reset message to the remote muxer - */ - async sendReset (): Promise { - this.sendFrame({ - type: FrameType.WindowUpdate, - flag: Flag.RST, - streamID: this._id, - length: 0 - }) - } - - /** - * Send a message to the remote muxer, informing them no more data messages - * will be sent by this end of the stream - */ - async sendCloseWrite (): Promise { - const flags = this.getSendFlags() | Flag.FIN - this.sendFrame({ - type: FrameType.WindowUpdate, - flag: flags, - streamID: this._id, - length: 0 - }) - } - - /** - * Send a message to the remote muxer, informing them no more data messages - * will be read by this end of the stream - */ - async sendCloseRead (): Promise { - - } - - /** - * Wait for the send window to be non-zero - * - * Will throw with ERR_STREAM_ABORT if the stream gets aborted - */ - async waitForSendWindowCapacity (options: AbortOptions = {}): Promise { - if (this.sendWindowCapacity > 0) { - return - } - - let resolve: () => void - let reject: (err: Error) => void - const abort = (): void => { - if (this.status === 'open') { - reject(new CodeError('stream aborted', ERR_STREAM_ABORT)) - } else { - // the stream was closed already, ignore the failure to send - resolve() - } - } - options.signal?.addEventListener('abort', abort) - - try { - await new Promise((_resolve, _reject) => { - this.sendWindowCapacityUpdate = () => { - _resolve() - } - reject = _reject - resolve = _resolve - }) - } finally { - options.signal?.removeEventListener('abort', abort) - } - } - - /** - * handleWindowUpdate is called when the stream receives a window update frame - */ - handleWindowUpdate (header: FrameHeader): void { - this.log?.trace('stream received window update id=%s', this._id) - this.processFlags(header.flag) - - // increase send window - const available = this.sendWindowCapacity - this.sendWindowCapacity += header.length - // if the update increments a 0 availability, notify the stream that sending can resume - if (available === 0 && header.length > 0) { - this.sendWindowCapacityUpdate?.() - } - } - - /** - * handleData is called when the stream receives a data frame - */ - async handleData (header: FrameHeader, readData: () => Promise): Promise { - this.log?.trace('stream received data id=%s', this._id) - this.processFlags(header.flag) - - // check that our recv window is not exceeded - if (this.recvWindowCapacity < header.length) { - throw new CodeError('receive window exceeded', ERR_RECV_WINDOW_EXCEEDED, { available: this.recvWindowCapacity, recv: header.length }) - } - - const data = await readData() - this.recvWindowCapacity -= header.length - - this.sourcePush(data) - } - - /** - * processFlags is used to update the state of the stream based on set flags, if any. - */ - private processFlags (flags: number): void { - if ((flags & Flag.ACK) === Flag.ACK) { - if (this.state === StreamState.SYNSent) { - this.state = StreamState.Established - } - } - if ((flags & Flag.FIN) === Flag.FIN) { - this.remoteCloseWrite() - } - if ((flags & Flag.RST) === Flag.RST) { - this.reset() - } - } - - /** - * getSendFlags determines any flags that are appropriate - * based on the current stream state. - * - * The state is updated as a side-effect. - */ - private getSendFlags (): number { - switch (this.state) { - case StreamState.Init: - this.state = StreamState.SYNSent - return Flag.SYN - case StreamState.SYNReceived: - this.state = StreamState.Established - return Flag.ACK - default: - return 0 - } - } - - /** - * potentially sends a window update enabling further writes to take place. - */ - sendWindowUpdate (): void { - // determine the flags if any - const flags = this.getSendFlags() - - // If the stream has already been established - // and we've processed data within the time it takes for 4 round trips - // then we (up to) double the recvWindow - const now = Date.now() - const rtt = this.getRTT() - if (flags === 0 && rtt > 0 && now - this.epochStart < rtt * 4) { - // we've already validated that maxStreamWindowSize can't be more than MAX_UINT32 - this.recvWindow = Math.min(this.recvWindow * 2, this.config.maxStreamWindowSize) - } - - if (this.recvWindowCapacity >= this.recvWindow && flags === 0) { - // a window update isn't needed - return - } - - // update the receive window - const delta = this.recvWindow - this.recvWindowCapacity - this.recvWindowCapacity = this.recvWindow - - // update the epoch start - this.epochStart = now - - // send window update - this.sendFrame({ - type: FrameType.WindowUpdate, - flag: flags, - streamID: this._id, - length: delta - }) - } -} diff --git a/packages/stream-multiplexer-yamux/test/bench/codec.bench.ts b/packages/stream-multiplexer-yamux/test/bench/codec.bench.ts deleted file mode 100644 index 5febc85190..0000000000 --- a/packages/stream-multiplexer-yamux/test/bench/codec.bench.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { itBench } from '@dapplion/benchmark' -import { decodeHeader } from '../../src/decode.js' -import { encodeHeader } from '../../src/encode.js' -import { Flag, type FrameHeader, FrameType } from '../../src/frame.js' -import { decodeHeaderNaive, encodeHeaderNaive } from '../codec.util.js' - -describe('codec benchmark', () => { - for (const { encode, name } of [ - { encode: encodeHeader, name: 'encodeFrameHeader' }, - { encode: encodeHeaderNaive, name: 'encodeFrameHeaderNaive' } - ]) { - itBench({ - id: `frame header - ${name}`, - timeoutBench: 100000000, - beforeEach: () => { - return { - type: FrameType.WindowUpdate, - flag: Flag.ACK, - streamID: 0xffffffff, - length: 0xffffffff - } - }, - fn: (header) => { - encode(header) - } - }) - } - - for (const { decode, name } of [ - { decode: decodeHeader, name: 'decodeHeader' }, - { decode: decodeHeaderNaive, name: 'decodeHeaderNaive' } - ]) { - itBench({ - id: `frame header ${name}`, - beforeEach: () => { - const header = new Uint8Array(12) - for (let i = 1; i < 12; i++) { - header[i] = 255 - } - return header - }, - fn: (header) => { - decode(header) - } - }) - } -}) diff --git a/packages/stream-multiplexer-yamux/test/bench/comparison.bench.ts b/packages/stream-multiplexer-yamux/test/bench/comparison.bench.ts deleted file mode 100644 index c601b3006c..0000000000 --- a/packages/stream-multiplexer-yamux/test/bench/comparison.bench.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { itBench } from '@dapplion/benchmark' -import drain from 'it-drain' -import { pipe } from 'it-pipe' -import { testClientServer as testMplexClientServer } from '../mplex.util.js' -import { testClientServer as testYamuxClientServer } from '../util.js' - -describe('comparison benchmark', () => { - for (const { impl, name } of [ - { impl: testYamuxClientServer, name: 'yamux' }, - { impl: testMplexClientServer, name: 'mplex' } - ]) { - for (const { numMessages, msgSize } of [ - { numMessages: 1, msgSize: 2 ** 6 }, - { numMessages: 1, msgSize: 2 ** 10 }, - { numMessages: 1, msgSize: 2 ** 16 }, - { numMessages: 1, msgSize: 2 ** 20 }, - { numMessages: 1000, msgSize: 2 ** 6 }, - { numMessages: 1000, msgSize: 2 ** 10 }, - { numMessages: 1000, msgSize: 2 ** 16 }, - { numMessages: 1000, msgSize: 2 ** 20 } - ]) { - itBench, undefined>({ - id: `${name} send and receive ${numMessages} ${msgSize / 1024}KB chunks`, - beforeEach: () => impl({ - onIncomingStream: (stream) => { - void pipe(stream, drain).then(async () => { await stream.close() }) - } - }), - fn: async ({ client, server }) => { - const stream = await client.newStream() - await pipe(Array.from({ length: numMessages }, () => new Uint8Array(msgSize)), stream, drain) - } - }) - } - } -}) diff --git a/packages/stream-multiplexer-yamux/test/codec.spec.ts b/packages/stream-multiplexer-yamux/test/codec.spec.ts deleted file mode 100644 index 1e84d1a63c..0000000000 --- a/packages/stream-multiplexer-yamux/test/codec.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { expect } from 'aegir/chai' -import { decodeHeader } from '../src/decode.js' -import { encodeHeader } from '../src/encode.js' -import { Flag, type FrameHeader, FrameType, GoAwayCode, stringifyHeader } from '../src/frame.js' -import { decodeHeaderNaive, encodeHeaderNaive } from './codec.util.js' - -const frames: Array<{ header: FrameHeader, data?: Uint8Array }> = [ - { header: { type: FrameType.Ping, flag: Flag.SYN, streamID: 0, length: 1 } }, - { header: { type: FrameType.WindowUpdate, flag: Flag.SYN, streamID: 1, length: 1 } }, - { header: { type: FrameType.GoAway, flag: 0, streamID: 0, length: GoAwayCode.NormalTermination } }, - { header: { type: FrameType.Ping, flag: Flag.ACK, streamID: 0, length: 100 } }, - { header: { type: FrameType.WindowUpdate, flag: 0, streamID: 99, length: 1000 } }, - { header: { type: FrameType.WindowUpdate, flag: 0, streamID: 0xffffffff, length: 0xffffffff } }, - { header: { type: FrameType.GoAway, flag: 0, streamID: 0, length: GoAwayCode.ProtocolError } } -] - -describe('codec', () => { - for (const { header } of frames) { - it(`should round trip encode/decode header ${stringifyHeader(header)}`, () => { - expect(decodeHeader(encodeHeader(header))).to.deep.equal(header) - }) - } - - for (const { header } of frames) { - it(`should match naive implementations of encode/decode for header ${stringifyHeader(header)}`, () => { - expect(encodeHeader(header)).to.deep.equal(encodeHeaderNaive(header)) - expect(decodeHeader(encodeHeader(header))).to.deep.equal(decodeHeaderNaive(encodeHeaderNaive(header))) - }) - } -}) diff --git a/packages/stream-multiplexer-yamux/test/codec.util.ts b/packages/stream-multiplexer-yamux/test/codec.util.ts deleted file mode 100644 index 088db59899..0000000000 --- a/packages/stream-multiplexer-yamux/test/codec.util.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CodeError } from '@libp2p/interface/errors' -import { ERR_DECODE_INVALID_VERSION } from '../src/constants.js' -import { type FrameHeader, HEADER_LENGTH, YAMUX_VERSION } from '../src/frame.js' - -// Slower encode / decode functions that use dataview - -export function decodeHeaderNaive (data: Uint8Array): FrameHeader { - const view = new DataView(data.buffer, data.byteOffset, data.byteLength) - - if (view.getUint8(0) !== YAMUX_VERSION) { - throw new CodeError('Invalid frame version', ERR_DECODE_INVALID_VERSION) - } - return { - type: view.getUint8(1), - flag: view.getUint16(2, false), - streamID: view.getUint32(4, false), - length: view.getUint32(8, false) - } -} - -export function encodeHeaderNaive (header: FrameHeader): Uint8Array { - const frame = new Uint8Array(HEADER_LENGTH) - - const frameView = new DataView(frame.buffer, frame.byteOffset, frame.byteLength) - - // always assume version 0 - // frameView.setUint8(0, header.version) - - frameView.setUint8(1, header.type) - frameView.setUint16(2, header.flag, false) - frameView.setUint32(4, header.streamID, false) - frameView.setUint32(8, header.length, false) - - return frame -} diff --git a/packages/stream-multiplexer-yamux/test/compliance.spec.ts b/packages/stream-multiplexer-yamux/test/compliance.spec.ts deleted file mode 100644 index 4ec47dcabe..0000000000 --- a/packages/stream-multiplexer-yamux/test/compliance.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-env mocha */ - -import tests from '@libp2p/interface-compliance-tests/stream-muxer' -import { TestYamux } from './util.js' - -describe('compliance', () => { - tests({ - async setup () { - return new TestYamux({}) - }, - async teardown () {} - }) -}) diff --git a/packages/stream-multiplexer-yamux/test/decode.spec.ts b/packages/stream-multiplexer-yamux/test/decode.spec.ts deleted file mode 100644 index e9c799e39c..0000000000 --- a/packages/stream-multiplexer-yamux/test/decode.spec.ts +++ /dev/null @@ -1,351 +0,0 @@ -/* eslint-disable @typescript-eslint/dot-notation */ -import { expect } from 'aegir/chai' -import { type Pushable, pushable } from 'it-pushable' -import { ERR_DECODE_IN_PROGRESS } from '../src/constants.js' -import { Decoder } from '../src/decode.js' -import { encodeHeader } from '../src/encode.js' -import { Flag, type FrameHeader, FrameType, GoAwayCode } from '../src/frame.js' -import { timeout } from './util.js' -import type { Uint8ArrayList } from 'uint8arraylist' - -const frames: Array<{ header: FrameHeader, data?: Uint8Array }> = [ - { header: { type: FrameType.Ping, flag: Flag.SYN, streamID: 0, length: 1 } }, - { header: { type: FrameType.WindowUpdate, flag: Flag.SYN, streamID: 1, length: 1 } }, - { header: { type: FrameType.GoAway, flag: 0, streamID: 0, length: GoAwayCode.NormalTermination } }, - { header: { type: FrameType.Ping, flag: Flag.ACK, streamID: 0, length: 100 } }, - { header: { type: FrameType.WindowUpdate, flag: 0, streamID: 99, length: 1000 } }, - { header: { type: FrameType.GoAway, flag: 0, streamID: 0, length: GoAwayCode.ProtocolError } } -] - -const data = (length: number): Uint8Array => Uint8Array.from(Array.from({ length }), (_, i) => i) - -const expectEqualBytes = (actual: Uint8Array | Uint8ArrayList, expected: Uint8Array | Uint8ArrayList, reason?: string): void => { - expect(actual instanceof Uint8Array ? actual : actual.subarray(), reason).to.deep.equal(expected instanceof Uint8Array ? expected : expected.subarray()) -} - -const expectEqualDataFrame = (actual: { header: FrameHeader, data?: Uint8Array | Uint8ArrayList }, expected: { header: FrameHeader, data?: Uint8Array | Uint8ArrayList }, reason = ''): void => { - expect(actual.header, reason + ' header').to.deep.equal(expected.header) - if (actual.data == null && expected.data != null) { - expect.fail('actual has no data but expected does') - } - if (actual.data != null && expected.data == null) { - expect.fail('actual has data but expected does not') - } - if (actual.data != null && expected.data != null) { - expectEqualBytes(actual.data, expected.data, reason + ' data?: string') - } -} - -const expectEqualDataFrames = (actual: Array<{ header: FrameHeader, data?: Uint8Array | Uint8ArrayList }>, expected: Array<{ header: FrameHeader, data?: Uint8Array | Uint8ArrayList }>): void => { - if (actual.length !== expected.length) { - expect.fail('actual') - } - for (let i = 0; i < actual.length; i++) { - expectEqualDataFrame(actual[i], expected[i], String(i)) - } -} - -const dataFrame = (length: number): { header: FrameHeader, data: Uint8Array } => ({ - header: { type: FrameType.Data, flag: 0, streamID: 1, length }, - data: data(length) -}) - -export const randomRanges = (length: number): number[][] => { - const indices = [] - let i = 0 - let j = 0 - while (i < length) { - j = i - i += Math.floor(Math.random() * length) - indices.push([j, i]) - } - return indices -} - -describe('Decoder internals', () => { - describe('readHeader', () => { - const frame = frames[0] - const p = pushable() - const d = new Decoder(p) - - afterEach(() => { - d['buffer'].consume(d['buffer'].length) - }) - - it('should handle an empty buffer', async () => { - expect(d['buffer'].length, 'a freshly created decoder should have an empty buffer').to.equal(0) - expect(d['readHeader'](), 'an empty buffer should read no header').to.equal(undefined) - }) - - it('should handle buffer length == header length', async () => { - d['buffer'].append(encodeHeader(frame.header)) - - expect(d['readHeader'](), 'the decoded header should match the input').to.deep.equal(frame.header) - expect(d['buffer'].length, 'the buffer should be fully drained').to.equal(0) - }) - - it('should handle buffer length < header length', async () => { - const upTo = 2 - - d['buffer'].append(encodeHeader(frame.header).slice(0, upTo)) - - expect(d['readHeader'](), 'an buffer that has insufficient bytes should read no header').to.equal(undefined) - expect(d['buffer'].length, 'a buffer that has insufficient bytes should not be consumed').to.equal(upTo) - - d['buffer'].append(encodeHeader(frame.header).slice(upTo)) - - expect(d['readHeader'](), 'the decoded header should match the input').to.deep.equal(frame.header) - expect(d['buffer'].length, 'the buffer should be fully drained').to.equal(0) - }) - - it('should handle buffer length > header length', async () => { - const more = 10 - - d['buffer'].append(encodeHeader(frame.header)) - d['buffer'].append(new Uint8Array(more)) - - expect(d['readHeader'](), 'the decoded header should match the input').to.deep.equal(frame.header) - expect(d['buffer'].length, 'the buffer should be partially drained').to.equal(more) - }) - }) - - describe('readBytes', () => { - const p = pushable() - const d = new Decoder(p) - - afterEach(() => { - d['buffer'].consume(d['buffer'].length) - }) - - it('should handle buffer length == requested length', async () => { - const requested = 10 - - d['buffer'].append(data(requested)) - - let actual - try { - actual = await Promise.race([timeout(1), d['readBytes'](requested)]) - } catch (e) { - expect.fail('readBytes timed out') - } - - expectEqualBytes(actual as Uint8ArrayList, data(requested), 'read bytes should equal input') - expect(d['buffer'].length, 'buffer should be drained').to.deep.equal(0) - }) - - it('should handle buffer length > requested length', async () => { - const requested = 10 - - d['buffer'].append(data(requested * 2)) - - let actual - try { - actual = await Promise.race([timeout(1), d['readBytes'](requested)]) - } catch (e) { - expect.fail('readBytes timed out') - } - - expectEqualBytes(actual as Uint8ArrayList, data(requested), 'read bytes should equal input') - expect(d['buffer'].length, 'buffer should be partially drained').to.deep.equal(requested) - }) - - it('should handle buffer length < requested length, data available', async () => { - const requested = 10 - - p.push(data(requested)) - - let actual - try { - actual = await Promise.race([timeout(10), d['readBytes'](requested)]) - } catch (e) { - expect.fail('readBytes timed out') - } - - expectEqualBytes(actual as Uint8ArrayList, data(requested), 'read bytes should equal input') - expect(d['buffer'].length, 'buffer should be drained').to.deep.equal(0) - }) - - it('should handle buffer length < requested length, data not available', async () => { - const requested = 10 - - p.push(data(requested - 1)) - - try { - await Promise.race([timeout(10), d['readBytes'](requested)]) - expect.fail('readBytes should not resolve until the source + buffer have enough bytes') - } catch (e) { - } - }) - }) -}) - -describe('Decoder', () => { - describe('emitFrames', () => { - let p: Pushable - let d: Decoder - - beforeEach(() => { - p = pushable() - d = new Decoder(p) - }) - - it('should emit frames from source chunked by frame', async () => { - const expected = [] - for (const [i, frame] of frames.entries()) { - p.push(encodeHeader(frame.header)) - expected.push(frame) - - // sprinkle in more data frames - if (i % 2 === 1) { - const df = dataFrame(i * 100) - p.push(encodeHeader(df.header)) - p.push(df.data) - expected.push(df) - } - } - p.end() - - const actual = [] - for await (const frame of d.emitFrames()) { - if (frame.readData === undefined) { - actual.push(frame) - } else { - actual.push({ header: frame.header, data: await frame.readData() }) - } - } - - expectEqualDataFrames(actual, expected) - }) - - it('should emit frames from source chunked by partial frame', async () => { - const chunkSize = 5 - const expected = [] - for (const [i, frame] of frames.entries()) { - const encoded = encodeHeader(frame.header) - for (let i = 0; i < encoded.length; i += chunkSize) { - p.push(encoded.slice(i, i + chunkSize)) - } - expected.push(frame) - - // sprinkle in more data frames - if (i % 2 === 1) { - const df = dataFrame(i * 100) - const encoded = Uint8Array.from([...encodeHeader(df.header), ...df.data]) - for (let i = 0; i < encoded.length; i += chunkSize) { - p.push(encoded.slice(i, i + chunkSize)) - } - expected.push(df) - } - } - p.end() - - const actual = [] - for await (const frame of d.emitFrames()) { - if (frame.readData === undefined) { - actual.push(frame) - } else { - actual.push({ header: frame.header, data: await frame.readData() }) - } - } - - expect(p.readableLength).to.equal(0) - expectEqualDataFrames(actual, expected) - }) - - it('should emit frames from source chunked by multiple frames', async () => { - const expected = [] - for (let i = 0; i < frames.length; i++) { - const encoded1 = encodeHeader(frames[i].header) - expected.push(frames[i]) - - i++ - const encoded2 = encodeHeader(frames[i].header) - expected.push(frames[i]) - - // sprinkle in more data frames - const df = dataFrame(i * 100) - const encoded3 = Uint8Array.from([...encodeHeader(df.header), ...df.data]) - expected.push(df) - - const encodedChunk = new Uint8Array(encoded1.length + encoded2.length + encoded3.length) - encodedChunk.set(encoded1, 0) - encodedChunk.set(encoded2, encoded1.length) - encodedChunk.set(encoded3, encoded1.length + encoded2.length) - - p.push(encodedChunk) - } - p.end() - - const actual = [] - for await (const frame of d.emitFrames()) { - if (frame.readData === undefined) { - actual.push(frame) - } else { - actual.push({ header: frame.header, data: await frame.readData() }) - } - } - - expectEqualDataFrames(actual, expected) - }) - - it('should emit frames from source chunked chaoticly', async () => { - const expected = [] - const encodedFrames = [] - for (const [i, frame] of frames.entries()) { - encodedFrames.push(encodeHeader(frame.header)) - expected.push(frame) - - // sprinkle in more data frames - if (i % 2 === 1) { - const df = dataFrame(i * 100) - encodedFrames.push(encodeHeader(df.header)) - encodedFrames.push(df.data) - expected.push(df) - } - } - - // create a single byte array of all frames to send - // so that we can chunk them chaoticly - const encoded = new Uint8Array(encodedFrames.reduce((a, b) => a + b.length, 0)) - let i = 0 - for (const e of encodedFrames) { - encoded.set(e, i) - i += e.length - } - - for (const [i, j] of randomRanges(encoded.length)) { - p.push(encoded.slice(i, j)) - } - p.end() - - const actual = [] - for await (const frame of d.emitFrames()) { - if (frame.readData === undefined) { - actual.push(frame) - } else { - actual.push({ header: frame.header, data: await frame.readData() }) - } - } - - expectEqualDataFrames(actual, expected) - }) - - it('should error decoding frame while another decode is in progress', async () => { - const df1 = dataFrame(100) - p.push(encodeHeader(df1.header)) - p.push(df1.data) - const df2 = dataFrame(100) - p.push(encodeHeader(df2.header)) - p.push(df2.data) - - try { - for await (const frame of d.emitFrames()) { - void frame - } - expect.fail('decoding another frame before the first is finished should error') - } catch (e) { - expect((e as { code: string }).code).to.equal(ERR_DECODE_IN_PROGRESS) - } - }) - }) -}) diff --git a/packages/stream-multiplexer-yamux/test/mplex.util.ts b/packages/stream-multiplexer-yamux/test/mplex.util.ts deleted file mode 100644 index 12ceeed083..0000000000 --- a/packages/stream-multiplexer-yamux/test/mplex.util.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { mplex } from '@libp2p/mplex' -import { duplexPair } from 'it-pair/duplex' -import { pipe } from 'it-pipe' -import type { StreamMuxer, StreamMuxerInit } from '@libp2p/interface/stream-muxer' -import type { Source, Transform } from 'it-stream-types' - -const factory = mplex()() - -export function testYamuxMuxer (name: string, client: boolean, conf: StreamMuxerInit = {}): StreamMuxer { - return factory.createStreamMuxer({ - ...conf, - direction: client ? 'outbound' : 'inbound' - }) -} - -/** - * Create a transform that can be paused and unpaused - */ -export function pauseableTransform (): { transform: Transform, AsyncGenerator>, pause: () => void, unpause: () => void } { - let resolvePausePromise: ((value: unknown) => void) | undefined - let pausePromise: Promise | undefined - const unpause = (): void => { - resolvePausePromise?.(null) - } - const pause = (): void => { - pausePromise = new Promise(resolve => { - resolvePausePromise = resolve - }) - } - const transform: Transform, AsyncGenerator> = async function * (source) { - for await (const d of source) { - if (pausePromise !== undefined) { - await pausePromise - pausePromise = undefined - resolvePausePromise = undefined - } - yield d - } - } - return { transform, pause, unpause } -} - -export function testClientServer (conf: StreamMuxerInit = {}): { - client: StreamMuxer & { - pauseRead: () => void - unpauseRead: () => void - pauseWrite: () => void - unpauseWrite: () => void - } - server: StreamMuxer & { - pauseRead: () => void - unpauseRead: () => void - pauseWrite: () => void - unpauseWrite: () => void - } -} { - const pair = duplexPair() - const client = testYamuxMuxer('libp2p:mplex:client', true, conf) - const server = testYamuxMuxer('libp2p:mplex:server', false, conf) - - const clientReadTransform = pauseableTransform() - const clientWriteTransform = pauseableTransform() - const serverReadTransform = pauseableTransform() - const serverWriteTransform = pauseableTransform() - - void pipe(pair[0], clientReadTransform.transform, client, clientWriteTransform.transform, pair[0]) - void pipe(pair[1], serverReadTransform.transform, server, serverWriteTransform.transform, pair[1]) - return { - client: Object.assign(client, { - pauseRead: clientReadTransform.pause, - unpauseRead: clientReadTransform.unpause, - pauseWrite: clientWriteTransform.pause, - unpauseWrite: clientWriteTransform.unpause - }), - server: Object.assign(server, { - pauseRead: serverReadTransform.pause, - unpauseRead: serverReadTransform.unpause, - pauseWrite: serverWriteTransform.pause, - unpauseWrite: serverWriteTransform.unpause - }) - } -} - -export async function timeout (ms: number): Promise { - return new Promise((_resolve, reject) => setTimeout(() => { reject(new Error(`timeout after ${ms}ms`)) }, ms)) -} - -export async function sleep (ms: number): Promise { - return new Promise(resolve => setTimeout(() => { resolve(ms) }, ms)) -} diff --git a/packages/stream-multiplexer-yamux/test/muxer.spec.ts b/packages/stream-multiplexer-yamux/test/muxer.spec.ts deleted file mode 100644 index 017125865c..0000000000 --- a/packages/stream-multiplexer-yamux/test/muxer.spec.ts +++ /dev/null @@ -1,145 +0,0 @@ -/* eslint-env mocha */ - -import { expect } from 'aegir/chai' -import { duplexPair } from 'it-pair/duplex' -import { pipe } from 'it-pipe' -import { ERR_MUXER_LOCAL_CLOSED } from '../src/constants.js' -import { sleep, testClientServer, testYamuxMuxer, type YamuxFixture } from './util.js' - -describe('muxer', () => { - let client: YamuxFixture - let server: YamuxFixture - - afterEach(async () => { - if (client != null) { - await client.close() - } - - if (server != null) { - await server.close() - } - }) - - it('test repeated close', async () => { - const client1 = testYamuxMuxer('libp2p:yamux:1', true) - // inspect logs to ensure its only closed once - await client1.close() - await client1.close() - await client1.close() - }) - - it('test client<->client', async () => { - const pair = duplexPair() - const client1 = testYamuxMuxer('libp2p:yamux:1', true) - const client2 = testYamuxMuxer('libp2p:yamux:2', true) - void pipe(pair[0], client1, pair[0]) - void pipe(pair[1], client2, pair[1]) - client1.newStream() - client2.newStream() - - await sleep(20) - - expect(client1.isClosed()).to.equal(true) - expect(client2.isClosed()).to.equal(true) - }) - - it('test server<->server', async () => { - const pair = duplexPair() - const client1 = testYamuxMuxer('libp2p:yamux:1', false) - const client2 = testYamuxMuxer('libp2p:yamux:2', false) - void pipe(pair[0], client1, pair[0]) - void pipe(pair[1], client2, pair[1]) - client1.newStream() - client2.newStream() - - await sleep(20) - - expect(client1.isClosed()).to.equal(true) - expect(client2.isClosed()).to.equal(true) - }) - - it('test ping', async () => { - ({ client, server } = testClientServer()) - - server.pauseRead() - const clientRTT = client.ping() - await sleep(10) - server.unpauseRead() - expect(await clientRTT).to.not.equal(0) - - server.pauseWrite() - const serverRTT = server.ping() - await sleep(10) - server.unpauseWrite() - expect(await serverRTT).to.not.equal(0) - }) - - it('test multiple simultaneous pings', async () => { - ({ client, server } = testClientServer()) - - client.pauseWrite() - const promise = [ - client.ping(), - client.ping(), - client.ping() - ] - await sleep(10) - client.unpauseWrite() - - const clientRTTs = await Promise.all(promise) - expect(clientRTTs[0]).to.not.equal(0) - expect(clientRTTs[0]).to.equal(clientRTTs[1]) - expect(clientRTTs[1]).to.equal(clientRTTs[2]) - - // eslint-disable-next-line @typescript-eslint/dot-notation - expect(client['nextPingID']).to.equal(1) - - await client.close() - }) - - it('test go away', async () => { - ({ client, server } = testClientServer()) - await client.close() - - expect(() => { - client.newStream() - }).to.throw().with.property('code', ERR_MUXER_LOCAL_CLOSED, 'should not be able to open a stream after close') - }) - - it('test keep alive', async () => { - ({ client, server } = testClientServer({ enableKeepAlive: true, keepAliveInterval: 10 })) - - await sleep(1000) - - // eslint-disable-next-line @typescript-eslint/dot-notation - expect(client['nextPingID']).to.be.gt(2) - await client.close() - await server.close() - }) - - it('test max inbound streams', async () => { - ({ client, server } = testClientServer({ maxInboundStreams: 1 })) - - client.newStream() - client.newStream() - await sleep(10) - - expect(server.streams.length).to.equal(1) - expect(client.streams.length).to.equal(1) - }) - - it('test max outbound streams', async () => { - ({ client, server } = testClientServer({ maxOutboundStreams: 1 })) - - client.newStream() - await sleep(10) - - try { - client.newStream() - expect.fail('stream creation should fail if exceeding maxOutboundStreams') - } catch (e) { - expect(server.streams.length).to.equal(1) - expect(client.streams.length).to.equal(1) - } - }) -}) diff --git a/packages/stream-multiplexer-yamux/test/stream.spec.ts b/packages/stream-multiplexer-yamux/test/stream.spec.ts deleted file mode 100644 index b32e761d20..0000000000 --- a/packages/stream-multiplexer-yamux/test/stream.spec.ts +++ /dev/null @@ -1,256 +0,0 @@ -/* eslint-env mocha */ - -import { expect } from 'aegir/chai' -import { pipe } from 'it-pipe' -import { type Pushable, pushable } from 'it-pushable' -import { defaultConfig } from '../src/config.js' -import { ERR_RECV_WINDOW_EXCEEDED } from '../src/constants.js' -import { GoAwayCode } from '../src/frame.js' -import { HalfStreamState, StreamState } from '../src/stream.js' -import { sleep, testClientServer, type YamuxFixture } from './util.js' -import type { Uint8ArrayList } from 'uint8arraylist' - -describe('stream', () => { - let client: YamuxFixture - let server: YamuxFixture - - afterEach(async () => { - if (client != null) { - await client.close() - } - - if (server != null) { - await server.close() - } - }) - - it('test send data - small', async () => { - ({ client, server } = testClientServer({ initialStreamWindowSize: defaultConfig.initialStreamWindowSize })) - const { default: drain } = await import('it-drain') - - const p = pushable() - const c1 = client.newStream() - await sleep(10) - - const s1 = server.streams[0] - const sendPipe = pipe(p, c1) - const recvPipe = pipe(s1, drain) - for (let i = 0; i < 10; i++) { - p.push(new Uint8Array(256)) - } - p.end() - - await Promise.all([sendPipe, recvPipe]) - - // the window capacities should have refilled via window updates as received data was consumed - - // eslint-disable-next-line @typescript-eslint/dot-notation - expect(c1['sendWindowCapacity']).to.equal(defaultConfig.initialStreamWindowSize) - // eslint-disable-next-line @typescript-eslint/dot-notation - expect(s1['recvWindowCapacity']).to.equal(defaultConfig.initialStreamWindowSize) - }) - - it('test send data - large', async () => { - ({ client, server } = testClientServer({ initialStreamWindowSize: defaultConfig.initialStreamWindowSize })) - const { default: drain } = await import('it-drain') - - const p = pushable() - const c1 = client.newStream() - await sleep(10) - - const s1 = server.streams[0] - const sendPipe = pipe(p, c1) - const recvPipe = pipe(s1, drain) - // amount of data is greater than initial window size - // and each payload is also greater than the max message size - // this will payload chunking and also waiting for window updates before continuing to send - for (let i = 0; i < 10; i++) { - p.push(new Uint8Array(defaultConfig.initialStreamWindowSize)) - } - p.end() - - await Promise.all([sendPipe, recvPipe]) - // the window capacities should have refilled via window updates as received data was consumed - - // eslint-disable-next-line @typescript-eslint/dot-notation - expect(c1['sendWindowCapacity']).to.equal(defaultConfig.initialStreamWindowSize) - // eslint-disable-next-line @typescript-eslint/dot-notation - expect(s1['recvWindowCapacity']).to.equal(defaultConfig.initialStreamWindowSize) - }) - - it('test send data - large with increasing recv window size', async () => { - ({ client, server } = testClientServer({ initialStreamWindowSize: defaultConfig.initialStreamWindowSize })) - const { default: drain } = await import('it-drain') - - const p = pushable() - const c1 = client.newStream() - - server.pauseWrite() - void server.ping() - await sleep(10) - server.unpauseWrite() - - const s1 = server.streams[0] - const sendPipe = pipe(p, c1) - const recvPipe = pipe(s1, drain) - // amount of data is greater than initial window size - // and each payload is also greater than the max message size - // this will payload chunking and also waiting for window updates before continuing to send - for (let i = 0; i < 10; i++) { - p.push(new Uint8Array(defaultConfig.initialStreamWindowSize)) - } - p.end() - - await Promise.all([sendPipe, recvPipe]) - // the window capacities should have refilled via window updates as received data was consumed - - // eslint-disable-next-line @typescript-eslint/dot-notation - expect(c1['sendWindowCapacity']).to.be.gt(defaultConfig.initialStreamWindowSize) - // eslint-disable-next-line @typescript-eslint/dot-notation - expect(s1['recvWindowCapacity']).to.be.gt(defaultConfig.initialStreamWindowSize) - }) - - it('test many streams', async () => { - ({ client, server } = testClientServer()) - for (let i = 0; i < 1000; i++) { - client.newStream() - } - await sleep(100) - - expect(client.streams.length).to.equal(1000) - expect(server.streams.length).to.equal(1000) - }) - - it('test many streams - ping pong', async () => { - ({ client, server } = testClientServer({ - // echo on incoming streams - onIncomingStream: (stream) => { void pipe(stream, stream) } - })) - const numStreams = 10 - - const p: Array> = [] - for (let i = 0; i < numStreams; i++) { - client.newStream() - p.push(pushable()) - } - await sleep(100) - - for (let i = 0; i < numStreams; i++) { - const s = client.streams[i] - void pipe(p[i], s) - p[i].push(new Uint8Array(16)) - } - await sleep(100) - - expect(client.streams.length).to.equal(numStreams) - expect(server.streams.length).to.equal(numStreams) - - await client.close() - }) - - it('test stream close', async () => { - ({ client, server } = testClientServer()) - - const c1 = client.newStream() - await c1.close() - await sleep(5) - - expect(c1.state).to.equal(StreamState.Finished) - - const s1 = server.streams[0] - expect(s1).to.not.be.undefined() - expect(s1.state).to.equal(StreamState.SYNReceived) - }) - - it('test stream close read', async () => { - ({ client, server } = testClientServer()) - - const c1 = client.newStream() - await c1.closeRead() - await sleep(5) - - expect(c1.readState).to.equal(HalfStreamState.Closed) - expect(c1.writeState).to.equal(HalfStreamState.Open) - - const s1 = server.streams[0] - expect(s1).to.not.be.undefined() - expect(s1.readState).to.equal(HalfStreamState.Open) - expect(s1.writeState).to.equal(HalfStreamState.Open) - }) - - it('test stream close write', async () => { - ({ client, server } = testClientServer()) - - const c1 = client.newStream() - await c1.closeWrite() - await sleep(5) - - expect(c1.readState).to.equal(HalfStreamState.Open) - expect(c1.writeState).to.equal(HalfStreamState.Closed) - - const s1 = server.streams[0] - expect(s1).to.not.be.undefined() - expect(s1.readState).to.equal(HalfStreamState.Closed) - expect(s1.writeState).to.equal(HalfStreamState.Open) - }) - - it('test window overflow', async () => { - ({ client, server } = testClientServer({ maxMessageSize: defaultConfig.initialStreamWindowSize, initialStreamWindowSize: defaultConfig.initialStreamWindowSize })) - const { default: drain } = await import('it-drain') - - const p = pushable() - const c1 = client.newStream() - await sleep(10) - - const s1 = server.streams[0] - const sendPipe = pipe(p, c1) - - const c1SendData = c1.sendData.bind(c1) - - c1.sendData = async (data: Uint8ArrayList): Promise => { - await c1SendData(data) - // eslint-disable-next-line @typescript-eslint/dot-notation - c1['sendWindowCapacity'] = defaultConfig.initialStreamWindowSize * 10 - } - p.push(new Uint8Array(defaultConfig.initialStreamWindowSize)) - p.push(new Uint8Array(defaultConfig.initialStreamWindowSize)) - - await sleep(10) - - const recvPipe = pipe(s1, drain) - p.end() - - try { - await Promise.all([sendPipe, recvPipe]) - } catch (e) { - expect((e as { code: string }).code).to.equal(ERR_RECV_WINDOW_EXCEEDED) - } - - expect(client).to.have.property('remoteGoAway', GoAwayCode.ProtocolError) - expect(server).to.have.property('localGoAway', GoAwayCode.ProtocolError) - }) - - it('test stream sink error', async () => { - ({ client, server } = testClientServer()) - - // don't let the server respond - server.pauseRead() - - const p = pushable() - const c1 = client.newStream() - - const sendPipe = pipe(p, c1) - - // send more data than the window size, will trigger a wait - p.push(new Uint8Array(defaultConfig.initialStreamWindowSize)) - p.push(new Uint8Array(defaultConfig.initialStreamWindowSize)) - - await sleep(10) - - // the client should close gracefully even though it was waiting to send more data - await client.close() - p.end() - - await sendPipe - }) -}) diff --git a/packages/stream-multiplexer-yamux/test/util.ts b/packages/stream-multiplexer-yamux/test/util.ts deleted file mode 100644 index 3c28a7ff76..0000000000 --- a/packages/stream-multiplexer-yamux/test/util.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { logger } from '@libp2p/logger' -import { duplexPair } from 'it-pair/duplex' -import { pipe } from 'it-pipe' -import { Yamux, YamuxMuxer, type YamuxMuxerInit } from '../src/muxer.js' -import type { Config } from '../src/config.js' -import type { Source, Transform } from 'it-stream-types' - -const isClient = (() => { - let client = false - return () => { - const isClient = !client - client = isClient - return isClient - } -})() - -export const testConf: Partial = { - enableKeepAlive: false -} - -/** - * Yamux must be configured with a client setting `client` to true - * and a server setting `client` to falsey - * - * Since the compliance tests create a dialer and listener, - * manually alternate setting `client` to true and false - */ -export class TestYamux extends Yamux { - createStreamMuxer (init?: YamuxMuxerInit): YamuxMuxer { - const client = isClient() - return super.createStreamMuxer({ ...testConf, ...init, direction: client ? 'outbound' : 'inbound', log: logger(`libp2p:yamux${client ? 1 : 2}`) }) - } -} - -export function testYamuxMuxer (name: string, client: boolean, conf: YamuxMuxerInit = {}): YamuxMuxer { - return new YamuxMuxer({ - ...testConf, - ...conf, - direction: client ? 'outbound' : 'inbound', - log: logger(name) - }) -} - -/** - * Create a transform that can be paused and unpaused - */ -export function pauseableTransform (): { transform: Transform, AsyncGenerator>, pause: () => void, unpause: () => void } { - let resolvePausePromise: ((value: unknown) => void) | undefined - let pausePromise: Promise | undefined - const unpause = (): void => { - resolvePausePromise?.(null) - } - const pause = (): void => { - pausePromise = new Promise(resolve => { - resolvePausePromise = resolve - }) - } - const transform: Transform, AsyncGenerator> = async function * (source) { - for await (const d of source) { - if (pausePromise !== undefined) { - await pausePromise - pausePromise = undefined - resolvePausePromise = undefined - } - yield d - } - } - return { transform, pause, unpause } -} - -export interface YamuxFixture extends YamuxMuxer { - pauseRead: () => void - unpauseRead: () => void - pauseWrite: () => void - unpauseWrite: () => void -} - -export function testClientServer (conf: YamuxMuxerInit = {}): { - client: YamuxFixture - server: YamuxFixture -} { - const pair = duplexPair() - const client = testYamuxMuxer('libp2p:yamux:client', true, conf) - const server = testYamuxMuxer('libp2p:yamux:server', false, conf) - - const clientReadTransform = pauseableTransform() - const clientWriteTransform = pauseableTransform() - const serverReadTransform = pauseableTransform() - const serverWriteTransform = pauseableTransform() - - void pipe(pair[0], clientReadTransform.transform, client, clientWriteTransform.transform, pair[0]) - void pipe(pair[1], serverReadTransform.transform, server, serverWriteTransform.transform, pair[1]) - return { - client: Object.assign(client, { - pauseRead: clientReadTransform.pause, - unpauseRead: clientReadTransform.unpause, - pauseWrite: clientWriteTransform.pause, - unpauseWrite: clientWriteTransform.unpause - }), - server: Object.assign(server, { - pauseRead: serverReadTransform.pause, - unpauseRead: serverReadTransform.unpause, - pauseWrite: serverWriteTransform.pause, - unpauseWrite: serverWriteTransform.unpause - }) - } -} - -export async function timeout (ms: number): Promise { - return new Promise((_resolve, reject) => setTimeout(() => { reject(new Error(`timeout after ${ms}ms`)) }, ms)) -} - -export async function sleep (ms: number): Promise { - return new Promise(resolve => setTimeout(() => { resolve(ms) }, ms)) -} diff --git a/packages/stream-multiplexer-yamux/tsconfig.json b/packages/stream-multiplexer-yamux/tsconfig.json deleted file mode 100644 index bedbf33e72..0000000000 --- a/packages/stream-multiplexer-yamux/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": "aegir/src/config/tsconfig.aegir.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": [ - "src", - "test" - ], - "references": [ - { - "path": "../interface" - }, - { - "path": "../interface-compliance-tests" - }, - { - "path": "../logger" - }, - { - "path": "../stream-multiplexer-mplex" - } - ] -} diff --git a/packages/stream-multiplexer-yamux/typedoc.json b/packages/stream-multiplexer-yamux/typedoc.json deleted file mode 100644 index 76271501d3..0000000000 --- a/packages/stream-multiplexer-yamux/typedoc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "entryPoints": [ - "./src/index.ts", - "./src/config.ts", - "./src/stream.ts" - ] -} diff --git a/packages/transport-tcp/CHANGELOG.md b/packages/transport-tcp/CHANGELOG.md index 67385fe0a7..d02a819cf2 100644 --- a/packages/transport-tcp/CHANGELOG.md +++ b/packages/transport-tcp/CHANGELOG.md @@ -5,6 +5,36 @@ * **dev:** bump aegir from 38.1.8 to 39.0.10 ([#279](https://github.com/libp2p/js-libp2p-tcp/issues/279)) ([3ed1235](https://github.com/libp2p/js-libp2p-tcp/commit/3ed12353aa48b5a933f80042846a8f1c2337fa47)) +## [8.0.0](https://www.github.com/libp2p/js-libp2p/compare/tcp-v7.0.3...tcp-v8.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* the `.close`, `closeRead` and `closeWrite` methods on the `Stream` interface are now asynchronous +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* close streams gracefully ([#1864](https://www.github.com/libp2p/js-libp2p/issues/1864)) ([b36ec7f](https://www.github.com/libp2p/js-libp2p/commit/b36ec7f24e477af21cec31effc086a6c611bf271)), closes [#1793](https://www.github.com/libp2p/js-libp2p/issues/1793) [#656](https://www.github.com/libp2p/js-libp2p/issues/656) +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * @libp2p/utils bumped from ^3.0.0 to ^4.0.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^3.0.0 to ^4.0.0 + ## [7.0.2](https://github.com/libp2p/js-libp2p-tcp/compare/v7.0.1...v7.0.2) (2023-06-15) @@ -905,4 +935,4 @@ -# 0.1.0 (2015-09-16) +# 0.1.0 (2015-09-16) \ No newline at end of file diff --git a/packages/transport-tcp/package.json b/packages/transport-tcp/package.json index 676c3f4cb7..4b8e61b316 100644 --- a/packages/transport-tcp/package.json +++ b/packages/transport-tcp/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/tcp", - "version": "7.0.3", + "version": "8.0.0", "description": "A TCP transport for libp2p", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/transport-tcp#readme", @@ -50,16 +50,16 @@ "test:electron-main": "aegir test -t electron-main" }, "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/utils": "^3.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/utils": "^4.0.0", "@multiformats/mafmt": "^12.1.2", "@multiformats/multiaddr": "^12.1.3", "@types/sinon": "^10.0.15", "stream-to-it": "^0.2.2" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^3.0.0", + "@libp2p/interface-compliance-tests": "^4.0.0", "aegir": "^40.0.1", "it-all": "^3.0.1", "it-pipe": "^3.0.1", diff --git a/packages/transport-webrtc/.aegir.js b/packages/transport-webrtc/.aegir.js index f19d27456f..491576df87 100644 --- a/packages/transport-webrtc/.aegir.js +++ b/packages/transport-webrtc/.aegir.js @@ -2,9 +2,6 @@ /** @type {import('aegir').PartialOptions} */ export default { build: { - config: { - platform: 'node' - }, bundlesizeMax: '117KB' }, test: { diff --git a/packages/transport-webrtc/CHANGELOG.md b/packages/transport-webrtc/CHANGELOG.md index d4076ee6de..be960a26ff 100644 --- a/packages/transport-webrtc/CHANGELOG.md +++ b/packages/transport-webrtc/CHANGELOG.md @@ -5,6 +5,63 @@ * add browser-to-browser test for bi-directional communication ([#172](https://github.com/libp2p/js-libp2p-webrtc/issues/172)) ([1ec3d8a](https://github.com/libp2p/js-libp2p-webrtc/commit/1ec3d8a8b611d5227f430037e2547fd86d115eaa)) +### [3.1.1](https://www.github.com/libp2p/js-libp2p/compare/webrtc-v3.1.0...webrtc-v3.1.1) (2023-08-01) + + +### Bug Fixes + +* update package config ([#1919](https://www.github.com/libp2p/js-libp2p/issues/1919)) ([8d49602](https://www.github.com/libp2p/js-libp2p/commit/8d49602fb6f0c906f1920d397ff28705bb0bc845)) + + +### Dependencies + +* The following workspace dependencies were updated + * devDependencies + * libp2p bumped from ^0.46.0 to ^0.46.1 + +## [3.1.0](https://www.github.com/libp2p/js-libp2p/compare/webrtc-v3.0.0...webrtc-v3.1.0) (2023-07-31) + + +### Features + +* add node.js/electron support for webrtc transport ([#1905](https://www.github.com/libp2p/js-libp2p/issues/1905)) ([72e81dc](https://www.github.com/libp2p/js-libp2p/commit/72e81dc1ab66fe0bbcafe3261ec20e2a28aaad5f)) + +## [3.0.0](https://www.github.com/libp2p/js-libp2p/compare/webrtc-v2.0.10...webrtc-v3.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* the `.close`, `closeRead` and `closeWrite` methods on the `Stream` interface are now asynchronous +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* mark connections with limits as transient ([#1890](https://www.github.com/libp2p/js-libp2p/issues/1890)) ([a1ec46b](https://www.github.com/libp2p/js-libp2p/commit/a1ec46b5f5606b7bdf3e5b085013fb88e26439f9)) +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* close streams gracefully ([#1864](https://www.github.com/libp2p/js-libp2p/issues/1864)) ([b36ec7f](https://www.github.com/libp2p/js-libp2p/commit/b36ec7f24e477af21cec31effc086a6c611bf271)), closes [#1793](https://www.github.com/libp2p/js-libp2p/issues/1793) [#656](https://www.github.com/libp2p/js-libp2p/issues/656) +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) +* update max message size SDP attribute ([#1909](https://www.github.com/libp2p/js-libp2p/issues/1909)) ([e6a41f7](https://www.github.com/libp2p/js-libp2p/commit/e6a41f7e9b8c06babfdec9852f0e5355d3405fd0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/interface-internal bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^3.0.0 to ^4.0.0 + * @libp2p/peer-id-factory bumped from ^2.0.0 to ^3.0.0 + * @libp2p/websockets bumped from ^6.0.0 to ^7.0.0 + * libp2p bumped from ^0.45.0 to ^0.46.0 + ## [2.0.9](https://github.com/libp2p/js-libp2p-webrtc/compare/v2.0.8...v2.0.9) (2023-06-12) @@ -239,4 +296,4 @@ ### Documentation -* fix 'browser to server' build config ([#66](https://github.com/libp2p/js-libp2p-webrtc/issues/66)) ([b54132c](https://github.com/libp2p/js-libp2p-webrtc/commit/b54132cecac180f0577a1b7905f79b20207c3647)) +* fix 'browser to server' build config ([#66](https://github.com/libp2p/js-libp2p-webrtc/issues/66)) ([b54132c](https://github.com/libp2p/js-libp2p-webrtc/commit/b54132cecac180f0577a1b7905f79b20207c3647)) \ No newline at end of file diff --git a/packages/transport-webrtc/package.json b/packages/transport-webrtc/package.json index 4e49845e30..063d449d1b 100644 --- a/packages/transport-webrtc/package.json +++ b/packages/transport-webrtc/package.json @@ -1,8 +1,7 @@ { "name": "@libp2p/webrtc", - "version": "2.0.10", + "version": "3.1.1", "description": "A libp2p transport using WebRTC connections", - "author": "", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/transport-webrtc#readme", "repository": { @@ -35,7 +34,8 @@ "scripts": { "generate": "protons src/private-to-private/pb/message.proto src/pb/message.proto", "build": "aegir build", - "test": "aegir test -t browser", + "test": "aegir test -t node -t browser -t electron-main -- --exit", + "test:node": "aegir test -t node --cov -- --exit", "test:chrome": "aegir test -t browser --cov", "test:firefox": "aegir test -t browser -- --browser firefox", "lint": "aegir lint", @@ -44,11 +44,11 @@ "dep-check": "aegir dep-check" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/interface-internal": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/peer-id": "^2.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/interface-internal": "^0.1.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/peer-id": "^3.0.0", "@multiformats/mafmt": "^12.1.2", "@multiformats/multiaddr": "^12.1.3", "abortable-iterator": "^5.0.1", @@ -61,6 +61,7 @@ "it-to-buffer": "^4.0.2", "multiformats": "^12.0.1", "multihashes": "^4.0.3", + "node-datachannel": "^0.4.3", "p-defer": "^4.0.0", "p-event": "^6.0.0", "protons-runtime": "^5.0.0", @@ -68,19 +69,22 @@ "uint8arrays": "^4.0.4" }, "devDependencies": { - "@chainsafe/libp2p-yamux": "^4.0.0", - "@libp2p/interface-compliance-tests": "^3.0.0", - "@libp2p/peer-id-factory": "^2.0.0", - "@libp2p/websockets": "^6.0.0", + "@chainsafe/libp2p-yamux": "^5.0.0", + "@libp2p/interface-compliance-tests": "^4.0.0", + "@libp2p/peer-id-factory": "^3.0.0", + "@libp2p/websockets": "^7.0.0", "@types/sinon": "^10.0.15", "aegir": "^40.0.1", "delay": "^6.0.0", "it-length": "^3.0.2", "it-map": "^3.0.3", "it-pair": "^2.0.6", - "libp2p": "^0.45.0", + "libp2p": "^0.46.1", "protons": "^7.0.2", "sinon": "^15.1.2", "sinon-ts": "^1.0.0" + }, + "browser": { + "./dist/src/webrtc/index.js": "./dist/src/webrtc/index.browser.js" } } diff --git a/packages/transport-webrtc/src/private-to-private/handler.ts b/packages/transport-webrtc/src/private-to-private/handler.ts index 2b0e09e19d..0f4f055b33 100644 --- a/packages/transport-webrtc/src/private-to-private/handler.ts +++ b/packages/transport-webrtc/src/private-to-private/handler.ts @@ -1,8 +1,10 @@ +import { CodeError } from '@libp2p/interface/errors' import { logger } from '@libp2p/logger' import { abortableDuplex } from 'abortable-iterator' import { pbStream } from 'it-protobuf-stream' import pDefer, { type DeferredPromise } from 'p-defer' import { DataChannelMuxerFactory } from '../muxer.js' +import { RTCPeerConnection, RTCSessionDescription } from '../webrtc/index.js' import { Message } from './pb/message.js' import { readCandidatesUntilConnected, resolveOnConnected } from './util.js' import type { DataChannelOpts } from '../stream.js' @@ -20,66 +22,75 @@ export async function handleIncomingStream ({ rtcConfiguration, dataChannelOptio const signal = AbortSignal.timeout(DEFAULT_TIMEOUT) const stream = pbStream(abortableDuplex(rawStream, signal)).pb(Message) const pc = new RTCPeerConnection(rtcConfiguration) - const muxerFactory = new DataChannelMuxerFactory({ peerConnection: pc, dataChannelOptions }) - const connectedPromise: DeferredPromise = pDefer() - const answerSentPromise: DeferredPromise = pDefer() - - signal.onabort = () => { connectedPromise.reject() } - // candidate callbacks - pc.onicecandidate = ({ candidate }) => { - answerSentPromise.promise.then( - async () => { - await stream.write({ - type: Message.Type.ICE_CANDIDATE, - data: (candidate != null) ? JSON.stringify(candidate.toJSON()) : '' - }) - }, - (err) => { - log.error('cannot set candidate since sending answer failed', err) - } - ) - } - resolveOnConnected(pc, connectedPromise) + try { + const muxerFactory = new DataChannelMuxerFactory({ peerConnection: pc, dataChannelOptions }) + const connectedPromise: DeferredPromise = pDefer() + const answerSentPromise: DeferredPromise = pDefer() + + signal.onabort = () => { + connectedPromise.reject(new CodeError('Timed out while trying to connect', 'ERR_TIMEOUT')) + } + // candidate callbacks + pc.onicecandidate = ({ candidate }) => { + answerSentPromise.promise.then( + async () => { + await stream.write({ + type: Message.Type.ICE_CANDIDATE, + data: (candidate != null) ? JSON.stringify(candidate.toJSON()) : '' + }) + }, + (err) => { + log.error('cannot set candidate since sending answer failed', err) + connectedPromise.reject(err) + } + ) + } + + resolveOnConnected(pc, connectedPromise) + + // read an SDP offer + const pbOffer = await stream.read() + if (pbOffer.type !== Message.Type.SDP_OFFER) { + throw new Error(`expected message type SDP_OFFER, received: ${pbOffer.type ?? 'undefined'} `) + } + const offer = new RTCSessionDescription({ + type: 'offer', + sdp: pbOffer.data + }) + + await pc.setRemoteDescription(offer).catch(err => { + log.error('could not execute setRemoteDescription', err) + throw new Error('Failed to set remoteDescription') + }) + + // create and write an SDP answer + const answer = await pc.createAnswer().catch(err => { + log.error('could not execute createAnswer', err) + answerSentPromise.reject(err) + throw new Error('Failed to create answer') + }) + // write the answer to the remote + await stream.write({ type: Message.Type.SDP_ANSWER, data: answer.sdp }) + + await pc.setLocalDescription(answer).catch(err => { + log.error('could not execute setLocalDescription', err) + answerSentPromise.reject(err) + throw new Error('Failed to set localDescription') + }) + + answerSentPromise.resolve() + + // wait until candidates are connected + await readCandidatesUntilConnected(connectedPromise, pc, stream) - // read an SDP offer - const pbOffer = await stream.read() - if (pbOffer.type !== Message.Type.SDP_OFFER) { - throw new Error(`expected message type SDP_OFFER, received: ${pbOffer.type ?? 'undefined'} `) + const remoteAddress = parseRemoteAddress(pc.currentRemoteDescription?.sdp ?? '') + + return { pc, muxerFactory, remoteAddress } + } catch (err) { + pc.close() + throw err } - const offer = new RTCSessionDescription({ - type: 'offer', - sdp: pbOffer.data - }) - - await pc.setRemoteDescription(offer).catch(err => { - log.error('could not execute setRemoteDescription', err) - throw new Error('Failed to set remoteDescription') - }) - - // create and write an SDP answer - const answer = await pc.createAnswer().catch(err => { - log.error('could not execute createAnswer', err) - answerSentPromise.reject(err) - throw new Error('Failed to create answer') - }) - // write the answer to the remote - await stream.write({ type: Message.Type.SDP_ANSWER, data: answer.sdp }) - - await pc.setLocalDescription(answer).catch(err => { - log.error('could not execute setLocalDescription', err) - answerSentPromise.reject(err) - throw new Error('Failed to set localDescription') - }) - - answerSentPromise.resolve() - - // wait until candidates are connected - await readCandidatesUntilConnected(connectedPromise, pc, stream) - - const remoteAddress = parseRemoteAddress(pc.currentRemoteDescription?.sdp ?? '') - - return { pc, muxerFactory, remoteAddress } } export interface ConnectOptions { @@ -93,56 +104,63 @@ export async function initiateConnection ({ rtcConfiguration, dataChannelOptions const stream = pbStream(abortableDuplex(rawStream, signal)).pb(Message) // setup peer connection const pc = new RTCPeerConnection(rtcConfiguration) - const muxerFactory = new DataChannelMuxerFactory({ peerConnection: pc, dataChannelOptions }) - - const connectedPromise: DeferredPromise = pDefer() - resolveOnConnected(pc, connectedPromise) - - // reject the connectedPromise if the signal aborts - signal.onabort = connectedPromise.reject - // we create the channel so that the peerconnection has a component for which - // to collect candidates. The label is not relevant to connection initiation - // but can be useful for debugging - const channel = pc.createDataChannel('init') - // setup callback to write ICE candidates to the remote - // peer - pc.onicecandidate = ({ candidate }) => { - void stream.write({ - type: Message.Type.ICE_CANDIDATE, - data: (candidate != null) ? JSON.stringify(candidate.toJSON()) : '' - }) - .catch(err => { - log.error('error sending ICE candidate', err) + + try { + const muxerFactory = new DataChannelMuxerFactory({ peerConnection: pc, dataChannelOptions }) + + const connectedPromise: DeferredPromise = pDefer() + resolveOnConnected(pc, connectedPromise) + + // reject the connectedPromise if the signal aborts + signal.onabort = connectedPromise.reject + // we create the channel so that the peerconnection has a component for which + // to collect candidates. The label is not relevant to connection initiation + // but can be useful for debugging + const channel = pc.createDataChannel('init') + // setup callback to write ICE candidates to the remote + // peer + pc.onicecandidate = ({ candidate }) => { + void stream.write({ + type: Message.Type.ICE_CANDIDATE, + data: (candidate != null) ? JSON.stringify(candidate.toJSON()) : '' }) - } - // create an offer - const offerSdp = await pc.createOffer() - // write the offer to the stream - await stream.write({ type: Message.Type.SDP_OFFER, data: offerSdp.sdp }) - // set offer as local description - await pc.setLocalDescription(offerSdp).catch(err => { - log.error('could not execute setLocalDescription', err) - throw new Error('Failed to set localDescription') - }) - - // read answer - const answerMessage = await stream.read() - if (answerMessage.type !== Message.Type.SDP_ANSWER) { - throw new Error('remote should send an SDP answer') - } + .catch(err => { + log.error('error sending ICE candidate', err) + }) + } + + // create an offer + const offerSdp = await pc.createOffer() + // write the offer to the stream + await stream.write({ type: Message.Type.SDP_OFFER, data: offerSdp.sdp }) + // set offer as local description + await pc.setLocalDescription(offerSdp).catch(err => { + log.error('could not execute setLocalDescription', err) + throw new Error('Failed to set localDescription') + }) - const answerSdp = new RTCSessionDescription({ type: 'answer', sdp: answerMessage.data }) - await pc.setRemoteDescription(answerSdp).catch(err => { - log.error('could not execute setRemoteDescription', err) - throw new Error('Failed to set remoteDescription') - }) + // read answer + const answerMessage = await stream.read() + if (answerMessage.type !== Message.Type.SDP_ANSWER) { + throw new Error('remote should send an SDP answer') + } - await readCandidatesUntilConnected(connectedPromise, pc, stream) - channel.close() + const answerSdp = new RTCSessionDescription({ type: 'answer', sdp: answerMessage.data }) + await pc.setRemoteDescription(answerSdp).catch(err => { + log.error('could not execute setRemoteDescription', err) + throw new Error('Failed to set remoteDescription') + }) - const remoteAddress = parseRemoteAddress(pc.currentRemoteDescription?.sdp ?? '') + await readCandidatesUntilConnected(connectedPromise, pc, stream) + channel.close() - return { pc, muxerFactory, remoteAddress } + const remoteAddress = parseRemoteAddress(pc.currentRemoteDescription?.sdp ?? '') + + return { pc, muxerFactory, remoteAddress } + } catch (err) { + pc.close() + throw err + } } function parseRemoteAddress (sdp: string): string { diff --git a/packages/transport-webrtc/src/private-to-private/transport.ts b/packages/transport-webrtc/src/private-to-private/transport.ts index e9d2f83bdb..7f93ac13e8 100644 --- a/packages/transport-webrtc/src/private-to-private/transport.ts +++ b/packages/transport-webrtc/src/private-to-private/transport.ts @@ -5,6 +5,7 @@ import { peerIdFromString } from '@libp2p/peer-id' import { multiaddr, type Multiaddr, protocols } from '@multiformats/multiaddr' import { codes } from '../error.js' import { WebRTCMultiaddrConnection } from '../maconn.js' +import { cleanup } from '../webrtc/index.js' import { initiateConnection, handleIncomingStream } from './handler.js' import { WebRTCPeerListener } from './listener.js' import type { DataChannelOpts } from '../stream.js' @@ -57,6 +58,7 @@ export class WebRTCTransport implements Transport, Startable { async stop (): Promise { await this.components.registrar.unhandle(SIGNALING_PROTO_ID) + cleanup() this._started = false } diff --git a/packages/transport-webrtc/src/private-to-private/util.ts b/packages/transport-webrtc/src/private-to-private/util.ts index e1b669778c..6d2b97898d 100644 --- a/packages/transport-webrtc/src/private-to-private/util.ts +++ b/packages/transport-webrtc/src/private-to-private/util.ts @@ -1,5 +1,6 @@ import { logger } from '@libp2p/logger' import { isFirefox } from '../util.js' +import { RTCIceCandidate } from '../webrtc/index.js' import { Message } from './pb/message.js' import type { DeferredPromise } from 'p-defer' diff --git a/packages/transport-webrtc/src/private-to-public/sdp.ts b/packages/transport-webrtc/src/private-to-public/sdp.ts index 474455021f..c487c8c57d 100644 --- a/packages/transport-webrtc/src/private-to-public/sdp.ts +++ b/packages/transport-webrtc/src/private-to-public/sdp.ts @@ -133,7 +133,7 @@ a=ice-ufrag:${ufrag} a=ice-pwd:${ufrag} a=fingerprint:${CERTFP} a=sctp-port:5000 -a=max-message-size:100000 +a=max-message-size:16384 a=candidate:1467250027 1 UDP 1467250027 ${host} ${port} typ host\r\n` } diff --git a/packages/transport-webrtc/src/private-to-public/transport.ts b/packages/transport-webrtc/src/private-to-public/transport.ts index c65f823387..f824197378 100644 --- a/packages/transport-webrtc/src/private-to-public/transport.ts +++ b/packages/transport-webrtc/src/private-to-public/transport.ts @@ -11,6 +11,7 @@ import { WebRTCMultiaddrConnection } from '../maconn.js' import { DataChannelMuxerFactory } from '../muxer.js' import { createStream } from '../stream.js' import { isFirefox } from '../util.js' +import { RTCPeerConnection } from '../webrtc/index.js' import * as sdp from './sdp.js' import { genUfrag } from './util.js' import type { WebRTCDialOptions } from './options.js' @@ -134,116 +135,121 @@ export class WebRTCDirectTransport implements Transport { const peerConnection = new RTCPeerConnection({ certificates: [certificate] }) - // create data channel for running the noise handshake. Once the data channel is opened, - // the remote will initiate the noise handshake. This is used to confirm the identity of - // the peer. - const dataChannelOpenPromise = new Promise((resolve, reject) => { - const handshakeDataChannel = peerConnection.createDataChannel('', { negotiated: true, id: 0 }) - const handshakeTimeout = setTimeout(() => { - const error = `Data channel was never opened: state: ${handshakeDataChannel.readyState}` - log.error(error) - this.metrics?.dialerEvents.increment({ open_error: true }) - reject(dataChannelError('data', error)) - }, HANDSHAKE_TIMEOUT_MS) - - handshakeDataChannel.onopen = (_) => { - clearTimeout(handshakeTimeout) - resolve(handshakeDataChannel) - } - - // ref: https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/error_event - handshakeDataChannel.onerror = (event: Event) => { - clearTimeout(handshakeTimeout) - const errorTarget = event.target?.toString() ?? 'not specified' - const error = `Error opening a data channel for handshaking: ${errorTarget}` - log.error(error) - // NOTE: We use unknown error here but this could potentially be considered a reset by some standards. - this.metrics?.dialerEvents.increment({ unknown_error: true }) - reject(dataChannelError('data', error)) - } - }) - - const ufrag = 'libp2p+webrtc+v1/' + genUfrag(32) - - // Create offer and munge sdp with ufrag == pwd. This allows the remote to - // respond to STUN messages without performing an actual SDP exchange. - // This is because it can infer the passwd field by reading the USERNAME - // attribute of the STUN message. - const offerSdp = await peerConnection.createOffer() - const mungedOfferSdp = sdp.munge(offerSdp, ufrag) - await peerConnection.setLocalDescription(mungedOfferSdp) - - // construct answer sdp from multiaddr and ufrag - const answerSdp = sdp.fromMultiAddr(ma, ufrag) - await peerConnection.setRemoteDescription(answerSdp) - - // wait for peerconnection.onopen to fire, or for the datachannel to open - const handshakeDataChannel = await dataChannelOpenPromise - - const myPeerId = this.components.peerId - - // Do noise handshake. - // Set the Noise Prologue to libp2p-webrtc-noise: before starting the actual Noise handshake. - // is the concatenation of the of the two TLS fingerprints of A and B in their multihash byte representation, sorted in ascending order. - const fingerprintsPrologue = this.generateNoisePrologue(peerConnection, remoteCerthash.code, ma) - - // Since we use the default crypto interface and do not use a static key or early data, - // we pass in undefined for these parameters. - const noise = Noise({ prologueBytes: fingerprintsPrologue })() - - const wrappedChannel = createStream({ channel: handshakeDataChannel, direction: 'inbound', dataChannelOptions: this.init.dataChannel }) - const wrappedDuplex = { - ...wrappedChannel, - sink: wrappedChannel.sink.bind(wrappedChannel), - source: (async function * () { - for await (const list of wrappedChannel.source) { - for (const buf of list) { - yield buf - } + try { + // create data channel for running the noise handshake. Once the data channel is opened, + // the remote will initiate the noise handshake. This is used to confirm the identity of + // the peer. + const dataChannelOpenPromise = new Promise((resolve, reject) => { + const handshakeDataChannel = peerConnection.createDataChannel('', { negotiated: true, id: 0 }) + const handshakeTimeout = setTimeout(() => { + const error = `Data channel was never opened: state: ${handshakeDataChannel.readyState}` + log.error(error) + this.metrics?.dialerEvents.increment({ open_error: true }) + reject(dataChannelError('data', error)) + }, HANDSHAKE_TIMEOUT_MS) + + handshakeDataChannel.onopen = (_) => { + clearTimeout(handshakeTimeout) + resolve(handshakeDataChannel) } - }()) - } - // Creating the connection before completion of the noise - // handshake ensures that the stream opening callback is set up - const maConn = new WebRTCMultiaddrConnection({ - peerConnection, - remoteAddr: ma, - timeline: { - open: Date.now() - }, - metrics: this.metrics?.dialerEvents - }) - - const eventListeningName = isFirefox ? 'iceconnectionstatechange' : 'connectionstatechange' - - peerConnection.addEventListener(eventListeningName, () => { - switch (peerConnection.connectionState) { - case 'failed': - case 'disconnected': - case 'closed': - maConn.close().catch((err) => { - log.error('error closing connection', err) - }).finally(() => { - // Remove the event listener once the connection is closed - controller.abort() - }) - break - default: - break + // ref: https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/error_event + handshakeDataChannel.onerror = (event: Event) => { + clearTimeout(handshakeTimeout) + const errorTarget = event.target?.toString() ?? 'not specified' + const error = `Error opening a data channel for handshaking: ${errorTarget}` + log.error(error) + // NOTE: We use unknown error here but this could potentially be considered a reset by some standards. + this.metrics?.dialerEvents.increment({ unknown_error: true }) + reject(dataChannelError('data', error)) + } + }) + + const ufrag = 'libp2p+webrtc+v1/' + genUfrag(32) + + // Create offer and munge sdp with ufrag == pwd. This allows the remote to + // respond to STUN messages without performing an actual SDP exchange. + // This is because it can infer the passwd field by reading the USERNAME + // attribute of the STUN message. + const offerSdp = await peerConnection.createOffer() + const mungedOfferSdp = sdp.munge(offerSdp, ufrag) + await peerConnection.setLocalDescription(mungedOfferSdp) + + // construct answer sdp from multiaddr and ufrag + const answerSdp = sdp.fromMultiAddr(ma, ufrag) + await peerConnection.setRemoteDescription(answerSdp) + + // wait for peerconnection.onopen to fire, or for the datachannel to open + const handshakeDataChannel = await dataChannelOpenPromise + + const myPeerId = this.components.peerId + + // Do noise handshake. + // Set the Noise Prologue to libp2p-webrtc-noise: before starting the actual Noise handshake. + // is the concatenation of the of the two TLS fingerprints of A and B in their multihash byte representation, sorted in ascending order. + const fingerprintsPrologue = this.generateNoisePrologue(peerConnection, remoteCerthash.code, ma) + + // Since we use the default crypto interface and do not use a static key or early data, + // we pass in undefined for these parameters. + const noise = Noise({ prologueBytes: fingerprintsPrologue })() + + const wrappedChannel = createStream({ channel: handshakeDataChannel, direction: 'inbound', dataChannelOptions: this.init.dataChannel }) + const wrappedDuplex = { + ...wrappedChannel, + sink: wrappedChannel.sink.bind(wrappedChannel), + source: (async function * () { + for await (const list of wrappedChannel.source) { + for (const buf of list) { + yield buf + } + } + }()) } - }, { signal }) - // Track opened peer connection - this.metrics?.dialerEvents.increment({ peer_connection: true }) + // Creating the connection before completion of the noise + // handshake ensures that the stream opening callback is set up + const maConn = new WebRTCMultiaddrConnection({ + peerConnection, + remoteAddr: ma, + timeline: { + open: Date.now() + }, + metrics: this.metrics?.dialerEvents + }) + + const eventListeningName = isFirefox ? 'iceconnectionstatechange' : 'connectionstatechange' + + peerConnection.addEventListener(eventListeningName, () => { + switch (peerConnection.connectionState) { + case 'failed': + case 'disconnected': + case 'closed': + maConn.close().catch((err) => { + log.error('error closing connection', err) + }).finally(() => { + // Remove the event listener once the connection is closed + controller.abort() + }) + break + default: + break + } + }, { signal }) - const muxerFactory = new DataChannelMuxerFactory({ peerConnection, metrics: this.metrics?.dialerEvents, dataChannelOptions: this.init.dataChannel }) + // Track opened peer connection + this.metrics?.dialerEvents.increment({ peer_connection: true }) - // For outbound connections, the remote is expected to start the noise handshake. - // Therefore, we need to secure an inbound noise connection from the remote. - await noise.secureInbound(myPeerId, wrappedDuplex, theirPeerId) + const muxerFactory = new DataChannelMuxerFactory({ peerConnection, metrics: this.metrics?.dialerEvents, dataChannelOptions: this.init.dataChannel }) - return options.upgrader.upgradeOutbound(maConn, { skipProtection: true, skipEncryption: true, muxerFactory }) + // For outbound connections, the remote is expected to start the noise handshake. + // Therefore, we need to secure an inbound noise connection from the remote. + await noise.secureInbound(myPeerId, wrappedDuplex, theirPeerId) + + return await options.upgrader.upgradeOutbound(maConn, { skipProtection: true, skipEncryption: true, muxerFactory }) + } catch (err) { + peerConnection.close() + throw err + } } /** diff --git a/packages/transport-webrtc/src/webrtc/index.browser.ts b/packages/transport-webrtc/src/webrtc/index.browser.ts new file mode 100644 index 0000000000..4e746e6f9a --- /dev/null +++ b/packages/transport-webrtc/src/webrtc/index.browser.ts @@ -0,0 +1,4 @@ +export const RTCPeerConnection = globalThis.RTCPeerConnection +export const RTCSessionDescription = globalThis.RTCSessionDescription +export const RTCIceCandidate = globalThis.RTCIceCandidate +export function cleanup (): void {} diff --git a/packages/transport-webrtc/src/webrtc/index.ts b/packages/transport-webrtc/src/webrtc/index.ts new file mode 100644 index 0000000000..6540ba340b --- /dev/null +++ b/packages/transport-webrtc/src/webrtc/index.ts @@ -0,0 +1,12 @@ +import node from 'node-datachannel' +import { IceCandidate } from './rtc-ice-candidate.js' +import { PeerConnection } from './rtc-peer-connection.js' +import { SessionDescription } from './rtc-session-description.js' + +export { SessionDescription as RTCSessionDescription } +export { IceCandidate as RTCIceCandidate } +export { PeerConnection as RTCPeerConnection } + +export function cleanup (): void { + node.cleanup() +} diff --git a/packages/transport-webrtc/src/webrtc/rtc-data-channel.ts b/packages/transport-webrtc/src/webrtc/rtc-data-channel.ts new file mode 100644 index 0000000000..b5129abe54 --- /dev/null +++ b/packages/transport-webrtc/src/webrtc/rtc-data-channel.ts @@ -0,0 +1,140 @@ +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import type node from 'node-datachannel' + +export class DataChannel extends EventTarget implements RTCDataChannel { + binaryType: BinaryType + + readonly maxPacketLifeTime: number | null + readonly maxRetransmits: number | null + readonly negotiated: boolean + readonly ordered: boolean + + onbufferedamountlow: ((this: RTCDataChannel, ev: Event) => any) | null + onclose: ((this: RTCDataChannel, ev: Event) => any) | null + onclosing: ((this: RTCDataChannel, ev: Event) => any) | null + onerror: ((this: RTCDataChannel, ev: Event) => any) | null + onmessage: ((this: RTCDataChannel, ev: MessageEvent) => any) | null + onopen: ((this: RTCDataChannel, ev: Event) => any) | null + + #dataChannel: node.DataChannel + #bufferedAmountLowThreshold: number + #readyState: RTCDataChannelState + + constructor (dataChannel: node.DataChannel, dataChannelDict: RTCDataChannelInit = {}) { + super() + + this.#dataChannel = dataChannel + this.#readyState = 'connecting' + this.#bufferedAmountLowThreshold = 0 + + this.binaryType = 'arraybuffer' + + this.#dataChannel.onOpen(() => { + this.#readyState = 'open' + this.dispatchEvent(new Event('open')) + }) + this.#dataChannel.onClosed(() => { + this.#readyState = 'closed' + this.dispatchEvent(new Event('close')) + }) + this.#dataChannel.onError((msg) => { + this.#readyState = 'closed' + this.dispatchEvent(new RTCErrorEvent('error', { + error: new RTCError({ + errorDetail: 'data-channel-failure' + }, msg) + })) + }) + this.#dataChannel.onBufferedAmountLow(() => { + this.dispatchEvent(new Event('bufferedamountlow')) + }) + this.#dataChannel.onMessage((data: string | Uint8Array) => { + if (typeof data === 'string') { + data = uint8ArrayFromString(data) + } + + this.dispatchEvent(new MessageEvent('message', { data })) + }) + + // forward events to properties + this.addEventListener('message', event => { + this.onmessage?.(event as MessageEvent) + }) + this.addEventListener('bufferedamountlow', event => { + this.onbufferedamountlow?.(event) + }) + this.addEventListener('error', event => { + this.onerror?.(event) + }) + this.addEventListener('close', event => { + this.onclose?.(event) + }) + this.addEventListener('closing', event => { + this.onclosing?.(event) + }) + this.addEventListener('open', event => { + this.onopen?.(event) + }) + + this.onbufferedamountlow = null + this.onclose = null + this.onclosing = null + this.onerror = null + this.onmessage = null + this.onopen = null + + this.maxPacketLifeTime = dataChannelDict.maxPacketLifeTime ?? null + this.maxRetransmits = dataChannelDict.maxRetransmits ?? null + this.negotiated = dataChannelDict.negotiated ?? false + this.ordered = dataChannelDict.ordered ?? true + } + + get id (): number { + return this.#dataChannel.getId() + } + + get label (): string { + return this.#dataChannel.getLabel() + } + + get protocol (): string { + return this.#dataChannel.getProtocol() + } + + get bufferedAmount (): number { + return this.#dataChannel.bufferedAmount() + } + + set bufferedAmountLowThreshold (threshold: number) { + this.#bufferedAmountLowThreshold = threshold + this.#dataChannel.setBufferedAmountLowThreshold(threshold) + } + + get bufferedAmountLowThreshold (): number { + return this.#bufferedAmountLowThreshold + } + + get readyState (): RTCDataChannelState { + return this.#readyState + } + + close (): void { + this.#readyState = 'closing' + this.dispatchEvent(new Event('closing')) + + this.#dataChannel.close() + } + + send (data: string): void + send (data: Blob): void + send (data: ArrayBuffer): void + send (data: ArrayBufferView): void + send (data: any): void { + // TODO: sending Blobs + if (typeof data === 'string') { + this.#dataChannel.sendMessage(data) + } else { + this.#dataChannel.sendMessageBinary(data) + } + } +} diff --git a/packages/transport-webrtc/src/webrtc/rtc-events.ts b/packages/transport-webrtc/src/webrtc/rtc-events.ts new file mode 100644 index 0000000000..b7a8772139 --- /dev/null +++ b/packages/transport-webrtc/src/webrtc/rtc-events.ts @@ -0,0 +1,19 @@ +export class PeerConnectionIceEvent extends Event implements RTCPeerConnectionIceEvent { + readonly candidate: RTCIceCandidate | null + + constructor (candidate: RTCIceCandidate) { + super('icecandidate') + + this.candidate = candidate + } +} + +export class DataChannelEvent extends Event implements RTCDataChannelEvent { + readonly channel: RTCDataChannel + + constructor (channel: RTCDataChannel) { + super('datachannel') + + this.channel = channel + } +} diff --git a/packages/transport-webrtc/src/webrtc/rtc-ice-candidate.ts b/packages/transport-webrtc/src/webrtc/rtc-ice-candidate.ts new file mode 100644 index 0000000000..ea02ec99e1 --- /dev/null +++ b/packages/transport-webrtc/src/webrtc/rtc-ice-candidate.ts @@ -0,0 +1,50 @@ +/** + * @see https://developer.mozilla.org/docs/Web/API/RTCIceCandidate + */ +export class IceCandidate implements RTCIceCandidate { + readonly address: string | null + readonly candidate: string + readonly component: RTCIceComponent | null + readonly foundation: string | null + readonly port: number | null + readonly priority: number | null + readonly protocol: RTCIceProtocol | null + readonly relatedAddress: string | null + readonly relatedPort: number | null + readonly sdpMLineIndex: number | null + readonly sdpMid: string | null + readonly tcpType: RTCIceTcpCandidateType | null + readonly type: RTCIceCandidateType | null + readonly usernameFragment: string | null + + constructor (init: RTCIceCandidateInit) { + if (init.candidate == null) { + throw new DOMException('candidate must be specified') + } + + this.candidate = init.candidate + this.sdpMLineIndex = init.sdpMLineIndex ?? null + this.sdpMid = init.sdpMid ?? null + this.usernameFragment = init.usernameFragment ?? null + + this.address = null + this.component = null + this.foundation = null + this.port = null + this.priority = null + this.protocol = null + this.relatedAddress = null + this.relatedPort = null + this.tcpType = null + this.type = null + } + + toJSON (): RTCIceCandidateInit { + return { + candidate: this.candidate, + sdpMLineIndex: this.sdpMLineIndex, + sdpMid: this.sdpMid, + usernameFragment: this.usernameFragment + } + } +} diff --git a/packages/transport-webrtc/src/webrtc/rtc-peer-connection.ts b/packages/transport-webrtc/src/webrtc/rtc-peer-connection.ts new file mode 100644 index 0000000000..7b2b5c6446 --- /dev/null +++ b/packages/transport-webrtc/src/webrtc/rtc-peer-connection.ts @@ -0,0 +1,306 @@ +import node from 'node-datachannel' +import defer, { type DeferredPromise } from 'p-defer' +import { DataChannel } from './rtc-data-channel.js' +import { DataChannelEvent, PeerConnectionIceEvent } from './rtc-events.js' +import { IceCandidate } from './rtc-ice-candidate.js' +import { SessionDescription } from './rtc-session-description.js' + +export class PeerConnection extends EventTarget implements RTCPeerConnection { + static async generateCertificate (keygenAlgorithm: AlgorithmIdentifier): Promise { + throw new Error('Not implemented') + } + + canTrickleIceCandidates: boolean | null + sctp: RTCSctpTransport | null + + onconnectionstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null + ondatachannel: ((this: RTCPeerConnection, ev: RTCDataChannelEvent) => any) | null + onicecandidate: ((this: RTCPeerConnection, ev: RTCPeerConnectionIceEvent) => any) | null + onicecandidateerror: ((this: RTCPeerConnection, ev: Event) => any) | null + oniceconnectionstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null + onicegatheringstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null + onnegotiationneeded: ((this: RTCPeerConnection, ev: Event) => any) | null + onsignalingstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null + ontrack: ((this: RTCPeerConnection, ev: RTCTrackEvent) => any) | null + + #peerConnection: node.PeerConnection + #config: RTCConfiguration + #localOffer: DeferredPromise + #localAnswer: DeferredPromise + #dataChannels: Set + + constructor (init: RTCConfiguration = {}) { + super() + + this.#config = init + this.#localOffer = defer() + this.#localAnswer = defer() + this.#dataChannels = new Set() + + const iceServers = init.iceServers ?? [] + + this.#peerConnection = new node.PeerConnection(`peer-${Math.random()}`, { + iceServers: iceServers.map(server => { + const urls = (Array.isArray(server.urls) ? server.urls : [server.urls]).map(str => new URL(str)) + + return urls.map(url => { + /** @type {import('../lib/index.js').IceServer} */ + const iceServer = { + hostname: url.hostname, + port: parseInt(url.port, 10), + username: server.username, + password: server.credential + // relayType - how to specify? + } + + return iceServer + }) + }) + .flat(), + iceTransportPolicy: init?.iceTransportPolicy + }) + + this.#peerConnection.onStateChange(() => { + this.dispatchEvent(new Event('connectionstatechange')) + }) + // https://github.com/murat-dogan/node-datachannel/pull/171 + // this.#peerConnection.onSignalingStateChange(() => { + // this.dispatchEvent(new Event('signalingstatechange')) + // }) + this.#peerConnection.onGatheringStateChange(() => { + this.dispatchEvent(new Event('icegatheringstatechange')) + }) + this.#peerConnection.onDataChannel(channel => { + this.dispatchEvent(new DataChannelEvent(new DataChannel(channel))) + }) + + // forward events to properties + this.addEventListener('connectionstatechange', event => { + this.onconnectionstatechange?.(event) + }) + this.addEventListener('signalingstatechange', event => { + this.onsignalingstatechange?.(event) + }) + this.addEventListener('icegatheringstatechange', event => { + this.onicegatheringstatechange?.(event) + }) + this.addEventListener('datachannel', event => { + this.ondatachannel?.(event as RTCDataChannelEvent) + }) + + this.#peerConnection.onLocalDescription((sdp, type) => { + if (type === 'offer') { + this.#localOffer.resolve({ + sdp, + type + }) + } + + if (type === 'answer') { + this.#localAnswer.resolve({ + sdp, + type + }) + } + }) + + this.#peerConnection.onLocalCandidate((candidate, mid) => { + if (mid === 'unspec') { + this.#localAnswer.reject(new Error(`Invalid description type ${mid}`)) + return + } + + const event = new PeerConnectionIceEvent(new IceCandidate({ candidate })) + + this.onicecandidate?.(event) + }) + + this.canTrickleIceCandidates = null + this.sctp = null + this.onconnectionstatechange = null + this.ondatachannel = null + this.onicecandidate = null + this.onicecandidateerror = null + this.oniceconnectionstatechange = null + this.onicegatheringstatechange = null + this.onnegotiationneeded = null + this.onsignalingstatechange = null + this.ontrack = null + } + + get connectionState (): RTCPeerConnectionState { + return assertState(this.#peerConnection.state(), RTCPeerConnectionStates) + } + + get iceConnectionState (): RTCIceConnectionState { + return assertState(this.#peerConnection.state(), RTCIceConnectionStates) + } + + get iceGatheringState (): RTCIceGatheringState { + return assertState(this.#peerConnection.gatheringState(), RTCIceGatheringStates) + } + + get signalingState (): RTCSignalingState { + return assertState(this.#peerConnection.signalingState(), RTCSignalingStates) + } + + get currentLocalDescription (): RTCSessionDescription | null { + return toSessionDescription(this.#peerConnection.localDescription()) + } + + get localDescription (): RTCSessionDescription | null { + return toSessionDescription(this.#peerConnection.localDescription()) + } + + get pendingLocalDescription (): RTCSessionDescription | null { + return toSessionDescription(this.#peerConnection.localDescription()) + } + + get currentRemoteDescription (): RTCSessionDescription | null { + // not exposed by node-datachannel + return toSessionDescription(null) + } + + get pendingRemoteDescription (): RTCSessionDescription | null { + // not exposed by node-datachannel + return toSessionDescription(null) + } + + get remoteDescription (): RTCSessionDescription | null { + // not exposed by node-datachannel + return toSessionDescription(null) + } + + async addIceCandidate (candidate?: RTCIceCandidateInit): Promise { + if (candidate == null || candidate.candidate == null) { + throw new Error('Candidate invalid') + } + + this.#peerConnection.addRemoteCandidate(candidate.candidate, candidate.sdpMid ?? '0') + } + + addTrack (track: MediaStreamTrack, ...streams: MediaStream[]): RTCRtpSender { + throw new Error('Not implemented') + } + + addTransceiver (trackOrKind: MediaStreamTrack | string, init?: RTCRtpTransceiverInit): RTCRtpTransceiver { + throw new Error('Not implemented') + } + + close (): void { + // close all channels before shutting down + this.#dataChannels.forEach(channel => { + channel.close() + }) + + this.#peerConnection.close() + this.#peerConnection.destroy() + } + + createDataChannel (label: string, dataChannelDict: RTCDataChannelInit = {}): RTCDataChannel { + const channel = this.#peerConnection.createDataChannel(label, dataChannelDict) + const dataChannel = new DataChannel(channel, dataChannelDict) + + // ensure we can close all channels when shutting down + this.#dataChannels.add(dataChannel) + dataChannel.addEventListener('close', () => { + this.#dataChannels.delete(dataChannel) + }) + + return dataChannel + } + + async createOffer (options?: RTCOfferOptions): Promise + async createOffer (successCallback: RTCSessionDescriptionCallback, failureCallback: RTCPeerConnectionErrorCallback, options?: RTCOfferOptions): Promise + async createOffer (...args: any[]): Promise { + return this.#localOffer.promise + } + + async createAnswer (options?: RTCAnswerOptions): Promise + async createAnswer (successCallback: RTCSessionDescriptionCallback, failureCallback: RTCPeerConnectionErrorCallback): Promise + async createAnswer (...args: any[]): Promise { + return this.#localAnswer.promise + } + + getConfiguration (): RTCConfiguration { + return this.#config + } + + getReceivers (): RTCRtpReceiver[] { + throw new Error('Not implemented') + } + + getSenders (): RTCRtpSender[] { + throw new Error('Not implemented') + } + + async getStats (selector?: MediaStreamTrack | null): Promise { + throw new Error('Not implemented') + } + + getTransceivers (): RTCRtpTransceiver[] { + throw new Error('Not implemented') + } + + removeTrack (sender: RTCRtpSender): void { + throw new Error('Not implemented') + } + + restartIce (): void { + throw new Error('Not implemented') + } + + setConfiguration (configuration: RTCConfiguration = {}): void { + this.#config = configuration + } + + async setLocalDescription (description?: RTCLocalSessionDescriptionInit): Promise { + if (description == null || description.type == null) { + throw new Error('Local description type must be set') + } + + if (description.type !== 'offer') { + // any other type causes libdatachannel to throw + return + } + + // @ts-expect-error types are wrong + this.#peerConnection.setLocalDescription(description.type) + } + + async setRemoteDescription (description: RTCSessionDescriptionInit): Promise { + if (description.sdp == null) { + throw new Error('Remote SDP must be set') + } + + // @ts-expect-error types are wrong + this.#peerConnection.setRemoteDescription(description.sdp, description.type) + } +} + +export { PeerConnection as RTCPeerConnection } + +function assertState (state: any, states: T[]): T { + if (state != null && !states.includes(state)) { + throw new Error(`Invalid value encountered - "${state}" must be one of ${states}`) + } + + return state as T +} + +function toSessionDescription (description: { sdp?: string, type: string } | null): RTCSessionDescription | null { + if (description == null) { + return null + } + + return new SessionDescription({ + sdp: description.sdp, + type: assertState(description.type, RTCSdpTypes) + }) +} + +const RTCPeerConnectionStates: RTCPeerConnectionState[] = ['closed', 'connected', 'connecting', 'disconnected', 'failed', 'new'] +const RTCSdpTypes: RTCSdpType[] = ['answer', 'offer', 'pranswer', 'rollback'] +const RTCIceConnectionStates: RTCIceConnectionState[] = ['checking', 'closed', 'completed', 'connected', 'disconnected', 'failed', 'new'] +const RTCIceGatheringStates: RTCIceGatheringState[] = ['complete', 'gathering', 'new'] +const RTCSignalingStates: RTCSignalingState[] = ['closed', 'have-local-offer', 'have-local-pranswer', 'have-remote-offer', 'have-remote-pranswer', 'stable'] diff --git a/packages/transport-webrtc/src/webrtc/rtc-session-description.ts b/packages/transport-webrtc/src/webrtc/rtc-session-description.ts new file mode 100644 index 0000000000..ae498cbdaf --- /dev/null +++ b/packages/transport-webrtc/src/webrtc/rtc-session-description.ts @@ -0,0 +1,19 @@ +/** + * @see https://developer.mozilla.org/docs/Web/API/RTCSessionDescription + */ +export class SessionDescription implements RTCSessionDescription { + readonly sdp: string + readonly type: RTCSdpType + + constructor (init: RTCSessionDescriptionInit) { + this.sdp = init.sdp ?? '' + this.type = init.type + } + + toJSON (): RTCSessionDescriptionInit { + return { + sdp: this.sdp, + type: this.type + } + } +} diff --git a/packages/transport-webrtc/test/listener.spec.ts b/packages/transport-webrtc/test/listener.spec.ts index e4e6dc149c..34feedb859 100644 --- a/packages/transport-webrtc/test/listener.spec.ts +++ b/packages/transport-webrtc/test/listener.spec.ts @@ -2,7 +2,7 @@ import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' import { stubInterface } from 'sinon-ts' -import { WebRTCPeerListener } from '../src/private-to-private/listener' +import { WebRTCPeerListener } from '../src/private-to-private/listener.js' import type { Listener } from '@libp2p/interface/transport' import type { TransportManager } from '@libp2p/interface-internal/transport-manager' diff --git a/packages/transport-webrtc/test/maconn.browser.spec.ts b/packages/transport-webrtc/test/maconn.browser.spec.ts index a8e91272c4..d93362f844 100644 --- a/packages/transport-webrtc/test/maconn.browser.spec.ts +++ b/packages/transport-webrtc/test/maconn.browser.spec.ts @@ -4,6 +4,7 @@ import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' import { stubObject } from 'sinon-ts' import { WebRTCMultiaddrConnection } from '../src/maconn.js' +import { RTCPeerConnection } from '../src/webrtc/index.js' import type { CounterGroup } from '@libp2p/interface/metrics' describe('Multiaddr Connection', () => { diff --git a/packages/transport-webrtc/test/peer.browser.spec.ts b/packages/transport-webrtc/test/peer.browser.spec.ts index 918ed2d0a5..8d490ff967 100644 --- a/packages/transport-webrtc/test/peer.browser.spec.ts +++ b/packages/transport-webrtc/test/peer.browser.spec.ts @@ -7,14 +7,16 @@ import { pair } from 'it-pair' import { duplexPair } from 'it-pair/duplex' import { pbStream } from 'it-protobuf-stream' import Sinon from 'sinon' -import { initiateConnection, handleIncomingStream } from '../src/private-to-private/handler' +import { initiateConnection, handleIncomingStream } from '../src/private-to-private/handler.js' import { Message } from '../src/private-to-private/pb/message.js' -import { WebRTCTransport, splitAddr } from '../src/private-to-private/transport' +import { WebRTCTransport, splitAddr } from '../src/private-to-private/transport.js' +import { RTCPeerConnection, RTCSessionDescription } from '../src/webrtc/index.js' const browser = detect() describe('webrtc basic', () => { const isFirefox = ((browser != null) && browser.name === 'firefox') + it('should connect', async () => { const [receiver, initiator] = duplexPair() const dstPeerId = await createEd25519PeerId() @@ -34,6 +36,9 @@ describe('webrtc basic', () => { } expect(pc0.connectionState).eq('connected') expect(pc1.connectionState).eq('connected') + + pc0.close() + pc1.close() }) }) @@ -59,10 +64,8 @@ describe('webrtc dialer', () => { const initiatorPeerConnectionPromise = initiateConnection({ signal: controller.signal, stream: mockStream(initiator) }) const stream = pbStream(receiver).pb(Message) - { - const offerMessage = await stream.read() - expect(offerMessage.type).to.eq(Message.Type.SDP_OFFER) - } + const offerMessage = await stream.read() + expect(offerMessage.type).to.eq(Message.Type.SDP_OFFER) await stream.write({ type: Message.Type.SDP_ANSWER, data: 'bad' }) await expect(initiatorPeerConnectionPromise).to.be.rejectedWith(/Failed to set remoteDescription/) @@ -78,17 +81,18 @@ describe('webrtc dialer', () => { pc.onicecandidate = ({ candidate }) => { void stream.write({ type: Message.Type.ICE_CANDIDATE, data: JSON.stringify(candidate?.toJSON()) }) } - { - const offerMessage = await stream.read() - expect(offerMessage.type).to.eq(Message.Type.SDP_OFFER) - const offer = new RTCSessionDescription({ type: 'offer', sdp: offerMessage.data }) - await pc.setRemoteDescription(offer) - - const answer = await pc.createAnswer() - await pc.setLocalDescription(answer) - } + + const offerMessage = await stream.read() + expect(offerMessage.type).to.eq(Message.Type.SDP_OFFER) + const offer = new RTCSessionDescription({ type: 'offer', sdp: offerMessage.data }) + await pc.setRemoteDescription(offer) + + const answer = await pc.createAnswer() + await pc.setLocalDescription(answer) await expect(initiatorPeerConnectionPromise).to.be.rejectedWith(/remote should send an SDP answer/) + + pc.close() }) }) @@ -128,5 +132,3 @@ describe('webrtc splitAddr', () => { expect(peerId.toString()).to.eq('12D3KooWFNBgv86tcpcYUHQz9FWGTrTmpMgr8feZwQXQySVTo3A7') }) }) - -export { } diff --git a/packages/transport-webrtc/test/sdp.spec.ts b/packages/transport-webrtc/test/sdp.spec.ts index 2c32f7967b..cff8a794b1 100644 --- a/packages/transport-webrtc/test/sdp.spec.ts +++ b/packages/transport-webrtc/test/sdp.spec.ts @@ -17,7 +17,7 @@ a=ice-ufrag:MyUserFragment a=ice-pwd:MyUserFragment a=fingerprint:SHA-256 72:68:47:CD:48:B0:5E:C5:60:4D:15:9C:BF:40:1D:6F:00:A1:23:EC:90:17:0E:2C:D1:B3:8F:D2:9D:37:E5:B1 a=sctp-port:5000 -a=max-message-size:100000 +a=max-message-size:16384 a=candidate:1467250027 1 UDP 1467250027 0.0.0.0 56093 typ host` describe('SDP', () => { @@ -73,7 +73,7 @@ a=ice-ufrag:someotheruserfragmentstring a=ice-pwd:someotheruserfragmentstring a=fingerprint:SHA-256 72:68:47:CD:48:B0:5E:C5:60:4D:15:9C:BF:40:1D:6F:00:A1:23:EC:90:17:0E:2C:D1:B3:8F:D2:9D:37:E5:B1 a=sctp-port:5000 -a=max-message-size:100000 +a=max-message-size:16384 a=candidate:1467250027 1 UDP 1467250027 0.0.0.0 56093 typ host` expect(result.sdp).to.equal(expected) diff --git a/packages/transport-webrtc/test/stream.browser.spec.ts b/packages/transport-webrtc/test/stream.browser.spec.ts index 9bf0172c5e..457f95317d 100644 --- a/packages/transport-webrtc/test/stream.browser.spec.ts +++ b/packages/transport-webrtc/test/stream.browser.spec.ts @@ -4,6 +4,7 @@ import * as lengthPrefixed from 'it-length-prefixed' import { bytes } from 'multiformats' import { Message } from '../src/pb/message.js' import { createStream, type WebRTCStream } from '../src/stream.js' +import { RTCPeerConnection } from '../src/webrtc/index.js' import type { Stream } from '@libp2p/interface/connection' const TEST_MESSAGE = 'test_message' @@ -26,9 +27,16 @@ function generatePbByFlag (flag?: Message.Flag): Uint8Array { describe('Stream Stats', () => { let stream: WebRTCStream + let peerConnection: RTCPeerConnection beforeEach(async () => { - ({ stream } = setup()) + ({ stream, peerConnection } = setup()) + }) + + afterEach(() => { + if (peerConnection != null) { + peerConnection.close() + } }) it('can construct', () => { @@ -86,9 +94,16 @@ describe('Stream Stats', () => { describe('Stream Read Stats Transition By Incoming Flag', () => { let dataChannel: RTCDataChannel let stream: Stream + let peerConnection: RTCPeerConnection beforeEach(async () => { - ({ dataChannel, stream } = setup()) + ({ dataChannel, stream, peerConnection } = setup()) + }) + + afterEach(() => { + if (peerConnection != null) { + peerConnection.close() + } }) it('no flag, no transition', () => { @@ -123,9 +138,16 @@ describe('Stream Read Stats Transition By Incoming Flag', () => { describe('Stream Write Stats Transition By Incoming Flag', () => { let dataChannel: RTCDataChannel let stream: Stream + let peerConnection: RTCPeerConnection beforeEach(async () => { - ({ dataChannel, stream } = setup()) + ({ dataChannel, stream, peerConnection } = setup()) + }) + + afterEach(() => { + if (peerConnection != null) { + peerConnection.close() + } }) it('open to write-close by flag:STOP_SENDING', async () => { diff --git a/packages/transport-webrtc/tsconfig.json b/packages/transport-webrtc/tsconfig.json index 98fa092578..4008808465 100644 --- a/packages/transport-webrtc/tsconfig.json +++ b/packages/transport-webrtc/tsconfig.json @@ -9,9 +9,6 @@ "proto_ts" ], "references": [ - { - "path": "../connection-encryption-noise" - }, { "path": "../interface" }, @@ -33,9 +30,6 @@ { "path": "../peer-id-factory" }, - { - "path": "../stream-multiplexer-yamux" - }, { "path": "../transport-websockets" } diff --git a/packages/transport-websockets/CHANGELOG.md b/packages/transport-websockets/CHANGELOG.md index b218a6315a..7153647b83 100644 --- a/packages/transport-websockets/CHANGELOG.md +++ b/packages/transport-websockets/CHANGELOG.md @@ -6,6 +6,36 @@ * **dev:** bump @libp2p/interface-mocks from 11.0.3 to 12.0.1 ([#241](https://github.com/libp2p/js-libp2p-websockets/issues/241)) ([f956836](https://github.com/libp2p/js-libp2p-websockets/commit/f95683641bda2f9b250768768451e0c121afc2a0)) * **dev:** bump aegir from 38.1.8 to 39.0.9 ([#245](https://github.com/libp2p/js-libp2p-websockets/issues/245)) ([4a35f6b](https://github.com/libp2p/js-libp2p-websockets/commit/4a35f6b39a918fb7ef779292553cb452a543afb0)) +## [7.0.0](https://www.github.com/libp2p/js-libp2p/compare/websockets-v6.0.3...websockets-v7.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* the `.close`, `closeRead` and `closeWrite` methods on the `Stream` interface are now asynchronous +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* close streams gracefully ([#1864](https://www.github.com/libp2p/js-libp2p/issues/1864)) ([b36ec7f](https://www.github.com/libp2p/js-libp2p/commit/b36ec7f24e477af21cec31effc086a6c611bf271)), closes [#1793](https://www.github.com/libp2p/js-libp2p/issues/1793) [#656](https://www.github.com/libp2p/js-libp2p/issues/656) +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * @libp2p/utils bumped from ^3.0.0 to ^4.0.0 + * devDependencies + * @libp2p/interface-compliance-tests bumped from ^3.0.0 to ^4.0.0 + ## [6.0.2](https://github.com/libp2p/js-libp2p-websockets/compare/v6.0.1...v6.0.2) (2023-06-06) @@ -710,4 +740,4 @@ -# 0.1.0 (2016-02-26) +# 0.1.0 (2016-02-26) \ No newline at end of file diff --git a/packages/transport-websockets/package.json b/packages/transport-websockets/package.json index d60863f562..8107419640 100644 --- a/packages/transport-websockets/package.json +++ b/packages/transport-websockets/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/websockets", - "version": "6.0.3", + "version": "7.0.0", "description": "JavaScript implementation of the WebSockets module that libp2p uses and that implements the interface-transport spec", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/transport-websockets#readme", @@ -68,9 +68,9 @@ "test:electron-main": "aegir test -t electron-main -f ./dist/test/node.js --cov" }, "dependencies": { - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/utils": "^3.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/utils": "^4.0.0", "@multiformats/mafmt": "^12.1.2", "@multiformats/multiaddr": "^12.1.3", "@multiformats/multiaddr-to-uri": "^9.0.2", @@ -82,7 +82,7 @@ "ws": "^8.12.1" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^3.0.0", + "@libp2p/interface-compliance-tests": "^4.0.0", "aegir": "^40.0.1", "is-loopback-addr": "^2.0.1", "it-all": "^3.0.1", diff --git a/packages/transport-webtransport/CHANGELOG.md b/packages/transport-webtransport/CHANGELOG.md index 562095f662..fdafdb64e7 100644 --- a/packages/transport-webtransport/CHANGELOG.md +++ b/packages/transport-webtransport/CHANGELOG.md @@ -11,6 +11,45 @@ * bump @chainsafe/libp2p-noise from 11.0.4 to 12.0.1 ([#80](https://github.com/libp2p/js-libp2p-webtransport/issues/80)) ([599dab1](https://github.com/libp2p/js-libp2p-webtransport/commit/599dab1b4f6ae816b0c0feefc926c1b38d24b676)) +### [3.0.1](https://www.github.com/libp2p/js-libp2p/compare/webtransport-v3.0.0...webtransport-v3.0.1) (2023-08-01) + + +### Dependencies + +* The following workspace dependencies were updated + * devDependencies + * libp2p bumped from ^0.46.0 to ^0.46.1 + +## [3.0.0](https://www.github.com/libp2p/js-libp2p/compare/webtransport-v2.0.2...webtransport-v3.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* the `.close`, `closeRead` and `closeWrite` methods on the `Stream` interface are now asynchronous +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* close streams gracefully ([#1864](https://www.github.com/libp2p/js-libp2p/issues/1864)) ([b36ec7f](https://www.github.com/libp2p/js-libp2p/commit/b36ec7f24e477af21cec31effc086a6c611bf271)), closes [#1793](https://www.github.com/libp2p/js-libp2p/issues/1793) [#656](https://www.github.com/libp2p/js-libp2p/issues/656) +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + * @libp2p/peer-id bumped from ^2.0.0 to ^3.0.0 + * devDependencies + * libp2p bumped from ^0.45.0 to ^0.46.0 + ## [2.0.1](https://github.com/libp2p/js-libp2p-webtransport/compare/v2.0.0...v2.0.1) (2023-04-28) diff --git a/packages/transport-webtransport/package.json b/packages/transport-webtransport/package.json index 7146b72325..2fabbd66dd 100644 --- a/packages/transport-webtransport/package.json +++ b/packages/transport-webtransport/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/webtransport", - "version": "2.0.2", + "version": "3.0.1", "description": "JavaScript implementation of the WebTransport module that libp2p uses and that implements the interface-transport spec", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/transport-webtransport#readme", @@ -64,10 +64,10 @@ "test:chrome-webworker": "aegir test -t webworker" }, "dependencies": { - "@chainsafe/libp2p-noise": "^12.0.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", - "@libp2p/peer-id": "^2.0.0", + "@chainsafe/libp2p-noise": "^13.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/logger": "^3.0.0", + "@libp2p/peer-id": "^3.0.0", "@multiformats/multiaddr": "^12.1.3", "it-stream-types": "^2.0.1", "multiformats": "^12.0.1", @@ -75,7 +75,7 @@ }, "devDependencies": { "aegir": "^40.0.1", - "libp2p": "^0.45.0", + "libp2p": "^0.46.1", "p-defer": "^4.0.0" }, "browser": { diff --git a/packages/transport-webtransport/tsconfig.json b/packages/transport-webtransport/tsconfig.json index b9d653b611..0344702f51 100644 --- a/packages/transport-webtransport/tsconfig.json +++ b/packages/transport-webtransport/tsconfig.json @@ -8,9 +8,6 @@ "test" ], "references": [ - { - "path": "../connection-encryption-noise" - }, { "path": "../interface" }, diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index 8ed268837a..a2f0ace5c6 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -11,6 +11,33 @@ * **dev:** bump aegir from 38.1.8 to 39.0.10 ([#100](https://github.com/libp2p/js-libp2p-utils/issues/100)) ([da6547c](https://github.com/libp2p/js-libp2p-utils/commit/da6547cdd073ba1a4225be5a419c6776c4ebe6f1)) +## [4.0.0](https://www.github.com/libp2p/js-libp2p/compare/utils-v3.0.12...utils-v4.0.0) (2023-07-31) + + +### ⚠ BREAKING CHANGES + +* the `.close`, `closeRead` and `closeWrite` methods on the `Stream` interface are now asynchronous +* `stream.stat.*` and `conn.stat.*` properties are now accessed via `stream.*` and `conn.*` +* consolidate interface modules (#1833) + +### Features + +* merge stat properties into stream/connection objects ([#1856](https://www.github.com/libp2p/js-libp2p/issues/1856)) ([e9cafd3](https://www.github.com/libp2p/js-libp2p/commit/e9cafd3d8ab0f8e0655ff44e04aa41fccc912b51)), closes [#1849](https://www.github.com/libp2p/js-libp2p/issues/1849) + + +### Bug Fixes + +* close streams gracefully ([#1864](https://www.github.com/libp2p/js-libp2p/issues/1864)) ([b36ec7f](https://www.github.com/libp2p/js-libp2p/commit/b36ec7f24e477af21cec31effc086a6c611bf271)), closes [#1793](https://www.github.com/libp2p/js-libp2p/issues/1793) [#656](https://www.github.com/libp2p/js-libp2p/issues/656) +* consolidate interface modules ([#1833](https://www.github.com/libp2p/js-libp2p/issues/1833)) ([4255b1e](https://www.github.com/libp2p/js-libp2p/commit/4255b1e2485d31e00c33efa029b6426246ea23e3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @libp2p/interface bumped from ~0.0.1 to ^0.1.0 + * @libp2p/logger bumped from ^2.0.0 to ^3.0.0 + ## [3.0.11](https://github.com/libp2p/js-libp2p-utils/compare/v3.0.10...v3.0.11) (2023-04-24) @@ -321,4 +348,4 @@ ### Features -* ip port to multiaddr ([#1](https://github.com/libp2p/js-libp2p-utils/issues/1)) ([426b421](https://github.com/libp2p/js-libp2p-utils/commit/426b421)) +* ip port to multiaddr ([#1](https://github.com/libp2p/js-libp2p-utils/issues/1)) ([426b421](https://github.com/libp2p/js-libp2p-utils/commit/426b421)) \ No newline at end of file diff --git a/packages/utils/package.json b/packages/utils/package.json index 90b7b7fd40..31ab0debb9 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@libp2p/utils", - "version": "3.0.12", + "version": "4.0.0", "description": "Package to aggregate shared logic and dependencies for the libp2p ecosystem", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/utils#readme", @@ -86,8 +86,8 @@ }, "dependencies": { "@achingbrain/ip-address": "^8.1.0", - "@libp2p/interface": "~0.0.1", - "@libp2p/logger": "^2.0.0", + "@libp2p/interface": "^0.1.0", + "@libp2p/logger": "^3.0.0", "@multiformats/multiaddr": "^12.1.3", "is-loopback-addr": "^2.0.1", "it-stream-types": "^2.0.1",