From 978eb3676fad5d5d50ddb28d1a7868f448cbb20b Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 20 Jan 2022 12:03:35 +0000 Subject: [PATCH] feat: async peerstore backed by datastores (#1058) We have a peerstore that keeps all data for all observed peers in memory with no eviction. This is fine when you don't discover many peers but when using the DHT you encounter a significant number of peers so our peer storage grows and grows over time. We have a persistent peer store, but it just periodically writes peers into the datastore to be read at startup, still keeping them in memory. It also means a restart doesn't give you any temporary reprieve from the memory leak as the previously observed peer data is read into memory at startup. This change refactors the peerstore to use a datastore by default, reading and writing peer info as it arrives. It can be configured with a MemoryDatastore if desired. It was necessary to change the peerstore and *book interfaces to be asynchronous since the datastore api is asynchronous. BREAKING CHANGE: `libp2p.handle`, `libp2p.registrar.register` and the peerstore methods have become async --- .github/workflows/examples.yml | 400 ++--------- .github/workflows/main.yml | 124 +--- examples/connection-encryption/1.js | 2 +- examples/delegated-routing/package.json | 20 +- examples/delegated-routing/src/App.js | 4 +- .../delegated-routing/src/libp2p-bundle.js | 4 +- examples/discovery-mechanisms/3.js | 2 +- examples/discovery-mechanisms/bootstrapers.js | 4 +- examples/discovery-mechanisms/test-1.js | 26 +- examples/libp2p-in-the-browser/.babelrc | 1 - examples/libp2p-in-the-browser/package.json | 7 +- examples/package.json | 2 +- examples/peer-and-content-routing/1.js | 4 +- examples/peer-and-content-routing/2.js | 4 +- examples/peer-and-content-routing/README.md | 4 +- examples/peer-and-content-routing/test-1.js | 19 +- examples/peer-and-content-routing/test-2.js | 22 +- examples/pnet/index.js | 2 +- examples/protocol-and-stream-muxing/1.js | 2 +- examples/protocol-and-stream-muxing/2.js | 2 +- examples/protocol-and-stream-muxing/3.js | 4 +- examples/protocol-and-stream-muxing/README.md | 2 +- examples/pubsub/1.js | 4 +- examples/pubsub/README.md | 2 +- examples/pubsub/message-filtering/1.js | 6 +- examples/pubsub/message-filtering/README.md | 4 +- examples/transports/2.js | 2 +- examples/transports/3.js | 6 +- examples/transports/README.md | 8 +- examples/utils.js | 2 +- examples/webrtc-direct/listener.js | 1 - examples/webrtc-direct/package.json | 4 +- package.json | 25 +- src/circuit/auto-relay.js | 38 +- src/connection-manager/auto-dialler.js | 9 +- src/connection-manager/index.js | 61 +- src/content-routing/utils.js | 8 +- src/dialer/dial-request.js | 5 +- src/dialer/index.js | 27 +- src/identify/index.js | 51 +- src/index.js | 60 +- src/metrics/index.js | 23 +- src/metrics/tracked-map.js | 54 +- src/peer-routing.js | 5 +- src/peer-store/address-book.js | 495 +++++++------- src/peer-store/book.js | 124 ---- src/peer-store/index.js | 185 +++-- src/peer-store/key-book.js | 157 +++-- src/peer-store/metadata-book.js | 298 ++++---- src/peer-store/pb/peer.d.ts | 222 ++++++ src/peer-store/pb/peer.js | 643 ++++++++++++++++++ src/peer-store/pb/peer.proto | 31 + src/peer-store/persistent/consts.js | 15 - src/peer-store/persistent/index.js | 408 ----------- .../persistent/pb/address-book.d.ts | 198 ------ src/peer-store/persistent/pb/address-book.js | 522 -------------- .../persistent/pb/address-book.proto | 27 - src/peer-store/persistent/pb/proto-book.d.ts | 59 -- src/peer-store/persistent/pb/proto-book.js | 157 ----- src/peer-store/persistent/pb/proto-book.proto | 5 - src/peer-store/proto-book.js | 324 +++++---- src/peer-store/store.js | 250 +++++++ src/peer-store/types.ts | 245 +++++++ src/ping/index.js | 6 + src/record/peer-record/index.js | 2 +- src/record/utils.js | 2 +- src/registrar.js | 8 +- src/upgrader.js | 4 +- test/configuration/protocol-prefix.node.js | 24 +- test/connection-manager/index.node.js | 8 +- test/connection-manager/index.spec.js | 6 +- test/content-routing/content-routing.node.js | 6 +- test/content-routing/dht/operation.node.js | 8 +- test/dialing/direct.node.js | 33 +- test/dialing/direct.spec.js | 9 +- test/identify/index.spec.js | 94 ++- test/metrics/index.spec.js | 6 +- test/peer-routing/peer-routing.node.js | 6 +- test/peer-store/address-book.spec.js | 242 ++++--- test/peer-store/key-book.spec.js | 83 ++- test/peer-store/metadata-book.spec.js | 200 +++--- test/peer-store/peer-store.node.js | 10 +- test/peer-store/peer-store.spec.js | 167 ++--- test/peer-store/persisted-peer-store.spec.js | 608 ----------------- test/peer-store/proto-book.spec.js | 209 +++--- test/registrar/registrar.spec.js | 44 +- test/relay/auto-relay.node.js | 42 +- test/relay/relay.node.js | 8 +- test/transports/transport-manager.node.js | 9 +- test/ts-use/package.json | 14 +- test/ts-use/src/main.ts | 3 +- test/utils/creators/peer.js | 5 +- test/utils/mockConnection.js | 2 +- tsconfig.json | 3 +- 94 files changed, 3263 insertions(+), 4039 deletions(-) delete mode 100644 src/peer-store/book.js create mode 100644 src/peer-store/pb/peer.d.ts create mode 100644 src/peer-store/pb/peer.js create mode 100644 src/peer-store/pb/peer.proto delete mode 100644 src/peer-store/persistent/consts.js delete mode 100644 src/peer-store/persistent/index.js delete mode 100644 src/peer-store/persistent/pb/address-book.d.ts delete mode 100644 src/peer-store/persistent/pb/address-book.js delete mode 100644 src/peer-store/persistent/pb/address-book.proto delete mode 100644 src/peer-store/persistent/pb/proto-book.d.ts delete mode 100644 src/peer-store/persistent/pb/proto-book.js delete mode 100644 src/peer-store/persistent/pb/proto-book.proto create mode 100644 src/peer-store/store.js create mode 100644 src/peer-store/types.ts delete mode 100644 test/peer-store/persisted-peer-store.spec.js diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 0e46e951..c99a8032 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -8,380 +8,58 @@ on: - '**' jobs: + build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist + directories: | ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - check: + ~/.cache + build: | + cd examples + npm i + npx playwright install + cache_name: cache-examples + + test-example: needs: build runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: npx aegir lint - - run: npx aegir ts -p check - test-auto-relay-example: - needs: check - runs-on: ubuntu-latest + strategy: + matrix: + example: [ + chat, + connection-encryption, + discovery-mechanisms, + echo, + libp2p-in-the-browser, + peer-and-content-routing, + pnet, + protocol-and-stream-muxing, + pubsub, + transports, + webrtc-direct + ] + fail-fast: true steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist + directories: | ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- auto-relay - test-chat-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- chat - test-connection-encryption-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- connection-encryption - test-discovery-mechanisms-example: - needs: check - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- discovery-mechanisms - test-echo-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- echo - test-libp2p-in-the-browser-example: - needs: check - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- libp2p-in-the-browser - test-peer-and-content-routing-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- peer-and-content-routing - test-pnet-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- pnet - test-protocol-and-stream-muxing-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- protocol-and-stream-muxing - test-pubsub-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- pubsub - test-transports-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- transports - test-webrtc-direct-example: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - ./examples/node_modules - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build - cd examples && npm i - - run: cd examples && npm run test -- webrtc-direct + build: | + cd examples + npm i + npx playwright install + cache_name: cache-examples + - run: | + cd examples + npm run test -- ${{ matrix.example }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e92a69b7..3a4f76f0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,29 +12,14 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest] node: [16] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + - uses: ipfs/aegir/actions/cache-node-modules@master check: needs: build @@ -43,23 +28,8 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - node-version: 16 - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master - run: npx aegir lint - run: npx aegir dep-check - uses: ipfs/aegir/actions/bundle-size@master @@ -80,23 +50,7 @@ jobs: - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v2 - id: cache - if: matrix.os != 'windows-latest' - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:node -- --cov --bail - uses: codecov/codecov-action@v1 test-chrome: @@ -107,22 +61,7 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:browser -- -t browser -t webworker --bail test-firefox: needs: check @@ -132,22 +71,7 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:browser -- -t browser -t webworker --bail -- --browser firefox test-ts: needs: check @@ -157,22 +81,7 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:ts test-interop: needs: check @@ -182,20 +91,5 @@ jobs: - uses: actions/setup-node@v2 with: node-version: lts/* - - uses: actions/cache@v2 - id: cache - env: - CACHE_NAME: cache-node-modules - with: - path: | - ~/.cache - ~/.npm - ./node_modules - ./dist - key: ${{ runner.os }}-build-${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.sha }} - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - npm install - npm run build + - uses: ipfs/aegir/actions/cache-node-modules@master - run: npm run test:interop -- --bail -- --exit diff --git a/examples/connection-encryption/1.js b/examples/connection-encryption/1.js index 0b869915..fccb7537 100644 --- a/examples/connection-encryption/1.js +++ b/examples/connection-encryption/1.js @@ -30,7 +30,7 @@ const createNode = async () => { createNode() ]) - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) node2.handle('/a-protocol', ({ stream }) => { pipe( diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 76a06b46..6893edc3 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -3,16 +3,16 @@ "version": "0.1.0", "private": true, "dependencies": { - "ipfs": "~0.34.4", - "libp2p": "github:libp2p/js-libp2p#master", - "libp2p-delegated-content-routing": "~0.2.2", - "libp2p-delegated-peer-routing": "~0.2.2", - "libp2p-kad-dht": "^0.26.5", - "libp2p-mplex": "~0.8.5", - "libp2p-secio": "~0.11.1", - "libp2p-webrtc-star": "~0.15.8", - "libp2p-websocket-star": "~0.10.2", - "libp2p-websockets": "~0.12.2", + "@chainsafe/libp2p-noise": "^5.0.2", + "ipfs-core": "^0.13.0", + "libp2p": "../../", + "libp2p-delegated-content-routing": "^0.11.0", + "libp2p-delegated-peer-routing": "^0.11.1", + "libp2p-kad-dht": "^0.28.6", + "libp2p-mplex": "^0.10.4", + "libp2p-webrtc-star": "^0.25.0", + "libp2p-websocket-star": "^0.10.2", + "libp2p-websockets": "^0.16.2", "react": "^16.8.6", "react-dom": "^16.8.6", "react-scripts": "2.1.8" diff --git a/examples/delegated-routing/src/App.js b/examples/delegated-routing/src/App.js index ce6af677..830fd758 100644 --- a/examples/delegated-routing/src/App.js +++ b/examples/delegated-routing/src/App.js @@ -2,7 +2,7 @@ 'use strict' import React from 'react' -import Ipfs from 'ipfs' +import Ipfs from 'ipfs-core' import libp2pBundle from './libp2p-bundle' const Component = React.Component @@ -70,7 +70,7 @@ class App extends Component { } componentDidMount () { - window.ipfs = this.ipfs = new Ipfs({ + window.ipfs = this.ipfs = Ipfs.create({ config: { Addresses: { Swarm: [] diff --git a/examples/delegated-routing/src/libp2p-bundle.js b/examples/delegated-routing/src/libp2p-bundle.js index 48fb0906..0ff6786e 100644 --- a/examples/delegated-routing/src/libp2p-bundle.js +++ b/examples/delegated-routing/src/libp2p-bundle.js @@ -6,7 +6,7 @@ const Websockets = require('libp2p-websockets') const WebSocketStar = require('libp2p-websocket-star') const WebRTCStar = require('libp2p-webrtc-star') const MPLEX = require('libp2p-mplex') -const SECIO = require('libp2p-secio') +const { NOISE } = require('@chainsafe/libp2p-noise') const KadDHT = require('libp2p-kad-dht') const DelegatedPeerRouter = require('libp2p-delegated-peer-routing') const DelegatedContentRouter = require('libp2p-delegated-content-routing') @@ -48,7 +48,7 @@ export default function Libp2pBundle ({peerInfo, peerBook}) { MPLEX ], connEncryption: [ - SECIO + NOISE ], dht: KadDHT }, diff --git a/examples/discovery-mechanisms/3.js b/examples/discovery-mechanisms/3.js index 4fb1be12..645025d4 100644 --- a/examples/discovery-mechanisms/3.js +++ b/examples/discovery-mechanisms/3.js @@ -5,7 +5,7 @@ const Libp2p = require('../../') const TCP = require('libp2p-tcp') const Mplex = require('libp2p-mplex') const { NOISE } = require('@chainsafe/libp2p-noise') -const Gossipsub = require('libp2p-gossipsub') +const Gossipsub = require('@achingbrain/libp2p-gossipsub') const Bootstrap = require('libp2p-bootstrap') const PubsubPeerDiscovery = require('libp2p-pubsub-peer-discovery') diff --git a/examples/discovery-mechanisms/bootstrapers.js b/examples/discovery-mechanisms/bootstrapers.js index 384f0b5e..8ebedb1f 100644 --- a/examples/discovery-mechanisms/bootstrapers.js +++ b/examples/discovery-mechanisms/bootstrapers.js @@ -1,13 +1,13 @@ 'use strict' -// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-core/src/runtime/config-nodejs.js +// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-core-config/src/config.js const bootstrapers = [ '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', '/dnsaddr/bootstrap.libp2p.io/p2p/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp', '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt' + '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt', ] module.exports = bootstrapers diff --git a/examples/discovery-mechanisms/test-1.js b/examples/discovery-mechanisms/test-1.js index 0f76fa2f..36f297b3 100644 --- a/examples/discovery-mechanisms/test-1.js +++ b/examples/discovery-mechanisms/test-1.js @@ -2,17 +2,9 @@ const path = require('path') const execa = require('execa') -const pWaitFor = require('p-wait-for') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') -const bootstrapers = require('./bootstrapers') - -const discoveredCopy = 'Discovered:' -const connectedCopy = 'Connection established to:' async function test () { - const discoveredNodes = [] - const connectedNodes = [] - process.stdout.write('1.js\n') const proc = execa('node', [path.join(__dirname, '1.js')], { @@ -20,23 +12,17 @@ async function test () { all: true }) + let output = '' + proc.all.on('data', async (data) => { process.stdout.write(data) - const line = uint8ArrayToString(data) + output += uint8ArrayToString(data) - // Discovered or Connected - if (line.includes(discoveredCopy)) { - const id = line.trim().split(discoveredCopy)[1] - discoveredNodes.push(id) - } else if (line.includes(connectedCopy)) { - const id = line.trim().split(connectedCopy)[1] - connectedNodes.push(id) + // Discovered and connected + if (output.includes('Connection established to:')) { + proc.kill() } }) - - await pWaitFor(() => discoveredNodes.length === bootstrapers.length && connectedNodes.length === bootstrapers.length) - - proc.kill() } module.exports = test diff --git a/examples/libp2p-in-the-browser/.babelrc b/examples/libp2p-in-the-browser/.babelrc index 620e7857..2145517d 100644 --- a/examples/libp2p-in-the-browser/.babelrc +++ b/examples/libp2p-in-the-browser/.babelrc @@ -1,4 +1,3 @@ { - "presets": ["@babel/preset-env"], "plugins": ["syntax-async-functions","transform-regenerator"] } \ No newline at end of file diff --git a/examples/libp2p-in-the-browser/package.json b/examples/libp2p-in-the-browser/package.json index 9a45c90d..f515deb4 100644 --- a/examples/libp2p-in-the-browser/package.json +++ b/examples/libp2p-in-the-browser/package.json @@ -14,12 +14,11 @@ "author": "", "license": "ISC", "dependencies": { - "@babel/preset-env": "^7.13.0", + "@chainsafe/libp2p-noise": "^5.0.2", "libp2p": "../../", - "libp2p-bootstrap": "^0.13.0", + "libp2p-bootstrap": "^0.14.0", "libp2p-mplex": "^0.10.4", - "@chainsafe/libp2p-noise": "^4.1.0", - "libp2p-webrtc-star": "^0.23.0", + "libp2p-webrtc-star": "^0.25.0", "libp2p-websockets": "^0.16.1" }, "devDependencies": { diff --git a/examples/package.json b/examples/package.json index b69c9ed6..bee67556 100644 --- a/examples/package.json +++ b/examples/package.json @@ -8,12 +8,12 @@ }, "license": "MIT", "dependencies": { + "@achingbrain/libp2p-gossipsub": "^0.12.2", "execa": "^2.1.0", "fs-extra": "^8.1.0", "libp2p": "../src", "libp2p-pubsub-peer-discovery": "^4.0.0", "libp2p-relay-server": "^0.3.0", - "libp2p-gossipsub": "^0.11.0", "p-defer": "^3.0.0", "uint8arrays": "^3.0.0", "which": "^2.0.1" diff --git a/examples/peer-and-content-routing/1.js b/examples/peer-and-content-routing/1.js index bf2c6d1b..16b2dfbc 100644 --- a/examples/peer-and-content-routing/1.js +++ b/examples/peer-and-content-routing/1.js @@ -38,8 +38,8 @@ const createNode = async () => { createNode() ]) - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) await Promise.all([ node1.dial(node2.peerId), diff --git a/examples/peer-and-content-routing/2.js b/examples/peer-and-content-routing/2.js index 4b528e4a..bcad5977 100644 --- a/examples/peer-and-content-routing/2.js +++ b/examples/peer-and-content-routing/2.js @@ -40,8 +40,8 @@ const createNode = async () => { createNode() ]) - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) await Promise.all([ node1.dial(node2.peerId), diff --git a/examples/peer-and-content-routing/README.md b/examples/peer-and-content-routing/README.md index 197ecf32..373f10d7 100644 --- a/examples/peer-and-content-routing/README.md +++ b/examples/peer-and-content-routing/README.md @@ -43,8 +43,8 @@ const node1 = nodes[0] const node2 = nodes[1] const node3 = nodes[2] -node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) -node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) await Promise.all([ node1.dial(node2.peerId), diff --git a/examples/peer-and-content-routing/test-1.js b/examples/peer-and-content-routing/test-1.js index 2a19b10d..9e10f7ae 100644 --- a/examples/peer-and-content-routing/test-1.js +++ b/examples/peer-and-content-routing/test-1.js @@ -2,35 +2,28 @@ const path = require('path') const execa = require('execa') -const pWaitFor = require('p-wait-for') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') async function test() { process.stdout.write('1.js\n') - const addrs = [] - let foundIt = false const proc = execa('node', [path.join(__dirname, '1.js')], { cwd: path.resolve(__dirname), all: true }) + let output = '' + proc.all.on('data', async (data) => { process.stdout.write(data) - const line = uint8ArrayToString(data) + output += uint8ArrayToString(data) - // Discovered peer - if (!foundIt && line.includes('Found it, multiaddrs are:')) { - foundIt = true + // Discovered peers + if (output.includes('Found it, multiaddrs are:')) { + proc.kill() } - - addrs.push(line) }) - - await pWaitFor(() => addrs.length === 2) - - proc.kill() } module.exports = test diff --git a/examples/peer-and-content-routing/test-2.js b/examples/peer-and-content-routing/test-2.js index 82dbcf92..644820b6 100644 --- a/examples/peer-and-content-routing/test-2.js +++ b/examples/peer-and-content-routing/test-2.js @@ -2,39 +2,27 @@ const path = require('path') const execa = require('execa') -const pDefer = require('p-defer') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') -const providedCopy = 'is providing' -const foundCopy = 'Found provider:' - async function test() { process.stdout.write('2.js\n') - const providedDefer = pDefer() - const foundDefer = pDefer() const proc = execa('node', [path.join(__dirname, '2.js')], { cwd: path.resolve(__dirname), all: true }) + let output = '' + proc.all.on('data', async (data) => { process.stdout.write(data) - const line = uint8ArrayToString(data) + output += uint8ArrayToString(data) - if (line.includes(providedCopy)) { - providedDefer.resolve() - } else if (line.includes(foundCopy)) { - foundDefer.resolve() + if (output.includes('Found provider:')) { + proc.kill() } }) - - await Promise.all([ - providedDefer.promise, - foundDefer.promise - ]) - proc.kill() } module.exports = test diff --git a/examples/pnet/index.js b/examples/pnet/index.js index 1e766d53..090b9e07 100644 --- a/examples/pnet/index.js +++ b/examples/pnet/index.js @@ -29,7 +29,7 @@ generate(otherSwarmKey) console.log('nodes started...') // Add node 2 data to node1's PeerStore - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) await node1.dial(node2.peerId) node2.handle('/private', ({ stream }) => { diff --git a/examples/protocol-and-stream-muxing/1.js b/examples/protocol-and-stream-muxing/1.js index 5499ba0b..3f71e0f5 100644 --- a/examples/protocol-and-stream-muxing/1.js +++ b/examples/protocol-and-stream-muxing/1.js @@ -31,7 +31,7 @@ const createNode = async () => { ]) // Add node's 2 data to the PeerStore - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) // exact matching node2.handle('/your-protocol', ({ stream }) => { diff --git a/examples/protocol-and-stream-muxing/2.js b/examples/protocol-and-stream-muxing/2.js index 2fecbe32..a7fdb175 100644 --- a/examples/protocol-and-stream-muxing/2.js +++ b/examples/protocol-and-stream-muxing/2.js @@ -31,7 +31,7 @@ const createNode = async () => { ]) // Add node's 2 data to the PeerStore - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) node2.handle(['/a', '/b'], ({ protocol, stream }) => { pipe( diff --git a/examples/protocol-and-stream-muxing/3.js b/examples/protocol-and-stream-muxing/3.js index 23cf4e76..c9a91483 100644 --- a/examples/protocol-and-stream-muxing/3.js +++ b/examples/protocol-and-stream-muxing/3.js @@ -32,8 +32,8 @@ const createNode = async () => { ]) // Add node's 2 data to the PeerStore - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + node1.handle('/node-1', ({ stream }) => { pipe( stream, diff --git a/examples/protocol-and-stream-muxing/README.md b/examples/protocol-and-stream-muxing/README.md index 8098391f..a4df1a8b 100644 --- a/examples/protocol-and-stream-muxing/README.md +++ b/examples/protocol-and-stream-muxing/README.md @@ -20,7 +20,7 @@ const node1 = nodes[0] const node2 = nodes[1] // Add node's 2 data to the PeerStore -node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) // Here we are telling libp2p that if someone dials this node to talk with the `/your-protocol` // multicodec, the protocol identifier, please call this handler and give it the stream diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js index f1f60eb9..20aa4970 100644 --- a/examples/pubsub/1.js +++ b/examples/pubsub/1.js @@ -5,7 +5,7 @@ const Libp2p = require('../../') const TCP = require('libp2p-tcp') const Mplex = require('libp2p-mplex') const { NOISE } = require('@chainsafe/libp2p-noise') -const Gossipsub = require('libp2p-gossipsub') +const Gossipsub = require('@achingbrain/libp2p-gossipsub') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') @@ -35,7 +35,7 @@ const createNode = async () => { ]) // Add node's 2 data to the PeerStore - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) await node1.dial(node2.peerId) node1.pubsub.on(topic, (msg) => { diff --git a/examples/pubsub/README.md b/examples/pubsub/README.md index ca8051cf..654dd76d 100644 --- a/examples/pubsub/README.md +++ b/examples/pubsub/README.md @@ -49,7 +49,7 @@ const node1 = nodes[0] const node2 = nodes[1] // Add node's 2 data to the PeerStore -node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) await node1.dial(node2.peerId) node1.pubsub.on(topic, (msg) => { diff --git a/examples/pubsub/message-filtering/1.js b/examples/pubsub/message-filtering/1.js index fce8d041..81a7830d 100644 --- a/examples/pubsub/message-filtering/1.js +++ b/examples/pubsub/message-filtering/1.js @@ -5,7 +5,7 @@ const Libp2p = require('../../../') const TCP = require('libp2p-tcp') const Mplex = require('libp2p-mplex') const { NOISE } = require('@chainsafe/libp2p-noise') -const Gossipsub = require('libp2p-gossipsub') +const Gossipsub = require('@achingbrain/libp2p-gossipsub') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') @@ -36,10 +36,10 @@ const createNode = async () => { ]) // node1 conect to node2 and node2 conect to node3 - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) await node1.dial(node2.peerId) - node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) + await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) await node2.dial(node3.peerId) //subscribe diff --git a/examples/pubsub/message-filtering/README.md b/examples/pubsub/message-filtering/README.md index df990430..eb554afa 100644 --- a/examples/pubsub/message-filtering/README.md +++ b/examples/pubsub/message-filtering/README.md @@ -32,10 +32,10 @@ const [node1, node2, node3] = await Promise.all([ createNode(), ]) -node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) await node1.dial(node2.peerId) -node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) +await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) await node2.dial(node3.peerId) ``` diff --git a/examples/transports/2.js b/examples/transports/2.js index ee1f1c83..2962a062 100644 --- a/examples/transports/2.js +++ b/examples/transports/2.js @@ -49,7 +49,7 @@ function printAddrs (node, number) { console.log(result.toString()) }) - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) const { stream } = await node1.dialProtocol(node2.peerId, '/print') await pipe( diff --git a/examples/transports/3.js b/examples/transports/3.js index b3843b15..d9a83697 100644 --- a/examples/transports/3.js +++ b/examples/transports/3.js @@ -60,9 +60,9 @@ function print ({ stream }) { node2.handle('/print', print) node3.handle('/print', print) - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) - node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) - node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) + await node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs) // node 1 (TCP) dials to node 2 (TCP+WebSockets) const { stream } = await node1.dialProtocol(node2.peerId, '/print') diff --git a/examples/transports/README.md b/examples/transports/README.md index 18f5975b..e2f41e3a 100644 --- a/examples/transports/README.md +++ b/examples/transports/README.md @@ -140,7 +140,7 @@ Then add, console.log(result.toString()) }) - node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) + await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) const { stream } = await node1.dialProtocol(node2.peerId, '/print') await pipe( @@ -224,9 +224,9 @@ node1.handle('/print', print) node2.handle('/print', print) node3.handle('/print', print) -node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) -node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) -node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs) +await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs) +await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs) +await node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs) // node 1 (TCP) dials to node 2 (TCP+WebSockets) const { stream } = await node1.dialProtocol(node2.peerId, '/print') diff --git a/examples/utils.js b/examples/utils.js index 95b509ce..1f6e571c 100644 --- a/examples/utils.js +++ b/examples/utils.js @@ -30,7 +30,7 @@ async function waitForOutput (expectedOutput, command, args = [], opts = {}) { const proc = execa(command, args, opts) let output = '' - let time = 120000 + let time = 600000 let timeout = setTimeout(() => { throw new Error(`Did not see "${expectedOutput}" in output from "${[command].concat(args).join(' ')}" after ${time/1000}s`) diff --git a/examples/webrtc-direct/listener.js b/examples/webrtc-direct/listener.js index a8ac6cd9..b0b9cb43 100644 --- a/examples/webrtc-direct/listener.js +++ b/examples/webrtc-direct/listener.js @@ -40,5 +40,4 @@ const PeerId = require('peer-id') console.log('Listening on:') node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`)) - })() diff --git a/examples/webrtc-direct/package.json b/examples/webrtc-direct/package.json index a77ab6fa..6eb9b4b0 100644 --- a/examples/webrtc-direct/package.json +++ b/examples/webrtc-direct/package.json @@ -20,10 +20,10 @@ "util": "^0.12.3" }, "dependencies": { + "@chainsafe/libp2p-noise": "^5.0.2", "libp2p": "../../", - "libp2p-bootstrap": "^0.13.0", + "libp2p-bootstrap": "^0.14.0", "libp2p-mplex": "^0.10.4", - "@chainsafe/libp2p-noise": "^4.1.0", "libp2p-webrtc-direct": "^0.7.0", "peer-id": "^0.16.0" }, diff --git a/package.json b/package.json index 64a358de..24f32a78 100644 --- a/package.json +++ b/package.json @@ -20,20 +20,18 @@ "scripts": { "lint": "aegir lint", "build": "aegir build", - "build:proto": "npm run build:proto:circuit && npm run build:proto:identify && npm run build:proto:plaintext && npm run build:proto:address-book && npm run build:proto:proto-book && npm run build:proto:peer-record && npm run build:proto:envelope", + "build:proto": "npm run build:proto:circuit && npm run build:proto:identify && npm run build:proto:plaintext && npm run build:proto:address-book && npm run build:proto:proto-book && npm run build:proto:peer && npm run build:proto:peer-record && npm run build:proto:envelope", "build:proto:circuit": "pbjs -t static-module -w commonjs -r libp2p-circuit --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/circuit/protocol/index.js ./src/circuit/protocol/index.proto", "build:proto:identify": "pbjs -t static-module -w commonjs -r libp2p-identify --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/identify/message.js ./src/identify/message.proto", "build:proto:plaintext": "pbjs -t static-module -w commonjs -r libp2p-plaintext --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/insecure/proto.js ./src/insecure/proto.proto", - "build:proto:address-book": "pbjs -t static-module -w commonjs -r libp2p-address-book --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/persistent/pb/address-book.js ./src/peer-store/persistent/pb/address-book.proto", - "build:proto:proto-book": "pbjs -t static-module -w commonjs -r libp2p-proto-book --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/persistent/pb/proto-book.js ./src/peer-store/persistent/pb/proto-book.proto", + "build:proto:peer": "pbjs -t static-module -w commonjs -r libp2p-peer --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/peer-store/pb/peer.js ./src/peer-store/pb/peer.proto", "build:proto:peer-record": "pbjs -t static-module -w commonjs -r libp2p-peer-record --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/peer-record/peer-record.js ./src/record/peer-record/peer-record.proto", "build:proto:envelope": "pbjs -t static-module -w commonjs -r libp2p-envelope --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/record/envelope/envelope.js ./src/record/envelope/envelope.proto", - "build:proto-types": "npm run build:proto-types:circuit && npm run build:proto-types:identify && npm run build:proto-types:plaintext && npm run build:proto-types:address-book && npm run build:proto-types:proto-book && npm run build:proto-types:peer-record && npm run build:proto-types:envelope", + "build:proto-types": "npm run build:proto-types:circuit && npm run build:proto-types:identify && npm run build:proto-types:plaintext && npm run build:proto-types:address-book && npm run build:proto-types:proto-book && npm run build:proto-types:peer && npm run build:proto-types:peer-record && npm run build:proto-types:envelope", "build:proto-types:circuit": "pbts -o src/circuit/protocol/index.d.ts src/circuit/protocol/index.js", "build:proto-types:identify": "pbts -o src/identify/message.d.ts src/identify/message.js", "build:proto-types:plaintext": "pbts -o src/insecure/proto.d.ts src/insecure/proto.js", - "build:proto-types:address-book": "pbts -o src/peer-store/persistent/pb/address-book.d.ts src/peer-store/persistent/pb/address-book.js", - "build:proto-types:proto-book": "pbts -o src/peer-store/persistent/pb/proto-book.d.ts src/peer-store/persistent/pb/proto-book.js", + "build:proto-types:peer": "pbts -o src/peer-store/pb/peer.d.ts src/peer-store/pb/peer.js", "build:proto-types:peer-record": "pbts -o src/record/peer-record/peer-record.d.ts src/record/peer-record/peer-record.js", "build:proto-types:envelope": "pbts -o src/record/envelope/envelope.d.ts src/record/envelope/envelope.js", "test": "aegir test", @@ -86,6 +84,7 @@ "any-signal": "^2.1.1", "bignumber.js": "^9.0.1", "class-is": "^1.1.0", + "datastore-core": "^7.0.0", "debug": "^4.3.1", "err-code": "^3.0.0", "es6-promisify": "^7.0.0", @@ -103,11 +102,12 @@ "it-merge": "^1.0.0", "it-pipe": "^1.1.0", "it-take": "^1.0.0", - "libp2p-crypto": "^0.21.0", - "libp2p-interfaces": "^2.0.1", + "libp2p-crypto": "^0.21.1", + "libp2p-interfaces": "^4.0.0", "libp2p-utils": "^0.4.0", "mafmt": "^10.0.0", "merge-options": "^3.0.4", + "mortice": "^2.0.1", "multiaddr": "^10.0.0", "multiformats": "^9.0.0", "multistream-select": "^2.0.0", @@ -140,7 +140,6 @@ "@types/varint": "^6.0.0", "aegir": "^36.0.0", "buffer": "^6.0.3", - "datastore-core": "^6.0.7", "delay": "^5.0.0", "into-stream": "^6.0.0", "ipfs-http-client": "^54.0.2", @@ -151,11 +150,11 @@ "libp2p-bootstrap": "^0.14.0", "libp2p-delegated-content-routing": "^0.11.0", "libp2p-delegated-peer-routing": "^0.11.1", - "libp2p-interfaces-compliance-tests": "^2.0.1", - "libp2p-interop": "^0.6.0", - "libp2p-kad-dht": "^0.27.1", + "libp2p-interfaces-compliance-tests": "^4.0.8", + "libp2p-interop": "^0.7.1", + "libp2p-kad-dht": "^0.28.6", "libp2p-mdns": "^0.18.0", - "libp2p-mplex": "^0.10.1", + "libp2p-mplex": "^0.10.4", "libp2p-tcp": "^0.17.0", "libp2p-webrtc-star": "^0.25.0", "libp2p-websockets": "^0.16.0", diff --git a/src/circuit/auto-relay.js b/src/circuit/auto-relay.js index 1c238f54..91d150ec 100644 --- a/src/circuit/auto-relay.js +++ b/src/circuit/auto-relay.js @@ -8,7 +8,6 @@ const log = Object.assign(debug('libp2p:auto-relay'), { const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') const { relay: multicodec } = require('./multicodec') const { canHop } = require('./circuit/hop') @@ -22,7 +21,8 @@ const { /** * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection - * @typedef {import('../peer-store/address-book').Address} Address + * @typedef {import('../peer-store/types').Address} Address + * @typedef {import('peer-id')} PeerId */ /** @@ -91,7 +91,7 @@ class AutoRelay { // If no protocol, check if we were keeping the peer before as a listenRelay if (!hasProtocol && this._listenRelays.has(id)) { - this._removeListenRelay(id) + await this._removeListenRelay(id) return } else if (!hasProtocol || this._listenRelays.has(id)) { return @@ -113,7 +113,7 @@ class AutoRelay { const supportsHop = await canHop({ connection }) if (supportsHop) { - this._peerStore.metadataBook.set(peerId, HOP_METADATA_KEY, uint8ArrayFromString(HOP_METADATA_VALUE)) + await this._peerStore.metadataBook.setValue(peerId, HOP_METADATA_KEY, uint8ArrayFromString(HOP_METADATA_VALUE)) await this._addListenRelay(connection, id) } } catch (/** @type {any} */ err) { @@ -125,7 +125,6 @@ class AutoRelay { * Peer disconnects. * * @param {Connection} connection - connection to the peer - * @returns {void} */ _onPeerDisconnected (connection) { const peerId = connection.remotePeer @@ -136,7 +135,9 @@ class AutoRelay { return } - this._removeListenRelay(id) + this._removeListenRelay(id).catch(err => { + log.error(err) + }) } /** @@ -154,7 +155,7 @@ class AutoRelay { } // Get peer known addresses and sort them per public addresses first - const remoteAddrs = this._peerStore.addressBook.getMultiaddrsForPeer( + const remoteAddrs = await this._peerStore.addressBook.getMultiaddrsForPeer( connection.remotePeer, this._addressSorter ) @@ -180,12 +181,11 @@ class AutoRelay { * * @private * @param {string} id - peer identifier string. - * @returns {void} */ - _removeListenRelay (id) { + async _removeListenRelay (id) { if (this._listenRelays.delete(id)) { // TODO: this should be responsibility of the connMgr - this._listenOnAvailableHopRelays([id]) + await this._listenOnAvailableHopRelays([id]) } } @@ -197,7 +197,6 @@ class AutoRelay { * 3. Search the network. * * @param {string[]} [peersToIgnore] - * @returns {Promise} */ async _listenOnAvailableHopRelays (peersToIgnore = []) { // TODO: The peer redial issue on disconnect should be handled by connection gating @@ -209,29 +208,30 @@ class AutoRelay { const knownHopsToDial = [] // Check if we have known hop peers to use and attempt to listen on the already connected - for (const [id, metadataMap] of this._peerStore.metadataBook.data.entries()) { + for await (const { id, metadata } of this._peerStore.getPeers()) { + const idStr = id.toB58String() + // Continue to next if listening on this or peer to ignore - if (this._listenRelays.has(id) || peersToIgnore.includes(id)) { + if (this._listenRelays.has(idStr) || peersToIgnore.includes(idStr)) { continue } - const supportsHop = metadataMap.get(HOP_METADATA_KEY) + const supportsHop = metadata.get(HOP_METADATA_KEY) // Continue to next if it does not support Hop if (!supportsHop || uint8ArrayToString(supportsHop) !== HOP_METADATA_VALUE) { continue } - const peerId = PeerId.createFromB58String(id) - const connection = this._connectionManager.get(peerId) + const connection = this._connectionManager.get(id) // If not connected, store for possible later use. if (!connection) { - knownHopsToDial.push(peerId) + knownHopsToDial.push(id) continue } - await this._addListenRelay(connection, id) + await this._addListenRelay(connection, idStr) // Check if already listening on enough relays if (this._listenRelays.size >= this.maxListeners) { @@ -258,7 +258,7 @@ class AutoRelay { } const peerId = provider.id - this._peerStore.addressBook.add(peerId, provider.multiaddrs) + await this._peerStore.addressBook.add(peerId, provider.multiaddrs) await this._tryToListenOnRelay(peerId) diff --git a/src/connection-manager/auto-dialler.js b/src/connection-manager/auto-dialler.js index 5e71d7ed..d0927387 100644 --- a/src/connection-manager/auto-dialler.js +++ b/src/connection-manager/auto-dialler.js @@ -4,6 +4,7 @@ const debug = require('debug') const mergeOptions = require('merge-options') // @ts-ignore retimer does not have types const retimer = require('retimer') +const all = require('it-all') const log = Object.assign(debug('libp2p:connection-manager:auto-dialler'), { error: debug('libp2p:connection-manager:auto-dialler:err') @@ -50,7 +51,7 @@ class AutoDialler { /** * Starts the auto dialer */ - start () { + async start () { if (!this._options.enabled) { log('not enabled') return @@ -86,8 +87,10 @@ class AutoDialler { return } - // Sort peers on wether we know protocols of public keys for them - const peers = Array.from(this._libp2p.peerStore.peers.values()) + // Sort peers on whether we know protocols of public keys for them + // TODO: assuming the `peerStore.getPeers()` order is stable this will mean + // we keep trying to connect to the same peers? + const peers = (await all(this._libp2p.peerStore.getPeers())) .sort((a, b) => { if (b.protocols && b.protocols.length && (!a.protocols || !a.protocols.length)) { return 1 diff --git a/src/connection-manager/index.js b/src/connection-manager/index.js index ebdab623..553455fd 100644 --- a/src/connection-manager/index.js +++ b/src/connection-manager/index.js @@ -87,14 +87,22 @@ class ConnectionManager extends EventEmitter { * * @type {Map} */ - this._peerValues = trackedMap(METRICS_COMPONENT, METRICS_PEER_VALUES, this._libp2p.metrics) + this._peerValues = trackedMap({ + component: METRICS_COMPONENT, + metric: METRICS_PEER_VALUES, + metrics: this._libp2p.metrics + }) /** * Map of connections per peer * * @type {Map} */ - this.connections = trackedMap(METRICS_COMPONENT, METRICS_PEER_CONNECTIONS, this._libp2p.metrics) + this.connections = trackedMap({ + component: METRICS_COMPONENT, + metric: METRICS_PEER_CONNECTIONS, + metrics: this._libp2p.metrics + }) this._started = false this._timer = null @@ -187,19 +195,22 @@ class ConnectionManager extends EventEmitter { * * @private */ - _checkMetrics () { + async _checkMetrics () { if (this._libp2p.metrics) { - const movingAverages = this._libp2p.metrics.global.movingAverages - // @ts-ignore moving averages object types - const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage() - this._checkMaxLimit('maxReceivedData', received) - // @ts-ignore moving averages object types - const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage() - this._checkMaxLimit('maxSentData', sent) - const total = received + sent - this._checkMaxLimit('maxData', total) - log('metrics update', total) - this._timer = retimer(this._checkMetrics, this._options.pollInterval) + try { + const movingAverages = this._libp2p.metrics.global.movingAverages + // @ts-ignore moving averages object types + const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage() + await this._checkMaxLimit('maxReceivedData', received) + // @ts-ignore moving averages object types + const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage() + await this._checkMaxLimit('maxSentData', sent) + const total = received + sent + await this._checkMaxLimit('maxData', total) + log('metrics update', total) + } finally { + this._timer = retimer(this._checkMetrics, this._options.pollInterval) + } } } @@ -207,9 +218,8 @@ class ConnectionManager extends EventEmitter { * Tracks the incoming connection and check the connection limit * * @param {Connection} connection - * @returns {void} */ - onConnect (connection) { + async onConnect (connection) { const peerId = connection.remotePeer const peerIdStr = peerId.toB58String() const storedConn = this.connections.get(peerIdStr) @@ -222,13 +232,13 @@ class ConnectionManager extends EventEmitter { this.connections.set(peerIdStr, [connection]) } - this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey) + await this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey) if (!this._peerValues.has(peerIdStr)) { this._peerValues.set(peerIdStr, this._options.defaultPeerValue) } - this._checkMaxLimit('maxConnections', this.size) + await this._checkMaxLimit('maxConnections', this.size) } /** @@ -296,6 +306,9 @@ class ConnectionManager extends EventEmitter { */ _onLatencyMeasure (summary) { this._checkMaxLimit('maxEventLoopDelay', summary.avgMs) + .catch(err => { + log.error(err) + }) } /** @@ -305,12 +318,12 @@ class ConnectionManager extends EventEmitter { * @param {string} name - The name of the field to check limits for * @param {number} value - The current value of the field */ - _checkMaxLimit (name, value) { + async _checkMaxLimit (name, value) { const limit = this._options[name] log('checking limit of %s. current value: %d of %d', name, value, limit) if (value > limit) { log('%s: limit exceeded: %s, %d', this._peerId, name, value) - this._maybeDisconnectOne() + await this._maybeDisconnectOne() } } @@ -320,7 +333,7 @@ class ConnectionManager extends EventEmitter { * * @private */ - _maybeDisconnectOne () { + async _maybeDisconnectOne () { if (this._options.minConnections < this.connections.size) { const peerValues = Array.from(new Map([...this._peerValues.entries()].sort((a, b) => a[1] - b[1]))) log('%s: sorted peer values: %j', this._peerId, peerValues) @@ -331,7 +344,11 @@ class ConnectionManager extends EventEmitter { log('%s: closing a connection to %j', this._peerId, peerId) for (const connections of this.connections.values()) { if (connections[0].remotePeer.toB58String() === peerId) { - connections[0].close() + connections[0].close().catch(err => { + log.error(err) + }) + // TODO: should not need to invoke this manually + this.onDisconnect(connections[0]) break } } diff --git a/src/content-routing/utils.js b/src/content-routing/utils.js index c43ec9a9..adcf8f2c 100644 --- a/src/content-routing/utils.js +++ b/src/content-routing/utils.js @@ -14,12 +14,12 @@ const take = require('it-take') * Store the multiaddrs from every peer in the passed peer store * * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source - * @param {import('../peer-store')} peerStore + * @param {import('../peer-store/types').PeerStore} peerStore */ -function storeAddresses (source, peerStore) { - return map(source, (peer) => { +async function * storeAddresses (source, peerStore) { + yield * map(source, async (peer) => { // ensure we have the addresses for a given peer - peerStore.addressBook.add(peer.id, peer.multiaddrs) + await peerStore.addressBook.add(peer.id, peer.multiaddrs) return peer }) diff --git a/src/dialer/dial-request.js b/src/dialer/dial-request.js index 397fc784..810cdfee 100644 --- a/src/dialer/dial-request.js +++ b/src/dialer/dial-request.js @@ -63,7 +63,10 @@ class DialRequest { tokens.forEach(token => tokenHolder.push(token)) const dialAbortControllers = this.addrs.map(() => { const controller = new AbortController() - setMaxListeners && setMaxListeners(Infinity, controller.signal) + try { + // fails on node < 15.4 + setMaxListeners && setMaxListeners(Infinity, controller.signal) + } catch {} return controller }) diff --git a/src/dialer/index.js b/src/dialer/index.js index e6f3a0d0..06fd4d0c 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -30,8 +30,8 @@ const METRICS_PENDING_DIAL_TARGETS = 'pending-dial-targets' /** * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection * @typedef {import('peer-id')} PeerId - * @typedef {import('../peer-store')} PeerStore - * @typedef {import('../peer-store/address-book').Address} Address + * @typedef {import('../peer-store/types').PeerStore} PeerStore + * @typedef {import('../peer-store/types').Address} Address * @typedef {import('../transport-manager')} TransportManager */ @@ -88,10 +88,18 @@ class Dialer { this.tokens = [...new Array(maxParallelDials)].map((_, index) => index) /** @type {Map} */ - this._pendingDials = trackedMap(METRICS_COMPONENT, METRICS_PENDING_DIALS, metrics) + this._pendingDials = trackedMap({ + component: METRICS_COMPONENT, + metric: METRICS_PENDING_DIALS, + metrics + }) /** @type {Map void, reject: (err: Error) => void}>} */ - this._pendingDialTargets = trackedMap(METRICS_COMPONENT, METRICS_PENDING_DIAL_TARGETS, metrics) + this._pendingDialTargets = trackedMap({ + component: METRICS_COMPONENT, + metric: METRICS_PENDING_DIAL_TARGETS, + metrics + }) for (const [key, value] of Object.entries(resolvers)) { Multiaddr.resolvers.set(key, value) @@ -192,10 +200,10 @@ class Dialer { const { id, multiaddrs } = getPeer(peer) if (multiaddrs) { - this.peerStore.addressBook.add(id, multiaddrs) + await this.peerStore.addressBook.add(id, multiaddrs) } - let knownAddrs = this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter) || [] + let knownAddrs = await this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter) || [] // If received a multiaddr to dial, it should be the first to use // But, if we know other multiaddrs for the peer, we should try them too. @@ -215,7 +223,7 @@ class Dialer { const supportedAddrs = addrs.filter(a => this.transportManager.transportForMultiaddr(a)) if (supportedAddrs.length > this.maxAddrsToDial) { - this.peerStore.delete(id) + await this.peerStore.delete(id) throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES) } @@ -259,7 +267,10 @@ class Dialer { // this signal will potentially be used while dialing lots of // peers so prevent MaxListenersExceededWarning appearing in the console - setMaxListeners && setMaxListeners(Infinity, signal) + try { + // fails on node < 15.4 + setMaxListeners && setMaxListeners(Infinity, signal) + } catch {} const pendingDial = { dialRequest, diff --git a/src/identify/index.js b/src/identify/index.js index d08d1196..08365516 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -77,8 +77,6 @@ class IdentifyService { ...libp2p._options.host } - this.peerStore.metadataBook.set(this.peerId, 'AgentVersion', uint8ArrayFromString(this._host.agentVersion)) - this.peerStore.metadataBook.set(this.peerId, 'ProtocolVersion', uint8ArrayFromString(this._host.protocolVersion)) // When a new connection happens, trigger identify this.connectionManager.on('peer:connect', (connection) => { this.identify(connection).catch(log.error) @@ -87,18 +85,27 @@ class IdentifyService { // When self multiaddrs change, trigger identify-push this.peerStore.on('change:multiaddrs', ({ peerId }) => { if (peerId.toString() === this.peerId.toString()) { - this.pushToPeerStore() + this.pushToPeerStore().catch(err => log.error(err)) } }) // When self protocols change, trigger identify-push this.peerStore.on('change:protocols', ({ peerId }) => { if (peerId.toString() === this.peerId.toString()) { - this.pushToPeerStore() + this.pushToPeerStore().catch(err => log.error(err)) } }) } + async start () { + await this.peerStore.metadataBook.setValue(this.peerId, 'AgentVersion', uint8ArrayFromString(this._host.agentVersion)) + await this.peerStore.metadataBook.setValue(this.peerId, 'ProtocolVersion', uint8ArrayFromString(this._host.protocolVersion)) + } + + async stop () { + + } + /** * Send an Identify Push update to the list of connections * @@ -108,7 +115,7 @@ class IdentifyService { async push (connections) { const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) const listenAddrs = this._libp2p.multiaddrs.map((ma) => ma.bytes) - const protocols = this.peerStore.protoBook.get(this.peerId) || [] + const protocols = await this.peerStore.protoBook.get(this.peerId) const pushes = connections.map(async connection => { try { @@ -135,10 +142,8 @@ class IdentifyService { /** * Calls `push` for all peers in the `peerStore` that are connected - * - * @returns {void} */ - pushToPeerStore () { + async pushToPeerStore () { // Do not try to push if libp2p node is not running if (!this._libp2p.isStarted()) { return @@ -146,13 +151,13 @@ class IdentifyService { const connections = [] let connection - for (const peer of this.peerStore.peers.values()) { + for await (const peer of this.peerStore.getPeers()) { if (peer.protocols.includes(this.identifyPushProtocolStr) && (connection = this.connectionManager.get(peer.id))) { connections.push(connection) } } - this.push(connections) + await this.push(connections) } /** @@ -205,10 +210,10 @@ class IdentifyService { try { const envelope = await Envelope.openAndCertify(signedPeerRecord, PeerRecord.DOMAIN) - if (this.peerStore.addressBook.consumePeerRecord(envelope)) { - this.peerStore.protoBook.set(id, protocols) - this.peerStore.metadataBook.set(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) - this.peerStore.metadataBook.set(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) + if (await this.peerStore.addressBook.consumePeerRecord(envelope)) { + await this.peerStore.protoBook.set(id, protocols) + await this.peerStore.metadataBook.setValue(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) + await this.peerStore.metadataBook.setValue(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) return } } catch (/** @type {any} */ err) { @@ -217,14 +222,14 @@ class IdentifyService { // LEGACY: Update peers data in PeerStore try { - this.peerStore.addressBook.set(id, listenAddrs.map((addr) => new Multiaddr(addr))) + await this.peerStore.addressBook.set(id, listenAddrs.map((addr) => new Multiaddr(addr))) } catch (/** @type {any} */ err) { log.error('received invalid addrs', err) } - this.peerStore.protoBook.set(id, protocols) - this.peerStore.metadataBook.set(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) - this.peerStore.metadataBook.set(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) + await this.peerStore.protoBook.set(id, protocols) + await this.peerStore.metadataBook.setValue(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion)) + await this.peerStore.metadataBook.setValue(id, 'ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) // TODO: Add and score our observed addr log('received observed address of %s', cleanObservedAddr) @@ -268,7 +273,7 @@ class IdentifyService { } const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) - const protocols = this.peerStore.protoBook.get(this.peerId) || [] + const protocols = await this.peerStore.protoBook.get(this.peerId) const message = Message.Identify.encode({ protocolVersion: this._host.protocolVersion, @@ -321,8 +326,8 @@ class IdentifyService { try { const envelope = await Envelope.openAndCertify(message.signedPeerRecord, PeerRecord.DOMAIN) - if (this.peerStore.addressBook.consumePeerRecord(envelope)) { - this.peerStore.protoBook.set(id, message.protocols) + if (await this.peerStore.addressBook.consumePeerRecord(envelope)) { + await this.peerStore.protoBook.set(id, message.protocols) return } } catch (/** @type {any} */ err) { @@ -331,14 +336,14 @@ class IdentifyService { // LEGACY: Update peers data in PeerStore try { - this.peerStore.addressBook.set(id, + await this.peerStore.addressBook.set(id, message.listenAddrs.map((addr) => new Multiaddr(addr))) } catch (/** @type {any} */ err) { log.error('received invalid addrs', err) } // Update the protocols - this.peerStore.protoBook.set(id, message.protocols) + await this.peerStore.protoBook.set(id, message.protocols) } /** diff --git a/src/index.js b/src/index.js index f9f2b6c3..edb71544 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,7 @@ const { EventEmitter } = require('events') const errCode = require('err-code') const PeerId = require('peer-id') const { Multiaddr } = require('multiaddr') - +const { MemoryDatastore } = require('datastore-core/memory') const PeerRouting = require('./peer-routing') const ContentRouting = require('./content-routing') const getPeer = require('./get-peer') @@ -28,7 +28,6 @@ const TransportManager = require('./transport-manager') const Upgrader = require('./upgrader') const PeerStore = require('./peer-store') const PubsubAdapter = require('./pubsub-adapter') -const PersistentPeerStore = require('./peer-store/persistent') const Registrar = require('./registrar') const ping = require('./ping') const IdentifyService = require('./identify') @@ -112,7 +111,7 @@ const { updateSelfPeerRecord } = require('./record/utils') * @property {KeychainOptions & import('./keychain/index').KeychainOptions} [keychain] * @property {MetricsOptions & import('./metrics').MetricsOptions} [metrics] * @property {import('./peer-routing').PeerRoutingOptions} [peerRouting] - * @property {PeerStoreOptions & PersistentPeerStoreOptions} [peerStore] + * @property {PeerStoreOptions} [peerStore] * @property {import('./transport-manager').TransportManagerOptions} [transportManager] * @property {Libp2pConfig} [config] * @@ -172,13 +171,11 @@ class Libp2p extends EventEmitter { this.metrics = metrics } - this.peerStore = (this.datastore && this._options.peerStore.persistence) - ? new PersistentPeerStore({ - peerId: this.peerId, - datastore: this.datastore, - ...this._options.peerStore - }) - : new PeerStore({ peerId: this.peerId }) + /** @type {import('./peer-store/types').PeerStore} */ + this.peerStore = new PeerStore({ + peerId: this.peerId, + datastore: (this.datastore && this._options.peerStore.persistence) ? this.datastore : new MemoryDatastore() + }) // Addresses {listen, announce, noAnnounce} this.addresses = this._options.addresses @@ -290,7 +287,6 @@ class Libp2p extends EventEmitter { // Add the identify service since we can multiplex this.identifyService = new IdentifyService({ libp2p: this }) - this.handle(Object.values(IdentifyService.getProtocolStr(this)), this.identifyService.handleMessage) } // Attach private network protector @@ -356,6 +352,10 @@ class Libp2p extends EventEmitter { async start () { log('libp2p is starting') + if (this.identifyService) { + await this.handle(Object.values(IdentifyService.getProtocolStr(this)), this.identifyService.handleMessage) + } + try { await this._onStarting() await this._onDidStart() @@ -380,9 +380,13 @@ class Libp2p extends EventEmitter { try { this._isStarted = false + if (this.identifyService) { + await this.identifyService.stop() + } + this.relay && this.relay.stop() this.peerRouting.stop() - this._autodialler.stop() + await this._autodialler.stop() await (this._dht && this._dht.stop()) for (const service of this._discovery.values()) { @@ -393,7 +397,6 @@ class Libp2p extends EventEmitter { this._discovery = new Map() - await this.peerStore.stop() await this.connectionManager.stop() await Promise.all([ @@ -499,7 +502,7 @@ class Libp2p extends EventEmitter { if (!connection) { connection = await this.dialer.connectToPeer(peer, options) } else if (multiaddrs) { - this.peerStore.addressBook.add(id, multiaddrs) + await this.peerStore.addressBook.add(id, multiaddrs) } return connection @@ -579,14 +582,14 @@ class Libp2p extends EventEmitter { * @param {string[]|string} protocols * @param {(props: HandlerProps) => void} handler */ - handle (protocols, handler) { + async handle (protocols, handler) { protocols = Array.isArray(protocols) ? protocols : [protocols] protocols.forEach(protocol => { this.upgrader.protocols.set(protocol, handler) }) // Add new protocols to self protocols in the Protobook - this.peerStore.protoBook.add(this.peerId, protocols) + await this.peerStore.protoBook.add(this.peerId, protocols) } /** @@ -595,14 +598,14 @@ class Libp2p extends EventEmitter { * * @param {string[]|string} protocols */ - unhandle (protocols) { + async unhandle (protocols) { protocols = Array.isArray(protocols) ? protocols : [protocols] protocols.forEach(protocol => { this.upgrader.protocols.delete(protocol) }) // Remove protocols from self protocols in the Protobook - this.peerStore.protoBook.remove(this.peerId, protocols) + await this.peerStore.protoBook.remove(this.peerId, protocols) } async _onStarting () { @@ -613,11 +616,8 @@ class Libp2p extends EventEmitter { // Manage your NATs this.natManager.start() - // Start PeerStore - await this.peerStore.start() - if (this._config.pubsub.enabled) { - this.pubsub && this.pubsub.start() + this.pubsub && await this.pubsub.start() } // DHT subsystem @@ -631,6 +631,10 @@ class Libp2p extends EventEmitter { // Start metrics if present this.metrics && this.metrics.start() + + if (this.identifyService) { + await this.identifyService.start() + } } /** @@ -643,17 +647,19 @@ class Libp2p extends EventEmitter { this.peerStore.on('peer', peerId => { this.emit('peer:discovery', peerId) - this._maybeConnect(peerId) + this._maybeConnect(peerId).catch(err => { + log.error(err) + }) }) // Once we start, emit any peers we may have already discovered // TODO: this should be removed, as we already discovered these peers in the past - for (const peer of this.peerStore.peers.values()) { + for await (const peer of this.peerStore.getPeers()) { this.emit('peer:discovery', peer.id) } this.connectionManager.start() - this._autodialler.start() + await this._autodialler.start() // Peer discovery await this._setupPeerDiscovery() @@ -677,8 +683,8 @@ class Libp2p extends EventEmitter { return } - peer.multiaddrs && this.peerStore.addressBook.add(peer.id, peer.multiaddrs) - peer.protocols && this.peerStore.protoBook.set(peer.id, peer.protocols) + peer.multiaddrs && this.peerStore.addressBook.add(peer.id, peer.multiaddrs).catch(err => log.error(err)) + peer.protocols && this.peerStore.protoBook.set(peer.id, peer.protocols).catch(err => log.error(err)) } /** diff --git a/src/metrics/index.js b/src/metrics/index.js index 5ea3f96a..2b65c289 100644 --- a/src/metrics/index.js +++ b/src/metrics/index.js @@ -44,7 +44,7 @@ class Metrics { this._oldPeers = oldPeerLRU(this._options.maxOldPeersRetention) this._running = false this._onMessage = this._onMessage.bind(this) - this._componentMetrics = new Map() + this._systems = new Map() } /** @@ -89,19 +89,26 @@ class Metrics { } /** - * @returns {Map} + * @returns {Map>>} */ getComponentMetrics () { - return this._componentMetrics + return this._systems } - updateComponentMetric (component, metric, value) { - if (!this._componentMetrics.has(component)) { - this._componentMetrics.set(component, new Map()) + updateComponentMetric ({ system = 'libp2p', component, metric, value }) { + if (!this._systems.has(system)) { + this._systems.set(system, new Map()) } - const map = this._componentMetrics.get(component) - map.set(metric, value) + const systemMetrics = this._systems.get(system) + + if (!systemMetrics.has(component)) { + systemMetrics.set(component, new Map()) + } + + const componentMetrics = systemMetrics.get(component) + + componentMetrics.set(metric, value) } /** diff --git a/src/metrics/tracked-map.js b/src/metrics/tracked-map.js index d37587a6..beb0fa72 100644 --- a/src/metrics/tracked-map.js +++ b/src/metrics/tracked-map.js @@ -6,18 +6,27 @@ */ class TrackedMap extends Map { /** - * @param {string} component - * @param {string} name - * @param {import('.')} metrics + * @param {object} options + * @param {string} options.system + * @param {string} options.component + * @param {string} options.metric + * @param {import('.')} options.metrics */ - constructor (component, name, metrics) { + constructor (options) { super() + const { system, component, metric, metrics } = options + this._system = system this._component = component - this._name = name + this._metric = metric this._metrics = metrics - this._metrics.updateComponentMetric(this._component, this._name, this.size) + this._metrics.updateComponentMetric({ + system: this._system, + component: this._component, + metric: this._metric, + value: this.size + }) } /** @@ -26,7 +35,12 @@ class TrackedMap extends Map { */ set (key, value) { super.set(key, value) - this._metrics.updateComponentMetric(this._component, this._name, this.size) + this._metrics.updateComponentMetric({ + system: this._system, + component: this._component, + metric: this._metric, + value: this.size + }) return this } @@ -35,31 +49,43 @@ class TrackedMap extends Map { */ delete (key) { const deleted = super.delete(key) - this._metrics.updateComponentMetric(this._component, this._name, this.size) + this._metrics.updateComponentMetric({ + system: this._system, + component: this._component, + metric: this._metric, + value: this.size + }) return deleted } clear () { super.clear() - this._metrics.updateComponentMetric(this._component, this._name, this.size) + this._metrics.updateComponentMetric({ + system: this._system, + component: this._component, + metric: this._metric, + value: this.size + }) } } /** * @template K * @template V - * @param {string} component - * @param {string} name - * @param {import('.')} [metrics] + * @param {object} options + * @param {string} [options.system] + * @param {string} options.component + * @param {string} options.metric + * @param {import('.')} [options.metrics] * @returns {Map} */ -module.exports = (component, name, metrics) => { +module.exports = ({ system = 'libp2p', component, metric, metrics }) => { /** @type {Map} */ let map if (metrics) { - map = new TrackedMap(component, name, metrics) + map = new TrackedMap({ system, component, metric, metrics }) } else { map = new Map() } diff --git a/src/peer-routing.js b/src/peer-routing.js index fd6f9920..b525920f 100644 --- a/src/peer-routing.js +++ b/src/peer-routing.js @@ -154,7 +154,10 @@ class PeerRouting { const controller = new TimeoutController(options.timeout) // this controller will potentially be used while dialing lots of // peers so prevent MaxListenersExceededWarning appearing in the console - setMaxListeners && setMaxListeners(Infinity, controller.signal) + try { + // fails on node < 15.4 + setMaxListeners && setMaxListeners(Infinity, controller.signal) + } catch {} options.signal = controller.signal } diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 4e28eca9..05581a2e 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -1,74 +1,36 @@ 'use strict' const debug = require('debug') -const log = Object.assign(debug('libp2p:peer-store:address-book'), { - error: debug('libp2p:peer-store:address-book:err') -}) const errcode = require('err-code') - const { Multiaddr } = require('multiaddr') const PeerId = require('peer-id') - -const Book = require('./book') +const { codes } = require('../errors') const PeerRecord = require('../record/peer-record') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') const Envelope = require('../record/envelope') /** - * @typedef {import('./')} PeerStore + * @typedef {import('./types').PeerStore} PeerStore + * @typedef {import('./types').Address} Address + * @typedef {import('./types').AddressBook} AddressBook */ -/** - * @typedef {Object} Address - * @property {Multiaddr} multiaddr peer multiaddr. - * @property {boolean} isCertified obtained from a signed peer record. - * - * @typedef {Object} CertifiedRecord - * @property {Uint8Array} raw raw envelope. - * @property {number} seqNumber seq counter. - * - * @typedef {Object} Entry - * @property {Address[]} addresses peer Addresses. - * @property {CertifiedRecord} record certified peer record. - */ +const log = Object.assign(debug('libp2p:peer-store:address-book'), { + error: debug('libp2p:peer-store:address-book:err') +}) + +const EVENT_NAME = 'change:multiaddrs' /** - * @extends {Book} + * @implements {AddressBook} */ -class AddressBook extends Book { +class PeerStoreAddressBook { /** - * The AddressBook is responsible for keeping the known multiaddrs of a peer. - * - * @class - * @param {PeerStore} peerStore + * @param {PeerStore["emit"]} emit + * @param {import('./types').Store} store */ - constructor (peerStore) { - /** - * PeerStore Event emitter, used by the AddressBook to emit: - * "peer" - emitted when a peer is discovered by the node. - * "change:multiaddrs" - emitted when the known multiaddrs of a peer change. - */ - super({ - peerStore, - eventName: 'change:multiaddrs', - eventProperty: 'multiaddrs', - eventTransformer: (data) => { - if (!data.addresses) { - return [] - } - return data.addresses.map((/** @type {Address} */ address) => address.multiaddr) - } - }) - - /** - * Map known peers to their known Address Entries. - * - * @type {Map} - */ - this.data = new Map() + constructor (emit, store) { + this._emit = emit + this._store = store } /** @@ -77,69 +39,90 @@ class AddressBook extends Book { * into the AddressBook. * * @param {Envelope} envelope - * @returns {boolean} */ - consumePeerRecord (envelope) { - let peerRecord + async consumePeerRecord (envelope) { + log('consumePeerRecord await write lock') + const release = await this._store.lock.writeLock() + log('consumePeerRecord got write lock') + + let peerId + let updatedPeer + try { - peerRecord = PeerRecord.createFromProtobuf(envelope.payload) - } catch (/** @type {any} */ err) { - log.error('invalid peer record received') - return false - } - - // Verify peerId - if (!peerRecord.peerId.equals(envelope.peerId)) { - log('signing key does not match PeerId in the PeerRecord') - return false - } - - // ensure the record has multiaddrs - if (!peerRecord.multiaddrs || !peerRecord.multiaddrs.length) { - return false - } - - const peerId = peerRecord.peerId - const id = peerId.toB58String() - const entry = this.data.get(id) || { record: undefined } - const storedRecord = entry.record - - // ensure seq is greater than, or equal to, the last received - if (storedRecord && storedRecord.seqNumber >= peerRecord.seqNumber) { - return false - } - - const addresses = this._toAddresses(peerRecord.multiaddrs, true) - - // Replace unsigned addresses by the new ones from the record - // TODO: Once we have ttls for the addresses, we should merge these in. - this._setData(peerId, { - addresses, - record: { - raw: envelope.marshal(), - seqNumber: peerRecord.seqNumber + let peerRecord + try { + peerRecord = PeerRecord.createFromProtobuf(envelope.payload) + } catch (/** @type {any} */ err) { + log.error('invalid peer record received') + return false } - }) - log(`stored provided peer record for ${id}`) + + peerId = peerRecord.peerId + const multiaddrs = peerRecord.multiaddrs + + // Verify peerId + if (!peerId.equals(envelope.peerId)) { + log('signing key does not match PeerId in the PeerRecord') + return false + } + + // ensure the record has multiaddrs + if (!multiaddrs || !multiaddrs.length) { + return false + } + + if (await this._store.has(peerId)) { + const peer = await this._store.load(peerId) + + if (peer.peerRecordEnvelope) { + const storedEnvelope = await Envelope.createFromProtobuf(peer.peerRecordEnvelope) + const storedRecord = PeerRecord.createFromProtobuf(storedEnvelope.payload) + + // ensure seq is greater than, or equal to, the last received + if (storedRecord.seqNumber >= peerRecord.seqNumber) { + return false + } + } + } + + // Replace unsigned addresses by the new ones from the record + // TODO: Once we have ttls for the addresses, we should merge these in + updatedPeer = await this._store.patchOrCreate(peerId, { + addresses: convertMultiaddrsToAddresses(multiaddrs, true), + peerRecordEnvelope: envelope.marshal() + }) + + log(`stored provided peer record for ${peerRecord.peerId.toB58String()}`) + } finally { + log('consumePeerRecord release write lock') + release() + } + + this._emit(EVENT_NAME, { peerId, multiaddrs: updatedPeer.addresses.map(({ multiaddr }) => multiaddr) }) return true } /** - * Get the raw Envelope for a peer. Returns - * undefined if no Envelope is found. - * * @param {PeerId} peerId - * @returns {Uint8Array|undefined} */ - getRawEnvelope (peerId) { - const entry = this.data.get(peerId.toB58String()) + async getRawEnvelope (peerId) { + log('getRawEnvelope await read lock') + const release = await this._store.lock.readLock() + log('getRawEnvelope got read lock') - if (!entry || !entry.record || !entry.record.raw) { - return undefined + try { + const peer = await this._store.load(peerId) + + return peer.peerRecordEnvelope + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('getRawEnvelope release read lock') + release() } - - return entry.record.raw } /** @@ -147,10 +130,9 @@ class AddressBook extends Book { * Returns undefined if no record exists. * * @param {PeerId} peerId - * @returns {Promise|undefined} */ - getPeerRecord (peerId) { - const raw = this.getRawEnvelope(peerId) + async getPeerRecord (peerId) { + const raw = await this.getRawEnvelope(peerId) if (!raw) { return undefined @@ -160,186 +142,189 @@ class AddressBook extends Book { } /** - * Set known multiaddrs of a provided peer. - * This will replace previously stored multiaddrs, if available. - * Replacing stored multiaddrs might result in losing obtained certified addresses. - * If you are not sure, it's recommended to use `add` instead. - * - * @override * @param {PeerId} peerId - * @param {Multiaddr[]} multiaddrs - * @returns {AddressBook} */ - set (peerId, multiaddrs) { + async get (peerId) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - const addresses = this._toAddresses(multiaddrs) + log('get wait for read lock') + const release = await this._store.lock.readLock() + log('get got read lock') - // Not replace multiaddrs - if (!addresses.length) { - return this - } + try { + const peer = await this._store.load(peerId) - const id = peerId.toB58String() - const entry = this.data.get(id) - - // Already knows the peer - if (entry && entry.addresses && entry.addresses.length === addresses.length) { - const intersection = entry.addresses.filter((addr) => addresses.some((newAddr) => addr.multiaddr.equals(newAddr.multiaddr))) - - // Are new addresses equal to the old ones? - // If yes, no changes needed! - if (intersection.length === entry.addresses.length) { - log(`the addresses provided to store are equal to the already stored for ${id}`) - return this + return peer.addresses + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err } + } finally { + log('get release read lock') + release() } - this._setData(peerId, { - addresses, - record: entry && entry.record - }) - log(`stored provided multiaddrs for ${id}`) - - // Notify the existance of a new peer - if (!entry) { - this._ps.emit('peer', peerId) - } - - return this + return [] } /** - * Add known addresses of a provided peer. - * If the peer is not known, it is set with the given addresses. - * * @param {PeerId} peerId * @param {Multiaddr[]} multiaddrs - * @returns {AddressBook} */ - add (peerId, multiaddrs) { + async set (peerId, multiaddrs) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - const addresses = this._toAddresses(multiaddrs) - const id = peerId.toB58String() + log('set await write lock') + const release = await this._store.lock.writeLock() + log('set got write lock') - // No addresses to be added - if (!addresses.length) { - return this - } + let hasPeer = false + let updatedPeer - const entry = this.data.get(id) + try { + const addresses = convertMultiaddrsToAddresses(multiaddrs) - if (entry && entry.addresses) { - // Add recorded uniquely to the new array (Union) - entry.addresses.forEach((addr) => { - if (!addresses.find(r => r.multiaddr.equals(addr.multiaddr))) { - addresses.push(addr) + // No valid addresses found + if (!addresses.length) { + return + } + + try { + const peer = await this._store.load(peerId) + hasPeer = true + + if (new Set([ + ...addresses.map(({ multiaddr }) => multiaddr.toString()), + ...peer.addresses.map(({ multiaddr }) => multiaddr.toString()) + ]).size === peer.addresses.length && addresses.length === peer.addresses.length) { + // not changing anything, no need to update + return + } + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err } - }) - - // If the recorded length is equal to the new after the unique union - // The content is the same, no need to update. - if (entry.addresses.length === addresses.length) { - log(`the addresses provided to store are already stored for ${id}`) - return this } + + updatedPeer = await this._store.patchOrCreate(peerId, { addresses }) + + log(`set multiaddrs for ${peerId.toB58String()}`) + } finally { + log('set release write lock') + release() } - this._setData(peerId, { - addresses, - record: entry && entry.record - }) + this._emit(EVENT_NAME, { peerId, multiaddrs: updatedPeer.addresses.map(addr => addr.multiaddr) }) - log(`added provided multiaddrs for ${id}`) - - // Notify the existance of a new peer - if (!(entry && entry.addresses)) { - this._ps.emit('peer', peerId) + // Notify the existence of a new peer + if (!hasPeer) { + this._emit('peer', peerId) } - - return this } /** - * Get the known data of a provided peer. - * - * @override * @param {PeerId} peerId - * @returns {Address[]|undefined} - */ - get (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - const entry = this.data.get(peerId.toB58String()) - - return entry && entry.addresses ? [...entry.addresses] : undefined - } - - /** - * Transforms received multiaddrs into Address. - * - * @private * @param {Multiaddr[]} multiaddrs - * @param {boolean} [isCertified] - * @returns {Address[]} */ - _toAddresses (multiaddrs, isCertified = false) { - if (!multiaddrs) { - log.error('multiaddrs must be provided to store data') - throw errcode(new Error('multiaddrs must be provided'), ERR_INVALID_PARAMETERS) + async add (peerId, multiaddrs) { + if (!PeerId.isPeerId(peerId)) { + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - // create Address for each address - /** @type {Address[]} */ - const addresses = [] - multiaddrs.forEach((addr) => { - if (!Multiaddr.isMultiaddr(addr)) { - log.error(`multiaddr ${addr} must be an instance of multiaddr`) - throw errcode(new Error(`multiaddr ${addr} must be an instance of multiaddr`), ERR_INVALID_PARAMETERS) + log('add await write lock') + const release = await this._store.lock.writeLock() + log('add got write lock') + + let hasPeer + let updatedPeer + + try { + const addresses = convertMultiaddrsToAddresses(multiaddrs) + + // No valid addresses found + if (!addresses.length) { + return } - // Guarantee no replicates - if (!addresses.find((a) => a.multiaddr.equals(addr))) { - addresses.push({ - multiaddr: addr, - isCertified - }) - } - }) + try { + const peer = await this._store.load(peerId) + hasPeer = true - return addresses + if (new Set([ + ...addresses.map(({ multiaddr }) => multiaddr.toString()), + ...peer.addresses.map(({ multiaddr }) => multiaddr.toString()) + ]).size === peer.addresses.length) { + return + } + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } + + updatedPeer = await this._store.mergeOrCreate(peerId, { addresses }) + + log(`added multiaddrs for ${peerId}`) + } finally { + log('set release write lock') + release() + } + + this._emit(EVENT_NAME, { peerId, multiaddrs: updatedPeer.addresses.map(addr => addr.multiaddr) }) + + // Notify the existence of a new peer + if (!hasPeer) { + this._emit('peer', peerId) + } + } + + /** + * @param {PeerId} peerId + */ + async delete (peerId) { + if (!PeerId.isPeerId(peerId)) { + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) + } + + log('delete await write lock') + const release = await this._store.lock.writeLock() + log('delete got write lock') + + let has + + try { + has = await this._store.has(peerId) + + await this._store.patchOrCreate(peerId, { + addresses: [] + }) + } finally { + log('delete release write lock') + release() + } + + if (has) { + this._emit(EVENT_NAME, { peerId, multiaddrs: [] }) + } } /** - * Get the known multiaddrs for a given peer. All returned multiaddrs - * will include the encapsulated `PeerId` of the peer. - * Returns `undefined` if there are no known multiaddrs for the given peer. - * * @param {PeerId} peerId * @param {(addresses: Address[]) => Address[]} [addressSorter] - * @returns {Multiaddr[]|undefined} */ - getMultiaddrsForPeer (peerId, addressSorter = (ms) => ms) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - const entry = this.data.get(peerId.toB58String()) - if (!entry || !entry.addresses) { - return undefined - } + async getMultiaddrsForPeer (peerId, addressSorter = (ms) => ms) { + const addresses = await this.get(peerId) return addressSorter( - entry.addresses || [] + addresses ).map((address) => { const multiaddr = address.multiaddr @@ -351,4 +336,34 @@ class AddressBook extends Book { } } -module.exports = AddressBook +/** + * Transforms received multiaddrs into Address. + * + * @private + * @param {Multiaddr[]} multiaddrs + * @param {boolean} [isCertified] + * @returns {Address[]} + */ +function convertMultiaddrsToAddresses (multiaddrs, isCertified = false) { + if (!multiaddrs) { + log.error('multiaddrs must be provided to store data') + throw errcode(new Error('multiaddrs must be provided'), codes.ERR_INVALID_PARAMETERS) + } + + // create Address for each address with no duplicates + return Array.from( + new Set(multiaddrs.map(ma => ma.toString())) + ) + .map(addr => { + try { + return { + multiaddr: new Multiaddr(addr), + isCertified + } + } catch (err) { + throw errcode(err, codes.ERR_INVALID_PARAMETERS) + } + }) +} + +module.exports = PeerStoreAddressBook diff --git a/src/peer-store/book.js b/src/peer-store/book.js deleted file mode 100644 index 3de04594..00000000 --- a/src/peer-store/book.js +++ /dev/null @@ -1,124 +0,0 @@ -'use strict' - -const errcode = require('err-code') -const PeerId = require('peer-id') -const { codes } = require('../errors') - -/** - * @param {any} data - */ -const passthrough = data => data - -/** - * @typedef {import('./')} PeerStore - */ - -class Book { - /** - * The Book is the skeleton for the PeerStore books. - * - * @class - * @param {Object} properties - * @param {PeerStore} properties.peerStore - PeerStore instance. - * @param {string} properties.eventName - Name of the event to emit by the PeerStore. - * @param {string} properties.eventProperty - Name of the property to emit by the PeerStore. - * @param {(data: any) => any[]} [properties.eventTransformer] - Transformer function of the provided data for being emitted. - */ - constructor ({ peerStore, eventName, eventProperty, eventTransformer = passthrough }) { - this._ps = peerStore - this.eventName = eventName - this.eventProperty = eventProperty - this.eventTransformer = eventTransformer - - /** - * Map known peers to their data. - * - * @type {Map} - */ - this.data = new Map() - } - - /** - * Set known data of a provided peer. - * - * @param {PeerId} peerId - * @param {any[]|any} data - */ - set (peerId, data) { - throw errcode(new Error('set must be implemented by the subclass'), codes.ERR_NOT_IMPLEMENTED) - } - - /** - * Set data into the datastructure, persistence and emit it using the provided transformers. - * - * @protected - * @param {PeerId} peerId - peerId of the data to store - * @param {any} data - data to store. - * @param {Object} [options] - storing options. - * @param {boolean} [options.emit = true] - emit the provided data. - * @returns {void} - */ - _setData (peerId, data, { emit = true } = {}) { - const b58key = peerId.toB58String() - - // Store data in memory - this.data.set(b58key, data) - - // Emit event - emit && this._emit(peerId, data) - } - - /** - * Emit data. - * - * @protected - * @param {PeerId} peerId - * @param {any} [data] - */ - _emit (peerId, data) { - this._ps.emit(this.eventName, { - peerId, - [this.eventProperty]: this.eventTransformer(data) - }) - } - - /** - * Get the known data of a provided peer. - * Returns `undefined` if there is no available data for the given peer. - * - * @param {PeerId} peerId - * @returns {any[]|any|undefined} - */ - get (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) - } - - const rec = this.data.get(peerId.toB58String()) - - // @ts-ignore - return rec ? [...rec] : undefined - } - - /** - * Deletes the provided peer from the book. - * - * @param {PeerId} peerId - * @returns {boolean} - */ - delete (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) - } - - if (!this.data.delete(peerId.toB58String())) { - return false - } - - this._emit(peerId, []) - - return true - } -} - -module.exports = Book diff --git a/src/peer-store/index.js b/src/peer-store/index.js index 72fd9043..d17a9f2d 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -1,152 +1,119 @@ 'use strict' -const errcode = require('err-code') - +const debug = require('debug') const { EventEmitter } = require('events') -const PeerId = require('peer-id') - const AddressBook = require('./address-book') const KeyBook = require('./key-book') const MetadataBook = require('./metadata-book') const ProtoBook = require('./proto-book') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') +const Store = require('./store') /** - * @typedef {import('./address-book').Address} Address + * @typedef {import('./types').PeerStore} PeerStore + * @typedef {import('./types').Peer} Peer + * @typedef {import('peer-id')} PeerId */ +const log = Object.assign(debug('libp2p:peer-store'), { + error: debug('libp2p:peer-store:err') +}) + /** - * @extends {EventEmitter} + * An implementation of PeerStore that stores data in a Datastore * - * @fires PeerStore#peer Emitted when a new peer is added. - * @fires PeerStore#change:protocols Emitted when a known peer supports a different set of protocols. - * @fires PeerStore#change:multiaddrs Emitted when a known peer has a different set of multiaddrs. - * @fires PeerStore#change:pubkey Emitted emitted when a peer's public key is known. - * @fires PeerStore#change:metadata Emitted when the known metadata of a peer change. + * @implements {PeerStore} */ -class PeerStore extends EventEmitter { +class DefaultPeerStore extends EventEmitter { /** - * Peer object - * - * @typedef {Object} Peer - * @property {PeerId} id peer's peer-id instance. - * @property {Address[]} addresses peer's addresses containing its multiaddrs and metadata. - * @property {string[]} protocols peer's supported protocols. - * @property {Map|undefined} metadata peer's metadata map. + * @param {object} properties + * @param {PeerId} properties.peerId + * @param {import('interface-datastore').Datastore} properties.datastore */ - - /** - * Responsible for managing known peers, as well as their addresses, protocols and metadata. - * - * @param {object} options - * @param {PeerId} options.peerId - * @class - */ - constructor ({ peerId }) { + constructor ({ peerId, datastore }) { super() this._peerId = peerId + this._store = new Store(datastore) - /** - * AddressBook containing a map of peerIdStr to Address. - */ - this.addressBook = new AddressBook(this) + this.addressBook = new AddressBook(this.emit.bind(this), this._store) + this.keyBook = new KeyBook(this.emit.bind(this), this._store) + this.metadataBook = new MetadataBook(this.emit.bind(this), this._store) + this.protoBook = new ProtoBook(this.emit.bind(this), this._store) + } - /** - * KeyBook containing a map of peerIdStr to their PeerId with public keys. - */ - this.keyBook = new KeyBook(this) + async * getPeers () { + log('getPeers await read lock') + const release = await this._store.lock.readLock() + log('getPeers got read lock') - /** - * MetadataBook containing a map of peerIdStr to their metadata Map. - */ - this.metadataBook = new MetadataBook(this) + try { + for await (const peer of this._store.all()) { + if (peer.id.toB58String() === this._peerId.toB58String()) { + // Remove self peer if present + continue + } - /** - * ProtoBook containing a map of peerIdStr to supported protocols. - */ - this.protoBook = new ProtoBook(this) + yield peer + } + } finally { + log('getPeers release read lock') + release() + } } /** - * Start the PeerStore. - */ - start () {} - - /** - * Stop the PeerStore. - */ - stop () {} - - /** - * Get all the stored information of every peer known. - * - * @returns {Map} - */ - get peers () { - const storedPeers = new Set([ - ...this.addressBook.data.keys(), - ...this.keyBook.data.keys(), - ...this.protoBook.data.keys(), - ...this.metadataBook.data.keys() - ]) - - // Remove self peer if present - this._peerId && storedPeers.delete(this._peerId.toB58String()) - - const peersData = new Map() - storedPeers.forEach((idStr) => { - peersData.set(idStr, this.get(PeerId.createFromB58String(idStr))) - }) - - return peersData - } - - /** - * Delete the information of the given peer in every book. + * Delete the information of the given peer in every book * * @param {PeerId} peerId - * @returns {boolean} true if found and removed */ - delete (peerId) { - const addressesDeleted = this.addressBook.delete(peerId) - const keyDeleted = this.keyBook.delete(peerId) - const protocolsDeleted = this.protoBook.delete(peerId) - const metadataDeleted = this.metadataBook.delete(peerId) + async delete (peerId) { + log('delete await write lock') + const release = await this._store.lock.writeLock() + log('delete got write lock') - return addressesDeleted || keyDeleted || protocolsDeleted || metadataDeleted + try { + await this._store.delete(peerId) + } finally { + log('delete release write lock') + release() + } } /** - * Get the stored information of a given peer. + * Get the stored information of a given peer * * @param {PeerId} peerId - * @returns {Peer|undefined} */ - get (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + async get (peerId) { + log('get await read lock') + const release = await this._store.lock.readLock() + log('get got read lock') + + try { + return this._store.load(peerId) + } finally { + log('get release read lock') + release() } + } - const id = this.keyBook.data.get(peerId.toB58String()) - const addresses = this.addressBook.get(peerId) - const metadata = this.metadataBook.get(peerId) - const protocols = this.protoBook.get(peerId) + /** + * Returns true if we have a record of the peer + * + * @param {PeerId} peerId + */ + async has (peerId) { + log('has await read lock') + const release = await this._store.lock.readLock() + log('has got read lock') - if (!id && !addresses && !metadata && !protocols) { - return undefined - } - - return { - id: id || peerId, - addresses: addresses || [], - protocols: protocols || [], - metadata: metadata + try { + return this._store.has(peerId) + } finally { + log('has release read lock') + release() } } } -module.exports = PeerStore +module.exports = DefaultPeerStore diff --git a/src/peer-store/key-book.js b/src/peer-store/key-book.js index 356c8186..dfd4c151 100644 --- a/src/peer-store/key-book.js +++ b/src/peer-store/key-book.js @@ -1,96 +1,141 @@ 'use strict' const debug = require('debug') +const errcode = require('err-code') +const { codes } = require('../errors') +const PeerId = require('peer-id') +const { equals: uint8arrayEquals } = require('uint8arrays/equals') + +/** + * @typedef {import('./types').PeerStore} PeerStore + * @typedef {import('./types').KeyBook} KeyBook + * @typedef {import('libp2p-interfaces/src/keys/types').PublicKey} PublicKey + */ + const log = Object.assign(debug('libp2p:peer-store:key-book'), { error: debug('libp2p:peer-store:key-book:err') }) -const errcode = require('err-code') -const PeerId = require('peer-id') - -const Book = require('./book') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') +const EVENT_NAME = 'change:pubkey' /** - * @typedef {import('./')} PeerStore - * @typedef {import('libp2p-crypto').PublicKey} PublicKey + * @implements {KeyBook} */ - -/** - * @extends {Book} - */ -class KeyBook extends Book { +class PeerStoreKeyBook { /** * The KeyBook is responsible for keeping the known public keys of a peer. * - * @class - * @param {PeerStore} peerStore + * @param {PeerStore["emit"]} emit + * @param {import('./types').Store} store */ - constructor (peerStore) { - super({ - peerStore, - eventName: 'change:pubkey', - eventProperty: 'pubkey', - eventTransformer: (data) => data.pubKey - }) - - /** - * Map known peers to their known Public Key. - * - * @type {Map} - */ - this.data = new Map() + constructor (emit, store) { + this._emit = emit + this._store = store } /** - * Set the Peer public key. + * Set the Peer public key * - * @override * @param {PeerId} peerId * @param {PublicKey} publicKey - * @returns {KeyBook} */ - set (peerId, publicKey) { + async set (peerId, publicKey) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - const id = peerId.toB58String() - const recPeerId = this.data.get(id) - - // If no record available, and this is valid - if (!recPeerId && publicKey) { - // This might be unecessary, but we want to store the PeerId - // to avoid an async operation when reconstructing the PeerId - peerId.pubKey = publicKey - - this._setData(peerId, peerId) - log(`stored provided public key for ${id}`) + if (!publicKey) { + log.error('publicKey must be an instance of PublicKey to store data') + throw errcode(new Error('publicKey must be an instance of PublicKey'), codes.ERR_INVALID_PARAMETERS) } - return this + log('set await write lock') + const release = await this._store.lock.writeLock() + log('set got write lock') + + let updatedKey = false + + try { + try { + const existing = await this._store.load(peerId) + + if (existing.pubKey && uint8arrayEquals(existing.pubKey.bytes, publicKey.bytes)) { + return + } + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } + + await this._store.patchOrCreate(peerId, { + pubKey: publicKey + }) + updatedKey = true + } finally { + log('set release write lock') + release() + } + + if (updatedKey) { + this._emit(EVENT_NAME, { peerId, pubKey: publicKey }) + } } /** - * Get Public key of the given PeerId, if stored. + * Get Public key of the given PeerId, if stored * - * @override * @param {PeerId} peerId - * @returns {PublicKey | undefined} */ - get (peerId) { + async get (peerId) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - const rec = this.data.get(peerId.toB58String()) + log('get await write lock') + const release = await this._store.lock.readLock() + log('get got write lock') - return rec ? rec.pubKey : undefined + try { + const peer = await this._store.load(peerId) + + return peer.pubKey + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('get release write lock') + release() + } + } + + /** + * @param {PeerId} peerId + */ + async delete (peerId) { + if (!PeerId.isPeerId(peerId)) { + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) + } + + log('delete await write lock') + const release = await this._store.lock.writeLock() + log('delete got write lock') + + try { + await this._store.patchOrCreate(peerId, { + pubKey: undefined + }) + } finally { + log('delete release write lock') + release() + } + + this._emit(EVENT_NAME, { peerId, pubKey: undefined }) } } -module.exports = KeyBook +module.exports = PeerStoreKeyBook diff --git a/src/peer-store/metadata-book.js b/src/peer-store/metadata-book.js index 3eb5b38e..7176a78a 100644 --- a/src/peer-store/metadata-book.js +++ b/src/peer-store/metadata-book.js @@ -1,120 +1,67 @@ 'use strict' const debug = require('debug') -const log = Object.assign(debug('libp2p:peer-store:proto-book'), { - error: debug('libp2p:peer-store:proto-book:err') -}) const errcode = require('err-code') +const { codes } = require('../errors') +const PeerId = require('peer-id') const { equals: uint8ArrayEquals } = require('uint8arrays/equals') -const PeerId = require('peer-id') - -const Book = require('./book') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') +const log = Object.assign(debug('libp2p:peer-store:metadata-book'), { + error: debug('libp2p:peer-store:metadata-book:err') +}) /** - * @typedef {import('./')} PeerStore + * @typedef {import('./types').PeerStore} PeerStore + * @typedef {import('./types').MetadataBook} MetadataBook */ +const EVENT_NAME = 'change:metadata' + /** - * @extends {Book} - * - * @fires MetadataBook#change:metadata + * @implements {MetadataBook} */ -class MetadataBook extends Book { +class PeerStoreMetadataBook { /** * The MetadataBook is responsible for keeping the known supported - * protocols of a peer. + * protocols of a peer * - * @class - * @param {PeerStore} peerStore + * @param {PeerStore["emit"]} emit + * @param {import('./types').Store} store */ - constructor (peerStore) { - /** - * PeerStore Event emitter, used by the MetadataBook to emit: - * "change:metadata" - emitted when the known metadata of a peer change. - */ - super({ - peerStore, - eventName: 'change:metadata', - eventProperty: 'metadata' - }) - - /** - * Map known peers to their known protocols. - * - * @type {Map>} - */ - this.data = new Map() + constructor (emit, store) { + this._emit = emit + this._store = store } /** - * Set metadata key and value of a provided peer. + * Get the known data of a provided peer * - * @override * @param {PeerId} peerId - * @param {string} key - metadata key - * @param {Uint8Array} value - metadata value - * @returns {MetadataBook} */ - // @ts-ignore override with more then the parameters expected in Book - set (peerId, key, value) { + async get (peerId) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - if (typeof key !== 'string' || !(value instanceof Uint8Array)) { - log.error('valid key and value must be provided to store data') - throw errcode(new Error('valid key and value must be provided'), ERR_INVALID_PARAMETERS) + log('get await read lock') + const release = await this._store.lock.readLock() + log('get got read lock') + + try { + const peer = await this._store.load(peerId) + + return peer.metadata + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('get release read lock') + release() } - this._setValue(peerId, key, value) - - return this - } - - /** - * Set data into the datastructure - * - * @param {PeerId} peerId - * @param {string} key - * @param {Uint8Array} value - * @param {object} [opts] - * @param {boolean} [opts.emit] - */ - _setValue (peerId, key, value, { emit = true } = {}) { - const id = peerId.toB58String() - const rec = this.data.get(id) || new Map() - const recMap = rec.get(key) - - // Already exists and is equal - if (recMap && uint8ArrayEquals(value, recMap)) { - log(`the metadata provided to store is equal to the already stored for ${id} on ${key}`) - return - } - - rec.set(key, value) - this.data.set(id, rec) - - emit && this._emit(peerId, key) - } - - /** - * Get the known data of a provided peer. - * - * @param {PeerId} peerId - * @returns {Map|undefined} - */ - get (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - return this.data.get(peerId.toB58String()) + return new Map() } /** @@ -122,59 +69,182 @@ class MetadataBook extends Book { * * @param {PeerId} peerId * @param {string} key - * @returns {Uint8Array | undefined} */ - getValue (peerId, key) { + async getValue (peerId, key) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - const rec = this.data.get(peerId.toB58String()) - return rec && rec.get(key) + log('getValue await read lock') + const release = await this._store.lock.readLock() + log('getValue got read lock') + + try { + const peer = await this._store.load(peerId) + + return peer.metadata.get(key) + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('getValue release write lock') + release() + } } /** - * Deletes the provided peer from the book. + * @param {PeerId} peerId + * @param {Map} metadata + */ + async set (peerId, metadata) { + if (!PeerId.isPeerId(peerId)) { + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) + } + + if (!metadata || !(metadata instanceof Map)) { + log.error('valid metadata must be provided to store data') + throw errcode(new Error('valid metadata must be provided'), codes.ERR_INVALID_PARAMETERS) + } + + log('set await write lock') + const release = await this._store.lock.writeLock() + log('set got write lock') + + try { + await this._store.mergeOrCreate(peerId, { + metadata + }) + } finally { + log('set release write lock') + release() + } + + this._emit(EVENT_NAME, { peerId, metadata }) + } + + /** + * Set metadata key and value of a provided peer * * @param {PeerId} peerId - * @returns {boolean} + * @param {string} key - metadata key + * @param {Uint8Array} value - metadata value */ - delete (peerId) { + async setValue (peerId, key, value) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - if (!this.data.delete(peerId.toB58String())) { - return false + if (typeof key !== 'string' || !(value instanceof Uint8Array)) { + log.error('valid key and value must be provided to store data') + throw errcode(new Error('valid key and value must be provided'), codes.ERR_INVALID_PARAMETERS) } - this._emit(peerId) + log('setValue await write lock') + const release = await this._store.lock.writeLock() + log('setValue got write lock') - return true + let updatedPeer + + try { + try { + const existingPeer = await this._store.load(peerId) + const existingValue = existingPeer.metadata.get(key) + + if (existingValue != null && uint8ArrayEquals(value, existingValue)) { + return + } + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } + + updatedPeer = await this._store.mergeOrCreate(peerId, { + metadata: new Map([[key, value]]) + }) + } finally { + log('setValue release write lock') + release() + } + + this._emit(EVENT_NAME, { peerId, metadata: updatedPeer.metadata }) + } + + /** + * @param {PeerId} peerId + */ + async delete (peerId) { + if (!PeerId.isPeerId(peerId)) { + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) + } + + log('delete await write lock') + const release = await this._store.lock.writeLock() + log('delete got write lock') + + let has + + try { + has = await this._store.has(peerId) + + if (has) { + await this._store.patch(peerId, { + metadata: new Map() + }) + } + } finally { + log('delete release write lock') + release() + } + + if (has) { + this._emit(EVENT_NAME, { peerId, metadata: new Map() }) + } } /** - * Deletes the provided peer metadata key from the book. - * * @param {PeerId} peerId * @param {string} key - * @returns {boolean} */ - deleteValue (peerId, key) { + async deleteValue (peerId, key) { if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - const rec = this.data.get(peerId.toB58String()) + log('deleteValue await write lock') + const release = await this._store.lock.writeLock() + log('deleteValue got write lock') - if (!rec || !rec.delete(key)) { - return false + let metadata + + try { + const peer = await this._store.load(peerId) + metadata = peer.metadata + + metadata.delete(key) + + await this._store.patch(peerId, { + metadata + }) + } catch (/** @type {any} **/ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('deleteValue release write lock') + release() } - this._emit(peerId, key) - - return true + if (metadata) { + this._emit(EVENT_NAME, { peerId, metadata }) + } } } -module.exports = MetadataBook +module.exports = PeerStoreMetadataBook diff --git a/src/peer-store/pb/peer.d.ts b/src/peer-store/pb/peer.d.ts new file mode 100644 index 00000000..e6ccdfe6 --- /dev/null +++ b/src/peer-store/pb/peer.d.ts @@ -0,0 +1,222 @@ +import * as $protobuf from "protobufjs"; +/** Properties of a Peer. */ +export interface IPeer { + + /** Peer addresses */ + addresses?: (IAddress[]|null); + + /** Peer protocols */ + protocols?: (string[]|null); + + /** Peer metadata */ + metadata?: (IMetadata[]|null); + + /** Peer pubKey */ + pubKey?: (Uint8Array|null); + + /** Peer peerRecordEnvelope */ + peerRecordEnvelope?: (Uint8Array|null); +} + +/** Represents a Peer. */ +export class Peer implements IPeer { + + /** + * Constructs a new Peer. + * @param [p] Properties to set + */ + constructor(p?: IPeer); + + /** Peer addresses. */ + public addresses: IAddress[]; + + /** Peer protocols. */ + public protocols: string[]; + + /** Peer metadata. */ + public metadata: IMetadata[]; + + /** Peer pubKey. */ + public pubKey?: (Uint8Array|null); + + /** Peer peerRecordEnvelope. */ + public peerRecordEnvelope?: (Uint8Array|null); + + /** Peer _pubKey. */ + public _pubKey?: "pubKey"; + + /** Peer _peerRecordEnvelope. */ + public _peerRecordEnvelope?: "peerRecordEnvelope"; + + /** + * Encodes the specified Peer message. Does not implicitly {@link Peer.verify|verify} messages. + * @param m Peer message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IPeer, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Peer message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Peer + * @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): Peer; + + /** + * Creates a Peer message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Peer + */ + public static fromObject(d: { [k: string]: any }): Peer; + + /** + * Creates a plain object from a Peer message. Also converts values to other types if specified. + * @param m Peer + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Peer, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Peer to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +/** Properties of an Address. */ +export interface IAddress { + + /** Address multiaddr */ + multiaddr?: (Uint8Array|null); + + /** Address isCertified */ + isCertified?: (boolean|null); +} + +/** Represents an Address. */ +export class Address implements IAddress { + + /** + * Constructs a new Address. + * @param [p] Properties to set + */ + constructor(p?: IAddress); + + /** Address multiaddr. */ + public multiaddr: Uint8Array; + + /** Address isCertified. */ + public isCertified?: (boolean|null); + + /** Address _isCertified. */ + public _isCertified?: "isCertified"; + + /** + * Encodes the specified Address message. Does not implicitly {@link Address.verify|verify} messages. + * @param m Address message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IAddress, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an Address message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Address + * @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): Address; + + /** + * Creates an Address message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Address + */ + public static fromObject(d: { [k: string]: any }): Address; + + /** + * Creates a plain object from an Address message. Also converts values to other types if specified. + * @param m Address + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Address, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Address to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +/** Properties of a Metadata. */ +export interface IMetadata { + + /** Metadata key */ + key?: (string|null); + + /** Metadata value */ + value?: (Uint8Array|null); +} + +/** Represents a Metadata. */ +export class Metadata implements IMetadata { + + /** + * Constructs a new Metadata. + * @param [p] Properties to set + */ + constructor(p?: IMetadata); + + /** Metadata key. */ + public key: string; + + /** Metadata value. */ + public value: Uint8Array; + + /** + * Encodes the specified Metadata message. Does not implicitly {@link Metadata.verify|verify} messages. + * @param m Metadata message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IMetadata, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Metadata message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Metadata + * @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): Metadata; + + /** + * Creates a Metadata message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Metadata + */ + public static fromObject(d: { [k: string]: any }): Metadata; + + /** + * Creates a plain object from a Metadata message. Also converts values to other types if specified. + * @param m Metadata + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Metadata, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Metadata to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} diff --git a/src/peer-store/pb/peer.js b/src/peer-store/pb/peer.js new file mode 100644 index 00000000..866911d8 --- /dev/null +++ b/src/peer-store/pb/peer.js @@ -0,0 +1,643 @@ +/*eslint-disable*/ +"use strict"; + +var $protobuf = require("protobufjs/minimal"); + +// Common aliases +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots["libp2p-peer"] || ($protobuf.roots["libp2p-peer"] = {}); + +$root.Peer = (function() { + + /** + * Properties of a Peer. + * @exports IPeer + * @interface IPeer + * @property {Array.|null} [addresses] Peer addresses + * @property {Array.|null} [protocols] Peer protocols + * @property {Array.|null} [metadata] Peer metadata + * @property {Uint8Array|null} [pubKey] Peer pubKey + * @property {Uint8Array|null} [peerRecordEnvelope] Peer peerRecordEnvelope + */ + + /** + * Constructs a new Peer. + * @exports Peer + * @classdesc Represents a Peer. + * @implements IPeer + * @constructor + * @param {IPeer=} [p] Properties to set + */ + function Peer(p) { + this.addresses = []; + this.protocols = []; + this.metadata = []; + 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]]; + } + + /** + * Peer addresses. + * @member {Array.} addresses + * @memberof Peer + * @instance + */ + Peer.prototype.addresses = $util.emptyArray; + + /** + * Peer protocols. + * @member {Array.} protocols + * @memberof Peer + * @instance + */ + Peer.prototype.protocols = $util.emptyArray; + + /** + * Peer metadata. + * @member {Array.} metadata + * @memberof Peer + * @instance + */ + Peer.prototype.metadata = $util.emptyArray; + + /** + * Peer pubKey. + * @member {Uint8Array|null|undefined} pubKey + * @memberof Peer + * @instance + */ + Peer.prototype.pubKey = null; + + /** + * Peer peerRecordEnvelope. + * @member {Uint8Array|null|undefined} peerRecordEnvelope + * @memberof Peer + * @instance + */ + Peer.prototype.peerRecordEnvelope = null; + + // OneOf field names bound to virtual getters and setters + var $oneOfFields; + + /** + * Peer _pubKey. + * @member {"pubKey"|undefined} _pubKey + * @memberof Peer + * @instance + */ + Object.defineProperty(Peer.prototype, "_pubKey", { + get: $util.oneOfGetter($oneOfFields = ["pubKey"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Peer _peerRecordEnvelope. + * @member {"peerRecordEnvelope"|undefined} _peerRecordEnvelope + * @memberof Peer + * @instance + */ + Object.defineProperty(Peer.prototype, "_peerRecordEnvelope", { + get: $util.oneOfGetter($oneOfFields = ["peerRecordEnvelope"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Encodes the specified Peer message. Does not implicitly {@link Peer.verify|verify} messages. + * @function encode + * @memberof Peer + * @static + * @param {IPeer} m Peer message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Peer.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.addresses != null && m.addresses.length) { + for (var i = 0; i < m.addresses.length; ++i) + $root.Address.encode(m.addresses[i], w.uint32(10).fork()).ldelim(); + } + if (m.protocols != null && m.protocols.length) { + for (var i = 0; i < m.protocols.length; ++i) + w.uint32(18).string(m.protocols[i]); + } + if (m.metadata != null && m.metadata.length) { + for (var i = 0; i < m.metadata.length; ++i) + $root.Metadata.encode(m.metadata[i], w.uint32(26).fork()).ldelim(); + } + if (m.pubKey != null && Object.hasOwnProperty.call(m, "pubKey")) + w.uint32(34).bytes(m.pubKey); + if (m.peerRecordEnvelope != null && Object.hasOwnProperty.call(m, "peerRecordEnvelope")) + w.uint32(42).bytes(m.peerRecordEnvelope); + return w; + }; + + /** + * Decodes a Peer message from the specified reader or buffer. + * @function decode + * @memberof Peer + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Peer} Peer + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Peer.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.Peer(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + if (!(m.addresses && m.addresses.length)) + m.addresses = []; + m.addresses.push($root.Address.decode(r, r.uint32())); + break; + case 2: + if (!(m.protocols && m.protocols.length)) + m.protocols = []; + m.protocols.push(r.string()); + break; + case 3: + if (!(m.metadata && m.metadata.length)) + m.metadata = []; + m.metadata.push($root.Metadata.decode(r, r.uint32())); + break; + case 4: + m.pubKey = r.bytes(); + break; + case 5: + m.peerRecordEnvelope = r.bytes(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a Peer message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Peer + * @static + * @param {Object.} d Plain object + * @returns {Peer} Peer + */ + Peer.fromObject = function fromObject(d) { + if (d instanceof $root.Peer) + return d; + var m = new $root.Peer(); + if (d.addresses) { + if (!Array.isArray(d.addresses)) + throw TypeError(".Peer.addresses: array expected"); + m.addresses = []; + for (var i = 0; i < d.addresses.length; ++i) { + if (typeof d.addresses[i] !== "object") + throw TypeError(".Peer.addresses: object expected"); + m.addresses[i] = $root.Address.fromObject(d.addresses[i]); + } + } + if (d.protocols) { + if (!Array.isArray(d.protocols)) + throw TypeError(".Peer.protocols: array expected"); + m.protocols = []; + for (var i = 0; i < d.protocols.length; ++i) { + m.protocols[i] = String(d.protocols[i]); + } + } + if (d.metadata) { + if (!Array.isArray(d.metadata)) + throw TypeError(".Peer.metadata: array expected"); + m.metadata = []; + for (var i = 0; i < d.metadata.length; ++i) { + if (typeof d.metadata[i] !== "object") + throw TypeError(".Peer.metadata: object expected"); + m.metadata[i] = $root.Metadata.fromObject(d.metadata[i]); + } + } + if (d.pubKey != null) { + if (typeof d.pubKey === "string") + $util.base64.decode(d.pubKey, m.pubKey = $util.newBuffer($util.base64.length(d.pubKey)), 0); + else if (d.pubKey.length) + m.pubKey = d.pubKey; + } + if (d.peerRecordEnvelope != null) { + if (typeof d.peerRecordEnvelope === "string") + $util.base64.decode(d.peerRecordEnvelope, m.peerRecordEnvelope = $util.newBuffer($util.base64.length(d.peerRecordEnvelope)), 0); + else if (d.peerRecordEnvelope.length) + m.peerRecordEnvelope = d.peerRecordEnvelope; + } + return m; + }; + + /** + * Creates a plain object from a Peer message. Also converts values to other types if specified. + * @function toObject + * @memberof Peer + * @static + * @param {Peer} m Peer + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Peer.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.arrays || o.defaults) { + d.addresses = []; + d.protocols = []; + d.metadata = []; + } + if (m.addresses && m.addresses.length) { + d.addresses = []; + for (var j = 0; j < m.addresses.length; ++j) { + d.addresses[j] = $root.Address.toObject(m.addresses[j], o); + } + } + if (m.protocols && m.protocols.length) { + d.protocols = []; + for (var j = 0; j < m.protocols.length; ++j) { + d.protocols[j] = m.protocols[j]; + } + } + if (m.metadata && m.metadata.length) { + d.metadata = []; + for (var j = 0; j < m.metadata.length; ++j) { + d.metadata[j] = $root.Metadata.toObject(m.metadata[j], o); + } + } + if (m.pubKey != null && m.hasOwnProperty("pubKey")) { + d.pubKey = o.bytes === String ? $util.base64.encode(m.pubKey, 0, m.pubKey.length) : o.bytes === Array ? Array.prototype.slice.call(m.pubKey) : m.pubKey; + if (o.oneofs) + d._pubKey = "pubKey"; + } + if (m.peerRecordEnvelope != null && m.hasOwnProperty("peerRecordEnvelope")) { + d.peerRecordEnvelope = o.bytes === String ? $util.base64.encode(m.peerRecordEnvelope, 0, m.peerRecordEnvelope.length) : o.bytes === Array ? Array.prototype.slice.call(m.peerRecordEnvelope) : m.peerRecordEnvelope; + if (o.oneofs) + d._peerRecordEnvelope = "peerRecordEnvelope"; + } + return d; + }; + + /** + * Converts this Peer to JSON. + * @function toJSON + * @memberof Peer + * @instance + * @returns {Object.} JSON object + */ + Peer.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Peer; +})(); + +$root.Address = (function() { + + /** + * Properties of an Address. + * @exports IAddress + * @interface IAddress + * @property {Uint8Array|null} [multiaddr] Address multiaddr + * @property {boolean|null} [isCertified] Address isCertified + */ + + /** + * Constructs a new Address. + * @exports Address + * @classdesc Represents an Address. + * @implements IAddress + * @constructor + * @param {IAddress=} [p] Properties to set + */ + function Address(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]]; + } + + /** + * Address multiaddr. + * @member {Uint8Array} multiaddr + * @memberof Address + * @instance + */ + Address.prototype.multiaddr = $util.newBuffer([]); + + /** + * Address isCertified. + * @member {boolean|null|undefined} isCertified + * @memberof Address + * @instance + */ + Address.prototype.isCertified = null; + + // OneOf field names bound to virtual getters and setters + var $oneOfFields; + + /** + * Address _isCertified. + * @member {"isCertified"|undefined} _isCertified + * @memberof Address + * @instance + */ + Object.defineProperty(Address.prototype, "_isCertified", { + get: $util.oneOfGetter($oneOfFields = ["isCertified"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Encodes the specified Address message. Does not implicitly {@link Address.verify|verify} messages. + * @function encode + * @memberof Address + * @static + * @param {IAddress} m Address message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Address.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.multiaddr != null && Object.hasOwnProperty.call(m, "multiaddr")) + w.uint32(10).bytes(m.multiaddr); + if (m.isCertified != null && Object.hasOwnProperty.call(m, "isCertified")) + w.uint32(16).bool(m.isCertified); + return w; + }; + + /** + * Decodes an Address message from the specified reader or buffer. + * @function decode + * @memberof Address + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Address} Address + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Address.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.Address(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.multiaddr = r.bytes(); + break; + case 2: + m.isCertified = r.bool(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates an Address message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Address + * @static + * @param {Object.} d Plain object + * @returns {Address} Address + */ + Address.fromObject = function fromObject(d) { + if (d instanceof $root.Address) + return d; + var m = new $root.Address(); + if (d.multiaddr != null) { + if (typeof d.multiaddr === "string") + $util.base64.decode(d.multiaddr, m.multiaddr = $util.newBuffer($util.base64.length(d.multiaddr)), 0); + else if (d.multiaddr.length) + m.multiaddr = d.multiaddr; + } + if (d.isCertified != null) { + m.isCertified = Boolean(d.isCertified); + } + return m; + }; + + /** + * Creates a plain object from an Address message. Also converts values to other types if specified. + * @function toObject + * @memberof Address + * @static + * @param {Address} m Address + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Address.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + if (o.bytes === String) + d.multiaddr = ""; + else { + d.multiaddr = []; + if (o.bytes !== Array) + d.multiaddr = $util.newBuffer(d.multiaddr); + } + } + if (m.multiaddr != null && m.hasOwnProperty("multiaddr")) { + d.multiaddr = o.bytes === String ? $util.base64.encode(m.multiaddr, 0, m.multiaddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.multiaddr) : m.multiaddr; + } + if (m.isCertified != null && m.hasOwnProperty("isCertified")) { + d.isCertified = m.isCertified; + if (o.oneofs) + d._isCertified = "isCertified"; + } + return d; + }; + + /** + * Converts this Address to JSON. + * @function toJSON + * @memberof Address + * @instance + * @returns {Object.} JSON object + */ + Address.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Address; +})(); + +$root.Metadata = (function() { + + /** + * Properties of a Metadata. + * @exports IMetadata + * @interface IMetadata + * @property {string|null} [key] Metadata key + * @property {Uint8Array|null} [value] Metadata value + */ + + /** + * Constructs a new Metadata. + * @exports Metadata + * @classdesc Represents a Metadata. + * @implements IMetadata + * @constructor + * @param {IMetadata=} [p] Properties to set + */ + function Metadata(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]]; + } + + /** + * Metadata key. + * @member {string} key + * @memberof Metadata + * @instance + */ + Metadata.prototype.key = ""; + + /** + * Metadata value. + * @member {Uint8Array} value + * @memberof Metadata + * @instance + */ + Metadata.prototype.value = $util.newBuffer([]); + + /** + * Encodes the specified Metadata message. Does not implicitly {@link Metadata.verify|verify} messages. + * @function encode + * @memberof Metadata + * @static + * @param {IMetadata} m Metadata message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Metadata.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.key != null && Object.hasOwnProperty.call(m, "key")) + w.uint32(10).string(m.key); + if (m.value != null && Object.hasOwnProperty.call(m, "value")) + w.uint32(18).bytes(m.value); + return w; + }; + + /** + * Decodes a Metadata message from the specified reader or buffer. + * @function decode + * @memberof Metadata + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Metadata} Metadata + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Metadata.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.Metadata(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.key = r.string(); + break; + case 2: + m.value = r.bytes(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a Metadata message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Metadata + * @static + * @param {Object.} d Plain object + * @returns {Metadata} Metadata + */ + Metadata.fromObject = function fromObject(d) { + if (d instanceof $root.Metadata) + return d; + var m = new $root.Metadata(); + if (d.key != null) { + m.key = String(d.key); + } + if (d.value != null) { + if (typeof d.value === "string") + $util.base64.decode(d.value, m.value = $util.newBuffer($util.base64.length(d.value)), 0); + else if (d.value.length) + m.value = d.value; + } + return m; + }; + + /** + * Creates a plain object from a Metadata message. Also converts values to other types if specified. + * @function toObject + * @memberof Metadata + * @static + * @param {Metadata} m Metadata + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Metadata.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + d.key = ""; + if (o.bytes === String) + d.value = ""; + else { + d.value = []; + if (o.bytes !== Array) + d.value = $util.newBuffer(d.value); + } + } + if (m.key != null && m.hasOwnProperty("key")) { + d.key = m.key; + } + if (m.value != null && m.hasOwnProperty("value")) { + d.value = o.bytes === String ? $util.base64.encode(m.value, 0, m.value.length) : o.bytes === Array ? Array.prototype.slice.call(m.value) : m.value; + } + return d; + }; + + /** + * Converts this Metadata to JSON. + * @function toJSON + * @memberof Metadata + * @instance + * @returns {Object.} JSON object + */ + Metadata.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Metadata; +})(); + +module.exports = $root; diff --git a/src/peer-store/pb/peer.proto b/src/peer-store/pb/peer.proto new file mode 100644 index 00000000..1c9cc166 --- /dev/null +++ b/src/peer-store/pb/peer.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +message Peer { + // Multiaddrs we know about + repeated Address addresses = 1; + + // The protocols the peer supports + repeated string protocols = 2; + + // Any peer metadata + repeated Metadata metadata = 3; + + // The public key of the peer + optional bytes pub_key = 4; + + // The most recently received signed PeerRecord + optional bytes peer_record_envelope = 5; +} + +// Address represents a single multiaddr +message Address { + bytes multiaddr = 1; + + // Flag to indicate if the address comes from a certified source + optional bool isCertified = 2; +} + +message Metadata { + string key = 1; + bytes value = 2; +} diff --git a/src/peer-store/persistent/consts.js b/src/peer-store/persistent/consts.js deleted file mode 100644 index 591c267e..00000000 --- a/src/peer-store/persistent/consts.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict' - -module.exports.NAMESPACE_COMMON = '/peers/' - -// /peers/protos/ -module.exports.NAMESPACE_ADDRESS = '/peers/addrs/' - -// /peers/keys/ -module.exports.NAMESPACE_KEYS = '/peers/keys/' - -// /peers/metadata// -module.exports.NAMESPACE_METADATA = '/peers/metadata/' - -// /peers/addrs/ -module.exports.NAMESPACE_PROTOCOL = '/peers/protos/' diff --git a/src/peer-store/persistent/index.js b/src/peer-store/persistent/index.js deleted file mode 100644 index 5655d207..00000000 --- a/src/peer-store/persistent/index.js +++ /dev/null @@ -1,408 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = Object.assign(debug('libp2p:persistent-peer-store'), { - error: debug('libp2p:persistent-peer-store:err') -}) -const { Key } = require('interface-datastore/key') -const { Multiaddr } = require('multiaddr') -const PeerId = require('peer-id') -const { base32 } = require('multiformats/bases/base32') - -const PeerStore = require('..') - -const { - NAMESPACE_ADDRESS, - NAMESPACE_COMMON, - NAMESPACE_KEYS, - NAMESPACE_METADATA, - NAMESPACE_PROTOCOL -} = require('./consts') - -const { Addresses } = require('./pb/address-book') -const { Protocols } = require('./pb/proto-book') - -/** - * @typedef {import('interface-datastore').Batch} Batch - * @typedef {import('../address-book.js').Address} Address - */ - -/** - * @typedef {Object} PersistentPeerStoreProperties - * @property {PeerId} peerId - * @property {import('interface-datastore').Datastore} datastore - * - * @typedef {Object} PersistentPeerStoreOptions - * @property {number} [threshold = 5] - Number of dirty peers allowed before commit data. - */ - -/** - * Responsible for managing the persistence of data in the PeerStore. - */ -class PersistentPeerStore extends PeerStore { - /** - * @class - * @param {PersistentPeerStoreProperties & PersistentPeerStoreOptions} properties - */ - constructor ({ peerId, datastore, threshold = 5 }) { - super({ peerId }) - - /** - * Backend datastore used to persist data. - */ - this._datastore = datastore - - /** - * Peers modified after the latest data persisted. - */ - this._dirtyPeers = new Set() - - /** - * Peers metadata changed mapping peer identifers to metadata changed. - * - * @type {Map>} - */ - this._dirtyMetadata = new Map() - - this.threshold = threshold - this._addDirtyPeer = this._addDirtyPeer.bind(this) - } - - /** - * Start Persistent PeerStore. - * - * @returns {Promise} - */ - async start () { - log('PeerStore is starting') - - // Handlers for dirty peers - this.on('change:protocols', this._addDirtyPeer) - this.on('change:multiaddrs', this._addDirtyPeer) - this.on('change:pubkey', this._addDirtyPeerKey) - this.on('change:metadata', this._addDirtyPeerMetadata) - - // Load data - for await (const entry of this._datastore.query({ prefix: NAMESPACE_COMMON })) { - await this._processDatastoreEntry(entry) - } - - log('PeerStore started') - } - - /** - * Stop Persistent PeerStore. - * - * @returns {Promise} - */ - async stop () { - log('PeerStore is stopping') - this.removeAllListeners() - await this._commitData() - log('PeerStore stopped') - } - - /** - * Add modified peer to the dirty set - * - * @private - * @param {Object} params - * @param {PeerId} params.peerId - */ - _addDirtyPeer ({ peerId }) { - const peerIdstr = peerId.toB58String() - - log('add dirty peer', peerIdstr) - this._dirtyPeers.add(peerIdstr) - - if (this._dirtyPeers.size >= this.threshold) { - // Commit current data - this._commitData().catch(err => { - log.error('error committing data', err) - }) - } - } - - /** - * Add modified peer key to the dirty set - * - * @private - * @param {Object} params - * @param {PeerId} params.peerId - */ - _addDirtyPeerKey ({ peerId }) { - // Not add if inline key available - if (peerId.hasInlinePublicKey()) { - return - } - - const peerIdstr = peerId.toB58String() - - log('add dirty peer key', peerIdstr) - this._dirtyPeers.add(peerIdstr) - - if (this._dirtyPeers.size >= this.threshold) { - // Commit current data - this._commitData().catch(err => { - log.error('error committing data', err) - }) - } - } - - /** - * Add modified metadata peer to the set. - * - * @private - * @param {Object} params - * @param {PeerId} params.peerId - * @param {string} params.metadata - */ - _addDirtyPeerMetadata ({ peerId, metadata }) { - const peerIdstr = peerId.toB58String() - - log('add dirty metadata peer', peerIdstr) - this._dirtyPeers.add(peerIdstr) - - // Add dirty metadata key - const mData = this._dirtyMetadata.get(peerIdstr) || new Set() - mData.add(metadata) - this._dirtyMetadata.set(peerIdstr, mData) - - if (this._dirtyPeers.size >= this.threshold) { - // Commit current data - this._commitData().catch(err => { - log.error('error committing data', err) - }) - } - } - - /** - * Add all the peers current data to a datastore batch and commit it. - * - * @private - * @returns {Promise} - */ - async _commitData () { - const commitPeers = Array.from(this._dirtyPeers) - - if (!commitPeers.length) { - return - } - - // Clear Dirty Peers set - this._dirtyPeers.clear() - - log('create batch commit') - const batch = this._datastore.batch() - for (const peerIdStr of commitPeers) { - // PeerId - const peerId = this.keyBook.data.get(peerIdStr) || PeerId.createFromB58String(peerIdStr) - - // Address Book - this._batchAddressBook(peerId, batch) - - // Key Book - !peerId.hasInlinePublicKey() && this._batchKeyBook(peerId, batch) - - // Metadata Book - this._batchMetadataBook(peerId, batch) - - // Proto Book - this._batchProtoBook(peerId, batch) - } - - await batch.commit() - log('batch committed') - } - - /** - * Add address book data of the peer to the batch. - * - * @private - * @param {PeerId} peerId - * @param {Batch} batch - */ - _batchAddressBook (peerId, batch) { - const b32key = peerId.toString() - const key = new Key(`${NAMESPACE_ADDRESS}${b32key}`) - - const entry = this.addressBook.data.get(peerId.toB58String()) - - try { - // Deleted from the book - if (!entry) { - batch.delete(key) - return - } - - const encodedData = Addresses.encode({ - addrs: entry.addresses.map((address) => ({ - multiaddr: address.multiaddr.bytes, - isCertified: address.isCertified - })), - certifiedRecord: entry.record - ? { - seq: entry.record.seqNumber, - raw: entry.record.raw - } - : undefined - }).finish() - - batch.put(key, encodedData) - } catch (/** @type {any} */ err) { - log.error(err) - } - } - - /** - * Add Key book data of the peer to the batch. - * - * @private - * @param {PeerId} peerId - * @param {Batch} batch - */ - _batchKeyBook (peerId, batch) { - const b32key = peerId.toString() - const key = new Key(`${NAMESPACE_KEYS}${b32key}`) - - try { - // Deleted from the book - if (!peerId.pubKey) { - batch.delete(key) - return - } - - const encodedData = peerId.marshalPubKey() - - batch.put(key, encodedData) - } catch (/** @type {any} */ err) { - log.error(err) - } - } - - /** - * Add metadata book data of the peer to the batch. - * - * @private - * @param {PeerId} peerId - * @param {Batch} batch - */ - _batchMetadataBook (peerId, batch) { - const b32key = peerId.toString() - const dirtyMetada = this._dirtyMetadata.get(peerId.toB58String()) || [] - - try { - dirtyMetada.forEach((/** @type {string} */ dirtyKey) => { - const key = new Key(`${NAMESPACE_METADATA}${b32key}/${dirtyKey}`) - const dirtyValue = this.metadataBook.getValue(peerId, dirtyKey) - - if (dirtyValue) { - batch.put(key, dirtyValue) - } else { - batch.delete(key) - } - }) - } catch (/** @type {any} */ err) { - log.error(err) - } - } - - /** - * Add proto book data of the peer to the batch. - * - * @private - * @param {PeerId} peerId - * @param {Batch} batch - */ - _batchProtoBook (peerId, batch) { - const b32key = peerId.toString() - const key = new Key(`${NAMESPACE_PROTOCOL}${b32key}`) - - const protocols = this.protoBook.get(peerId) - - try { - // Deleted from the book - if (!protocols) { - batch.delete(key) - return - } - - const encodedData = Protocols.encode({ protocols }).finish() - - batch.put(key, encodedData) - } catch (/** @type {any} */ err) { - log.error(err) - } - } - - /** - * Process datastore entry and add its data to the correct book. - * - * @private - * @param {Object} params - * @param {Key} params.key - datastore key - * @param {Uint8Array} params.value - datastore value stored - * @returns {Promise} - */ - async _processDatastoreEntry ({ key, value }) { - try { - const keyParts = key.toString().split('/') - const peerId = PeerId.createFromBytes(base32.decode(keyParts[3])) - - let decoded - switch (keyParts[2]) { - case 'addrs': - decoded = Addresses.decode(value) - - // @ts-ignore protected function - this.addressBook._setData( - peerId, - { - addresses: decoded.addrs.map((address) => ({ - multiaddr: new Multiaddr(address.multiaddr), - isCertified: Boolean(address.isCertified) - })), - record: decoded.certifiedRecord - ? { - raw: decoded.certifiedRecord.raw, - seqNumber: decoded.certifiedRecord.seq - } - : undefined - }, - { emit: false }) - break - case 'keys': - decoded = await PeerId.createFromPubKey(value) - - // @ts-ignore protected function - this.keyBook._setData( - decoded, - decoded, - { emit: false }) - break - case 'metadata': - this.metadataBook._setValue( - peerId, - keyParts[4], - value, - { emit: false }) - break - case 'protos': - decoded = Protocols.decode(value) - - // @ts-ignore protected function - this.protoBook._setData( - peerId, - new Set(decoded.protocols), - { emit: false }) - break - default: - log('invalid data persisted for: ', key.toString()) - } - } catch (/** @type {any} */ err) { - log.error(err) - } - } -} - -module.exports = PersistentPeerStore diff --git a/src/peer-store/persistent/pb/address-book.d.ts b/src/peer-store/persistent/pb/address-book.d.ts deleted file mode 100644 index 0080a639..00000000 --- a/src/peer-store/persistent/pb/address-book.d.ts +++ /dev/null @@ -1,198 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of an Addresses. */ -export interface IAddresses { - - /** Addresses addrs */ - addrs?: (Addresses.IAddress[]|null); - - /** Addresses certifiedRecord */ - certifiedRecord?: (Addresses.ICertifiedRecord|null); -} - -/** Represents an Addresses. */ -export class Addresses implements IAddresses { - - /** - * Constructs a new Addresses. - * @param [p] Properties to set - */ - constructor(p?: IAddresses); - - /** Addresses addrs. */ - public addrs: Addresses.IAddress[]; - - /** Addresses certifiedRecord. */ - public certifiedRecord?: (Addresses.ICertifiedRecord|null); - - /** - * Encodes the specified Addresses message. Does not implicitly {@link Addresses.verify|verify} messages. - * @param m Addresses message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IAddresses, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes an Addresses message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Addresses - * @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): Addresses; - - /** - * Creates an Addresses message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Addresses - */ - public static fromObject(d: { [k: string]: any }): Addresses; - - /** - * Creates a plain object from an Addresses message. Also converts values to other types if specified. - * @param m Addresses - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Addresses, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Addresses to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} - -export namespace Addresses { - - /** Properties of an Address. */ - interface IAddress { - - /** Address multiaddr */ - multiaddr?: (Uint8Array|null); - - /** Address isCertified */ - isCertified?: (boolean|null); - } - - /** Represents an Address. */ - class Address implements IAddress { - - /** - * Constructs a new Address. - * @param [p] Properties to set - */ - constructor(p?: Addresses.IAddress); - - /** Address multiaddr. */ - public multiaddr: Uint8Array; - - /** Address isCertified. */ - public isCertified: boolean; - - /** - * Encodes the specified Address message. Does not implicitly {@link Addresses.Address.verify|verify} messages. - * @param m Address message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: Addresses.IAddress, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes an Address message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Address - * @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): Addresses.Address; - - /** - * Creates an Address message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Address - */ - public static fromObject(d: { [k: string]: any }): Addresses.Address; - - /** - * Creates a plain object from an Address message. Also converts values to other types if specified. - * @param m Address - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Addresses.Address, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Address to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a CertifiedRecord. */ - interface ICertifiedRecord { - - /** CertifiedRecord seq */ - seq?: (number|null); - - /** CertifiedRecord raw */ - raw?: (Uint8Array|null); - } - - /** Represents a CertifiedRecord. */ - class CertifiedRecord implements ICertifiedRecord { - - /** - * Constructs a new CertifiedRecord. - * @param [p] Properties to set - */ - constructor(p?: Addresses.ICertifiedRecord); - - /** CertifiedRecord seq. */ - public seq: number; - - /** CertifiedRecord raw. */ - public raw: Uint8Array; - - /** - * Encodes the specified CertifiedRecord message. Does not implicitly {@link Addresses.CertifiedRecord.verify|verify} messages. - * @param m CertifiedRecord message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: Addresses.ICertifiedRecord, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a CertifiedRecord message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns CertifiedRecord - * @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): Addresses.CertifiedRecord; - - /** - * Creates a CertifiedRecord message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns CertifiedRecord - */ - public static fromObject(d: { [k: string]: any }): Addresses.CertifiedRecord; - - /** - * Creates a plain object from a CertifiedRecord message. Also converts values to other types if specified. - * @param m CertifiedRecord - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Addresses.CertifiedRecord, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this CertifiedRecord to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } -} diff --git a/src/peer-store/persistent/pb/address-book.js b/src/peer-store/persistent/pb/address-book.js deleted file mode 100644 index f45bc942..00000000 --- a/src/peer-store/persistent/pb/address-book.js +++ /dev/null @@ -1,522 +0,0 @@ -/*eslint-disable*/ -"use strict"; - -var $protobuf = require("protobufjs/minimal"); - -// Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - -// Exported root namespace -var $root = $protobuf.roots["libp2p-address-book"] || ($protobuf.roots["libp2p-address-book"] = {}); - -$root.Addresses = (function() { - - /** - * Properties of an Addresses. - * @exports IAddresses - * @interface IAddresses - * @property {Array.|null} [addrs] Addresses addrs - * @property {Addresses.ICertifiedRecord|null} [certifiedRecord] Addresses certifiedRecord - */ - - /** - * Constructs a new Addresses. - * @exports Addresses - * @classdesc Represents an Addresses. - * @implements IAddresses - * @constructor - * @param {IAddresses=} [p] Properties to set - */ - function Addresses(p) { - this.addrs = []; - 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]]; - } - - /** - * Addresses addrs. - * @member {Array.} addrs - * @memberof Addresses - * @instance - */ - Addresses.prototype.addrs = $util.emptyArray; - - /** - * Addresses certifiedRecord. - * @member {Addresses.ICertifiedRecord|null|undefined} certifiedRecord - * @memberof Addresses - * @instance - */ - Addresses.prototype.certifiedRecord = null; - - /** - * Encodes the specified Addresses message. Does not implicitly {@link Addresses.verify|verify} messages. - * @function encode - * @memberof Addresses - * @static - * @param {IAddresses} m Addresses message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Addresses.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.addrs != null && m.addrs.length) { - for (var i = 0; i < m.addrs.length; ++i) - $root.Addresses.Address.encode(m.addrs[i], w.uint32(10).fork()).ldelim(); - } - if (m.certifiedRecord != null && Object.hasOwnProperty.call(m, "certifiedRecord")) - $root.Addresses.CertifiedRecord.encode(m.certifiedRecord, w.uint32(18).fork()).ldelim(); - return w; - }; - - /** - * Decodes an Addresses message from the specified reader or buffer. - * @function decode - * @memberof Addresses - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Addresses} Addresses - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Addresses.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.Addresses(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - if (!(m.addrs && m.addrs.length)) - m.addrs = []; - m.addrs.push($root.Addresses.Address.decode(r, r.uint32())); - break; - case 2: - m.certifiedRecord = $root.Addresses.CertifiedRecord.decode(r, r.uint32()); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates an Addresses message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Addresses - * @static - * @param {Object.} d Plain object - * @returns {Addresses} Addresses - */ - Addresses.fromObject = function fromObject(d) { - if (d instanceof $root.Addresses) - return d; - var m = new $root.Addresses(); - if (d.addrs) { - if (!Array.isArray(d.addrs)) - throw TypeError(".Addresses.addrs: array expected"); - m.addrs = []; - for (var i = 0; i < d.addrs.length; ++i) { - if (typeof d.addrs[i] !== "object") - throw TypeError(".Addresses.addrs: object expected"); - m.addrs[i] = $root.Addresses.Address.fromObject(d.addrs[i]); - } - } - if (d.certifiedRecord != null) { - if (typeof d.certifiedRecord !== "object") - throw TypeError(".Addresses.certifiedRecord: object expected"); - m.certifiedRecord = $root.Addresses.CertifiedRecord.fromObject(d.certifiedRecord); - } - return m; - }; - - /** - * Creates a plain object from an Addresses message. Also converts values to other types if specified. - * @function toObject - * @memberof Addresses - * @static - * @param {Addresses} m Addresses - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Addresses.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.addrs = []; - } - if (o.defaults) { - d.certifiedRecord = null; - } - if (m.addrs && m.addrs.length) { - d.addrs = []; - for (var j = 0; j < m.addrs.length; ++j) { - d.addrs[j] = $root.Addresses.Address.toObject(m.addrs[j], o); - } - } - if (m.certifiedRecord != null && m.hasOwnProperty("certifiedRecord")) { - d.certifiedRecord = $root.Addresses.CertifiedRecord.toObject(m.certifiedRecord, o); - } - return d; - }; - - /** - * Converts this Addresses to JSON. - * @function toJSON - * @memberof Addresses - * @instance - * @returns {Object.} JSON object - */ - Addresses.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - Addresses.Address = (function() { - - /** - * Properties of an Address. - * @memberof Addresses - * @interface IAddress - * @property {Uint8Array|null} [multiaddr] Address multiaddr - * @property {boolean|null} [isCertified] Address isCertified - */ - - /** - * Constructs a new Address. - * @memberof Addresses - * @classdesc Represents an Address. - * @implements IAddress - * @constructor - * @param {Addresses.IAddress=} [p] Properties to set - */ - function Address(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]]; - } - - /** - * Address multiaddr. - * @member {Uint8Array} multiaddr - * @memberof Addresses.Address - * @instance - */ - Address.prototype.multiaddr = $util.newBuffer([]); - - /** - * Address isCertified. - * @member {boolean} isCertified - * @memberof Addresses.Address - * @instance - */ - Address.prototype.isCertified = false; - - /** - * Encodes the specified Address message. Does not implicitly {@link Addresses.Address.verify|verify} messages. - * @function encode - * @memberof Addresses.Address - * @static - * @param {Addresses.IAddress} m Address message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Address.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.multiaddr != null && Object.hasOwnProperty.call(m, "multiaddr")) - w.uint32(10).bytes(m.multiaddr); - if (m.isCertified != null && Object.hasOwnProperty.call(m, "isCertified")) - w.uint32(16).bool(m.isCertified); - return w; - }; - - /** - * Decodes an Address message from the specified reader or buffer. - * @function decode - * @memberof Addresses.Address - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Addresses.Address} Address - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Address.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.Addresses.Address(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.multiaddr = r.bytes(); - break; - case 2: - m.isCertified = r.bool(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates an Address message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Addresses.Address - * @static - * @param {Object.} d Plain object - * @returns {Addresses.Address} Address - */ - Address.fromObject = function fromObject(d) { - if (d instanceof $root.Addresses.Address) - return d; - var m = new $root.Addresses.Address(); - if (d.multiaddr != null) { - if (typeof d.multiaddr === "string") - $util.base64.decode(d.multiaddr, m.multiaddr = $util.newBuffer($util.base64.length(d.multiaddr)), 0); - else if (d.multiaddr.length) - m.multiaddr = d.multiaddr; - } - if (d.isCertified != null) { - m.isCertified = Boolean(d.isCertified); - } - return m; - }; - - /** - * Creates a plain object from an Address message. Also converts values to other types if specified. - * @function toObject - * @memberof Addresses.Address - * @static - * @param {Addresses.Address} m Address - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Address.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - if (o.bytes === String) - d.multiaddr = ""; - else { - d.multiaddr = []; - if (o.bytes !== Array) - d.multiaddr = $util.newBuffer(d.multiaddr); - } - d.isCertified = false; - } - if (m.multiaddr != null && m.hasOwnProperty("multiaddr")) { - d.multiaddr = o.bytes === String ? $util.base64.encode(m.multiaddr, 0, m.multiaddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.multiaddr) : m.multiaddr; - } - if (m.isCertified != null && m.hasOwnProperty("isCertified")) { - d.isCertified = m.isCertified; - } - return d; - }; - - /** - * Converts this Address to JSON. - * @function toJSON - * @memberof Addresses.Address - * @instance - * @returns {Object.} JSON object - */ - Address.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Address; - })(); - - Addresses.CertifiedRecord = (function() { - - /** - * Properties of a CertifiedRecord. - * @memberof Addresses - * @interface ICertifiedRecord - * @property {number|null} [seq] CertifiedRecord seq - * @property {Uint8Array|null} [raw] CertifiedRecord raw - */ - - /** - * Constructs a new CertifiedRecord. - * @memberof Addresses - * @classdesc Represents a CertifiedRecord. - * @implements ICertifiedRecord - * @constructor - * @param {Addresses.ICertifiedRecord=} [p] Properties to set - */ - function CertifiedRecord(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]]; - } - - /** - * CertifiedRecord seq. - * @member {number} seq - * @memberof Addresses.CertifiedRecord - * @instance - */ - CertifiedRecord.prototype.seq = $util.Long ? $util.Long.fromBits(0,0,true) : 0; - - /** - * CertifiedRecord raw. - * @member {Uint8Array} raw - * @memberof Addresses.CertifiedRecord - * @instance - */ - CertifiedRecord.prototype.raw = $util.newBuffer([]); - - /** - * Encodes the specified CertifiedRecord message. Does not implicitly {@link Addresses.CertifiedRecord.verify|verify} messages. - * @function encode - * @memberof Addresses.CertifiedRecord - * @static - * @param {Addresses.ICertifiedRecord} m CertifiedRecord message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - CertifiedRecord.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.seq != null && Object.hasOwnProperty.call(m, "seq")) - w.uint32(8).uint64(m.seq); - if (m.raw != null && Object.hasOwnProperty.call(m, "raw")) - w.uint32(18).bytes(m.raw); - return w; - }; - - /** - * Decodes a CertifiedRecord message from the specified reader or buffer. - * @function decode - * @memberof Addresses.CertifiedRecord - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Addresses.CertifiedRecord} CertifiedRecord - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - CertifiedRecord.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.Addresses.CertifiedRecord(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.seq = r.uint64(); - break; - case 2: - m.raw = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a CertifiedRecord message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Addresses.CertifiedRecord - * @static - * @param {Object.} d Plain object - * @returns {Addresses.CertifiedRecord} CertifiedRecord - */ - CertifiedRecord.fromObject = function fromObject(d) { - if (d instanceof $root.Addresses.CertifiedRecord) - return d; - var m = new $root.Addresses.CertifiedRecord(); - if (d.seq != null) { - if ($util.Long) - (m.seq = $util.Long.fromValue(d.seq)).unsigned = true; - else if (typeof d.seq === "string") - m.seq = parseInt(d.seq, 10); - else if (typeof d.seq === "number") - m.seq = d.seq; - else if (typeof d.seq === "object") - m.seq = new $util.LongBits(d.seq.low >>> 0, d.seq.high >>> 0).toNumber(true); - } - if (d.raw != null) { - if (typeof d.raw === "string") - $util.base64.decode(d.raw, m.raw = $util.newBuffer($util.base64.length(d.raw)), 0); - else if (d.raw.length) - m.raw = d.raw; - } - return m; - }; - - /** - * Creates a plain object from a CertifiedRecord message. Also converts values to other types if specified. - * @function toObject - * @memberof Addresses.CertifiedRecord - * @static - * @param {Addresses.CertifiedRecord} m CertifiedRecord - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - CertifiedRecord.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - if ($util.Long) { - var n = new $util.Long(0, 0, true); - d.seq = o.longs === String ? n.toString() : o.longs === Number ? n.toNumber() : n; - } else - d.seq = o.longs === String ? "0" : 0; - if (o.bytes === String) - d.raw = ""; - else { - d.raw = []; - if (o.bytes !== Array) - d.raw = $util.newBuffer(d.raw); - } - } - if (m.seq != null && m.hasOwnProperty("seq")) { - if (typeof m.seq === "number") - d.seq = o.longs === String ? String(m.seq) : m.seq; - else - d.seq = o.longs === String ? $util.Long.prototype.toString.call(m.seq) : o.longs === Number ? new $util.LongBits(m.seq.low >>> 0, m.seq.high >>> 0).toNumber(true) : m.seq; - } - if (m.raw != null && m.hasOwnProperty("raw")) { - d.raw = o.bytes === String ? $util.base64.encode(m.raw, 0, m.raw.length) : o.bytes === Array ? Array.prototype.slice.call(m.raw) : m.raw; - } - return d; - }; - - /** - * Converts this CertifiedRecord to JSON. - * @function toJSON - * @memberof Addresses.CertifiedRecord - * @instance - * @returns {Object.} JSON object - */ - CertifiedRecord.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return CertifiedRecord; - })(); - - return Addresses; -})(); - -module.exports = $root; diff --git a/src/peer-store/persistent/pb/address-book.proto b/src/peer-store/persistent/pb/address-book.proto deleted file mode 100644 index d154bf0d..00000000 --- a/src/peer-store/persistent/pb/address-book.proto +++ /dev/null @@ -1,27 +0,0 @@ -syntax = "proto3"; - -message Addresses { - // Address represents a single multiaddr. - message Address { - bytes multiaddr = 1; - - // Flag to indicate if the address comes from a certified source. - optional bool isCertified = 2; - } - - // CertifiedRecord contains a serialized signed PeerRecord used to - // populate the signedAddrs list. - message CertifiedRecord { - // The Seq counter from the signed PeerRecord envelope - uint64 seq = 1; - - // The serialized bytes of the SignedEnvelope containing the PeerRecord. - bytes raw = 2; - } - - // The known multiaddrs. - repeated Address addrs = 1; - - // The most recently received signed PeerRecord. - CertifiedRecord certified_record = 2; -} \ No newline at end of file diff --git a/src/peer-store/persistent/pb/proto-book.d.ts b/src/peer-store/persistent/pb/proto-book.d.ts deleted file mode 100644 index f3590f87..00000000 --- a/src/peer-store/persistent/pb/proto-book.d.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of a Protocols. */ -export interface IProtocols { - - /** Protocols protocols */ - protocols?: (string[]|null); -} - -/** Represents a Protocols. */ -export class Protocols implements IProtocols { - - /** - * Constructs a new Protocols. - * @param [p] Properties to set - */ - constructor(p?: IProtocols); - - /** Protocols protocols. */ - public protocols: string[]; - - /** - * Encodes the specified Protocols message. Does not implicitly {@link Protocols.verify|verify} messages. - * @param m Protocols message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IProtocols, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a Protocols message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Protocols - * @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): Protocols; - - /** - * Creates a Protocols message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Protocols - */ - public static fromObject(d: { [k: string]: any }): Protocols; - - /** - * Creates a plain object from a Protocols message. Also converts values to other types if specified. - * @param m Protocols - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: Protocols, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Protocols to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} diff --git a/src/peer-store/persistent/pb/proto-book.js b/src/peer-store/persistent/pb/proto-book.js deleted file mode 100644 index fc3633dd..00000000 --- a/src/peer-store/persistent/pb/proto-book.js +++ /dev/null @@ -1,157 +0,0 @@ -/*eslint-disable*/ -"use strict"; - -var $protobuf = require("protobufjs/minimal"); - -// Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - -// Exported root namespace -var $root = $protobuf.roots["libp2p-proto-book"] || ($protobuf.roots["libp2p-proto-book"] = {}); - -$root.Protocols = (function() { - - /** - * Properties of a Protocols. - * @exports IProtocols - * @interface IProtocols - * @property {Array.|null} [protocols] Protocols protocols - */ - - /** - * Constructs a new Protocols. - * @exports Protocols - * @classdesc Represents a Protocols. - * @implements IProtocols - * @constructor - * @param {IProtocols=} [p] Properties to set - */ - function Protocols(p) { - this.protocols = []; - 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]]; - } - - /** - * Protocols protocols. - * @member {Array.} protocols - * @memberof Protocols - * @instance - */ - Protocols.prototype.protocols = $util.emptyArray; - - /** - * Encodes the specified Protocols message. Does not implicitly {@link Protocols.verify|verify} messages. - * @function encode - * @memberof Protocols - * @static - * @param {IProtocols} m Protocols message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Protocols.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.protocols != null && m.protocols.length) { - for (var i = 0; i < m.protocols.length; ++i) - w.uint32(10).string(m.protocols[i]); - } - return w; - }; - - /** - * Decodes a Protocols message from the specified reader or buffer. - * @function decode - * @memberof Protocols - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {Protocols} Protocols - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Protocols.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.Protocols(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - if (!(m.protocols && m.protocols.length)) - m.protocols = []; - m.protocols.push(r.string()); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a Protocols message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof Protocols - * @static - * @param {Object.} d Plain object - * @returns {Protocols} Protocols - */ - Protocols.fromObject = function fromObject(d) { - if (d instanceof $root.Protocols) - return d; - var m = new $root.Protocols(); - if (d.protocols) { - if (!Array.isArray(d.protocols)) - throw TypeError(".Protocols.protocols: array expected"); - m.protocols = []; - for (var i = 0; i < d.protocols.length; ++i) { - m.protocols[i] = String(d.protocols[i]); - } - } - return m; - }; - - /** - * Creates a plain object from a Protocols message. Also converts values to other types if specified. - * @function toObject - * @memberof Protocols - * @static - * @param {Protocols} m Protocols - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Protocols.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.protocols = []; - } - if (m.protocols && m.protocols.length) { - d.protocols = []; - for (var j = 0; j < m.protocols.length; ++j) { - d.protocols[j] = m.protocols[j]; - } - } - return d; - }; - - /** - * Converts this Protocols to JSON. - * @function toJSON - * @memberof Protocols - * @instance - * @returns {Object.} JSON object - */ - Protocols.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Protocols; -})(); - -module.exports = $root; diff --git a/src/peer-store/persistent/pb/proto-book.proto b/src/peer-store/persistent/pb/proto-book.proto deleted file mode 100644 index a452f0ca..00000000 --- a/src/peer-store/persistent/pb/proto-book.proto +++ /dev/null @@ -1,5 +0,0 @@ -syntax = "proto3"; - -message Protocols { - repeated string protocols = 1; -} \ No newline at end of file diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js index 3ce6d306..686957ec 100644 --- a/src/peer-store/proto-book.js +++ b/src/peer-store/proto-book.js @@ -1,171 +1,237 @@ 'use strict' const debug = require('debug') +const errcode = require('err-code') +const { codes } = require('../errors') +const PeerId = require('peer-id') + +/** + * @typedef {import('./types').PeerStore} PeerStore + * @typedef {import('./types').ProtoBook} ProtoBook + */ + const log = Object.assign(debug('libp2p:peer-store:proto-book'), { error: debug('libp2p:peer-store:proto-book:err') }) -const errcode = require('err-code') -const PeerId = require('peer-id') -const Book = require('./book') - -const { - codes: { ERR_INVALID_PARAMETERS } -} = require('../errors') +const EVENT_NAME = 'change:protocols' /** - * @typedef {import('./')} PeerStore + * @implements {ProtoBook} */ - -/** - * @extends {Book} - * - * @fires ProtoBook#change:protocols - */ -class ProtoBook extends Book { +class PersistentProtoBook { /** - * The ProtoBook is responsible for keeping the known supported - * protocols of a peer. - * - * @class - * @param {PeerStore} peerStore + * @param {PeerStore["emit"]} emit + * @param {import('./types').Store} store */ - constructor (peerStore) { - /** - * PeerStore Event emitter, used by the ProtoBook to emit: - * "change:protocols" - emitted when the known protocols of a peer change. - */ - super({ - peerStore, - eventName: 'change:protocols', - eventProperty: 'protocols', - eventTransformer: (data) => Array.from(data) - }) - - /** - * Map known peers to their known protocols. - * - * @type {Map>} - */ - this.data = new Map() + constructor (emit, store) { + this._emit = emit + this._store = store } /** - * Set known protocols of a provided peer. - * If the peer was not known before, it will be added. - * - * @override * @param {PeerId} peerId - * @param {string[]} protocols - * @returns {ProtoBook} */ - set (peerId, protocols) { - if (!PeerId.isPeerId(peerId)) { - log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + async get (peerId) { + log('get wait for read lock') + const release = await this._store.lock.readLock() + log('get got read lock') + + try { + const peer = await this._store.load(peerId) + + return peer.protocols + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('get release read lock') + release() } - if (!protocols) { - log.error('protocols must be provided to store data') - throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS) - } - - const id = peerId.toB58String() - const recSet = this.data.get(id) - const newSet = new Set(protocols) - - /** - * @param {Set} a - * @param {Set} b - */ - const isSetEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)) - - // Already knows the peer and the recorded protocols are the same? - // If yes, no changes needed! - if (recSet && isSetEqual(recSet, newSet)) { - log(`the protocols provided to store are equal to the already stored for ${id}`) - return this - } - - this._setData(peerId, newSet) - log(`stored provided protocols for ${id}`) - - return this + return [] } /** - * Adds known protocols of a provided peer. - * If the peer was not known before, it will be added. - * * @param {PeerId} peerId * @param {string[]} protocols - * @returns {ProtoBook} */ - add (peerId, protocols) { + async set (peerId, protocols) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) } - if (!protocols) { + if (!Array.isArray(protocols)) { log.error('protocols must be provided to store data') - throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS) + throw errcode(new Error('protocols must be provided'), codes.ERR_INVALID_PARAMETERS) } - const id = peerId.toB58String() - const recSet = this.data.get(id) || new Set() - const newSet = new Set([...recSet, ...protocols]) // Set Union + log('set await write lock') + const release = await this._store.lock.writeLock() + log('set got write lock') - // Any new protocol added? - if (recSet.size === newSet.size) { - log(`the protocols provided to store are already stored for ${id}`) - return this - } + let updatedPeer - this._setData(peerId, newSet) - log(`added provided protocols for ${id}`) + try { + try { + const peer = await this._store.load(peerId) - return this - } - - /** - * Removes known protocols of a provided peer. - * If the protocols did not exist before, nothing will be done. - * - * @param {PeerId} peerId - * @param {string[]} protocols - * @returns {ProtoBook} - */ - remove (peerId, protocols) { - if (!PeerId.isPeerId(peerId)) { - log.error('peerId must be an instance of peer-id to store data') - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - if (!protocols) { - log.error('protocols must be provided to store data') - throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS) - } - - const id = peerId.toB58String() - const recSet = this.data.get(id) - - if (recSet) { - const newSet = new Set([ - ...recSet - ].filter((p) => !protocols.includes(p))) - - // Any protocol removed? - if (recSet.size === newSet.size) { - return this + if (new Set([ + ...protocols + ]).size === peer.protocols.length) { + return + } + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } } - this._setData(peerId, newSet) - log(`removed provided protocols for ${id}`) + updatedPeer = await this._store.patchOrCreate(peerId, { + protocols + }) + + log(`stored provided protocols for ${peerId.toB58String()}`) + } finally { + log('set release write lock') + release() } - return this + this._emit(EVENT_NAME, { peerId, protocols: updatedPeer.protocols }) + } + + /** + * @param {PeerId} peerId + * @param {string[]} protocols + */ + async add (peerId, protocols) { + if (!PeerId.isPeerId(peerId)) { + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) + } + + if (!Array.isArray(protocols)) { + log.error('protocols must be provided to store data') + throw errcode(new Error('protocols must be provided'), codes.ERR_INVALID_PARAMETERS) + } + + log('add await write lock') + const release = await this._store.lock.writeLock() + log('add got write lock') + + let updatedPeer + + try { + try { + const peer = await this._store.load(peerId) + + if (new Set([ + ...peer.protocols, + ...protocols + ]).size === peer.protocols.length) { + return + } + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } + + updatedPeer = await this._store.mergeOrCreate(peerId, { + protocols + }) + + log(`added provided protocols for ${peerId.toB58String()}`) + } finally { + log('add release write lock') + release() + } + + this._emit(EVENT_NAME, { peerId, protocols: updatedPeer.protocols }) + } + + /** + * @param {PeerId} peerId + * @param {string[]} protocols + */ + async remove (peerId, protocols) { + if (!PeerId.isPeerId(peerId)) { + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) + } + + if (!Array.isArray(protocols)) { + log.error('protocols must be provided to store data') + throw errcode(new Error('protocols must be provided'), codes.ERR_INVALID_PARAMETERS) + } + + log('remove await write lock') + const release = await this._store.lock.writeLock() + log('remove got write lock') + + let updatedPeer + + try { + try { + const peer = await this._store.load(peerId) + const protocolSet = new Set(peer.protocols) + + for (const protocol of protocols) { + protocolSet.delete(protocol) + } + + if (peer.protocols.length === protocolSet.size) { + return + } + + protocols = Array.from(protocolSet) + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } + + updatedPeer = await this._store.patchOrCreate(peerId, { + protocols + }) + } finally { + log('remove release write lock') + release() + } + + this._emit(EVENT_NAME, { peerId, protocols: updatedPeer.protocols }) + } + + /** + * @param {PeerId} peerId + */ + async delete (peerId) { + log('delete await write lock') + const release = await this._store.lock.writeLock() + log('delete got write lock') + let has + + try { + has = await this._store.has(peerId) + + await this._store.patchOrCreate(peerId, { + protocols: [] + }) + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } finally { + log('delete release write lock') + release() + } + + if (has) { + this._emit(EVENT_NAME, { peerId, protocols: [] }) + } } } -module.exports = ProtoBook +module.exports = PersistentProtoBook diff --git a/src/peer-store/store.js b/src/peer-store/store.js new file mode 100644 index 00000000..8b3d29f4 --- /dev/null +++ b/src/peer-store/store.js @@ -0,0 +1,250 @@ +'use strict' + +const debug = require('debug') +const PeerId = require('peer-id') +const errcode = require('err-code') +const { codes } = require('../errors') +const { Key } = require('interface-datastore/key') +const { base32 } = require('multiformats/bases/base32') +const { keys: { unmarshalPublicKey, marshalPublicKey } } = require('libp2p-crypto') +const { Multiaddr } = require('multiaddr') +const { Peer: PeerPB } = require('./pb/peer') +// @ts-expect-error no types +const mortice = require('mortice') +const { equals: uint8arrayEquals } = require('uint8arrays/equals') + +const log = Object.assign(debug('libp2p:peer-store:store'), { + error: debug('libp2p:peer-store:store:err') +}) + +/** + * @typedef {import('./types').PeerStore} PeerStore + * @typedef {import('./types').EventName} EventName + * @typedef {import('./types').Peer} Peer + */ + +const NAMESPACE_COMMON = '/peers/' + +class PersistentStore { + /** + * @param {import('interface-datastore').Datastore} datastore + */ + constructor (datastore) { + this._datastore = datastore + this.lock = mortice('peer-store', { + singleProcess: true + }) + } + + /** + * @param {PeerId} peerId + * @returns {Key} + */ + _peerIdToDatastoreKey (peerId) { + if (!PeerId.isPeerId(peerId)) { + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS) + } + + const b32key = peerId.toString() + return new Key(`${NAMESPACE_COMMON}${b32key}`) + } + + /** + * @param {PeerId} peerId + */ + async has (peerId) { + return this._datastore.has(this._peerIdToDatastoreKey(peerId)) + } + + /** + * @param {PeerId} peerId + */ + async delete (peerId) { + await this._datastore.delete(this._peerIdToDatastoreKey(peerId)) + } + + /** + * @param {PeerId} peerId + * @returns {Promise} peer + */ + async load (peerId) { + const buf = await this._datastore.get(this._peerIdToDatastoreKey(peerId)) + const peer = PeerPB.decode(buf) + const pubKey = peer.pubKey ? unmarshalPublicKey(peer.pubKey) : peerId.pubKey + const metadata = new Map() + + for (const meta of peer.metadata) { + metadata.set(meta.key, meta.value) + } + + return { + ...peer, + id: peerId, + pubKey, + addresses: peer.addresses.map(({ multiaddr, isCertified }) => ({ + multiaddr: new Multiaddr(multiaddr), + isCertified: isCertified || false + })), + metadata, + peerRecordEnvelope: peer.peerRecordEnvelope || undefined + } + } + + /** + * @param {Peer} peer + */ + async save (peer) { + if (peer.pubKey != null && peer.id.pubKey != null && !uint8arrayEquals(peer.pubKey.bytes, peer.id.pubKey.bytes)) { + log.error('peer publicKey bytes do not match peer id publicKey bytes') + throw errcode(new Error('publicKey bytes do not match peer id publicKey bytes'), codes.ERR_INVALID_PARAMETERS) + } + + const buf = PeerPB.encode({ + addresses: peer.addresses.sort((a, b) => { + return a.multiaddr.toString().localeCompare(b.multiaddr.toString()) + }).map(({ multiaddr, isCertified }) => ({ + multiaddr: multiaddr.bytes, + isCertified + })), + protocols: peer.protocols.sort(), + pubKey: peer.pubKey ? marshalPublicKey(peer.pubKey) : undefined, + metadata: [...peer.metadata.keys()].sort().map(key => ({ key, value: peer.metadata.get(key) })), + peerRecordEnvelope: peer.peerRecordEnvelope + }).finish() + + await this._datastore.put(this._peerIdToDatastoreKey(peer.id), buf) + + return this.load(peer.id) + } + + /** + * @param {PeerId} peerId + * @param {Partial} data + */ + async patch (peerId, data) { + const peer = await this.load(peerId) + + return await this._patch(peerId, data, peer) + } + + /** + * @param {PeerId} peerId + * @param {Partial} data + */ + async patchOrCreate (peerId, data) { + /** @type {Peer} */ + let peer + + try { + peer = await this.load(peerId) + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + + peer = { id: peerId, addresses: [], protocols: [], metadata: new Map() } + } + + return await this._patch(peerId, data, peer) + } + + /** + * @param {PeerId} peerId + * @param {Partial} data + * @param {Peer} peer + */ + async _patch (peerId, data, peer) { + return await this.save({ + ...peer, + ...data, + id: peerId + }) + } + + /** + * @param {PeerId} peerId + * @param {Partial} data + */ + async merge (peerId, data) { + const peer = await this.load(peerId) + + return this._merge(peerId, data, peer) + } + + /** + * @param {PeerId} peerId + * @param {Partial} data + */ + async mergeOrCreate (peerId, data) { + /** @type {Peer} */ + let peer + + try { + peer = await this.load(peerId) + } catch (/** @type {any} */ err) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + + peer = { id: peerId, addresses: [], protocols: [], metadata: new Map() } + } + + return await this._merge(peerId, data, peer) + } + + /** + * @param {PeerId} peerId + * @param {Partial} data + * @param {Peer} peer + */ + async _merge (peerId, data, peer) { + // if the peer has certified addresses, use those in + // favour of the supplied versions + /** @type {Map} */ + const addresses = new Map() + + ;(data.addresses || []).forEach(addr => { + addresses.set(addr.multiaddr.toString(), addr.isCertified) + }) + + peer.addresses.forEach(({ multiaddr, isCertified }) => { + const addrStr = multiaddr.toString() + addresses.set(addrStr, Boolean(addresses.get(addrStr) || isCertified)) + }) + + return await this.save({ + id: peerId, + addresses: Array.from(addresses.entries()).map(([addrStr, isCertified]) => { + return { + multiaddr: new Multiaddr(addrStr), + isCertified + } + }), + protocols: Array.from(new Set([ + ...(peer.protocols || []), + ...(data.protocols || []) + ])), + metadata: new Map([ + ...(peer.metadata ? peer.metadata.entries() : []), + ...(data.metadata ? data.metadata.entries() : []) + ]), + pubKey: data.pubKey || (peer != null ? peer.pubKey : undefined), + peerRecordEnvelope: data.peerRecordEnvelope || (peer != null ? peer.peerRecordEnvelope : undefined) + }) + } + + async * all () { + for await (const key of this._datastore.queryKeys({ + prefix: NAMESPACE_COMMON + })) { + // /peers/${peer-id-as-libp2p-key-cid-string-in-base-32} + const base32Str = key.toString().split('/')[2] + const buf = base32.decode(base32Str) + + yield this.load(PeerId.createFromBytes(buf)) + } + } +} + +module.exports = PersistentStore diff --git a/src/peer-store/types.ts b/src/peer-store/types.ts new file mode 100644 index 00000000..0ae16b46 --- /dev/null +++ b/src/peer-store/types.ts @@ -0,0 +1,245 @@ +import type PeerId from 'peer-id' +import type { Multiaddr } from 'multiaddr' +import type Envelope from '../record/envelope' +import type { PublicKey } from 'libp2p-interfaces/src/keys/types' + +export interface Address { + /** + * Peer multiaddr + */ + multiaddr: Multiaddr + + /** + * Obtained from a signed peer record + */ + isCertified: boolean +} + +export interface Peer { + /** + * Peer's peer-id instance + */ + id: PeerId + + /** + * Peer's addresses containing its multiaddrs and metadata + */ + addresses: Address[] + + /** + * Peer's supported protocols + */ + protocols: string[] + + /** + * Peer's metadata map + */ + metadata: Map + + /** + * May be set if the key that this Peer has is an RSA key + */ + pubKey?: PublicKey + + /** + * The last peer record envelope received + */ + peerRecordEnvelope?: Uint8Array +} + +export interface CertifiedRecord { + raw: Uint8Array + seqNumber: number +} + +export interface AddressBookEntry { + addresses: Address[] + record: CertifiedRecord +} + +export interface Book { + /** + * Get the known data of a peer + */ + get: (peerId: PeerId) => Promise + + /** + * Set the known data of a peer + */ + set: (peerId: PeerId, data: Type) => Promise + + /** + * Remove the known data of a peer + */ + delete: (peerId: PeerId) => Promise +} + +/** + * AddressBook containing a map of peerIdStr to Address. + */ +export interface AddressBook { + /** + * ConsumePeerRecord adds addresses from a signed peer record contained in a record envelope. + * This will return a boolean that indicates if the record was successfully processed and added + * into the AddressBook + */ + consumePeerRecord: (envelope: Envelope) => Promise + + /** + * Get the raw Envelope for a peer. Returns + * undefined if no Envelope is found + */ + getRawEnvelope: (peerId: PeerId) => Promise + + /** + * Get an Envelope containing a PeerRecord for the given peer. + * Returns undefined if no record exists. + */ + getPeerRecord: (peerId: PeerId) => Promise + + /** + * Add known addresses of a provided peer. + * If the peer is not known, it is set with the given addresses. + */ + add: (peerId: PeerId, multiaddrs: Multiaddr[]) => Promise + + /** + * Set the known addresses of a peer + */ + set: (peerId: PeerId, data: Multiaddr[]) => Promise + + /** + * Return the known addresses of a peer + */ + get: (peerId: PeerId) => Promise + + /** + * Get the known multiaddrs for a given peer. All returned multiaddrs + * will include the encapsulated `PeerId` of the peer. + */ + getMultiaddrsForPeer: (peerId: PeerId, addressSorter?: (ms: Address[]) => Address[]) => Promise +} + +/** + * KeyBook containing a map of peerIdStr to their PeerId with public keys. + */ +export interface KeyBook { + /** + * Get the known data of a peer + */ + get: (peerId: PeerId) => Promise + + /** + * Set the known data of a peer + */ + set: (peerId: PeerId, data: PublicKey) => Promise + + /** + * Remove the known data of a peer + */ + delete: (peerId: PeerId) => Promise +} + +/** + * MetadataBook containing a map of peerIdStr to their metadata Map. + */ +export interface MetadataBook extends Book> { + /** + * Set a specific metadata value + */ + setValue: (peerId: PeerId, key: string, value: Uint8Array) => Promise + + /** + * Get specific metadata value, if it exists + */ + getValue: (peerId: PeerId, key: string) => Promise + + /** + * Deletes the provided peer metadata key from the book + */ + deleteValue: (peerId: PeerId, key: string) => Promise +} + +/** + * ProtoBook containing a map of peerIdStr to supported protocols. + */ +export interface ProtoBook extends Book { + /** + * Adds known protocols of a provided peer. + * If the peer was not known before, it will be added. + */ + add: (peerId: PeerId, protocols: string[]) => Promise + + /** + * Removes known protocols of a provided peer. + * If the protocols did not exist before, nothing will be done. + */ + remove: (peerId: PeerId, protocols: string[]) => Promise +} + +export interface PeerProtocolsChangeEvent { + peerId: PeerId + protocols: string[] +} + +export interface PeerMultiaddrsChangeEvent { + peerId: PeerId + multiaddrs: Multiaddr[] +} + +export interface PeerPublicKeyChangeEvent { + peerId: PeerId + pubKey?: PublicKey +} + +export interface PeerMetadataChangeEvent { + peerId: PeerId + metadata: Map +} + +export type EventName = 'peer' | 'change:protocols' | 'change:multiaddrs' | 'change:pubkey' | 'change:metadata' + +export interface PeerStoreEvents { + 'peer': (event: PeerId) => void + 'change:protocols': (event: PeerProtocolsChangeEvent) => void + 'change:multiaddrs': (event: PeerMultiaddrsChangeEvent) => void + 'change:pubkey': (event: PeerPublicKeyChangeEvent) => void + 'change:metadata': (event: PeerMetadataChangeEvent) => void +} + +export interface PeerStore { + addressBook: AddressBook + keyBook: KeyBook + metadataBook: MetadataBook + protoBook: ProtoBook + + getPeers: () => AsyncIterable + delete: (peerId: PeerId) => Promise + has: (peerId: PeerId) => Promise + get: (peerId: PeerId) => Promise + on: ( + event: U, listener: PeerStoreEvents[U] + ) => this + once: ( + event: U, listener: PeerStoreEvents[U] + ) => this + emit: ( + event: U, ...args: Parameters + ) => boolean +} + +export interface Store { + has: (peerId: PeerId) => Promise + save: (peer: Peer) => Promise + load: (peerId: PeerId) => Promise + merge: (peerId: PeerId, data: Partial) => Promise + mergeOrCreate: (peerId: PeerId, data: Partial) => Promise + patch: (peerId: PeerId, data: Partial) => Promise + patchOrCreate: (peerId: PeerId, data: Partial) => Promise + all: () => AsyncIterable + + lock: { + readLock: () => Promise<() => void> + writeLock: () => Promise<() => void> + } +} diff --git a/src/ping/index.js b/src/ping/index.js index 15b26923..75e13b9a 100644 --- a/src/ping/index.js +++ b/src/ping/index.js @@ -63,6 +63,9 @@ async function ping (node, peer) { */ function mount (node) { node.handle(`/${node._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`, ({ stream }) => pipe(stream, stream)) + .catch(err => { + log.error(err) + }) } /** @@ -72,6 +75,9 @@ function mount (node) { */ function unmount (node) { node.unhandle(`/${node._config.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`) + .catch(err => { + log.error(err) + }) } exports = module.exports = ping diff --git a/src/record/peer-record/index.js b/src/record/peer-record/index.js index dcdc7b62..bcf637b8 100644 --- a/src/record/peer-record/index.js +++ b/src/record/peer-record/index.js @@ -11,7 +11,7 @@ const { } = require('./consts') /** - * @typedef {import('../../peer-store/address-book.js').Address} Address + * @typedef {import('../../peer-store/types').Address} Address * @typedef {import('libp2p-interfaces/src/record/types').Record} Record */ diff --git a/src/record/utils.js b/src/record/utils.js index 0a92ade1..512d62a2 100644 --- a/src/record/utils.js +++ b/src/record/utils.js @@ -19,7 +19,7 @@ async function updateSelfPeerRecord (libp2p) { multiaddrs: libp2p.multiaddrs }) const envelope = await Envelope.seal(peerRecord, libp2p.peerId) - libp2p.peerStore.addressBook.consumePeerRecord(envelope) + await libp2p.peerStore.addressBook.consumePeerRecord(envelope) } module.exports.updateSelfPeerRecord = updateSelfPeerRecord diff --git a/src/registrar.js b/src/registrar.js index 2a2f9f40..812c7601 100644 --- a/src/registrar.js +++ b/src/registrar.js @@ -13,7 +13,7 @@ const Topology = require('libp2p-interfaces/src/topology') /** * @typedef {import('peer-id')} PeerId - * @typedef {import('./peer-store')} PeerStore + * @typedef {import('./peer-store/types').PeerStore} PeerStore * @typedef {import('./connection-manager')} ConnectionManager * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection * @typedef {import('./').HandlerProps} HandlerProps @@ -82,9 +82,9 @@ class Registrar { * Register handlers for a set of multicodecs given * * @param {Topology} topology - protocol topology - * @returns {string} registrar identifier + * @returns {Promise} registrar identifier */ - register (topology) { + async register (topology) { if (!Topology.isTopology(topology)) { log.error('topology must be an instance of interfaces/topology') throw errcode(new Error('topology must be an instance of interfaces/topology'), ERR_INVALID_PARAMETERS) @@ -96,7 +96,7 @@ class Registrar { this.topologies.set(id, topology) // Set registrar - topology.registrar = this + await topology.setRegistrar(this) return id } diff --git a/src/upgrader.js b/src/upgrader.js index 23f9a338..8e28b715 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -288,7 +288,9 @@ class Upgrader { } finally { this.onConnectionEnd(connection) } - })() + })().catch(err => { + log.error(err) + }) } return Reflect.set(...args) diff --git a/test/configuration/protocol-prefix.node.js b/test/configuration/protocol-prefix.node.js index 6a718e65..aff8579e 100644 --- a/test/configuration/protocol-prefix.node.js +++ b/test/configuration/protocol-prefix.node.js @@ -10,6 +10,10 @@ const { baseOptions } = require('./utils') describe('Protocol prefix is configurable', () => { let libp2p + afterEach(async () => { + libp2p && await libp2p.stop() + }) + it('protocolPrefix is provided', async () => { const testProtocol = 'test-protocol' libp2p = await create(mergeOptions(baseOptions, { @@ -17,31 +21,27 @@ describe('Protocol prefix is configurable', () => { protocolPrefix: testProtocol } })) + await libp2p.start() - const protocols = libp2p.peerStore.protoBook.get(libp2p.peerId); - [ + const protocols = await libp2p.peerStore.protoBook.get(libp2p.peerId) + expect(protocols).to.include.members([ '/libp2p/circuit/relay/0.1.0', `/${testProtocol}/id/1.0.0`, `/${testProtocol}/id/push/1.0.0`, `/${testProtocol}/ping/1.0.0` - ].forEach((i, idx) => { - expect(protocols[idx]).equals(i) - }) - await libp2p.stop() + ]) }) it('protocolPrefix is not provided', async () => { libp2p = await create(baseOptions) + await libp2p.start() - const protocols = libp2p.peerStore.protoBook.get(libp2p.peerId); - [ + const protocols = await libp2p.peerStore.protoBook.get(libp2p.peerId) + expect(protocols).to.include.members([ '/libp2p/circuit/relay/0.1.0', '/ipfs/id/1.0.0', '/ipfs/id/push/1.0.0', '/ipfs/ping/1.0.0' - ].forEach((i, idx) => { - expect(protocols[idx]).equals(i) - }) - await libp2p.stop() + ]) }) }) diff --git a/test/connection-manager/index.node.js b/test/connection-manager/index.node.js index aae0b346..bcd381b2 100644 --- a/test/connection-manager/index.node.js +++ b/test/connection-manager/index.node.js @@ -71,7 +71,7 @@ describe('Connection Manager', () => { sinon.spy(libp2p.connectionManager, 'emit') sinon.spy(remoteLibp2p.connectionManager, 'emit') - libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.multiaddrs) await libp2p.dial(remoteLibp2p.peerId) // check connect event @@ -219,9 +219,9 @@ describe('libp2p.connections', () => { }) // Populate PeerStore before starting - libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) - libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs) - libp2p.peerStore.protoBook.set(nodes[1].peerId, ['/protocol-min-conns']) + await libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) + await libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs) + await libp2p.peerStore.protoBook.set(nodes[1].peerId, ['/protocol-min-conns']) await libp2p.start() diff --git a/test/connection-manager/index.spec.js b/test/connection-manager/index.spec.js index 77e0934c..d400f54b 100644 --- a/test/connection-manager/index.spec.js +++ b/test/connection-manager/index.spec.js @@ -77,7 +77,7 @@ describe('Connection Manager', () => { const value = Math.random() spies.set(value, spy) libp2p.connectionManager.setPeerValue(connection.remotePeer, value) - libp2p.connectionManager.onConnect(connection) + await libp2p.connectionManager.onConnect(connection) })) // get the lowest value @@ -109,8 +109,8 @@ describe('Connection Manager', () => { const spy = sinon.spy() await Promise.all([...new Array(max + 1)].map(async () => { const connection = await mockConnection() - sinon.stub(connection, 'close').callsFake(() => spy()) // eslint-disable-line - libp2p.connectionManager.onConnect(connection) + sinon.stub(connection, 'close').callsFake(async () => spy()) // eslint-disable-line + await libp2p.connectionManager.onConnect(connection) })) expect(libp2p.connectionManager._maybeDisconnectOne).to.have.property('callCount', 1) diff --git a/test/content-routing/content-routing.node.js b/test/content-routing/content-routing.node.js index e35651e4..694d71cf 100644 --- a/test/content-routing/content-routing.node.js +++ b/test/content-routing/content-routing.node.js @@ -291,11 +291,11 @@ describe('content-routing', () => { yield result }) - expect(node.peerStore.addressBook.get(providerPeerId)).to.not.be.ok() + expect(await node.peerStore.has(providerPeerId)).to.not.be.ok() await drain(node.contentRouting.findProviders('a cid')) - expect(node.peerStore.addressBook.get(providerPeerId)).to.deep.include({ + expect(await node.peerStore.addressBook.get(providerPeerId)).to.deep.include({ isCertified: false, multiaddr: result.multiaddrs[0] }) @@ -377,7 +377,7 @@ describe('content-routing', () => { await drain(node.contentRouting.findProviders('a cid')) - expect(node.peerStore.addressBook.get(providerPeerId)).to.deep.include({ + expect(await node.peerStore.addressBook.get(providerPeerId)).to.deep.include({ isCertified: false, multiaddr: result1.multiaddrs[0] }).and.to.deep.include({ diff --git a/test/content-routing/dht/operation.node.js b/test/content-routing/dht/operation.node.js index 646e8431..e6662b20 100644 --- a/test/content-routing/dht/operation.node.js +++ b/test/content-routing/dht/operation.node.js @@ -45,8 +45,8 @@ describe('DHT subsystem operates correctly', () => { remoteLibp2p.start() ]) - libp2p.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]) - remAddr = libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerId)[0] + await libp2p.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]); + [remAddr] = await libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerId) }) afterEach(() => Promise.all([ @@ -106,8 +106,8 @@ describe('DHT subsystem operates correctly', () => { await libp2p.start() await remoteLibp2p.start() - libp2p.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]) - remAddr = libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerId)[0] + await libp2p.peerStore.addressBook.set(remotePeerId, [remoteListenAddr]) + remAddr = (await libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerId))[0] }) afterEach(() => Promise.all([ diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index 6db0cb6b..0d88a397 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -18,7 +18,7 @@ const AggregateError = require('aggregate-error') const { Connection } = require('libp2p-interfaces/src/connection') const { AbortError } = require('libp2p-interfaces/src/transport/errors') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - +const { MemoryDatastore } = require('datastore-core/memory') const Libp2p = require('../../src') const Dialer = require('../../src/dialer') const AddressManager = require('../../src/address-manager') @@ -48,7 +48,10 @@ describe('Dialing (direct, TCP)', () => { PeerId.createFromJSON(Peers[1]) ]) - peerStore = new PeerStore({ peerId: remotePeerId }) + peerStore = new PeerStore({ + peerId: remotePeerId, + datastore: new MemoryDatastore() + }) remoteTM = new TransportManager({ libp2p: { addressManager: new AddressManager(remotePeerId, { listen: [listenAddr] }), @@ -62,7 +65,10 @@ describe('Dialing (direct, TCP)', () => { localTM = new TransportManager({ libp2p: { peerId: localPeerId, - peerStore: new PeerStore({ peerId: localPeerId }) + peerStore: new PeerStore({ + peerId: localPeerId, + datastore: new MemoryDatastore() + }) }, upgrader: mockUpgrader }) @@ -113,7 +119,10 @@ describe('Dialing (direct, TCP)', () => { it('should be able to connect to a given peer id', async () => { const peerId = await PeerId.createFromJSON(Peers[0]) - const peerStore = new PeerStore({ peerId }) + const peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) const dialer = new Dialer({ transportManager: localTM, peerStore @@ -249,7 +258,7 @@ describe('Dialing (direct, TCP)', () => { connEncryption: [Crypto] } }) - remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) + await remoteLibp2p.handle('/echo/1.0.0', ({ stream }) => pipe(stream, stream)) await remoteLibp2p.start() remoteAddr = remoteLibp2p.transportManager.getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toB58String()}`) @@ -339,12 +348,12 @@ describe('Dialing (direct, TCP)', () => { }) // register some stream handlers to simulate several protocols - libp2p.handle('/stream-count/1', ({ stream }) => pipe(stream, stream)) - libp2p.handle('/stream-count/2', ({ stream }) => pipe(stream, stream)) - remoteLibp2p.handle('/stream-count/3', ({ stream }) => pipe(stream, stream)) - remoteLibp2p.handle('/stream-count/4', ({ stream }) => pipe(stream, stream)) + await libp2p.handle('/stream-count/1', ({ stream }) => pipe(stream, stream)) + await libp2p.handle('/stream-count/2', ({ stream }) => pipe(stream, stream)) + await remoteLibp2p.handle('/stream-count/3', ({ stream }) => pipe(stream, stream)) + await remoteLibp2p.handle('/stream-count/4', ({ stream }) => pipe(stream, stream)) - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) const connection = await libp2p.dial(remotePeerId) // Create local to remote streams @@ -363,8 +372,8 @@ describe('Dialing (direct, TCP)', () => { // Verify stream count const remoteConn = remoteLibp2p.connectionManager.get(libp2p.peerId) - expect(connection.streams).to.have.length(5) - expect(remoteConn.streams).to.have.length(5) + expect(connection.streams).to.have.length(6) + expect(remoteConn.streams).to.have.length(6) // Close the connection and verify all streams have been closed await connection.close() diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index 1617322b..14beacb4 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -13,7 +13,7 @@ const { NOISE: Crypto } = require('@chainsafe/libp2p-noise') const { Multiaddr } = require('multiaddr') const AggregateError = require('aggregate-error') const { AbortError } = require('libp2p-interfaces/src/transport/errors') - +const { MemoryDatastore } = require('datastore-core/memory') const { codes: ErrorCodes } = require('../../src/errors') const Constants = require('../../src/constants') const Dialer = require('../../src/dialer') @@ -36,7 +36,10 @@ describe('Dialing (direct, WebSockets)', () => { before(async () => { [peerId] = await createPeerId() - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) localTM = new TransportManager({ libp2p: {}, upgrader: mockUpgrader, @@ -215,7 +218,7 @@ describe('Dialing (direct, WebSockets)', () => { }) // Inject data in the AddressBook - peerStore.addressBook.add(peerId, peerMultiaddrs) + await peerStore.addressBook.add(peerId, peerMultiaddrs) // Perform 3 multiaddr dials await dialer.connectToPeer(peerId) diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index a0c9ba63..88a61eee 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -10,7 +10,6 @@ const duplexPair = require('it-pair/duplex') const { Multiaddr } = require('multiaddr') const pWaitFor = require('p-wait-for') const { toString: unit8ArrayToString } = require('uint8arrays/to-string') - const { codes: Errors } = require('../../src/errors') const IdentifyService = require('../../src/identify') const multicodecs = IdentifyService.multicodecs @@ -22,7 +21,7 @@ const baseOptions = require('../utils/base-options.browser') const { updateSelfPeerRecord } = require('../../src/record/utils') const pkg = require('../../package.json') const AddressManager = require('../../src/address-manager') - +const { MemoryDatastore } = require('datastore-core/memory') const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') const remoteAddr = MULTIADDRS_WEBSOCKETS[0] const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] @@ -38,10 +37,16 @@ describe('Identify', () => { PeerId.createFromJSON(Peers[1]) ])) - localPeerStore = new PeerStore({ peerId: localPeer }) + localPeerStore = new PeerStore({ + peerId: localPeer, + datastore: new MemoryDatastore() + }) localPeerStore.protoBook.set(localPeer, protocols) - remotePeerStore = new PeerStore({ peerId: remotePeer }) + remotePeerStore = new PeerStore({ + peerId: remotePeer, + datastore: new MemoryDatastore() + }) remotePeerStore.protoBook.set(remotePeer, protocols) localAddressManager = new AddressManager(localPeer) @@ -103,7 +108,7 @@ describe('Identify', () => { expect(localIdentify.peerStore.protoBook.set.callCount).to.equal(1) // Validate the remote peer gets updated in the peer store - const addresses = localIdentify.peerStore.addressBook.get(remotePeer) + const addresses = await localIdentify.peerStore.addressBook.get(remotePeer) expect(addresses).to.exist() expect(addresses).have.lengthOf(listenMaddrs.length) expect(addresses.map((a) => a.multiaddr)[0].equals(listenMaddrs[0])) @@ -149,7 +154,7 @@ describe('Identify', () => { sinon.spy(localIdentify.peerStore.addressBook, 'set') sinon.spy(localIdentify.peerStore.protoBook, 'set') - sinon.spy(localIdentify.peerStore.metadataBook, 'set') + sinon.spy(localIdentify.peerStore.metadataBook, 'setValue') // Run identify await Promise.all([ @@ -164,7 +169,7 @@ describe('Identify', () => { expect(localIdentify.peerStore.addressBook.set.callCount).to.equal(1) expect(localIdentify.peerStore.protoBook.set.callCount).to.equal(1) - const metadataArgs = localIdentify.peerStore.metadataBook.set.firstCall.args + const metadataArgs = localIdentify.peerStore.metadataBook.setValue.firstCall.args expect(metadataArgs[0].id.bytes).to.equal(remotePeer.bytes) expect(metadataArgs[1]).to.equal('AgentVersion') expect(unit8ArrayToString(metadataArgs[2])).to.equal(agentVersion) @@ -221,13 +226,16 @@ describe('Identify', () => { .and.to.have.property('code', Errors.ERR_INVALID_PEER) }) - it('should store host data and protocol version into metadataBook', () => { + it('should store host data and protocol version into metadataBook', async () => { const agentVersion = 'js-project/1.0.0' - const peerStore = new PeerStore({ peerId: localPeer }) + const peerStore = new PeerStore({ + peerId: localPeer, + datastore: new MemoryDatastore() + }) - sinon.spy(peerStore.metadataBook, 'set') + sinon.spy(peerStore.metadataBook, 'setValue') - new IdentifyService({ // eslint-disable-line no-new + const service = new IdentifyService({ // eslint-disable-line no-new libp2p: { peerId: localPeer, connectionManager: new EventEmitter(), @@ -243,23 +251,30 @@ describe('Identify', () => { protocols }) - expect(peerStore.metadataBook.set.callCount).to.eql(2) + await service.start() - const storedAgentVersion = peerStore.metadataBook.getValue(localPeer, 'AgentVersion') - const storedProtocolVersion = peerStore.metadataBook.getValue(localPeer, 'ProtocolVersion') + expect(peerStore.metadataBook.setValue.callCount).to.eql(2) + + const storedAgentVersion = await peerStore.metadataBook.getValue(localPeer, 'AgentVersion') + const storedProtocolVersion = await peerStore.metadataBook.getValue(localPeer, 'ProtocolVersion') expect(agentVersion).to.eql(unit8ArrayToString(storedAgentVersion)) expect(storedProtocolVersion).to.exist() + + await service.stop() }) describe('push', () => { it('should be able to push identify updates to another peer', async () => { - const storedProtocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0'] + const storedProtocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0'].sort() const connectionManager = new EventEmitter() connectionManager.getConnection = () => { } - const localPeerStore = new PeerStore({ peerId: localPeer }) - localPeerStore.protoBook.set(localPeer, storedProtocols) + const localPeerStore = new PeerStore({ + peerId: localPeer, + datastore: new MemoryDatastore() + }) + await localPeerStore.protoBook.set(localPeer, storedProtocols) const localIdentify = new IdentifyService({ libp2p: { @@ -273,8 +288,11 @@ describe('Identify', () => { } }) - const remotePeerStore = new PeerStore({ peerId: remotePeer }) - remotePeerStore.protoBook.set(remotePeer, storedProtocols) + const remotePeerStore = new PeerStore({ + peerId: remotePeer, + datastore: new MemoryDatastore() + }) + await remotePeerStore.protoBook.set(remotePeer, storedProtocols) const remoteIdentify = new IdentifyService({ libp2p: { @@ -316,7 +334,7 @@ describe('Identify', () => { expect(remoteIdentify.peerStore.addressBook.consumePeerRecord.callCount).to.equal(2) expect(remoteIdentify.peerStore.protoBook.set.callCount).to.equal(1) - const addresses = localIdentify.peerStore.addressBook.get(localPeer) + const addresses = await localIdentify.peerStore.addressBook.get(localPeer) expect(addresses).to.exist() expect(addresses).have.lengthOf(listenMaddrs.length) expect(addresses.map((a) => a.multiaddr)).to.eql(listenMaddrs) @@ -328,12 +346,15 @@ describe('Identify', () => { // LEGACY it('should be able to push identify updates to another peer with no certified peer records support', async () => { - const storedProtocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0'] + const storedProtocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0'].sort() const connectionManager = new EventEmitter() connectionManager.getConnection = () => { } - const localPeerStore = new PeerStore({ peerId: localPeer }) - localPeerStore.protoBook.set(localPeer, storedProtocols) + const localPeerStore = new PeerStore({ + peerId: localPeer, + datastore: new MemoryDatastore() + }) + await localPeerStore.protoBook.set(localPeer, storedProtocols) const localIdentify = new IdentifyService({ libp2p: { @@ -347,14 +368,17 @@ describe('Identify', () => { } }) - const remotePeerStore = new PeerStore({ peerId: remotePeer }) + const remotePeerStore = new PeerStore({ + peerId: remotePeer, + datastore: new MemoryDatastore() + }) remotePeerStore.protoBook.set(remotePeer, storedProtocols) const remoteIdentify = new IdentifyService({ libp2p: { peerId: remotePeer, connectionManager, - peerStore: new PeerStore({ peerId: remotePeer }), + peerStore: remotePeerStore, multiaddrs: [], _options: { host: {} }, _config: { protocolPrefix: 'ipfs' }, @@ -492,11 +516,15 @@ describe('Identify', () => { await libp2p.identifyService.identify.firstCall.returnValue sinon.stub(libp2p, 'isStarted').returns(true) - libp2p.handle('/echo/2.0.0', () => {}) - libp2p.unhandle('/echo/2.0.0') + await libp2p.handle('/echo/2.0.0', () => {}) + await libp2p.unhandle('/echo/2.0.0') + + // the protocol change event listener in the identity service is async + await pWaitFor(() => libp2p.identifyService.push.callCount === 2) // Verify the remote peer is notified of both changes expect(libp2p.identifyService.push.callCount).to.equal(2) + for (const call of libp2p.identifyService.push.getCalls()) { const [connections] = call.args expect(connections.length).to.equal(1) @@ -509,7 +537,7 @@ describe('Identify', () => { await pWaitFor(() => connection.streams.length === 0) }) - it('should store host data and protocol version into metadataBook', () => { + it('should store host data and protocol version into metadataBook', async () => { const agentVersion = 'js-project/1.0.0' libp2p = new Libp2p({ @@ -519,9 +547,10 @@ describe('Identify', () => { agentVersion } }) + await libp2p.start() - const storedAgentVersion = libp2p.peerStore.metadataBook.getValue(localPeer, 'AgentVersion') - const storedProtocolVersion = libp2p.peerStore.metadataBook.getValue(localPeer, 'ProtocolVersion') + const storedAgentVersion = await libp2p.peerStore.metadataBook.getValue(localPeer, 'AgentVersion') + const storedProtocolVersion = await libp2p.peerStore.metadataBook.getValue(localPeer, 'ProtocolVersion') expect(agentVersion).to.eql(unit8ArrayToString(storedAgentVersion)) expect(storedProtocolVersion).to.exist() @@ -545,7 +574,10 @@ describe('Identify', () => { await libp2p.identifyService.identify.firstCall.returnValue sinon.stub(libp2p, 'isStarted').returns(true) - libp2p.peerStore.addressBook.add(libp2p.peerId, [new Multiaddr('/ip4/180.0.0.1/tcp/15001/ws')]) + await libp2p.peerStore.addressBook.add(libp2p.peerId, [new Multiaddr('/ip4/180.0.0.1/tcp/15001/ws')]) + + // the protocol change event listener in the identity service is async + await pWaitFor(() => libp2p.identifyService.push.callCount === 1) // Verify the remote peer is notified of change expect(libp2p.identifyService.push.callCount).to.equal(1) diff --git a/test/metrics/index.spec.js b/test/metrics/index.spec.js index ed17c57e..7107e97b 100644 --- a/test/metrics/index.spec.js +++ b/test/metrics/index.spec.js @@ -266,10 +266,10 @@ describe('Metrics', () => { const metric = 'some-metric' const value = 1 - metrics.updateComponentMetric(component, metric, value) + metrics.updateComponentMetric({ component, metric, value }) expect(metrics.getComponentMetrics()).to.have.lengthOf(1) - expect(metrics.getComponentMetrics().get(component)).to.have.lengthOf(1) - expect(metrics.getComponentMetrics().get(component).get(metric)).to.equal(value) + expect(metrics.getComponentMetrics().get('libp2p').get(component)).to.have.lengthOf(1) + expect(metrics.getComponentMetrics().get('libp2p').get(component).get(metric)).to.equal(value) }) }) diff --git a/test/peer-routing/peer-routing.node.js b/test/peer-routing/peer-routing.node.js index 4c008a1c..29492bd6 100644 --- a/test/peer-routing/peer-routing.node.js +++ b/test/peer-routing/peer-routing.node.js @@ -658,10 +658,8 @@ describe('peer-routing', () => { await node.start() - await delay(300) - expect(node._dht.getClosestPeers.callCount).to.eql(1) - await delay(500) - expect(node._dht.getClosestPeers.callCount).to.eql(2) + // should run more than once + await pWaitFor(() => node._dht.getClosestPeers.callCount === 2) }) }) }) diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js index 929a45fb..671e94f9 100644 --- a/test/peer-store/address-book.spec.js +++ b/test/peer-store/address-book.spec.js @@ -9,7 +9,7 @@ const arrayEquals = require('libp2p-utils/src/array-equals') const addressSort = require('libp2p-utils/src/address-sort') const PeerId = require('peer-id') const pDefer = require('p-defer') - +const { MemoryDatastore } = require('datastore-core/memory') const PeerStore = require('../../src/peer-store') const Envelope = require('../../src/record/envelope') const PeerRecord = require('../../src/record/peer-record') @@ -19,6 +19,11 @@ const { codes: { ERR_INVALID_PARAMETERS } } = require('../../src/errors') +/** + * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore + * @typedef {import('../../src/peer-store/types').AddressBook} AddressBook + */ + const addr1 = new Multiaddr('/ip4/127.0.0.1/tcp/8000') const addr2 = new Multiaddr('/ip4/20.0.0.1/tcp/8001') const addr3 = new Multiaddr('/ip4/127.0.0.1/tcp/8002') @@ -31,10 +36,16 @@ describe('addressBook', () => { }) describe('addressBook.set', () => { - let peerStore, ab + /** @type {PeerStore} */ + let peerStore + /** @type {AddressBook} */ + let ab beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) @@ -42,9 +53,9 @@ describe('addressBook', () => { peerStore.removeAllListeners() }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - ab.set('invalid peerId') + await ab.set('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -52,9 +63,9 @@ describe('addressBook', () => { throw new Error('invalid peerId should throw error') }) - it('throwns invalid parameters error if no addresses provided', () => { + it('throws invalid parameters error if no addresses provided', async () => { try { - ab.set(peerId) + await ab.set(peerId) } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -62,9 +73,9 @@ describe('addressBook', () => { throw new Error('no addresses should throw error') }) - it('throwns invalid parameters error if invalid multiaddrs are provided', () => { + it('throws invalid parameters error if invalid multiaddrs are provided', async () => { try { - ab.set(peerId, ['invalid multiaddr']) + await ab.set(peerId, ['invalid multiaddr']) } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -72,7 +83,7 @@ describe('addressBook', () => { throw new Error('invalid multiaddrs should throw error') }) - it('replaces the stored content by default and emit change event', () => { + it('replaces the stored content by default and emit change event', async () => { const defer = pDefer() const supportedMultiaddrs = [addr1, addr2] @@ -82,8 +93,8 @@ describe('addressBook', () => { defer.resolve() }) - ab.set(peerId, supportedMultiaddrs) - const addresses = ab.get(peerId) + await ab.set(peerId, supportedMultiaddrs) + const addresses = await ab.get(peerId) const multiaddrs = addresses.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(supportedMultiaddrs) @@ -105,11 +116,11 @@ describe('addressBook', () => { }) // set 1 - ab.set(peerId, supportedMultiaddrsA) + await ab.set(peerId, supportedMultiaddrsA) // set 2 (same content) - ab.set(peerId, supportedMultiaddrsB) - const addresses = ab.get(peerId) + await ab.set(peerId, supportedMultiaddrsB) + const addresses = await ab.get(peerId) const multiaddrs = addresses.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(supportedMultiaddrsB) @@ -130,10 +141,10 @@ describe('addressBook', () => { }) // set 1 - ab.set(peerId, supportedMultiaddrs) + await ab.set(peerId, supportedMultiaddrs) // set 2 (same content) - ab.set(peerId, supportedMultiaddrs) + await ab.set(peerId, supportedMultiaddrs) // Wait 50ms for incorrect second event setTimeout(() => { @@ -145,10 +156,16 @@ describe('addressBook', () => { }) describe('addressBook.add', () => { - let peerStore, ab + /** @type {PeerStore} */ + let peerStore + /** @type {AddressBook} */ + let ab beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) @@ -156,9 +173,9 @@ describe('addressBook', () => { peerStore.removeAllListeners() }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - ab.add('invalid peerId') + await ab.add('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -166,9 +183,9 @@ describe('addressBook', () => { throw new Error('invalid peerId should throw error') }) - it('throwns invalid parameters error if no addresses provided', () => { + it('throws invalid parameters error if no addresses provided', async () => { try { - ab.add(peerId) + await ab.add(peerId) } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -176,9 +193,9 @@ describe('addressBook', () => { throw new Error('no addresses provided should throw error') }) - it('throwns invalid parameters error if invalid multiaddrs are provided', () => { + it('throws invalid parameters error if invalid multiaddrs are provided', async () => { try { - ab.add(peerId, ['invalid multiaddr']) + await ab.add(peerId, ['invalid multiaddr']) } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -193,7 +210,7 @@ describe('addressBook', () => { defer.reject() }) - ab.add(peerId, []) + await ab.add(peerId, []) // Wait 50ms for incorrect second event setTimeout(() => { @@ -203,7 +220,7 @@ describe('addressBook', () => { await defer.promise }) - it('adds the new content and emits change event', () => { + it('adds the new content and emits change event', async () => { const defer = pDefer() const supportedMultiaddrsA = [addr1, addr2] @@ -219,14 +236,14 @@ describe('addressBook', () => { }) // Replace - ab.set(peerId, supportedMultiaddrsA) - let addresses = ab.get(peerId) + await ab.set(peerId, supportedMultiaddrsA) + let addresses = await ab.get(peerId) let multiaddrs = addresses.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(supportedMultiaddrsA) // Add - ab.add(peerId, supportedMultiaddrsB) - addresses = ab.get(peerId) + await ab.add(peerId, supportedMultiaddrsB) + addresses = await ab.get(peerId) multiaddrs = addresses.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(finalMultiaddrs) @@ -249,11 +266,11 @@ describe('addressBook', () => { }) // set 1 - ab.set(peerId, supportedMultiaddrsA) + await ab.set(peerId, supportedMultiaddrsA) // set 2 (content already existing) - ab.add(peerId, supportedMultiaddrsB) - const addresses = ab.get(peerId) + await ab.add(peerId, supportedMultiaddrsB) + const addresses = await ab.get(peerId) const multiaddrs = addresses.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(finalMultiaddrs) @@ -275,10 +292,10 @@ describe('addressBook', () => { }) // set 1 - ab.set(peerId, supportedMultiaddrsA) + await ab.set(peerId, supportedMultiaddrsA) // set 2 (content already existing) - ab.add(peerId, supportedMultiaddrsB) + await ab.add(peerId, supportedMultiaddrsB) // Wait 50ms for incorrect second event setTimeout(() => { @@ -288,26 +305,32 @@ describe('addressBook', () => { await defer.promise }) - it('does not add replicated content', () => { + it('does not add replicated content', async () => { // set 1 - ab.set(peerId, [addr1, addr1]) + await ab.set(peerId, [addr1, addr1]) - const addresses = ab.get(peerId) + const addresses = await ab.get(peerId) expect(addresses).to.have.lengthOf(1) }) }) describe('addressBook.get', () => { - let peerStore, ab + /** @type {PeerStore} */ + let peerStore + /** @type {AddressBook} */ + let ab beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - ab.get('invalid peerId') + await ab.get('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -315,34 +338,40 @@ describe('addressBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns undefined if no multiaddrs are known for the provided peer', () => { - const addresses = ab.get(peerId) + it('returns empty if no multiaddrs are known for the provided peer', async () => { + const addresses = await ab.get(peerId) - expect(addresses).to.not.exist() + expect(addresses).to.be.empty() }) - it('returns the multiaddrs stored', () => { + it('returns the multiaddrs stored', async () => { const supportedMultiaddrs = [addr1, addr2] - ab.set(peerId, supportedMultiaddrs) + await ab.set(peerId, supportedMultiaddrs) - const addresses = ab.get(peerId) + const addresses = await ab.get(peerId) const multiaddrs = addresses.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(supportedMultiaddrs) }) }) describe('addressBook.getMultiaddrsForPeer', () => { - let peerStore, ab + /** @type {PeerStore} */ + let peerStore + /** @type {AddressBook} */ + let ab beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - ab.getMultiaddrsForPeer('invalid peerId') + await ab.getMultiaddrsForPeer('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -350,28 +379,28 @@ describe('addressBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns undefined if no multiaddrs are known for the provided peer', () => { - const addresses = ab.getMultiaddrsForPeer(peerId) + it('returns empty if no multiaddrs are known for the provided peer', async () => { + const addresses = await ab.getMultiaddrsForPeer(peerId) - expect(addresses).to.not.exist() + expect(addresses).to.be.empty() }) - it('returns the multiaddrs stored', () => { + it('returns the multiaddrs stored', async () => { const supportedMultiaddrs = [addr1, addr2] - ab.set(peerId, supportedMultiaddrs) + await ab.set(peerId, supportedMultiaddrs) - const multiaddrs = ab.getMultiaddrsForPeer(peerId) + const multiaddrs = await ab.getMultiaddrsForPeer(peerId) multiaddrs.forEach((m) => { expect(m.getPeerId()).to.equal(peerId.toB58String()) }) }) - it('can sort multiaddrs providing a sorter', () => { + it('can sort multiaddrs providing a sorter', async () => { const supportedMultiaddrs = [addr1, addr2] - ab.set(peerId, supportedMultiaddrs) + await ab.set(peerId, supportedMultiaddrs) - const multiaddrs = ab.getMultiaddrsForPeer(peerId, addressSort.publicAddressesFirst) + const multiaddrs = await ab.getMultiaddrsForPeer(peerId, addressSort.publicAddressesFirst) const sortedAddresses = addressSort.publicAddressesFirst(supportedMultiaddrs.map((m) => ({ multiaddr: m }))) multiaddrs.forEach((m, index) => { @@ -381,16 +410,22 @@ describe('addressBook', () => { }) describe('addressBook.delete', () => { - let peerStore, ab + /** @type {PeerStore} */ + let peerStore + /** @type {AddressBook} */ + let ab beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - ab.delete('invalid peerId') + await ab.delete('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -398,16 +433,14 @@ describe('addressBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns false if no records exist for the peer and no event is emitted', () => { + it('does not emit an event if no records exist for the peer', async () => { const defer = pDefer() peerStore.on('change:multiaddrs', () => { defer.reject() }) - const deleted = ab.delete(peerId) - - expect(deleted).to.equal(false) + await ab.delete(peerId) // Wait 50ms for incorrect invalid event setTimeout(() => { @@ -417,11 +450,11 @@ describe('addressBook', () => { return defer.promise }) - it('returns true if the record exists and an event is emitted', () => { + it('emits an event if the record exists', async () => { const defer = pDefer() const supportedMultiaddrs = [addr1, addr2] - ab.set(peerId, supportedMultiaddrs) + await ab.set(peerId, supportedMultiaddrs) // Listen after set peerStore.on('change:multiaddrs', ({ multiaddrs }) => { @@ -429,20 +462,24 @@ describe('addressBook', () => { defer.resolve() }) - const deleted = ab.delete(peerId) - - expect(deleted).to.equal(true) + await ab.delete(peerId) return defer.promise }) }) describe('certified records', () => { - let peerStore, ab + /** @type {PeerStore} */ + let peerStore + /** @type {AddressBook} */ + let ab describe('consumes a valid peer record and stores its data', () => { beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) @@ -455,15 +492,11 @@ describe('addressBook', () => { const envelope = await Envelope.seal(peerRecord, peerId) // consume peer record - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(true) - // Validate stored envelope - const storedEnvelope = await ab.getPeerRecord(peerId) - expect(envelope.equals(storedEnvelope)).to.eql(true) - // Validate AddressBook addresses - const addrs = ab.get(peerId) + const addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(multiaddrs.length) addrs.forEach((addr, index) => { @@ -488,7 +521,7 @@ describe('addressBook', () => { }) // consume peer record - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(true) return defer.promise @@ -499,10 +532,10 @@ describe('addressBook', () => { const multiaddrs = [addr1, addr2] // Set addressBook data - ab.set(peerId, multiaddrs) + await ab.set(peerId, multiaddrs) // Validate data exists, but not certified - let addrs = ab.get(peerId) + let addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(multiaddrs.length) @@ -525,14 +558,14 @@ describe('addressBook', () => { }) // consume peer record - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(true) // Wait event await defer.promise // Validate data exists and certified - addrs = ab.get(peerId) + addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(multiaddrs.length) addrs.forEach((addr, index) => { @@ -546,10 +579,10 @@ describe('addressBook', () => { const multiaddrs = [addr1, addr2] // Set addressBook data - ab.set(peerId, [addr1]) + await ab.set(peerId, [addr1]) // Validate data exists, but not certified - let addrs = ab.get(peerId) + let addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(1) expect(addrs[0].isCertified).to.eql(false) @@ -569,14 +602,14 @@ describe('addressBook', () => { }) // consume peer record - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(true) // Wait event await defer.promise // Validate data exists and certified - addrs = ab.get(peerId) + addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(multiaddrs.length) addrs.forEach((addr, index) => { @@ -591,10 +624,10 @@ describe('addressBook', () => { const multiaddrsCertified = [addr1, addr2] // Set addressBook data - ab.set(peerId, multiaddrsUncertified) + await ab.set(peerId, multiaddrsUncertified) // Validate data exists, but not certified - let addrs = ab.get(peerId) + let addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(multiaddrsUncertified.length) addrs.forEach((addr, index) => { @@ -616,14 +649,14 @@ describe('addressBook', () => { }) // consume peer record - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(true) // Wait event await defer.promise // Validate data exists and certified - addrs = ab.get(peerId) + addrs = await ab.get(peerId) expect(addrs).to.exist() expect(addrs).to.have.lengthOf(multiaddrsCertified.length) addrs.forEach((addr, index) => { @@ -635,16 +668,19 @@ describe('addressBook', () => { describe('fails to consume invalid peer records', () => { beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) ab = peerStore.addressBook }) - it('invalid peer record', () => { + it('invalid peer record', async () => { const invalidEnvelope = { payload: Buffer.from('invalid-peerRecord') } - const consumed = ab.consumePeerRecord(invalidEnvelope) + const consumed = await ab.consumePeerRecord(invalidEnvelope) expect(consumed).to.eql(false) }) @@ -659,7 +695,7 @@ describe('addressBook', () => { }) const envelope = await Envelope.seal(peerRecord, peerId) - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(false) }) @@ -679,10 +715,10 @@ describe('addressBook', () => { const envelope2 = await Envelope.seal(peerRecord2, peerId) // Consume envelope1 (bigger seqNumber) - let consumed = ab.consumePeerRecord(envelope1) + let consumed = await ab.consumePeerRecord(envelope1) expect(consumed).to.eql(true) - consumed = ab.consumePeerRecord(envelope2) + consumed = await ab.consumePeerRecord(envelope2) expect(consumed).to.eql(false) }) @@ -693,7 +729,7 @@ describe('addressBook', () => { }) const envelope = await Envelope.seal(peerRecord, peerId) - const consumed = ab.consumePeerRecord(envelope) + const consumed = await ab.consumePeerRecord(envelope) expect(consumed).to.eql(false) }) }) diff --git a/test/peer-store/key-book.spec.js b/test/peer-store/key-book.spec.js index 8c439a90..c5c70db7 100644 --- a/test/peer-store/key-book.spec.js +++ b/test/peer-store/key-book.spec.js @@ -3,26 +3,43 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') - +const { MemoryDatastore } = require('datastore-core/memory') const PeerStore = require('../../src/peer-store') - +const pDefer = require('p-defer') const peerUtils = require('../utils/creators/peer') const { codes: { ERR_INVALID_PARAMETERS } } = require('../../src/errors') +/** + * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore + * @typedef {import('../../src/peer-store/types').KeyBook} KeyBook + * @typedef {import('peer-id')} PeerId + */ + describe('keyBook', () => { - let peerId, peerStore, kb + /** @type {PeerId} */ + let peerId + /** @type {PeerStore} */ + let peerStore + /** @type {KeyBook} */ + let kb + /** @type {MemoryDatastore} */ + let datastore beforeEach(async () => { [peerId] = await peerUtils.createPeerId() - peerStore = new PeerStore({ peerId }) + datastore = new MemoryDatastore() + peerStore = new PeerStore({ + peerId, + datastore + }) kb = peerStore.keyBook }) - it('throws invalid parameters error if invalid PeerId is provided in set', () => { + it('throws invalid parameters error if invalid PeerId is provided in set', async () => { try { - kb.set('invalid peerId') + await kb.set('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -30,9 +47,9 @@ describe('keyBook', () => { throw new Error('invalid peerId should throw error') }) - it('throws invalid parameters error if invalid PeerId is provided in get', () => { + it('throws invalid parameters error if invalid PeerId is provided in get', async () => { try { - kb.get('invalid peerId') + await kb.get('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -40,22 +57,58 @@ describe('keyBook', () => { throw new Error('invalid peerId should throw error') }) - it('stores the peerId in the book and returns the public key', () => { + it('stores the peerId in the book and returns the public key', async () => { // Set PeerId - kb.set(peerId, peerId.pubKey) + await kb.set(peerId, peerId.pubKey) // Get public key - const pubKey = kb.get(peerId) + const pubKey = await kb.get(peerId) expect(peerId.pubKey.bytes).to.equalBytes(pubKey.bytes) }) - it('should not store if already stored', () => { - const spy = sinon.spy(kb, '_setData') + it('should not store if already stored', async () => { + const spy = sinon.spy(datastore, 'put') // Set PeerId - kb.set(peerId, peerId.pubKey) - kb.set(peerId, peerId.pubKey) + await kb.set(peerId, peerId.pubKey) + await kb.set(peerId, peerId.pubKey) expect(spy).to.have.property('callCount', 1) }) + + it('should emit an event when setting a key', async () => { + const defer = pDefer() + + peerStore.on('change:pubkey', ({ peerId: id, pubKey }) => { + expect(id.toB58String()).to.equal(peerId.toB58String()) + expect(pubKey.bytes).to.equalBytes(peerId.pubKey.bytes) + defer.resolve() + }) + + // Set PeerId + await kb.set(peerId, peerId.pubKey) + await defer.promise + }) + + it('should not set when key does not match', async () => { + const [edKey] = await peerUtils.createPeerId({ fixture: false, opts: { keyType: 'Ed25519' } }) + + // Set PeerId + await expect(kb.set(edKey, peerId.pubKey)).to.eventually.be.rejectedWith(/bytes do not match/) + }) + + it('should emit an event when deleting a key', async () => { + const defer = pDefer() + + await kb.set(peerId, peerId.pubKey) + + peerStore.on('change:pubkey', ({ peerId: id, pubKey }) => { + expect(id.toB58String()).to.equal(peerId.toB58String()) + expect(pubKey).to.be.undefined() + defer.resolve() + }) + + await kb.delete(peerId) + await defer.promise + }) }) diff --git a/test/peer-store/metadata-book.spec.js b/test/peer-store/metadata-book.spec.js index 478dfff5..214a23ca 100644 --- a/test/peer-store/metadata-book.spec.js +++ b/test/peer-store/metadata-book.spec.js @@ -3,7 +3,7 @@ const { expect } = require('aegir/utils/chai') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - +const { MemoryDatastore } = require('datastore-core/memory') const pDefer = require('p-defer') const PeerStore = require('../../src/peer-store') @@ -12,7 +12,14 @@ const { codes: { ERR_INVALID_PARAMETERS } } = require('../../src/errors') +/** + * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore + * @typedef {import('../../src/peer-store/types').MetadataBook} MetadataBook + * @typedef {import('peer-id')} PeerId + */ + describe('metadataBook', () => { + /** @type {PeerId} */ let peerId before(async () => { @@ -20,10 +27,16 @@ describe('metadataBook', () => { }) describe('metadataBook.set', () => { - let peerStore, mb + /** @type {PeerStore} */ + let peerStore + /** @type {MetadataBook} */ + let mb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) mb = peerStore.metadataBook }) @@ -31,9 +44,9 @@ describe('metadataBook', () => { peerStore.removeAllListeners() }) - it('throws invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - mb.set('invalid peerId') + await mb.set('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -41,9 +54,9 @@ describe('metadataBook', () => { throw new Error('invalid peerId should throw error') }) - it('throws invalid parameters error if no key provided', () => { + it('throws invalid parameters error if no metadata provided', async () => { try { - mb.set(peerId) + await mb.set(peerId) } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -51,9 +64,9 @@ describe('metadataBook', () => { throw new Error('no key provided should throw error') }) - it('throws invalid parameters error if no value provided', () => { + it('throws invalid parameters error if no value provided', async () => { try { - mb.set(peerId, 'location') + await mb.setValue(peerId, 'location') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -61,9 +74,9 @@ describe('metadataBook', () => { throw new Error('no value provided should throw error') }) - it('throws invalid parameters error if value is not a buffer', () => { + it('throws invalid parameters error if value is not a buffer', async () => { try { - mb.set(peerId, 'location', 'mars') + await mb.setValue(peerId, 'location', 'mars') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -71,30 +84,30 @@ describe('metadataBook', () => { throw new Error('invalid value provided should throw error') }) - it('stores the content and emit change event', () => { + it('stores the content and emit change event', async () => { const defer = pDefer() const metadataKey = 'location' const metadataValue = uint8ArrayFromString('mars') peerStore.once('change:metadata', ({ peerId, metadata }) => { expect(peerId).to.exist() - expect(metadata).to.equal(metadataKey) + expect(metadata.get(metadataKey)).to.equalBytes(metadataValue) defer.resolve() }) - mb.set(peerId, metadataKey, metadataValue) + await mb.setValue(peerId, metadataKey, metadataValue) - const value = mb.getValue(peerId, metadataKey) + const value = await mb.getValue(peerId, metadataKey) expect(value).to.equalBytes(metadataValue) - const peerMetadata = mb.get(peerId) + const peerMetadata = await mb.get(peerId) expect(peerMetadata).to.exist() expect(peerMetadata.get(metadataKey)).to.equalBytes(metadataValue) return defer.promise }) - it('emits on set if not storing the exact same content', () => { + it('emits on set if not storing the exact same content', async () => { const defer = pDefer() const metadataKey = 'location' const metadataValue1 = uint8ArrayFromString('mars') @@ -109,22 +122,22 @@ describe('metadataBook', () => { }) // set 1 - mb.set(peerId, metadataKey, metadataValue1) + await mb.setValue(peerId, metadataKey, metadataValue1) // set 2 (same content) - mb.set(peerId, metadataKey, metadataValue2) + await mb.setValue(peerId, metadataKey, metadataValue2) - const value = mb.getValue(peerId, metadataKey) + const value = await mb.getValue(peerId, metadataKey) expect(value).to.equalBytes(metadataValue2) - const peerMetadata = mb.get(peerId) + const peerMetadata = await mb.get(peerId) expect(peerMetadata).to.exist() expect(peerMetadata.get(metadataKey)).to.equalBytes(metadataValue2) return defer.promise }) - it('does not emit on set if it is storing the exact same content', () => { + it('does not emit on set if it is storing the exact same content', async () => { const defer = pDefer() const metadataKey = 'location' const metadataValue = uint8ArrayFromString('mars') @@ -138,10 +151,10 @@ describe('metadataBook', () => { }) // set 1 - mb.set(peerId, metadataKey, metadataValue) + await mb.setValue(peerId, metadataKey, metadataValue) // set 2 (same content) - mb.set(peerId, metadataKey, metadataValue) + await mb.setValue(peerId, metadataKey, metadataValue) // Wait 50ms for incorrect second event setTimeout(() => { @@ -153,16 +166,22 @@ describe('metadataBook', () => { }) describe('metadataBook.get', () => { - let peerStore, mb + /** @type {PeerStore} */ + let peerStore + /** @type {MetadataBook} */ + let mb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) mb = peerStore.metadataBook }) - it('throws invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - mb.get('invalid peerId') + await mb.get('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -170,35 +189,43 @@ describe('metadataBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns undefined if no metadata is known for the provided peer', () => { - const metadata = mb.get(peerId) + it('returns empty if no metadata is known for the provided peer', async () => { + const metadata = await mb.get(peerId) - expect(metadata).to.not.exist() + expect(metadata).to.be.empty() }) - it('returns the metadata stored', () => { + it('returns the metadata stored', async () => { const metadataKey = 'location' const metadataValue = uint8ArrayFromString('mars') + const metadata = new Map() + metadata.set(metadataKey, metadataValue) - mb.set(peerId, metadataKey, metadataValue) + await mb.set(peerId, metadata) - const peerMetadata = mb.get(peerId) + const peerMetadata = await mb.get(peerId) expect(peerMetadata).to.exist() expect(peerMetadata.get(metadataKey)).to.equalBytes(metadataValue) }) }) describe('metadataBook.getValue', () => { - let peerStore, mb + /** @type {PeerStore} */ + let peerStore + /** @type {MetadataBook} */ + let mb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) mb = peerStore.metadataBook }) - it('throws invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - mb.getValue('invalid peerId') + await mb.getValue('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -206,48 +233,53 @@ describe('metadataBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns undefined if no metadata is known for the provided peer', () => { + it('returns undefined if no metadata is known for the provided peer', async () => { const metadataKey = 'location' - const metadata = mb.getValue(peerId, metadataKey) + const metadata = await mb.getValue(peerId, metadataKey) expect(metadata).to.not.exist() }) - it('returns the metadata value stored for the given key', () => { + it('returns the metadata value stored for the given key', async () => { const metadataKey = 'location' const metadataValue = uint8ArrayFromString('mars') - mb.set(peerId, metadataKey, metadataValue) + await mb.setValue(peerId, metadataKey, metadataValue) - const value = mb.getValue(peerId, metadataKey) + const value = await mb.getValue(peerId, metadataKey) expect(value).to.exist() expect(value).to.equalBytes(metadataValue) }) - it('returns undefined if no metadata is known for the provided peer and key', () => { + it('returns undefined if no metadata is known for the provided peer and key', async () => { const metadataKey = 'location' const metadataBadKey = 'nickname' const metadataValue = uint8ArrayFromString('mars') - mb.set(peerId, metadataKey, metadataValue) - - const metadata = mb.getValue(peerId, metadataBadKey) + await mb.setValue(peerId, metadataKey, metadataValue) + const metadata = await mb.getValue(peerId, metadataBadKey) expect(metadata).to.not.exist() }) }) describe('metadataBook.delete', () => { - let peerStore, mb + /** @type {PeerStore} */ + let peerStore + /** @type {MetadataBook} */ + let mb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) mb = peerStore.metadataBook }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - mb.delete('invalid peerId') + await mb.delete('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -255,16 +287,14 @@ describe('metadataBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns false if no records exist for the peer and no event is emitted', () => { + it('should not emit event if no records exist for the peer', async () => { const defer = pDefer() peerStore.on('change:metadata', () => { defer.reject() }) - const deleted = mb.delete(peerId) - - expect(deleted).to.equal(false) + await mb.delete(peerId) // Wait 50ms for incorrect invalid event setTimeout(() => { @@ -274,37 +304,41 @@ describe('metadataBook', () => { return defer.promise }) - it('returns true if the record exists and an event is emitted', () => { + it('should emit an event if the record exists for the peer', async () => { const defer = pDefer() const metadataKey = 'location' const metadataValue = uint8ArrayFromString('mars') - mb.set(peerId, metadataKey, metadataValue) + await mb.setValue(peerId, metadataKey, metadataValue) // Listen after set peerStore.on('change:metadata', () => { defer.resolve() }) - const deleted = mb.delete(peerId) - - expect(deleted).to.equal(true) + await mb.delete(peerId) return defer.promise }) }) describe('metadataBook.deleteValue', () => { - let peerStore, mb + /** @type {PeerStore} */ + let peerStore + /** @type {MetadataBook} */ + let mb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) mb = peerStore.metadataBook }) - it('throws invalid parameters error if invalid PeerId is provided', () => { + it('throws invalid parameters error if invalid PeerId is provided', async () => { try { - mb.deleteValue('invalid peerId') + await mb.deleteValue('invalid peerId') } catch (/** @type {any} */ err) { expect(err.code).to.equal(ERR_INVALID_PARAMETERS) return @@ -312,7 +346,7 @@ describe('metadataBook', () => { throw new Error('invalid peerId should throw error') }) - it('returns false if no records exist for the peer and no event is emitted', () => { + it('should not emit event if no records exist for the peer', async () => { const defer = pDefer() const metadataKey = 'location' @@ -320,9 +354,7 @@ describe('metadataBook', () => { defer.reject() }) - const deleted = mb.deleteValue(peerId, metadataKey) - - expect(deleted).to.equal(false) + await mb.deleteValue(peerId, metadataKey) // Wait 50ms for incorrect invalid event setTimeout(() => { @@ -332,45 +364,19 @@ describe('metadataBook', () => { return defer.promise }) - it('returns true if the record exists and an event is emitted', () => { + it('should emit event if a record exists for the peer', async () => { const defer = pDefer() const metadataKey = 'location' const metadataValue = uint8ArrayFromString('mars') - mb.set(peerId, metadataKey, metadataValue) + await mb.setValue(peerId, metadataKey, metadataValue) // Listen after set peerStore.on('change:metadata', () => { defer.resolve() }) - const deleted = mb.deleteValue(peerId, metadataKey) - - expect(deleted).to.equal(true) - - return defer.promise - }) - - it('returns false if there is a record for the peer but not the given metadata key', () => { - const defer = pDefer() - const metadataKey = 'location' - const metadataBadKey = 'nickname' - const metadataValue = uint8ArrayFromString('mars') - - mb.set(peerId, metadataKey, metadataValue) - - peerStore.on('change:metadata', () => { - defer.reject() - }) - - const deleted = mb.deleteValue(peerId, metadataBadKey) - - expect(deleted).to.equal(false) - - // Wait 50ms for incorrect invalid event - setTimeout(() => { - defer.resolve() - }, 50) + await mb.deleteValue(peerId, metadataKey) return defer.promise }) diff --git a/test/peer-store/peer-store.node.js b/test/peer-store/peer-store.node.js index d7a462ad..c7d14f11 100644 --- a/test/peer-store/peer-store.node.js +++ b/test/peer-store/peer-store.node.js @@ -6,6 +6,7 @@ const sinon = require('sinon') const baseOptions = require('../utils/base-options') const peerUtils = require('../utils/creators/peer') +const all = require('it-all') describe('libp2p.peerStore', () => { let libp2p, remoteLibp2p @@ -35,13 +36,14 @@ describe('libp2p.peerStore', () => { expect(spyAddressBook).to.have.property('called', true) expect(spyKeyBook).to.have.property('called', true) - const localPeers = libp2p.peerStore.peers - expect(localPeers.size).to.equal(1) + const localPeers = await all(libp2p.peerStore.getPeers()) - const publicKeyInLocalPeer = localPeers.get(remoteIdStr).id.pubKey + expect(localPeers.length).to.equal(1) + + const publicKeyInLocalPeer = localPeers[0].id.pubKey expect(publicKeyInLocalPeer.bytes).to.equalBytes(remoteLibp2p.peerId.pubKey.bytes) - const publicKeyInRemotePeer = remoteLibp2p.peerStore.keyBook.get(libp2p.peerId) + const publicKeyInRemotePeer = await remoteLibp2p.peerStore.keyBook.get(libp2p.peerId) expect(publicKeyInRemotePeer).to.exist() expect(publicKeyInRemotePeer.bytes).to.equalBytes(libp2p.peerId.pubKey.bytes) }) diff --git a/test/peer-store/peer-store.spec.js b/test/peer-store/peer-store.spec.js index 4cfaecfe..3456417a 100644 --- a/test/peer-store/peer-store.spec.js +++ b/test/peer-store/peer-store.spec.js @@ -2,11 +2,11 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') - +const all = require('it-all') const PeerStore = require('../../src/peer-store') const { Multiaddr } = require('multiaddr') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - +const { MemoryDatastore } = require('datastore-core/memory') const peerUtils = require('../utils/creators/peer') const addr1 = new Multiaddr('/ip4/127.0.0.1/tcp/8000') @@ -18,6 +18,10 @@ const proto1 = '/protocol1' const proto2 = '/protocol2' const proto3 = '/protocol3' +/** + * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore + */ + describe('peer-store', () => { let peerIds before(async () => { @@ -27,62 +31,65 @@ describe('peer-store', () => { }) describe('empty books', () => { + /** @type {PeerStore} */ let peerStore beforeEach(() => { - peerStore = new PeerStore({ peerId: peerIds[4] }) + peerStore = new PeerStore({ + peerId: peerIds[4], + datastore: new MemoryDatastore() + }) }) - it('has an empty map of peers', () => { - const peers = peerStore.peers - expect(peers.size).to.equal(0) + it('has an empty map of peers', async () => { + const peers = await all(peerStore.getPeers()) + expect(peers.length).to.equal(0) }) - it('returns false on trying to delete a non existant peerId', () => { - const deleted = peerStore.delete(peerIds[0]) - expect(deleted).to.equal(false) + it('deletes a peerId', async () => { + await peerStore.addressBook.set(peerIds[0], [new Multiaddr('/ip4/127.0.0.1/tcp/4001')]) + await expect(peerStore.has(peerIds[0])).to.eventually.be.true() + await peerStore.delete(peerIds[0]) + await expect(peerStore.has(peerIds[0])).to.eventually.be.false() }) - it('returns undefined on trying to find a non existant peerId', () => { - const peer = peerStore.get(peerIds[0]) - expect(peer).to.not.exist() - }) - - it('sets the peer\'s public key to the KeyBook', () => { - peerStore.keyBook.set(peerIds[0], peerIds[0].pubKey) - - const pubKey = peerStore.keyBook.get(peerIds[0]) - expect(pubKey).to.exist() + it('sets the peer\'s public key to the KeyBook', async () => { + await peerStore.keyBook.set(peerIds[0], peerIds[0].pubKey) + await expect(peerStore.keyBook.get(peerIds[0])).to.eventually.deep.equal(peerIds[0].pubKey) }) }) describe('previously populated books', () => { + /** @type {PeerStore} */ let peerStore - beforeEach(() => { - peerStore = new PeerStore({ peerId: peerIds[4] }) + beforeEach(async () => { + peerStore = new PeerStore({ + peerId: peerIds[4], + datastore: new MemoryDatastore() + }) // Add peer0 with { addr1, addr2 } and { proto1 } - peerStore.addressBook.set(peerIds[0], [addr1, addr2]) - peerStore.protoBook.set(peerIds[0], [proto1]) + await peerStore.addressBook.set(peerIds[0], [addr1, addr2]) + await peerStore.protoBook.set(peerIds[0], [proto1]) // Add peer1 with { addr3 } and { proto2, proto3 } - peerStore.addressBook.set(peerIds[1], [addr3]) - peerStore.protoBook.set(peerIds[1], [proto2, proto3]) + await peerStore.addressBook.set(peerIds[1], [addr3]) + await peerStore.protoBook.set(peerIds[1], [proto2, proto3]) // Add peer2 with { addr4 } - peerStore.addressBook.set(peerIds[2], [addr4]) + await peerStore.addressBook.set(peerIds[2], [addr4]) // Add peer3 with { addr4 } and { proto2 } - peerStore.addressBook.set(peerIds[3], [addr4]) - peerStore.protoBook.set(peerIds[3], [proto2]) + await peerStore.addressBook.set(peerIds[3], [addr4]) + await peerStore.protoBook.set(peerIds[3], [proto2]) }) - it('has peers', () => { - const peers = peerStore.peers + it('has peers', async () => { + const peers = await all(peerStore.getPeers()) - expect(peers.size).to.equal(4) - expect(Array.from(peers.keys())).to.have.members([ + expect(peers.length).to.equal(4) + expect(peers.map(peer => peer.id.toB58String())).to.have.members([ peerIds[0].toB58String(), peerIds[1].toB58String(), peerIds[2].toB58String(), @@ -90,47 +97,45 @@ describe('peer-store', () => { ]) }) - it('returns true on deleting a stored peer', () => { - const deleted = peerStore.delete(peerIds[0]) - expect(deleted).to.equal(true) + it('deletes a stored peer', async () => { + await peerStore.delete(peerIds[0]) - const peers = peerStore.peers - expect(peers.size).to.equal(3) + const peers = await all(peerStore.getPeers()) + expect(peers.length).to.equal(3) expect(Array.from(peers.keys())).to.not.have.members([peerIds[0].toB58String()]) }) - it('returns true on deleting a stored peer which is only on one book', () => { - const deleted = peerStore.delete(peerIds[2]) - expect(deleted).to.equal(true) + it('deletes a stored peer which is only on one book', async () => { + await peerStore.delete(peerIds[2]) - const peers = peerStore.peers - expect(peers.size).to.equal(3) + const peers = await all(peerStore.getPeers()) + expect(peers.length).to.equal(3) }) - it('gets the stored information of a peer in all its books', () => { - const peer = peerStore.get(peerIds[0]) + it('gets the stored information of a peer in all its books', async () => { + const peer = await peerStore.get(peerIds[0]) expect(peer).to.exist() expect(peer.protocols).to.have.members([proto1]) const peerMultiaddrs = peer.addresses.map((mi) => mi.multiaddr) - expect(peerMultiaddrs).to.have.members([addr1, addr2]) + expect(peerMultiaddrs).to.have.deep.members([addr1, addr2]) - expect(peer.id).to.exist() + expect(peer.id.toB58String()).to.equal(peerIds[0].toB58String()) }) - it('gets the stored information of a peer that is not present in all its books', () => { - const peers = peerStore.get(peerIds[2]) + it('gets the stored information of a peer that is not present in all its books', async () => { + const peers = await peerStore.get(peerIds[2]) expect(peers).to.exist() expect(peers.protocols.length).to.eql(0) const peerMultiaddrs = peers.addresses.map((mi) => mi.multiaddr) - expect(peerMultiaddrs).to.have.members([addr4]) + expect(peerMultiaddrs).to.have.deep.members([addr4]) }) - it('can find all the peers supporting a protocol', () => { + it('can find all the peers supporting a protocol', async () => { const peerSupporting2 = [] - for (const [, peer] of peerStore.peers.entries()) { + for await (const peer of peerStore.getPeers()) { if (peer.protocols.includes(proto2)) { peerSupporting2.push(peer) } @@ -141,67 +146,71 @@ describe('peer-store', () => { expect(peerSupporting2[1].id.toB58String()).to.eql(peerIds[3].toB58String()) }) - it('can find all the peers listening on a given address', () => { - const peerListenint4 = [] + it('can find all the peers listening on a given address', async () => { + const peerListening4 = [] - for (const [, peer] of peerStore.peers.entries()) { - const multiaddrs = peer.addresses.map((mi) => mi.multiaddr) + for await (const peer of peerStore.getPeers()) { + const multiaddrs = peer.addresses.map((mi) => mi.multiaddr.toString()) - if (multiaddrs.includes(addr4)) { - peerListenint4.push(peer) + if (multiaddrs.includes(addr4.toString())) { + peerListening4.push(peer) } } - expect(peerListenint4.length).to.eql(2) - expect(peerListenint4[0].id.toB58String()).to.eql(peerIds[2].toB58String()) - expect(peerListenint4[1].id.toB58String()).to.eql(peerIds[3].toB58String()) + expect(peerListening4.length).to.eql(2) + expect(peerListening4[0].id.toB58String()).to.eql(peerIds[2].toB58String()) + expect(peerListening4[1].id.toB58String()).to.eql(peerIds[3].toB58String()) }) }) - describe('peerStore.peers', () => { + describe('peerStore.getPeers', () => { + /** @type {PeerStore} */ let peerStore beforeEach(() => { - peerStore = new PeerStore({ peerId: peerIds[4] }) + peerStore = new PeerStore({ + peerId: peerIds[4], + datastore: new MemoryDatastore() + }) }) - it('returns peers if only addresses are known', () => { - peerStore.addressBook.set(peerIds[0], [addr1]) + it('returns peers if only addresses are known', async () => { + await peerStore.addressBook.set(peerIds[0], [addr1]) - const peers = peerStore.peers - expect(peers.size).to.equal(1) + const peers = await all(peerStore.getPeers()) + expect(peers.length).to.equal(1) - const peerData = peers.get(peerIds[0].toB58String()) + const peerData = peers[0] expect(peerData).to.exist() expect(peerData.id).to.exist() expect(peerData.addresses).to.have.lengthOf(1) expect(peerData.protocols).to.have.lengthOf(0) - expect(peerData.metadata).to.not.exist() + expect(peerData.metadata).to.be.empty() }) - it('returns peers if only protocols are known', () => { - peerStore.protoBook.set(peerIds[0], [proto1]) + it('returns peers if only protocols are known', async () => { + await peerStore.protoBook.set(peerIds[0], [proto1]) - const peers = peerStore.peers - expect(peers.size).to.equal(1) + const peers = await all(peerStore.getPeers()) + expect(peers.length).to.equal(1) - const peerData = peers.get(peerIds[0].toB58String()) + const peerData = peers[0] expect(peerData).to.exist() expect(peerData.id).to.exist() expect(peerData.addresses).to.have.lengthOf(0) expect(peerData.protocols).to.have.lengthOf(1) - expect(peerData.metadata).to.not.exist() + expect(peerData.metadata).to.be.empty() }) - it('returns peers if only metadata is known', () => { + it('returns peers if only metadata is known', async () => { const metadataKey = 'location' const metadataValue = uint8ArrayFromString('earth') - peerStore.metadataBook.set(peerIds[0], metadataKey, metadataValue) + await peerStore.metadataBook.setValue(peerIds[0], metadataKey, metadataValue) - const peers = peerStore.peers - expect(peers.size).to.equal(1) + const peers = await all(peerStore.getPeers()) + expect(peers.length).to.equal(1) - const peerData = peers.get(peerIds[0].toB58String()) + const peerData = peers[0] expect(peerData).to.exist() expect(peerData.id).to.exist() expect(peerData.addresses).to.have.lengthOf(0) diff --git a/test/peer-store/persisted-peer-store.spec.js b/test/peer-store/persisted-peer-store.spec.js deleted file mode 100644 index 67638a10..00000000 --- a/test/peer-store/persisted-peer-store.spec.js +++ /dev/null @@ -1,608 +0,0 @@ -'use strict' -/* eslint-env mocha */ - -const { expect } = require('aegir/utils/chai') -const sinon = require('sinon') - -const Envelope = require('../../src/record/envelope') -const PeerRecord = require('../../src/record/peer-record') -const PeerStore = require('../../src/peer-store/persistent') - -const { Multiaddr } = require('multiaddr') -const { MemoryDatastore } = require('datastore-core/memory') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - -const peerUtils = require('../utils/creators/peer') - -describe('Persisted PeerStore', () => { - let datastore, peerStore - let peerId - - before(async () => { - [peerId] = await peerUtils.createPeerId({ fixture: false }) - }) - - describe('start and stop flows', () => { - beforeEach(() => { - datastore = new MemoryDatastore() - peerStore = new PeerStore({ datastore, peerId }) - }) - - afterEach(() => peerStore.stop()) - - it('should try to load content from an empty datastore on start', async () => { - const spyQuery = sinon.spy(datastore, 'query') - const spyProcessEntry = sinon.spy(peerStore, '_processDatastoreEntry') - - await peerStore.start() - expect(spyQuery).to.have.property('callCount', 1) - expect(spyProcessEntry).to.have.property('callCount', 0) - - // No data to populate - expect(peerStore.peers.size).to.eq(0) - }) - - it('should try to commit data on stop but should not add to batch if not exists', async () => { - const spyDs = sinon.spy(peerStore, '_commitData') - const spyBatch = sinon.spy(datastore, 'batch') - - await peerStore.start() - expect(spyDs).to.have.property('callCount', 0) - - await peerStore.stop() - expect(spyBatch).to.have.property('callCount', 0) - expect(spyDs).to.have.property('callCount', 1) - }) - }) - - describe('simple setup with content stored per change (threshold 1)', () => { - beforeEach(() => { - datastore = new MemoryDatastore() - peerStore = new PeerStore({ datastore, peerId, threshold: 1 }) - }) - - afterEach(() => peerStore.stop()) - - it('should store peerStore content on datastore after peer marked as dirty (threshold 1)', async () => { - const [peer] = await peerUtils.createPeerId({ number: 2 }) - const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] - const protocols = ['/ping/1.0.0'] - const spyDirty = sinon.spy(peerStore, '_addDirtyPeer') - const spyDs = sinon.spy(datastore, 'batch') - const commitSpy = sinon.spy(peerStore, '_commitData') - - await peerStore.start() - - // AddressBook - peerStore.addressBook.set(peer, multiaddrs) - - expect(spyDirty).to.have.property('callCount', 1) // Address - expect(spyDs).to.have.property('callCount', 1) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - // ProtoBook - peerStore.protoBook.set(peer, protocols) - - expect(spyDirty).to.have.property('callCount', 2) // Protocol - expect(spyDs).to.have.property('callCount', 2) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - // Should have three peer records stored in the datastore - const queryParams = { - prefix: '/peers/' - } - - let count = 0 - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - count++ - } - expect(count).to.equal(2) - - // Validate data - const storedPeer = peerStore.get(peer) - expect(storedPeer.id.toB58String()).to.eql(peer.toB58String()) - expect(storedPeer.protocols).to.have.members(protocols) - expect(storedPeer.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[0].toString()]) - expect(storedPeer.addresses.map((a) => a.isCertified)).to.have.members([false]) - }) - - it('should load content to the peerStore when restart but not put in datastore again', async () => { - const spyDs = sinon.spy(datastore, 'batch') - const peers = await peerUtils.createPeerId({ number: 2 }) - const commitSpy = sinon.spy(peerStore, '_commitData') - const multiaddrs = [ - new Multiaddr('/ip4/156.10.1.22/tcp/1000'), - new Multiaddr('/ip4/156.10.1.23/tcp/1000') - ] - const protocols = ['/ping/1.0.0'] - - await peerStore.start() - - // AddressBook - peerStore.addressBook.set(peers[0], [multiaddrs[0]]) - peerStore.addressBook.set(peers[1], [multiaddrs[1]]) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - // KeyBook - peerStore.keyBook.set(peers[0], peers[0].pubKey) - peerStore.keyBook.set(peers[1], peers[1].pubKey) - - // no batch commit as public key inline - - // ProtoBook - peerStore.protoBook.set(peers[0], protocols) - peerStore.protoBook.set(peers[1], protocols) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - // MetadataBook - peerStore.metadataBook.set(peers[0], 'location', uint8ArrayFromString('earth')) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - expect(spyDs).to.have.property('callCount', 5) // 2 Address + 2 Proto + 1 Metadata - expect(peerStore.peers.size).to.equal(2) - - await peerStore.stop() - peerStore.keyBook.data.clear() - peerStore.addressBook.data.clear() - peerStore.protoBook.data.clear() - - // Load on restart - const spy = sinon.spy(peerStore, '_processDatastoreEntry') - - await peerStore.start() - - expect(spy).to.have.property('callCount', 5) - expect(spyDs).to.have.property('callCount', 5) - - expect(peerStore.peers.size).to.equal(2) - expect(peerStore.addressBook.data.size).to.equal(2) - expect(peerStore.keyBook.data.size).to.equal(0) - expect(peerStore.protoBook.data.size).to.equal(2) - expect(peerStore.metadataBook.data.size).to.equal(1) - }) - - it('should delete content from the datastore on delete', async () => { - const [peer] = await peerUtils.createPeerId() - const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] - const protocols = ['/ping/1.0.0'] - const commitSpy = sinon.spy(peerStore, '_commitData') - - await peerStore.start() - - // AddressBook - peerStore.addressBook.set(peer, multiaddrs) - // ProtoBook - peerStore.protoBook.set(peer, protocols) - // MetadataBook - peerStore.metadataBook.set(peer, 'location', uint8ArrayFromString('earth')) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - const spyDs = sinon.spy(datastore, 'batch') - const spyAddressBook = sinon.spy(peerStore.addressBook, 'delete') - const spyKeyBook = sinon.spy(peerStore.keyBook, 'delete') - const spyProtoBook = sinon.spy(peerStore.protoBook, 'delete') - const spyMetadataBook = sinon.spy(peerStore.metadataBook, 'delete') - - // Delete from PeerStore - peerStore.delete(peer) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - await peerStore.stop() - - expect(spyAddressBook).to.have.property('callCount', 1) - expect(spyKeyBook).to.have.property('callCount', 1) - expect(spyProtoBook).to.have.property('callCount', 1) - expect(spyMetadataBook).to.have.property('callCount', 1) - expect(spyDs).to.have.property('callCount', 3) - - // Should have zero peer records stored in the datastore - const queryParams = { - prefix: '/peers/' - } - - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - throw new Error('Datastore should be empty') - } - }) - - it('should store certified peer records after peer marked as dirty (threshold 1)', async () => { - const [peerId] = await peerUtils.createPeerId() - const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] - const spyDirty = sinon.spy(peerStore, '_addDirtyPeer') - const spyDs = sinon.spy(datastore, 'batch') - const commitSpy = sinon.spy(peerStore, '_commitData') - - await peerStore.start() - - const peerRecord = new PeerRecord({ - peerId, - multiaddrs - }) - const envelope = await Envelope.seal(peerRecord, peerId) - - // consume peer record - const consumed = peerStore.addressBook.consumePeerRecord(envelope) - expect(consumed).to.eql(true) - expect(spyDirty).to.have.property('callCount', 1) // Address - expect(spyDs).to.have.property('callCount', 1) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - // Should have three peer records stored in the datastore - const queryParams = { - prefix: '/peers/' - } - - let count = 0 - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - count++ - } - expect(count).to.equal(1) - - // Validate data - const storedPeer = peerStore.get(peerId) - expect(storedPeer.id.toB58String()).to.eql(peerId.toB58String()) - expect(storedPeer.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[0].toString()]) - expect(storedPeer.addresses.map((a) => a.isCertified)).to.have.members([true]) - }) - - it('should load certified peer records to the peerStore when restart but not put in datastore again', async () => { - const spyDs = sinon.spy(datastore, 'batch') - const peers = await peerUtils.createPeerId({ number: 2 }) - const commitSpy = sinon.spy(peerStore, '_commitData') - const multiaddrs = [ - new Multiaddr('/ip4/156.10.1.22/tcp/1000'), - new Multiaddr('/ip4/156.10.1.23/tcp/1000') - ] - const peerRecord0 = new PeerRecord({ - peerId: peers[0], - multiaddrs: [multiaddrs[0]] - }) - const envelope0 = await Envelope.seal(peerRecord0, peers[0]) - const peerRecord1 = new PeerRecord({ - peerId: peers[1], - multiaddrs: [multiaddrs[1]] - }) - const envelope1 = await Envelope.seal(peerRecord1, peers[1]) - - await peerStore.start() - - // AddressBook - let consumed = peerStore.addressBook.consumePeerRecord(envelope0) - expect(consumed).to.eql(true) - consumed = peerStore.addressBook.consumePeerRecord(envelope1) - expect(consumed).to.eql(true) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - expect(spyDs).to.have.property('callCount', 2) // 2 Address + 2 Key + 2 Proto + 1 Metadata - expect(peerStore.peers.size).to.equal(2) - - await peerStore.stop() - peerStore.addressBook.data.clear() - - // Load on restart - const spy = sinon.spy(peerStore, '_processDatastoreEntry') - - await peerStore.start() - - expect(spy).to.have.property('callCount', 2) - expect(spyDs).to.have.property('callCount', 2) - - expect(peerStore.peers.size).to.equal(2) - expect(peerStore.addressBook.data.size).to.equal(2) - - expect(peerStore.addressBook.getRawEnvelope(peers[0])).to.exist() - expect(peerStore.addressBook.getRawEnvelope(peers[1])).to.exist() - - // Validate stored envelopes - const storedEnvelope0 = await peerStore.addressBook.getPeerRecord(peers[0]) - expect(envelope0.equals(storedEnvelope0)).to.eql(true) - - const storedEnvelope1 = await peerStore.addressBook.getPeerRecord(peers[1]) - expect(envelope1.equals(storedEnvelope1)).to.eql(true) - - // Validate multiaddrs - const storedPeer0 = peerStore.get(peers[0]) - expect(storedPeer0.id.toB58String()).to.eql(peers[0].toB58String()) - expect(storedPeer0.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[0].toString()]) - expect(storedPeer0.addresses.map((a) => a.isCertified)).to.have.members([true]) - - const storedPeer1 = peerStore.get(peers[1]) - expect(storedPeer1.id.toB58String()).to.eql(peers[1].toB58String()) - expect(storedPeer1.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[1].toString()]) - expect(storedPeer1.addresses.map((a) => a.isCertified)).to.have.members([true]) - }) - - it('should delete certified peer records from the datastore on delete', async () => { - const [peer] = await peerUtils.createPeerId() - const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] - const commitSpy = sinon.spy(peerStore, '_commitData') - - await peerStore.start() - - // AddressBook - const peerRecord = new PeerRecord({ - peerId: peer, - multiaddrs - }) - const envelope = await Envelope.seal(peerRecord, peer) - - // consume peer record - const consumed = peerStore.addressBook.consumePeerRecord(envelope) - expect(consumed).to.eql(true) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - expect(peerStore.addressBook.getRawEnvelope(peer)).to.exist() - - const spyDs = sinon.spy(datastore, 'batch') - const spyAddressBook = sinon.spy(peerStore.addressBook, 'delete') - - // Delete from PeerStore - peerStore.delete(peer) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - await peerStore.stop() - - expect(spyAddressBook).to.have.property('callCount', 1) - expect(spyDs).to.have.property('callCount', 1) - - // Should have zero peer records stored in the datastore - const queryParams = { - prefix: '/peers/' - } - - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - throw new Error('Datastore should be empty') - } - - expect(peerStore.addressBook.getRawEnvelope(peer)).to.not.exist() - }) - }) - - describe('setup with content not stored per change (threshold 2)', () => { - beforeEach(() => { - datastore = new MemoryDatastore() - peerStore = new PeerStore({ datastore, peerId, threshold: 2 }) - }) - - afterEach(() => peerStore.stop()) - - it('should not commit until threshold is reached', async () => { - const spyDirty = sinon.spy(peerStore, '_addDirtyPeer') - const spyDirtyMetadata = sinon.spy(peerStore, '_addDirtyPeerMetadata') - const spyDs = sinon.spy(datastore, 'batch') - const commitSpy = sinon.spy(peerStore, '_commitData') - - const peers = await peerUtils.createPeerId({ number: 2 }) - - const multiaddrs = [new Multiaddr('/ip4/156.10.1.22/tcp/1000')] - const protocols = ['/ping/1.0.0'] - - await peerStore.start() - - expect(spyDirty).to.have.property('callCount', 0) - expect(spyDs).to.have.property('callCount', 0) - - // Add Peer0 data in multiple books - peerStore.addressBook.set(peers[0], multiaddrs) - peerStore.protoBook.set(peers[0], protocols) - peerStore.metadataBook.set(peers[0], 'location', uint8ArrayFromString('earth')) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - // Remove data from the same Peer - peerStore.addressBook.delete(peers[0]) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - expect(spyDirty).to.have.property('callCount', 3) // 2 AddrBook ops, 1 ProtoBook op - expect(spyDirtyMetadata).to.have.property('callCount', 1) // 1 MetadataBook op - expect(peerStore._dirtyPeers.size).to.equal(1) - expect(spyDs).to.have.property('callCount', 0) - - const queryParams = { - prefix: '/peers/' - } - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - throw new Error('Datastore should be empty') - } - - // Add data for second book - peerStore.addressBook.set(peers[1], multiaddrs) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - expect(spyDirty).to.have.property('callCount', 4) - expect(spyDirtyMetadata).to.have.property('callCount', 1) - expect(spyDs).to.have.property('callCount', 1) - - // Should have three peer records stored in the datastore - let count = 0 - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - count++ - } - expect(count).to.equal(3) - expect(peerStore.peers.size).to.equal(2) - }) - - it('should commit on stop if threshold was not reached', async () => { - const spyDirty = sinon.spy(peerStore, '_addDirtyPeer') - const spyDs = sinon.spy(datastore, 'batch') - - const protocols = ['/ping/1.0.0'] - const [peer] = await peerUtils.createPeerId() - - await peerStore.start() - - // Add Peer data in a book - peerStore.protoBook.set(peer, protocols) - - expect(spyDs).to.have.property('callCount', 0) - expect(spyDirty).to.have.property('callCount', 1) // ProtoBook - expect(peerStore._dirtyPeers.size).to.equal(1) - - const queryParams = { - prefix: '/peers/' - } - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - throw new Error('Datastore should be empty') - } - - await peerStore.stop() - - expect(spyDirty).to.have.property('callCount', 1) - expect(spyDs).to.have.property('callCount', 1) - expect(peerStore._dirtyPeers.size).to.equal(0) // Reset - - // Should have one peer record stored in the datastore - let count = 0 - for await (const _ of datastore.query(queryParams)) { // eslint-disable-line - count++ - } - expect(count).to.equal(1) - expect(peerStore.peers.size).to.equal(1) - }) - }) -}) - -describe('libp2p.peerStore (Persisted)', () => { - describe('disabled by default', () => { - let libp2p - - before(async () => { - [libp2p] = await peerUtils.createPeer({ - started: false - }) - }) - - afterEach(() => libp2p.stop()) - - it('should not have have persistence capabilities', async () => { - await libp2p.start() - expect(libp2p.peerStore._dirtyPeers).to.not.exist() - expect(libp2p.peerStore.threshold).to.not.exist() - }) - }) - - describe('enabled', () => { - let libp2p - let memoryDatastore - - beforeEach(async () => { - memoryDatastore = new MemoryDatastore() - ;[libp2p] = await peerUtils.createPeer({ - started: false, - config: { - datastore: memoryDatastore, - peerStore: { - persistence: true, - threshold: 2 // trigger on second peer changed - } - } - }) - }) - - afterEach(() => libp2p.stop()) - - it('should start on libp2p start and load content', async () => { - const spyPeerStore = sinon.spy(libp2p.peerStore, 'start') - const spyDs = sinon.spy(memoryDatastore, 'query') - - await libp2p.start() - expect(spyPeerStore).to.have.property('callCount', 1) - expect(spyDs).to.have.property('callCount', 1) - }) - - it('should load content to the peerStore when a new node is started with the same datastore', async () => { - const commitSpy = sinon.spy(libp2p.peerStore, '_commitData') - const peers = await peerUtils.createPeerId({ number: 3 }) - const multiaddrs = [ - new Multiaddr('/ip4/156.10.1.22/tcp/1000'), - new Multiaddr('/ip4/156.10.1.23/tcp/1000') - ] - const protocols = ['/ping/1.0.0'] - - await libp2p.start() - - // AddressBook - libp2p.peerStore.addressBook.set(peers[1], [multiaddrs[0]]) - libp2p.peerStore.addressBook.set(peers[2], [multiaddrs[1]]) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - // ProtoBook - libp2p.peerStore.protoBook.set(peers[1], protocols) - libp2p.peerStore.protoBook.set(peers[2], protocols) - - // let batch commit complete - await Promise.all(commitSpy.returnValues) - - expect(libp2p.peerStore.peers.size).to.equal(2) - - await libp2p.stop() - - // Use a new node with the previously populated datastore - const [newNode] = await peerUtils.createPeer({ - started: false, - config: { - datastore: memoryDatastore, - peerStore: { - persistence: true - }, - config: { - peerDiscovery: { - autoDial: false - } - } - } - }) - - expect(newNode.peerStore.peers.size).to.equal(0) - - const spy = sinon.spy(newNode.peerStore, '_processDatastoreEntry') - - await newNode.start() - - expect(spy).to.have.property('callCount', 4) // 4 datastore entries - - expect(newNode.peerStore.peers.size).to.equal(2) - - // Validate data - const peer0 = newNode.peerStore.get(peers[1]) - expect(peer0.id.toB58String()).to.eql(peers[1].toB58String()) - expect(peer0.protocols).to.have.members(protocols) - expect(peer0.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[0].toString()]) - - const peer1 = newNode.peerStore.get(peers[2]) - expect(peer1.id.toB58String()).to.eql(peers[2].toB58String()) - expect(peer1.protocols).to.have.members(protocols) - expect(peer1.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[1].toString()]) - - await newNode.stop() - }) - }) -}) diff --git a/test/peer-store/proto-book.spec.js b/test/peer-store/proto-book.spec.js index db05955f..667ec4aa 100644 --- a/test/peer-store/proto-book.spec.js +++ b/test/peer-store/proto-book.spec.js @@ -3,7 +3,7 @@ const { expect } = require('aegir/utils/chai') const sinon = require('sinon') - +const { MemoryDatastore } = require('datastore-core/memory') const pDefer = require('p-defer') const pWaitFor = require('p-wait-for') @@ -11,12 +11,19 @@ const PeerStore = require('../../src/peer-store') const peerUtils = require('../utils/creators/peer') const { - ERR_INVALID_PARAMETERS + codes: { ERR_INVALID_PARAMETERS } } = require('../../src/errors') +/** + * @typedef {import('../../src/peer-store/types').PeerStore} PeerStore + * @typedef {import('../../src/peer-store/types').ProtoBook} ProtoBook + * @typedef {import('peer-id')} PeerId + */ + const arraysAreEqual = (a, b) => a.length === b.length && a.sort().every((item, index) => b[index] === item) describe('protoBook', () => { + /** @type {PeerId} */ let peerId before(async () => { @@ -24,10 +31,16 @@ describe('protoBook', () => { }) describe('protoBook.set', () => { - let peerStore, pb + /** @type {PeerStore} */ + let peerStore + /** @type {ProtoBook} */ + let pb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) pb = peerStore.protoBook }) @@ -35,19 +48,15 @@ describe('protoBook', () => { peerStore.removeAllListeners() }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { - pb.set('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if invalid PeerId is provided', async () => { + await expect(pb.set('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('throwns invalid parameters error if no protocols provided', () => { - expect(() => { - pb.set(peerId) - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if no protocols provided', async () => { + await expect(pb.set(peerId)).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('replaces the stored content by default and emit change event', () => { + it('replaces the stored content by default and emit change event', async () => { const defer = pDefer() const supportedProtocols = ['protocol1', 'protocol2'] @@ -57,14 +66,14 @@ describe('protoBook', () => { defer.resolve() }) - pb.set(peerId, supportedProtocols) - const protocols = pb.get(peerId) + await pb.set(peerId, supportedProtocols) + const protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(supportedProtocols) - return defer.promise + await defer.promise }) - it('emits on set if not storing the exact same content', () => { + it('emits on set if not storing the exact same content', async () => { const defer = pDefer() const supportedProtocolsA = ['protocol1', 'protocol2'] @@ -79,17 +88,17 @@ describe('protoBook', () => { }) // set 1 - pb.set(peerId, supportedProtocolsA) + await pb.set(peerId, supportedProtocolsA) // set 2 (same content) - pb.set(peerId, supportedProtocolsB) - const protocols = pb.get(peerId) + await pb.set(peerId, supportedProtocolsB) + const protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(supportedProtocolsB) - return defer.promise + await defer.promise }) - it('does not emit on set if it is storing the exact same content', () => { + it('does not emit on set if it is storing the exact same content', async () => { const defer = pDefer() const supportedProtocols = ['protocol1', 'protocol2'] @@ -103,10 +112,10 @@ describe('protoBook', () => { }) // set 1 - pb.set(peerId, supportedProtocols) + await pb.set(peerId, supportedProtocols) // set 2 (same content) - pb.set(peerId, supportedProtocols) + await pb.set(peerId, supportedProtocols) // Wait 50ms for incorrect second event setTimeout(() => { @@ -118,10 +127,16 @@ describe('protoBook', () => { }) describe('protoBook.add', () => { - let peerStore, pb + /** @type {PeerStore} */ + let peerStore + /** @type {ProtoBook} */ + let pb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) pb = peerStore.protoBook }) @@ -129,19 +144,15 @@ describe('protoBook', () => { peerStore.removeAllListeners() }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { - pb.add('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if invalid PeerId is provided', async () => { + await expect(pb.add('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('throwns invalid parameters error if no protocols provided', () => { - expect(() => { - pb.add(peerId) - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if no protocols provided', async () => { + await expect(pb.add(peerId)).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('adds the new content and emits change event', () => { + it('adds the new content and emits change event', async () => { const defer = pDefer() const supportedProtocolsA = ['protocol1', 'protocol2'] @@ -157,19 +168,19 @@ describe('protoBook', () => { }) // Replace - pb.set(peerId, supportedProtocolsA) - let protocols = pb.get(peerId) + await pb.set(peerId, supportedProtocolsA) + let protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(supportedProtocolsA) // Add - pb.add(peerId, supportedProtocolsB) - protocols = pb.get(peerId) + await pb.add(peerId, supportedProtocolsB) + protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(finalProtocols) return defer.promise }) - it('emits on add if the content to add not exists', () => { + it('emits on add if the content to add not exists', async () => { const defer = pDefer() const supportedProtocolsA = ['protocol1'] @@ -185,17 +196,17 @@ describe('protoBook', () => { }) // set 1 - pb.set(peerId, supportedProtocolsA) + await pb.set(peerId, supportedProtocolsA) // set 2 (content already existing) - pb.add(peerId, supportedProtocolsB) - const protocols = pb.get(peerId) + await pb.add(peerId, supportedProtocolsB) + const protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(finalProtocols) return defer.promise }) - it('does not emit on add if the content to add already exists', () => { + it('does not emit on add if the content to add already exists', async () => { const defer = pDefer() const supportedProtocolsA = ['protocol1', 'protocol2'] @@ -210,10 +221,10 @@ describe('protoBook', () => { }) // set 1 - pb.set(peerId, supportedProtocolsA) + await pb.set(peerId, supportedProtocolsA) // set 2 (content already existing) - pb.add(peerId, supportedProtocolsB) + await pb.add(peerId, supportedProtocolsB) // Wait 50ms for incorrect second event setTimeout(() => { @@ -225,10 +236,16 @@ describe('protoBook', () => { }) describe('protoBook.remove', () => { - let peerStore, pb + /** @type {PeerStore} */ + let peerStore + /** @type {ProtoBook} */ + let pb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) pb = peerStore.protoBook }) @@ -236,16 +253,12 @@ describe('protoBook', () => { peerStore.removeAllListeners() }) - it('throws invalid parameters error if invalid PeerId is provided', () => { - expect(() => { - pb.remove('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if invalid PeerId is provided', async () => { + await expect(pb.remove('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('throws invalid parameters error if no protocols provided', () => { - expect(() => { - pb.remove(peerId) - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if no protocols provided', async () => { + await expect(pb.remove(peerId)).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) it('removes the given protocol and emits change event', async () => { @@ -258,13 +271,13 @@ describe('protoBook', () => { peerStore.on('change:protocols', spy) // Replace - pb.set(peerId, supportedProtocols) - let protocols = pb.get(peerId) + await pb.set(peerId, supportedProtocols) + let protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(supportedProtocols) // Remove - pb.remove(peerId, removedProtocols) - protocols = pb.get(peerId) + await pb.remove(peerId, removedProtocols) + protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(finalProtocols) await pWaitFor(() => spy.callCount === 2) @@ -275,7 +288,7 @@ describe('protoBook', () => { expect(arraysAreEqual(secondCallArgs.protocols, finalProtocols)) }) - it('emits on remove if the content changes', () => { + it('emits on remove if the content changes', async () => { const spy = sinon.spy() const supportedProtocols = ['protocol1', 'protocol2'] @@ -285,17 +298,17 @@ describe('protoBook', () => { peerStore.on('change:protocols', spy) // set - pb.set(peerId, supportedProtocols) + await pb.set(peerId, supportedProtocols) // remove (content already existing) - pb.remove(peerId, removedProtocols) - const protocols = pb.get(peerId) + await pb.remove(peerId, removedProtocols) + const protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(finalProtocols) return pWaitFor(() => spy.callCount === 2) }) - it('does not emit on remove if the content does not change', () => { + it('does not emit on remove if the content does not change', async () => { const spy = sinon.spy() const supportedProtocols = ['protocol1', 'protocol2'] @@ -304,10 +317,10 @@ describe('protoBook', () => { peerStore.on('change:protocols', spy) // set - pb.set(peerId, supportedProtocols) + await pb.set(peerId, supportedProtocols) // remove - pb.remove(peerId, removedProtocols) + await pb.remove(peerId, removedProtocols) // Only one event expect(spy.callCount).to.eql(1) @@ -315,73 +328,79 @@ describe('protoBook', () => { }) describe('protoBook.get', () => { - let peerStore, pb + /** @type {PeerStore} */ + let peerStore + /** @type {ProtoBook} */ + let pb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) pb = peerStore.protoBook }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { - pb.get('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if invalid PeerId is provided', async () => { + await expect(pb.get('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('returns undefined if no protocols are known for the provided peer', () => { - const protocols = pb.get(peerId) + it('returns empty if no protocols are known for the provided peer', async () => { + const protocols = await pb.get(peerId) - expect(protocols).to.not.exist() + expect(protocols).to.be.empty() }) - it('returns the protocols stored', () => { + it('returns the protocols stored', async () => { const supportedProtocols = ['protocol1', 'protocol2'] - pb.set(peerId, supportedProtocols) + await pb.set(peerId, supportedProtocols) - const protocols = pb.get(peerId) + const protocols = await pb.get(peerId) expect(protocols).to.have.deep.members(supportedProtocols) }) }) describe('protoBook.delete', () => { - let peerStore, pb + /** @type {PeerStore} */ + let peerStore + /** @type {ProtoBook} */ + let pb beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) pb = peerStore.protoBook }) - it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { - pb.delete('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + it('throws invalid parameters error if invalid PeerId is provided', async () => { + await expect(pb.delete('invalid peerId')).to.eventually.be.rejected().with.property('code', ERR_INVALID_PARAMETERS) }) - it('returns false if no records exist for the peer and no event is emitted', () => { + it('should not emit event if no records exist for the peer', async () => { const defer = pDefer() peerStore.on('change:protocols', () => { defer.reject() }) - const deleted = pb.delete(peerId) - - expect(deleted).to.equal(false) + await pb.delete(peerId) // Wait 50ms for incorrect invalid event setTimeout(() => { defer.resolve() }, 50) - return defer.promise + await defer.promise }) - it('returns true if the record exists and an event is emitted', () => { + it('should emit event if a record exists for the peer', async () => { const defer = pDefer() const supportedProtocols = ['protocol1', 'protocol2'] - pb.set(peerId, supportedProtocols) + await pb.set(peerId, supportedProtocols) // Listen after set peerStore.on('change:protocols', ({ protocols }) => { @@ -389,11 +408,9 @@ describe('protoBook', () => { defer.resolve() }) - const deleted = pb.delete(peerId) + await pb.delete(peerId) - expect(deleted).to.equal(true) - - return defer.promise + await defer.promise }) }) }) diff --git a/test/registrar/registrar.spec.js b/test/registrar/registrar.spec.js index 8f90b9fc..b86f52a8 100644 --- a/test/registrar/registrar.spec.js +++ b/test/registrar/registrar.spec.js @@ -5,7 +5,7 @@ const { expect } = require('aegir/utils/chai') const pDefer = require('p-defer') const { EventEmitter } = require('events') - +const { MemoryDatastore } = require('datastore-core/memory') const Topology = require('libp2p-interfaces/src/topology/multicodec-topology') const PeerStore = require('../../src/peer-store') const Registrar = require('../../src/registrar') @@ -27,19 +27,23 @@ describe('registrar', () => { describe('errors', () => { beforeEach(() => { - peerStore = new PeerStore({ peerId }) + peerStore = new PeerStore({ + peerId, + datastore: new MemoryDatastore() + }) registrar = new Registrar({ peerStore, connectionManager: new EventEmitter() }) }) it('should fail to register a protocol if no multicodec is provided', () => { - expect(() => registrar.register()).to.throw() + return expect(registrar.register()).to.eventually.be.rejected() }) it('should fail to register a protocol if an invalid topology is provided', () => { const fakeTopology = { random: 1 } - expect(() => registrar.register(fakeTopology)).to.throw() + + return expect(registrar.register(fakeTopology)).to.eventually.be.rejected() }) }) @@ -57,7 +61,7 @@ describe('registrar', () => { afterEach(() => libp2p.stop()) - it('should be able to register a protocol', () => { + it('should be able to register a protocol', async () => { const topologyProps = new Topology({ multicodecs: multicodec, handlers: { @@ -66,12 +70,12 @@ describe('registrar', () => { } }) - const identifier = libp2p.registrar.register(topologyProps) + const identifier = await libp2p.registrar.register(topologyProps) expect(identifier).to.exist() }) - it('should be able to unregister a protocol', () => { + it('should be able to unregister a protocol', async () => { const topologyProps = new Topology({ multicodecs: multicodec, handlers: { @@ -80,7 +84,7 @@ describe('registrar', () => { } }) - const identifier = libp2p.registrar.register(topologyProps) + const identifier = await libp2p.registrar.register(topologyProps) const success = libp2p.registrar.unregister(identifier) expect(success).to.eql(true) @@ -100,12 +104,6 @@ describe('registrar', () => { const conn = await createMockConnection() const remotePeerId = conn.remotePeer - // Add connected peer with protocol to peerStore and registrar - libp2p.peerStore.protoBook.add(remotePeerId, [multicodec]) - - libp2p.connectionManager.onConnect(conn) - expect(libp2p.connectionManager.size).to.eql(1) - const topologyProps = new Topology({ multicodecs: multicodec, handlers: { @@ -124,12 +122,18 @@ describe('registrar', () => { }) // Register protocol - const identifier = libp2p.registrar.register(topologyProps) + const identifier = await libp2p.registrar.register(topologyProps) const topology = libp2p.registrar.topologies.get(identifier) // Topology created expect(topology).to.exist() + // Add connected peer with protocol to peerStore and registrar + await libp2p.peerStore.protoBook.add(remotePeerId, [multicodec]) + + await libp2p.connectionManager.onConnect(conn) + expect(libp2p.connectionManager.size).to.eql(1) + await conn.close() libp2p.connectionManager.onDisconnect(conn) @@ -159,7 +163,7 @@ describe('registrar', () => { }) // Register protocol - const identifier = libp2p.registrar.register(topologyProps) + const identifier = await libp2p.registrar.register(topologyProps) const topology = libp2p.registrar.topologies.get(identifier) // Topology created @@ -171,16 +175,16 @@ describe('registrar', () => { const remotePeerId = conn.remotePeer // Add connected peer to peerStore and registrar - libp2p.peerStore.protoBook.set(remotePeerId, []) - libp2p.connectionManager.onConnect(conn) + await libp2p.peerStore.protoBook.set(remotePeerId, []) // Add protocol to peer and update it - libp2p.peerStore.protoBook.add(remotePeerId, [multicodec]) + await libp2p.peerStore.protoBook.add(remotePeerId, [multicodec]) + await libp2p.connectionManager.onConnect(conn) await onConnectDefer.promise // Remove protocol to peer and update it - libp2p.peerStore.protoBook.set(remotePeerId, []) + await libp2p.peerStore.protoBook.set(remotePeerId, []) await onDisconnectDefer.promise }) diff --git a/test/relay/auto-relay.node.js b/test/relay/auto-relay.node.js index aaddd214..5ec2643e 100644 --- a/test/relay/auto-relay.node.js +++ b/test/relay/auto-relay.node.js @@ -82,7 +82,7 @@ describe('auto-relay', () => { const originalMultiaddrsLength = relayLibp2p.multiaddrs.length // Discover relay - libp2p.peerStore.addressBook.add(relayLibp2p.peerId, relayLibp2p.multiaddrs) + await libp2p.peerStore.addressBook.add(relayLibp2p.peerId, relayLibp2p.multiaddrs) await libp2p.dial(relayLibp2p.peerId) // Wait for peer added as listen relay @@ -94,7 +94,7 @@ describe('auto-relay', () => { expect(libp2p.multiaddrs[originalMultiaddrsLength].getPeerId()).to.eql(relayLibp2p.peerId.toB58String()) // Peer has relay multicodec - const knownProtocols = libp2p.peerStore.protoBook.get(relayLibp2p.peerId) + const knownProtocols = await libp2p.peerStore.protoBook.get(relayLibp2p.peerId) expect(knownProtocols).to.include(relayMulticodec) }) }) @@ -165,7 +165,7 @@ describe('auto-relay', () => { sinon.spy(autoRelay1, '_addListenRelay') // Discover relay - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) const originalMultiaddrs1Length = relayLibp2p1.multiaddrs.length const originalMultiaddrs2Length = relayLibp2p2.multiaddrs.length @@ -184,7 +184,7 @@ describe('auto-relay', () => { expect(relayLibp2p1.multiaddrs[originalMultiaddrs1Length].getPeerId()).to.eql(relayLibp2p2.peerId.toB58String()) // Peer has relay multicodec - const knownProtocols = relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId) + const knownProtocols = await relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId) expect(knownProtocols).to.include(relayMulticodec) }) @@ -193,7 +193,7 @@ describe('auto-relay', () => { const originalMultiaddrs2Length = relayLibp2p2.multiaddrs.length // Discover relay - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) @@ -206,7 +206,7 @@ describe('auto-relay', () => { // Dial from the other through a relay const relayedMultiaddr2 = new Multiaddr(`${relayLibp2p1.multiaddrs[0]}/p2p/${relayLibp2p1.peerId.toB58String()}/p2p-circuit`) - libp2p.peerStore.addressBook.add(relayLibp2p2.peerId, [relayedMultiaddr2]) + await libp2p.peerStore.addressBook.add(relayLibp2p2.peerId, [relayedMultiaddr2]) await libp2p.dial(relayLibp2p2.peerId) }) @@ -220,7 +220,7 @@ describe('auto-relay', () => { sinon.spy(autoRelay1._listenRelays, 'add') // Discover one relay and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) expect(relayLibp2p1.connectionManager.size).to.eql(1) @@ -237,11 +237,11 @@ describe('auto-relay', () => { expect(relayLibp2p1.multiaddrs[originalMultiaddrs1Length].getPeerId()).to.eql(relayLibp2p2.peerId.toB58String()) // Relay2 has relay multicodec - const knownProtocols2 = relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId) + const knownProtocols2 = await relayLibp2p1.peerStore.protoBook.get(relayLibp2p2.peerId) expect(knownProtocols2).to.include(relayMulticodec) // Discover an extra relay and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) await relayLibp2p1.dial(relayLibp2p3.peerId) // Wait to guarantee the dialed peer is not added as a listen relay @@ -253,7 +253,7 @@ describe('auto-relay', () => { expect(relayLibp2p1.connectionManager.size).to.eql(2) // Relay2 has relay multicodec - const knownProtocols3 = relayLibp2p1.peerStore.protoBook.get(relayLibp2p3.peerId) + const knownProtocols3 = await relayLibp2p1.peerStore.protoBook.get(relayLibp2p3.peerId) expect(knownProtocols3).to.include(relayMulticodec) }) @@ -264,7 +264,7 @@ describe('auto-relay', () => { sinon.spy(relayLibp2p1.identifyService, 'pushToPeerStore') // Discover one relay and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) // Wait for listenning on the relay @@ -283,7 +283,7 @@ describe('auto-relay', () => { expect(autoRelay1._listenRelays.size).to.equal(0) // Identify push for removing listen relay multiaddr - expect(relayLibp2p1.identifyService.pushToPeerStore.callCount).to.equal(2) + await pWaitFor(() => relayLibp2p1.identifyService.pushToPeerStore.callCount === 2) }) it('should try to listen on other connected peers relayed address if one used relay disconnects', async () => { @@ -294,11 +294,11 @@ describe('auto-relay', () => { sinon.spy(relayLibp2p1.transportManager, 'listen') // Discover one relay and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) // Discover an extra relay and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) await relayLibp2p1.dial(relayLibp2p3.peerId) // Wait for both peer to be attempted to added as listen relay @@ -337,11 +337,11 @@ describe('auto-relay', () => { sinon.spy(relayLibp2p1.transportManager, 'listen') // Discover one relay and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) // Discover an extra relay and connect to gather its Hop support - relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) await relayLibp2p1.dial(relayLibp2p3.peerId) // Wait for both peer to be attempted to added as listen relay @@ -382,11 +382,11 @@ describe('auto-relay', () => { sinon.spy(relayLibp2p1.transportManager, 'listen') // Discover one relay and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, relayLibp2p2.multiaddrs) await relayLibp2p1.dial(relayLibp2p2.peerId) // Discover an extra relay and connect to gather its Hop support - relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) await relayLibp2p1.dial(relayLibp2p3.peerId) // Wait for both peer to be attempted to added as listen relay @@ -479,7 +479,7 @@ describe('auto-relay', () => { sinon.spy(autoRelay2, '_addListenRelay') // Relay 1 discovers Relay 3 and connect - relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) await relayLibp2p1.dial(relayLibp2p3.peerId) // Wait for peer added as listen relay @@ -487,7 +487,7 @@ describe('auto-relay', () => { expect(autoRelay1._listenRelays.size).to.equal(1) // Relay 2 discovers Relay 3 and connect - relayLibp2p2.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) + await relayLibp2p2.peerStore.addressBook.add(relayLibp2p3.peerId, relayLibp2p3.multiaddrs) await relayLibp2p2.dial(relayLibp2p3.peerId) // Wait for peer added as listen relay @@ -496,7 +496,7 @@ describe('auto-relay', () => { // Relay 1 discovers Relay 2 relayed multiaddr via Relay 3 const ma2RelayedBy3 = relayLibp2p2.multiaddrs[relayLibp2p2.multiaddrs.length - 1] - relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, [ma2RelayedBy3]) + await relayLibp2p1.peerStore.addressBook.add(relayLibp2p2.peerId, [ma2RelayedBy3]) await relayLibp2p1.dial(relayLibp2p2.peerId) // Peer not added as listen relay diff --git a/test/relay/relay.node.js b/test/relay/relay.node.js index f2290a74..803a3849 100644 --- a/test/relay/relay.node.js +++ b/test/relay/relay.node.js @@ -8,7 +8,6 @@ const { Multiaddr } = require('multiaddr') const { collect } = require('streaming-iterables') const pipe = require('it-pipe') const AggregateError = require('aggregate-error') -const PeerId = require('peer-id') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { createPeerId } = require('../utils/creators/peer') @@ -46,14 +45,13 @@ describe('Dialing (via relay, TCP)', () => { return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(libp2p => libp2p.start())) }) - afterEach(() => { + afterEach(async () => { // Stop each node return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(async libp2p => { await libp2p.stop() // Clear the peer stores - for (const peerIdStr of libp2p.peerStore.peers.keys()) { - const peerId = PeerId.createFromB58String(peerIdStr) - libp2p.peerStore.delete(peerId) + for await (const peer of libp2p.peerStore.getPeers()) { + libp2p.peerStore.delete(peer.id) } })) }) diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index 49876a05..f762bbe4 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -2,7 +2,7 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') - +const { MemoryDatastore } = require('datastore-core/memory') const AddressManager = require('../../src/address-manager') const TransportManager = require('../../src/transport-manager') const PeerStore = require('../../src/peer-store') @@ -33,7 +33,10 @@ describe('Transport Manager (TCP)', () => { peerId: localPeer, multiaddrs: addrs, addressManager: new AddressManager({ listen: addrs }), - peerStore: new PeerStore({ peerId: localPeer }) + peerStore: new PeerStore({ + peerId: localPeer, + datastore: new MemoryDatastore() + }) }, upgrader: mockUpgrader, onConnection: () => {} @@ -67,7 +70,7 @@ describe('Transport Manager (TCP)', () => { }) it('should create self signed peer record on listen', async () => { - let signedPeerRecord = await tm.libp2p.peerStore.addressBook.getPeerRecord(localPeer) + let signedPeerRecord = await tm.libp2p.peerStore.addressBook.getRawEnvelope(localPeer) expect(signedPeerRecord).to.not.exist() tm.add(Transport.prototype[Symbol.toStringTag], Transport) diff --git a/test/ts-use/package.json b/test/ts-use/package.json index ac98a7b4..87ec9b5b 100644 --- a/test/ts-use/package.json +++ b/test/ts-use/package.json @@ -2,17 +2,17 @@ "name": "ts-use", "private": true, "dependencies": { - "datastore-level": "^6.0.0", - "ipfs-http-client": "^50.1.2", + "@achingbrain/libp2p-gossipsub": "^0.12.2", + "@chainsafe/libp2p-noise": "^5.0.0", + "datastore-level": "^7.0.1", + "ipfs-http-client": "^55.0.0", "libp2p": "file:../..", - "libp2p-bootstrap": "^0.13.0", + "libp2p-bootstrap": "^0.14.0", "libp2p-delegated-content-routing": "^0.11.0", "libp2p-delegated-peer-routing": "^0.11.1", - "libp2p-gossipsub": "^0.9.0", - "libp2p-interfaces": "^1.0.1", - "libp2p-kad-dht": "^0.26.5", + "libp2p-interfaces": "^4.0.0", + "libp2p-kad-dht": "^0.28.6", "libp2p-mplex": "^0.10.4", - "@chainsafe/libp2p-noise": "^4.1.0", "libp2p-record": "^0.10.4", "libp2p-tcp": "^0.17.1", "libp2p-websockets": "^0.16.1", diff --git a/test/ts-use/src/main.ts b/test/ts-use/src/main.ts index 17c5e4d6..15b927b8 100644 --- a/test/ts-use/src/main.ts +++ b/test/ts-use/src/main.ts @@ -105,8 +105,7 @@ async function main() { }, datastore: new LevelStore('path/to/store'), peerStore: { - persistence: false, - threshold: 5 + persistence: false }, keychain: { pass: 'notsafepassword123456789', diff --git a/test/utils/creators/peer.js b/test/utils/creators/peer.js index c9f213f4..ef93d237 100644 --- a/test/utils/creators/peer.js +++ b/test/utils/creators/peer.js @@ -58,12 +58,13 @@ function _populateAddressBooks (peers) { * @param {Object} [properties] * @param {number} [properties.number] - number of peers (default: 1). * @param {boolean} [properties.fixture] - use fixture for peer-id generation (default: true) + * @param {PeerId.CreateOptions} [properties.opts] * @returns {Promise>} */ -function createPeerId ({ number = 1, fixture = true } = {}) { +function createPeerId ({ number = 1, fixture = true, opts = {} } = {}) { return pTimes(number, (i) => fixture ? PeerId.createFromJSON(Peers[i]) - : PeerId.create() + : PeerId.create(opts) ) } diff --git a/test/utils/mockConnection.js b/test/utils/mockConnection.js index 6c1439aa..55c8d606 100644 --- a/test/utils/mockConnection.js +++ b/test/utils/mockConnection.js @@ -48,7 +48,7 @@ module.exports = async (properties = {}) => { protocol: protocols[0] } }, - close: () => { }, + close: async () => { }, getStreams: () => openStreams, ...properties }) diff --git a/tsconfig.json b/tsconfig.json index 47b635e4..0f357f93 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,8 +10,7 @@ "src/circuit/protocol/index.js", // exclude generated file "src/identify/message.js", // exclude generated file "src/insecure/proto.js", // exclude generated file - "src/peer-store/persistent/pb/address-book.js", // exclude generated file - "src/peer-store/persistent/pb/proto-book.js", // exclude generated file + "src/peer-store/pb/peer.js", // exclude generated file "src/record/peer-record/peer-record.js", // exclude generated file "src/record/envelope/envelope.js" // exclude generated file ]