Compare commits

...

108 Commits

Author SHA1 Message Date
Vasco Santos
d163ffd224 chore: release version v0.31.4 2021-05-12 17:05:35 +02:00
Vasco Santos
b29d6c9304 chore: update contributors 2021-05-12 17:05:35 +02:00
zeim839
890dd05941 replaced libp2p with node (#942)
Co-authored-by: zeim839 <kassissahil@gmail.com>
2021-05-12 16:47:29 +02:00
zeim839
a79c6b50d7 fix: peerRouting.findPeer() trying to find self (#941)
* throw error if node attempts to find itself

Co-authored-by: Vasco Santos <vasco.santos@ua.pt>
2021-05-12 16:46:20 +02:00
Vasco Santos
d372a68692 chore: add github issue templates (#938) 2021-05-05 15:06:56 +02:00
Vasco Santos
4e3fc19623 chore: release version v0.31.3 2021-05-04 12:23:58 +02:00
Vasco Santos
2fa82b387c chore: update contributors 2021-05-04 12:23:57 +02:00
Alex Potsides
8fc6f8af81 chore: update ipfs-utils dep (#937) 2021-05-04 12:14:11 +02:00
Vasco Santos
924585b143 chore: release version v0.31.2 2021-04-30 15:45:45 +02:00
Vasco Santos
556f0203db chore: update contributors 2021-04-30 15:45:44 +02:00
Vasco Santos
b5a9eb2087 fix: moving averages record types (#935) 2021-04-30 15:42:34 +02:00
Vasco Santos
e5187d02ba chore: release version v0.31.1 2021-04-30 13:40:29 +02:00
Vasco Santos
150e4f97c1 chore: update contributors 2021-04-30 13:40:28 +02:00
Vasco Santos
302bb90058 fix: event emitter and interfaces types for discovery and routing (#934) 2021-04-30 13:20:12 +02:00
Vasco Santos
f860ffb3e7 chore: add migration guide to 0.31 (#912) 2021-04-30 09:30:41 +02:00
Vasco Santos
2572f3e034 chore: update libp2p dht and gossipsub to releases (#933) 2021-04-29 10:10:57 +02:00
Vasco Santos
d76356e56a chore: release version v0.31.0 2021-04-28 15:39:48 +02:00
Vasco Santos
e9543eb2e1 chore: update contributors 2021-04-28 15:39:48 +02:00
Vasco Santos
5282708263 chore: release version v0.31.0-rc.7 2021-04-27 17:18:12 +02:00
Vasco Santos
ed494f03ae chore: update contributors 2021-04-27 17:18:12 +02:00
Vasco Santos
ac370fc967 fix: address book guarantees no replicated entries are added (#927) 2021-04-27 17:14:48 +02:00
Vasco Santos
ef4393649f chore: remove chai deps (#929) 2021-04-27 17:13:34 +02:00
Vasco Santos
f23fd4b7c7 chore: update noise (#926) 2021-04-27 13:24:31 +02:00
Vasco Santos
5372f7af2f chore: release version v0.31.0-rc.6 2021-04-22 10:09:18 +02:00
Vasco Santos
97da0ba740 chore: update contributors 2021-04-22 10:09:18 +02:00
Vasco Santos
88b04156bf fix: keychain optional pw and use interfaces for validators and selectors instead (#924) 2021-04-22 09:53:55 +02:00
Vasco Santos
64f3af897b chore: release version v0.31.0-rc.5 2021-04-21 15:01:31 +02:00
Vasco Santos
54e502afcb chore: update contributors 2021-04-21 15:01:31 +02:00
Alex Potsides
086b0ec0df fix: demand pubsub subclass instead of pubsub instance (#922)
Changes the `Libp2pModules.pubsub` property to be a class that maybe
extends `PubsubBaseProtocol` instead of an instance of that class.
2021-04-21 14:55:21 +02:00
Vasco Santos
cc1f4af879 fix: dht configuration selectors and validators (#919) 2021-04-21 13:28:37 +02:00
Franck Royer
6456a0fff8 chore: browser example dependencies must be installed at the root first (#921)
Fixes #920
2021-04-21 10:45:50 +02:00
Vasco Santos
44463b9145 fix: address book should not emit peer event if no addresses are known #887 2021-04-21 09:14:01 +02:00
Vasco Santos
7eb2cea570 chore: release version v0.31.0-rc.4 2021-04-20 17:14:17 +02:00
Vasco Santos
c381be3510 chore: update contributors 2021-04-20 17:14:16 +02:00
Alex Potsides
975e77991e fix: add clientMode dht arg and upgrade interface-datastore (#918) 2021-04-20 17:11:23 +02:00
Vasco Santos
b043bca607 fix: specify pbjs root (#917) 2021-04-20 16:25:48 +02:00
Vasco Santos
3ffeb4ebe6 chore: apply suggestions from code review
Co-authored-by: Irakli Gozalishvili <contact@gozala.io>
2021-04-20 11:41:50 +02:00
Vasco Santos
06e8f3dd42 fix: do not add abort signals to useless addresses (#913) 2021-04-20 09:31:16 +02:00
Vasco Santos
3f7dde3e13 chore: release version v0.31.0-rc.3 2021-04-19 14:12:25 +02:00
Vasco Santos
6f4e7ceeac chore: update contributors 2021-04-19 14:12:25 +02:00
Alex Potsides
2af692fb4d fix: remove inline arg types from function definitions (#916)
Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>
2021-04-19 14:08:00 +02:00
Vasco Santos
906315ce73 chore: release version v0.31.0-rc.2 2021-04-16 17:12:57 +02:00
Vasco Santos
49f04cbe70 chore: update contributors 2021-04-16 17:12:57 +02:00
Alex Potsides
3d0a79eff3 fix: metrics stats and moving averages types (#915)
* fix: give stats initial values

Otherwise the compiler cannot derive the type and thinks `stats.snapshot` returns `{}`

* fix: add type shape to moving averages as well
2021-04-16 17:10:22 +02:00
Vasco Santos
55ee332907 chore: release version v0.31.0-rc.1 2021-04-16 15:55:22 +02:00
Vasco Santos
ffe122d47e chore: update contributors 2021-04-16 15:55:22 +02:00
Vasco Santos
21c9aeecb1 fix: dial protocol should throw if no protocol is provided (#914)
BREAKING CHANGE: dialProtocol does not return connection when no protocols are provided
2021-04-16 15:49:57 +02:00
Vasco Santos
a93cca9178 chore: update release template with examples testing (#911) 2021-04-16 09:27:44 +02:00
Vasco Santos
828a32d4f5 chore: add jsdoc 2021-04-15 14:38:25 +02:00
Vasco Santos
b4fb9b7bf2 fix: address book should not emit peer event if no addresses are known 2021-04-15 14:19:59 +02:00
Vasco Santos
0aed9fe1b3 chore: release version v0.31.0-rc.0 2021-04-15 09:52:56 +02:00
Vasco Santos
9d1b917e8a chore: update contributors 2021-04-15 09:52:55 +02:00
Vasco Santos
8506414ea1 chore: config types and dependencies update (#904)
BREAKING CHANGES:

top level types were updated, multiaddr@9.0.0 is used, dialer and keychain internal property names changed and connectionManager minPeers is not supported anymore
2021-04-15 09:40:02 +02:00
Vasco Santos
8e1743cac4 chore: release version v0.30.12 2021-03-27 13:43:36 +01:00
Vasco Santos
3ea95ce642 chore: update contributors 2021-03-27 13:43:35 +01:00
Alex Potsides
c4cae29ef3 chore: do not look up external IP during test (#906)
The NAT manager test will throw if the current computer is behind a
double NAT as at runtime it won't be possible to map external ports.

We don't care about that during the test so configure a fake external
IP so the test will still test the shut-down functionality on a computer
that is behind a double NAT.
2021-03-27 13:15:35 +01:00
Alex Potsides
a7128f07ec fix: the API of es6-promisify is not the same as promisify-es6 (#905) 2021-03-26 18:13:07 +01:00
Vasco Santos
c3e147df6b chore: delegates config md properties for client (#903) 2021-03-24 09:35:17 +01:00
Vasco Santos
b89445274d chore: release version v0.30.11 2021-03-23 09:51:32 +01:00
Vasco Santos
b9e3bcd91e chore: update contributors 2021-03-23 09:51:32 +01:00
Vasco Santos
f5c1cd1fb0 fix: interface-datastore update 2021-03-23 09:39:28 +01:00
Vasco Santos
975e4b0fe0 chore: increase bundle size 2021-03-23 09:39:28 +01:00
Vasco Santos
9504f1951a fix: connection direction should be only inbound or outbound 2021-03-23 09:39:28 +01:00
Vasco Santos
8e1fc78353 chore: release version v0.30.10 2021-03-09 18:52:37 +01:00
Vasco Santos
8895a092b6 chore: update contributors 2021-03-09 18:52:37 +01:00
TJKoury
f2f361998d chore: swap promisify to maintained package (#896)
Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>
2021-03-09 16:55:38 +01:00
Vasco Santos
5f702f3481 fix: conn mgr access to moving averages record object (#897)
* fix: conn mgr access to moving averages record object

* chore: remove node 12

* chore: add parcel workaround
2021-03-09 16:51:41 +01:00
Philipp Muens
03b34cac7d docs: fix link to connection encryption example (#894) 2021-03-02 13:07:52 +01:00
Aleksei
9c67364caa Add an example of webrtc-direct (#868)
Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>
2021-02-25 16:34:02 +01:00
Vasco Santos
a1424826e7 chore: release version v0.30.9 2021-02-25 15:31:26 +01:00
Vasco Santos
3d5bba070b chore: update contributors 2021-02-25 15:31:26 +01:00
Vasco Santos
3f314d5e90 fix: transport manager fault tolerance should include tolerance to transport listen fail (#893) 2021-02-25 15:23:07 +01:00
Miguel Mota
4ee3e1973b chore: minor grammar fixes on discovery example (#890) 2021-02-18 11:36:35 +01:00
Vasco Santos
fc6558b897 chore: release version v0.30.8 2021-02-11 14:57:43 +01:00
Vasco Santos
3e302570e5 chore: update contributors 2021-02-11 14:57:43 +01:00
Vasco Santos
a34d2bbcc3 fix: routers should only use dht if enabled (#885) 2021-02-11 14:37:34 +01:00
Vasco Santos
9941414a91 chore: update delegates config docs to use http client (#853) 2021-02-11 11:42:10 +01:00
Vasco Santos
46cb46188a chore: add discovery example with relay and pubsub discovery (#855) 2021-02-11 11:37:11 +01:00
Vasco Santos
1af8472dc6 chore: add transports example (#851) 2021-02-11 11:12:23 +01:00
Vasco Santos
f6a4cad827 chore: add pubsub example tests (#850) 2021-02-10 21:00:40 +01:00
Vasco Santos
b1079474de chore: add protocol and stream muxing example tests (#849) 2021-02-10 15:40:19 +01:00
Vasco Santos
a150ea60c5 chore: add peer and content routing example tests (#848) 2021-02-08 11:03:42 +01:00
Vasco Santos
aec8e3d3bb chore: release version v0.30.7 2021-02-01 18:40:05 +01:00
Vasco Santos
3abf4aeb35 chore: update contributors 2021-02-01 18:40:05 +01:00
Alex Potsides
a36b2112aa fix: do not add observed address received from peers (#882) 2021-02-01 18:32:57 +01:00
Vasco Santos
8d3b61710a chore: release version v0.30.6 2021-01-29 14:39:41 +01:00
Vasco Santos
5dbbeef311 chore: update contributors 2021-01-29 14:39:41 +01:00
Vasco Santos
3e7594f697 fix: peer discovery type in config (#878)
to any
2021-01-29 14:32:13 +01:00
Alex Potsides
ce2a624a09 fix: unref nat manager retries (#877)
The retry operation in the NAT Manager can prevent node from shutting
down, so unref the retries so they don't keep adding work to the
event loop.
2021-01-29 14:09:59 +01:00
Vasco Santos
a64c02838c chore: release version v0.30.5 2021-01-28 16:50:46 +01:00
Vasco Santos
74d07e5e8c chore: update contributors 2021-01-28 16:50:46 +01:00
Vasco Santos
eeda056883 fix: create has optional peer id type (#875) 2021-01-28 16:41:04 +01:00
Kevin Lacker
f06e06a006 chore: update bootstrapers example url 2021-01-28 11:06:11 +01:00
Vasco Santos
28f52bbf75 chore: release version v0.30.4 2021-01-27 15:23:56 +01:00
Vasco Santos
ed5f8f853f chore: update contributors 2021-01-27 15:23:56 +01:00
Alex Potsides
0a6bc0d101 feat: add UPnP NAT manager (#810)
* feat: add uPnP nat manager

Adds a really basic nat manager that attempts to use UPnP to punch
a hole through your router for any IPV4 tcp addresses you have
configured.

Adds any configured addresses to the node's observed addresses list
and adds observed addresses to `libp2p.multiaddrs` so we exchange
them with peers when performing `identify` and people can dial you.

Adds configuration options under `config.nat`

Hole punching is async to not affect start up time.

Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>
2021-01-27 14:55:26 +01:00
Vasco Santos
b5c9e48b68 chore: release version v0.30.3 2021-01-27 14:33:33 +01:00
Vasco Santos
9942cbd50c chore: update contributors 2021-01-27 14:33:32 +01:00
Vasco Santos
037c965a67 chore: update deps (#869) 2021-01-27 09:45:31 +01:00
Vasco Santos
748b552876 chore: pnet example test (#845) 2021-01-22 10:24:15 +01:00
Vasco Santos
961b48bb8d chore: release version v0.30.2 2021-01-21 13:50:49 +01:00
Vasco Santos
000826db21 chore: update contributors 2021-01-21 13:50:48 +01:00
Alex Potsides
45c33675a7 fix: store multiaddrs during content and peer routing queries (#865)
* fix: store provider multiaddrs during find providers

Changes the behaviour of `libp2p.contentRouting.findProviders` to store
the multiaddrs reported by the routers before yielding results to
the caller, so when they try to dial the provider, the multiaddrs are
already in the peer store's address book.

Also dedupes providers reported by routers but keeps all of the addresses
reported, even for duplicates.

Also, also fixes a performance bug where the previous implementation would
wait for any router to completely finish finding providers before sending
any results to the caller.  It'll now yield results as they come in which
makes it much, much faster.
2021-01-21 13:41:27 +01:00
Samlior
a28c878f4a chore: fix close for ConnectionManager (#861) 2021-01-21 12:09:53 +01:00
Vasco Santos
67067c97d5 chore: connection encryption example test (#843) 2021-01-21 09:27:27 +01:00
Vasco Santos
f45cd1c4b5 chore: echo example test (#842) 2021-01-20 10:46:04 +01:00
Vasco Santos
0a02207116 chore: add discovery example tests (#841) 2021-01-19 11:02:56 +01:00
Vasco Santos
0b854a949f chore: add browser example test (#846) 2021-01-19 09:57:56 +01:00
167 changed files with 7759 additions and 986 deletions

View File

@@ -1,5 +1,6 @@
'use strict'
const path = require('path')
const Libp2p = require('./src')
const { MULTIADDRS_WEBSOCKETS } = require('./test/fixtures/browser')
const Peers = require('./test/fixtures/peers')
@@ -31,6 +32,9 @@ const before = async () => {
enabled: true,
active: false
}
},
nat: {
enabled: false
}
}
})
@@ -44,16 +48,23 @@ const after = async () => {
await libp2p.stop()
}
/** @type {import('aegir').Options["build"]["config"]} */
const esbuild = {
inject: [path.join(__dirname, './scripts/node-globals.js')]
}
/** @type {import('aegir').PartialOptions} */
module.exports = {
bundlesize: { maxSize: '225kB' },
hooks: {
pre: before,
post: after
build: {
bundlesizeMax: '253kB'
},
webpack: {
node: {
// needed by bcrypto
Buffer: true
test: {
before,
after,
browser: {
config: {
buildConfig: esbuild
}
}
}
}

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: libp2p Official Forum
url: https://discuss.libp2p.io
about: For general questions, support requests and discussions

55
.github/ISSUE_TEMPLATE/open_an_issue.md vendored Normal file
View File

@@ -0,0 +1,55 @@
---
name: Open an issue
about: For reporting bugs or errors in the JavaScript libp2p implementation
title: ''
labels: need/triage
assignees: ''
---
<!--
Thank you for reporting an issue.
This issue tracker is for bugs found within the JavaScript implementation of libp2p.
If you are asking a question about how to use libp2p, please ask on https://discuss.libp2p.io
Otherwise please fill in as much of the template below as possible.
-->
- **Version**:
<!--
Check package.json version
-->
- **Platform**:
<!--
Output of `uname -a` (UNIX), or version and 32 or 64-bit (Windows). If using in a Browser, please share the browser version as well
-->
- **Subsystem**:
<!--
If known, please specify affected core module name (e.g Dialer, Pubsub, Relay etc)
-->
#### Severity:
<!--
One of following:
Critical - System crash, application panic.
High - The main functionality of the application does not work, API breakage, repo format breakage, etc.
Medium - A non-essential functionality does not work, performance issues, etc.
Low - An optional functionality does not work.
Very Low - Translation or documentation mistake. Something that won't give anyone a bad day.
-->
#### Description:
<!--
- What you did
- What happened
- What you expected to happen
-->
#### Steps to reproduce the error:
<!--
If possible, please provide code that demonstrates the problem, keeping it as simple and free of external dependencies as you are able
-->

View File

@@ -25,8 +25,6 @@
- [ ] [js-ipfs](https://github.com/ipfs/js-ipfs)
- Documentation
- [ ] Ensure that README.md is up to date
- [ ] Ensure that all the examples run
- [ ] Ensure [libp2p/js-libp2p-examples](https://github.com/libp2p/js-libp2p-examples) is updated
- [ ] Ensure that [libp2p/docs](https://github.com/libp2p/docs) is updated
- Communication
- [ ] Create the release issue

102
.github/workflows/examples.yml vendored Normal file
View File

@@ -0,0 +1,102 @@
name: examples
on:
push:
branches:
- master
pull_request:
branches:
- '**'
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: npx aegir lint
- run: npx aegir ts -p check
- run: npx aegir build
test-auto-relay-example:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: cd examples && yarn && npm run test -- auto-relay
test-chat-example:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: cd examples && yarn && npm run test -- chat
test-connection-encryption-example:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: cd examples && yarn && npm run test -- connection-encryption
test-discovery-mechanisms-example:
needs: check
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: cd examples && yarn && npm run test -- discovery-mechanisms
test-echo-example:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: cd examples && yarn && npm run test -- echo
test-libp2p-in-the-browser-example:
needs: check
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: cd examples && yarn && npm run test -- libp2p-in-the-browser
test-peer-and-content-routing-example:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: cd examples && yarn && npm run test -- peer-and-content-routing
test-pnet-example:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: cd examples && yarn && npm run test -- pnet
test-protocol-and-stream-muxing-example:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: cd examples && yarn && npm run test -- protocol-and-stream-muxing
test-pubsub-example:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: cd examples && yarn && npm run test -- pubsub
test-transports-example:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: cd examples && yarn && npm run test -- transports
test-webrtc-direct-example:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: cd examples && yarn && npm run test -- webrtc-direct

View File

@@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: yarn
- run: yarn lint
- run: npm install
- run: npx aegir lint
- uses: gozala/typescript-error-reporter-action@v1.0.8
- run: yarn build
- run: yarn aegir dep-check
- run: npx aegir build
- run: npx aegir dep-check
- uses: ipfs/aegir/actions/bundle-size@master
name: size
with:
@@ -27,48 +27,41 @@ jobs:
strategy:
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
node: [12, 14]
node: [14]
fail-fast: true
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: yarn
- run: npx nyc --reporter=lcov aegir test -t node -- --bail
- run: npm install
- run: npx aegir test -t node --cov --bail
- uses: codecov/codecov-action@v1
test-chrome:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: yarn
- run: npm install
- run: npx aegir test -t browser -t webworker --bail
test-firefox:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: yarn
- run: npx aegir test -t browser -t webworker --bail -- --browsers FirefoxHeadless
- run: npm install
- run: npx aegir test -t browser -t webworker --bail -- --browser firefox
test-ts:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: npm run test:ts
test-interop:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: yarn
- run: npm install
- run: cd node_modules/interop-libp2p && yarn && LIBP2P_JS=${GITHUB_WORKSPACE}/src/index.js npx aegir test -t node --bail
test-auto-relay-example:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: yarn
- run: cd examples && yarn && npm run test -- auto-relay
test-chat-example:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: yarn
- run: cd examples && yarn && npm run test -- chat

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@ docs
test/repo-tests*
**/bundle.js
.cache
.parcel-cache
# Logs
logs

View File

@@ -1,3 +1,210 @@
## [0.31.4](https://github.com/libp2p/js-libp2p/compare/v0.31.3...v0.31.4) (2021-05-12)
### Bug Fixes
* peerRouting.findPeer() trying to find self ([#941](https://github.com/libp2p/js-libp2p/issues/941)) ([a79c6b5](https://github.com/libp2p/js-libp2p/commit/a79c6b50d7fddbcdb1af53efae922cecad4c9a83))
## [0.31.3](https://github.com/libp2p/js-libp2p/compare/v0.31.2...v0.31.3) (2021-05-04)
## [0.31.2](https://github.com/libp2p/js-libp2p/compare/v0.31.1...v0.31.2) (2021-04-30)
### Bug Fixes
* moving averages record types ([#935](https://github.com/libp2p/js-libp2p/issues/935)) ([b5a9eb2](https://github.com/libp2p/js-libp2p/commit/b5a9eb208763efa027d0b4caae87c515b6f5869b))
## [0.31.1](https://github.com/libp2p/js-libp2p/compare/v0.31.0...v0.31.1) (2021-04-30)
### Bug Fixes
* event emitter and interfaces types for discovery and routing ([#934](https://github.com/libp2p/js-libp2p/issues/934)) ([302bb90](https://github.com/libp2p/js-libp2p/commit/302bb9005891aa06b70a5f354bfac6b2d5a3c3b8))
# [0.31.0](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.7...v0.31.0) (2021-04-28)
# [0.31.0-rc.7](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.6...v0.31.0-rc.7) (2021-04-27)
### Bug Fixes
* address book guarantees no replicated entries are added ([#927](https://github.com/libp2p/js-libp2p/issues/927)) ([ac370fc](https://github.com/libp2p/js-libp2p/commit/ac370fc9679b51da8cee3791b6dd268d0695d136))
# [0.31.0-rc.6](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.5...v0.31.0-rc.6) (2021-04-22)
### Bug Fixes
* keychain optional pw and use interfaces for validators and selectors instead ([#924](https://github.com/libp2p/js-libp2p/issues/924)) ([88b0415](https://github.com/libp2p/js-libp2p/commit/88b04156bf614650c2b14d49b12e969c5eecf04d))
# [0.31.0-rc.5](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.4...v0.31.0-rc.5) (2021-04-21)
### Bug Fixes
* address book should not emit peer event if no addresses are known ([b4fb9b7](https://github.com/libp2p/js-libp2p/commit/b4fb9b7bf266ba03c4462c0a41b1c2691e4e88d4))
* demand pubsub subclass instead of pubsub instance ([#922](https://github.com/libp2p/js-libp2p/issues/922)) ([086b0ec](https://github.com/libp2p/js-libp2p/commit/086b0ec0df2fac93845d0a0a6b2e2464e869afcd))
* dht configuration selectors and validators ([#919](https://github.com/libp2p/js-libp2p/issues/919)) ([cc1f4af](https://github.com/libp2p/js-libp2p/commit/cc1f4af879a58e94538591851d0085ff98cd2641))
# [0.31.0-rc.4](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.3...v0.31.0-rc.4) (2021-04-20)
### Bug Fixes
* add clientMode dht arg and upgrade interface-datastore ([#918](https://github.com/libp2p/js-libp2p/issues/918)) ([975e779](https://github.com/libp2p/js-libp2p/commit/975e77991e67dd9bff790b83df7bd6fa5ddcfc67))
* do not add abort signals to useless addresses ([#913](https://github.com/libp2p/js-libp2p/issues/913)) ([06e8f3d](https://github.com/libp2p/js-libp2p/commit/06e8f3dd42432e4b37ab7904b02abde7d1cadda3))
* specify pbjs root ([#917](https://github.com/libp2p/js-libp2p/issues/917)) ([b043bca](https://github.com/libp2p/js-libp2p/commit/b043bca607565cf534771e6cf975288a8ff3030b))
# [0.31.0-rc.3](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.2...v0.31.0-rc.3) (2021-04-19)
### Bug Fixes
* remove inline arg types from function definitions ([#916](https://github.com/libp2p/js-libp2p/issues/916)) ([2af692f](https://github.com/libp2p/js-libp2p/commit/2af692fb4de572168524ae684608fc6526de4ef7))
# [0.31.0-rc.2](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.1...v0.31.0-rc.2) (2021-04-16)
### Bug Fixes
* metrics stats and moving averages types ([#915](https://github.com/libp2p/js-libp2p/issues/915)) ([3d0a79e](https://github.com/libp2p/js-libp2p/commit/3d0a79eff3bc34a5bdc8ffa31e9b09345a02ad9d))
# [0.31.0-rc.1](https://github.com/libp2p/js-libp2p/compare/v0.31.0-rc.0...v0.31.0-rc.1) (2021-04-16)
### Bug Fixes
* dial protocol should throw if no protocol is provided ([#914](https://github.com/libp2p/js-libp2p/issues/914)) ([21c9aee](https://github.com/libp2p/js-libp2p/commit/21c9aeecb13440238aa6b0fb5a6731d2f87d4938))
### BREAKING CHANGES
* dialProtocol does not return connection when no protocols are provided
# [0.31.0-rc.0](https://github.com/libp2p/js-libp2p/compare/v0.30.12...v0.31.0-rc.0) (2021-04-15)
## [0.30.12](https://github.com/libp2p/js-libp2p/compare/v0.30.11...v0.30.12) (2021-03-27)
### Bug Fixes
* the API of es6-promisify is not the same as promisify-es6 ([#905](https://github.com/libp2p/js-libp2p/issues/905)) ([a7128f0](https://github.com/libp2p/js-libp2p/commit/a7128f07ec8d4b729145ecfc6ad1d585ffddea46))
## [0.30.11](https://github.com/libp2p/js-libp2p/compare/v0.30.10...v0.30.11) (2021-03-23)
### Bug Fixes
* connection direction should be only inbound or outbound ([9504f19](https://github.com/libp2p/js-libp2p/commit/9504f1951a3cca55bb7b4e25e4934e4024034ee8))
* interface-datastore update ([f5c1cd1](https://github.com/libp2p/js-libp2p/commit/f5c1cd1fb07bc73cf9d9da3c2eb4327bed4279a4))
## [0.30.10](https://github.com/libp2p/js-libp2p/compare/v0.30.9...v0.30.10) (2021-03-09)
### Bug Fixes
* conn mgr access to moving averages record object ([#897](https://github.com/libp2p/js-libp2p/issues/897)) ([5f702f3](https://github.com/libp2p/js-libp2p/commit/5f702f3481afd4ad4fbc89f0e9b75a6d56b03520))
## [0.30.9](https://github.com/libp2p/js-libp2p/compare/v0.30.8...v0.30.9) (2021-02-25)
### Bug Fixes
* transport manager fault tolerance should include tolerance to transport listen fail ([#893](https://github.com/libp2p/js-libp2p/issues/893)) ([3f314d5](https://github.com/libp2p/js-libp2p/commit/3f314d5e90f74583b721386d0c9c5d8363cd4de7))
## [0.30.8](https://github.com/libp2p/js-libp2p/compare/v0.30.7...v0.30.8) (2021-02-11)
### Bug Fixes
* routers should only use dht if enabled ([#885](https://github.com/libp2p/js-libp2p/issues/885)) ([a34d2bb](https://github.com/libp2p/js-libp2p/commit/a34d2bbcc3d69ec3006137a909a7e8c53b9d378e))
## [0.30.7](https://github.com/libp2p/js-libp2p/compare/v0.30.6...v0.30.7) (2021-02-01)
### Bug Fixes
* do not add observed address received from peers ([#882](https://github.com/libp2p/js-libp2p/issues/882)) ([a36b211](https://github.com/libp2p/js-libp2p/commit/a36b2112aafcee309a02de0cff5440cf69cd53a7))
## [0.30.6](https://github.com/libp2p/js-libp2p/compare/v0.30.5...v0.30.6) (2021-01-29)
### Bug Fixes
* peer discovery type in config ([#878](https://github.com/libp2p/js-libp2p/issues/878)) ([3e7594f](https://github.com/libp2p/js-libp2p/commit/3e7594f69733bf374b374a6065458fa6cae81c5f))
* unref nat manager retries ([#877](https://github.com/libp2p/js-libp2p/issues/877)) ([ce2a624](https://github.com/libp2p/js-libp2p/commit/ce2a624a09b3107c0b2b4752e666804ecea54fb5))
## [0.30.5](https://github.com/libp2p/js-libp2p/compare/v0.30.4...v0.30.5) (2021-01-28)
### Bug Fixes
* create has optional peer id type ([#875](https://github.com/libp2p/js-libp2p/issues/875)) ([eeda056](https://github.com/libp2p/js-libp2p/commit/eeda05688330c17b810bf47544ef977386623317))
## [0.30.4](https://github.com/libp2p/js-libp2p/compare/v0.30.3...v0.30.4) (2021-01-27)
### Features
* add UPnP NAT manager ([#810](https://github.com/libp2p/js-libp2p/issues/810)) ([0a6bc0d](https://github.com/libp2p/js-libp2p/commit/0a6bc0d1013dfd80ab600e8f74c1544b433ece29))
## [0.30.3](https://github.com/libp2p/js-libp2p/compare/v0.30.2...v0.30.3) (2021-01-27)
## [0.30.2](https://github.com/libp2p/js-libp2p/compare/v0.30.1...v0.30.2) (2021-01-21)
### Bug Fixes
* store multiaddrs during content and peer routing queries ([#865](https://github.com/libp2p/js-libp2p/issues/865)) ([45c3367](https://github.com/libp2p/js-libp2p/commit/45c33675a7412c66d0fd4e113ef8506077b6f492))
## [0.30.1](https://github.com/libp2p/js-libp2p/compare/v0.30.0...v0.30.1) (2021-01-18)

View File

@@ -2055,6 +2055,15 @@ This event will be triggered anytime we are disconnected from another peer, rega
- `peerId`: instance of [`PeerId`][peer-id]
- `protocols`: array of known, supported protocols for the peer (string identifiers)
### libp2p.addressManager
#### Our addresses have changed
This could be in response to a peer telling us about addresses they have observed, or
the NatManager performing NAT hole punching.
`libp2p.addressManager.on('change:addresses', () => {})`
## Types
### Stats

View File

@@ -28,6 +28,9 @@
- [Configuring Metrics](#configuring-metrics)
- [Configuring PeerStore](#configuring-peerstore)
- [Customizing Transports](#customizing-transports)
- [Configuring the NAT Manager](#configuring-the-nat-manager)
- [Browser support](#browser-support)
- [UPnP and NAT-PMP](#upnp-and-nat-pmp)
- [Configuration examples](#configuration-examples)
## Overview
@@ -388,6 +391,7 @@ const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const ipfsHttpClient = require('ipfs-http-client')
const DelegatedPeerRouter = require('libp2p-delegated-peer-routing')
const DelegatedContentRouter = require('libp2p-delegated-content-routing')
const PeerId = require('peer-id')
@@ -395,17 +399,25 @@ const PeerId = require('peer-id')
// create a peerId
const peerId = await PeerId.create()
const delegatedPeerRouting = new DelegatedPeerRouter(ipfsHttpClient({
host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates
protocol: 'https',
port: 443
}))
const delegatedContentRouting = new DelegatedContentRouter(peerId, ipfsHttpClient({
host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates
protocol: 'https',
port: 443
}))
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [NOISE],
contentRouting: [
new DelegatedContentRouter(peerId)
],
peerRouting: [
new DelegatedPeerRouter()
],
contentRouting: [delegatedContentRouting],
peerRouting: [delegatedPeerRouting],
},
peerId,
peerRouting: { // Peer routing configuration
@@ -503,7 +515,7 @@ const node = await Libp2p.create({
}
})
await libp2p.loadKeychain()
await node.loadKeychain()
```
#### Configuring Dialing
@@ -587,7 +599,7 @@ const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const { FaultTolerance } = require('libp2p/src/transport-manager')}
const { FaultTolerance } = require('libp2p/src/transport-manager')
const node = await Libp2p.create({
modules: {
@@ -733,6 +745,40 @@ const node = await Libp2p.create({
})
```
#### Configuring the NAT Manager
Network Address Translation (NAT) is a function performed by your router to enable multiple devices on your local network to share a single IPv4 address. It's done transparently for outgoing connections, ensuring the correct response traffic is routed to your computer, but if you wish to accept incoming connections some configuration is necessary.
The NAT manager can be configured as follows:
```js
const node = await Libp2p.create({
config: {
nat: {
description: 'my-node', // set as the port mapping description on the router, defaults the current libp2p version and your peer id
enabled: true, // defaults to true
gateway: '192.168.1.1', // leave unset to auto-discover
externalIp: '80.1.1.1', // leave unset to auto-discover
ttl: 7200, // TTL for port mappings (min 20 minutes)
keepAlive: true, // Refresh port mapping after TTL expires
pmp: {
enabled: false, // defaults to false
}
}
}
})
```
##### Browser support
Browsers cannot open TCP ports or send the UDP datagrams necessary to configure external port mapping - to accept incoming connections in the browser please use a WebRTC transport.
##### UPnP and NAT-PMP
By default under nodejs libp2p will attempt to use [UPnP](https://en.wikipedia.org/wiki/Universal_Plug_and_Play) to configure your router to allow incoming connections to any TCP transports that have been configured.
[NAT-PMP](http://miniupnp.free.fr/nat-pmp.html) is a feature of some modern routers which performs a similar job to UPnP. NAT-PMP is disabled by default, if enabled libp2p will try to use NAT-PMP and will fall back to UPnP if it fails.
## Configuration examples
As libp2p is designed to be a modular networking library, its usage will vary based on individual project needs. We've included links to some existing project configurations for your reference, in case you wish to replicate their configuration:

View File

@@ -0,0 +1,123 @@
<!--Specify versions for migration below-->
# Migrating to libp2p@31
A migration guide for refactoring your application code from libp2p v0.30.x to v0.31.0.
## Table of Contents
- [Types](#types)
- [API](#api)
- [Module Updates](#module-updates)
## Types
Most of the type definitions in the libp2p configuration were `any` or were not included before this release. This might cause breaking changes on upstream projects relying on the previous provided types, as well as to libp2p modules implemented by the libp2p community.
## API
### Core API
`libp2p.dialProtocol` does not accept empty or null protocols returning a connection anymore and `dial` must be used instead.
```js
const connection = await libp2p.dialProtocol(peerId)
```
**After**
```js
const connection = await libp2p.dial(peerId)
```
### Connection Manager Options
We updated the connection manager options naming in `libp2p@0.29` but kept it backward compatible until now.
**Before**
```js
const node = await Libp2p.create({
connectionManager: {
minPeers: 0
}
})
```
**After**
```js
const node = await Libp2p.create({
connectionManager: {
minConnections: 0
}
})
```
You can see full details on how to configure the connection manager [here](https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md#configuring-connection-manager).
### Dialer and Keychain components
Internal property names to create a libp2p `Dialer` and `Keychain` were updated to reflect the properties naming in the libp2p configuration. These are internal modules of libp2p core and should not impact most of the users, but as it is possible to use them separately here follow the changes:
***Before**
```js
const dialer = new Dialer({
transportManager,
peerStore,
concurrency,
perPeerLimit,
timeout,
resolvers,
addressSorter
})
const keychain = new Keychain(datastore, {
passPhrase
})
```
**After**
```js
this.dialer = new Dialer({
transportManager,
peerStore,
maxParallelDials,
maxDialsPerPeer,
dialTimeout,
resolvers,
addressSorter
})
const keychain = new Keychain(datastore, {
pass
})
```
## Module Updates
With this release you should update the following libp2p modules if you are relying on them:
<!--Specify module versions in JSON for migration below.
It's recommended to check package.json changes for this:
`git diff <release> <prev> -- package.json`
-->
```json
"libp2p-bootstrap": "^0.12.3",
"libp2p-crypto": "^0.19.4",
"libp2p-interfaces": "^0.10.0",
"libp2p-delegated-content-routing": "^0.10.0",
"libp2p-delegated-peer-routing": "^0.9.0",
"libp2p-floodsub": "^0.25.1",
"libp2p-gossipsub": "^0.9.0",
"libp2p-kad-dht": "^0.22.0",
"libp2p-mdns": "^0.16.0",
"libp2p-noise": "^3.0.0",
"libp2p-tcp": "^0.15.4",
"libp2p-webrtc-star": "^0.22.2",
"libp2p-websockets": "^0.15.6"
```
One of the main changes in this new release is the update to `multiaddr@9.0.0`. This should also be updated in upstream projects to avoid several multiaddr versions in the bundle and to avoid potential problems when libp2p interacts with provided outdated multiaddr instances.

View File

@@ -8,7 +8,7 @@ Let us know if you find any issues, or if you want to contribute and add a new t
- [Transports](./transports)
- [Protocol and Stream Muxing](./protocol-and-stream-muxing)
- [Encrypted Communications](./encrypted-communications)
- [Connection Encryption](./connection-encryption)
- [Discovery Mechanisms](./discovery-mechanisms)
- [Peer and Content Routing](./peer-and-content-routing)
- [PubSub](./pubsub)

View File

@@ -1,6 +1,6 @@
'use strict'
const Libp2p = require('../../')
const Libp2p = require('../..')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')

View File

@@ -1,4 +1,4 @@
# Encrypted Communications
# Connection Encryption
libp2p can leverage the encrypted communications from the transports it uses (i.e WebRTC). To ensure that every connection is encrypted, independently of how it was set up, libp2p also supports a set of modules that encrypt every communication established.

View File

@@ -0,0 +1,30 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pDefer = require('p-defer')
const uint8ArrayToString = require('uint8arrays/to-string')
async function test () {
const messageReceived = pDefer()
process.stdout.write('1.js\n')
const proc = execa('node', [path.join(__dirname, '1.js')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const s = uint8ArrayToString(data)
if (s.includes('This information is sent out encrypted to the other peer')) {
messageReceived.resolve()
}
})
await messageReceived.promise
proc.kill()
}
module.exports = test

View File

@@ -7,15 +7,7 @@ const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const Bootstrap = require('libp2p-bootstrap')
// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/config-nodejs.json
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'
]
const bootstrapers = require('./bootstrapers')
;(async () => {
const node = await Libp2p.create({

View File

@@ -0,0 +1,68 @@
/* eslint-disable no-console */
'use strict'
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const Gossipsub = require('libp2p-gossipsub')
const Bootstrap = require('libp2p-bootstrap')
const PubsubPeerDiscovery = require('libp2p-pubsub-peer-discovery')
const createRelayServer = require('libp2p-relay-server')
const createNode = async (bootstrapers) => {
const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE],
pubsub: Gossipsub,
peerDiscovery: [Bootstrap, PubsubPeerDiscovery]
},
config: {
peerDiscovery: {
[PubsubPeerDiscovery.tag]: {
interval: 1000,
enabled: true
},
[Bootstrap.tag]: {
enabled: true,
list: bootstrapers
}
}
}
})
return node
}
;(async () => {
const relay = await createRelayServer({
listenAddresses: ['/ip4/0.0.0.0/tcp/0']
})
console.log(`libp2p relay starting with id: ${relay.peerId.toB58String()}`)
await relay.start()
const relayMultiaddrs = relay.multiaddrs.map((m) => `${m.toString()}/p2p/${relay.peerId.toB58String()}`)
const [node1, node2] = await Promise.all([
createNode(relayMultiaddrs),
createNode(relayMultiaddrs)
])
node1.on('peer:discovery', (peerId) => {
console.log(`Peer ${node1.peerId.toB58String()} discovered: ${peerId.toB58String()}`)
})
node2.on('peer:discovery', (peerId) => {
console.log(`Peer ${node2.peerId.toB58String()} discovered: ${peerId.toB58String()}`)
})
;[node1, node2].forEach((node, index) => console.log(`Node ${index} starting with id: ${node.peerId.toB58String()}`))
await Promise.all([
node1.start(),
node2.start()
])
})();

View File

@@ -2,13 +2,13 @@
A Peer Discovery module enables libp2p to find peers to connect to. Think of these mechanisms as ways to join the rest of the network, as railing points.
With these system, a libp2p node can both have a set of nodes to always connect on boot (bootstraper nodes), discover nodes through locality (e.g connected in the same LAN) or through serendipity (random walks on a DHT).
With this system, a libp2p node can both have a set of nodes to always connect on boot (bootstraper nodes), discover nodes through locality (e.g connected in the same LAN) or through serendipity (random walks on a DHT).
These mechanisms save configuration and enable a node to operate without any explicit dials, it will just work. Once new peers are discovered, their known data is stored in the peer's PeerStore.
## 1. Bootstrap list of Peers when booting a node
For this demo, we will connect to IPFS default bootstrapper nodes and so, we will need to support the same set of features those nodes have, that are: TCP, mplex and NOISE. You can see the complete example at [1.js](./1.js).
For this demo, we will connect to IPFS default bootstrapper nodes and so, we will need to support the same set of features those nodes have, that are: TCP, mplex, and NOISE. You can see the complete example at [1.js](./1.js).
First, we create our libp2p node.
@@ -16,7 +16,7 @@ First, we create our libp2p node.
const Libp2p = require('libp2p')
const Bootstrap = require('libp2p-bootstrap')
const node = Libp2p.create({
const node = await Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
@@ -156,10 +156,103 @@ Discovered: QmSSbQpuKrxkoXHm1v4Pi35hPN5hUHMZoBoawEs2Nhvi8m
Discovered: QmRcXXhtG8vTqwVBRonKWtV4ovDoC1Fe56WYtcrw694eiJ
```
## 3. Where to find other Peer Discovery Mechanisms
## 3. Pubsub based Peer Discovery
For this example, we need [`libp2p-pubsub-peer-discovery`](https://github.com/libp2p/js-libp2p-pubsub-peer-discovery/), go ahead and `npm install` it. You also need to spin up a set of [`libp2p-relay-servers`](https://github.com/libp2p/js-libp2p-relay-server). These servers act as relay servers and a peer discovery source.
In the context of this example, we will create and run the `libp2p-relay-server` in the same code snippet. You can find the complete solution at [3.js](./3.js).
You can create your libp2p nodes as follows:
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const Gossipsub = require('libp2p-gossipsub')
const Bootstrap = require('libp2p-bootstrap')
const PubsubPeerDiscovery = require('libp2p-pubsub-peer-discovery')
const createNode = async (bootstrapers) => {
const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE],
pubsub: Gossipsub,
peerDiscovery: [Bootstrap, PubsubPeerDiscovery]
},
config: {
peerDiscovery: {
[PubsubPeerDiscovery.tag]: {
interval: 1000,
enabled: true
},
[Bootstrap.tag]: {
enabled: true,
list: bootstrapers
}
}
}
})
return node
}
```
We will use the `libp2p-relay-server` as bootstrap nodes for the libp2p nodes, so that they establish a connection with the relay after starting. As a result, after they establish a connection with the relay, the pubsub discovery will kick in and the relay will advertise them.
```js
const relay = await createRelayServer({
listenAddresses: ['/ip4/0.0.0.0/tcp/0']
})
console.log(`libp2p relay starting with id: ${relay.peerId.toB58String()}`)
await relay.start()
const relayMultiaddrs = relay.multiaddrs.map((m) => `${m.toString()}/p2p/${relay.peerId.toB58String()}`)
const [node1, node2] = await Promise.all([
createNode(relayMultiaddrs),
createNode(relayMultiaddrs)
])
node1.on('peer:discovery', (peerId) => {
console.log(`Peer ${node1.peerId.toB58String()} discovered: ${peerId.toB58String()}`)
})
node2.on('peer:discovery', (peerId) => {
console.log(`Peer ${node2.peerId.toB58String()} discovered: ${peerId.toB58String()}`)
})
;[node1, node2].forEach((node, index) => console.log(`Node ${index} starting with id: ${node.peerId.toB58String()}`))
await Promise.all([
node1.start(),
node2.start()
])
```
If you run this example, you will see the other peers being discovered.
```bash
> node 3.js
libp2p relay starting with id: QmW6FqVV6RsyoGC5zaeFGW9gSWA3LcBRVZrjkKMruh38Bo
Node 0 starting with id: QmezqDTmEjZ5BfMgVqjSpLY19mVVLTQ9bE9mRpZwtGxL8N
Node 1 starting with id: QmYWeom2odTkm79DzB68NHULqVHDaNDqHhoyqLdcV1fqdv
Peer QmezqDTmEjZ5BfMgVqjSpLY19mVVLTQ9bE9mRpZwtGxL8N discovered: QmW6FqVV6RsyoGC5zaeFGW9gSWA3LcBRVZrjkKMruh38Bo
Peer QmYWeom2odTkm79DzB68NHULqVHDaNDqHhoyqLdcV1fqdv discovered: QmW6FqVV6RsyoGC5zaeFGW9gSWA3LcBRVZrjkKMruh38Bo
Peer QmYWeom2odTkm79DzB68NHULqVHDaNDqHhoyqLdcV1fqdv discovered: QmezqDTmEjZ5BfMgVqjSpLY19mVVLTQ9bE9mRpZwtGxL8N
Peer QmezqDTmEjZ5BfMgVqjSpLY19mVVLTQ9bE9mRpZwtGxL8N discovered: QmYWeom2odTkm79DzB68NHULqVHDaNDqHhoyqLdcV1fqdv
```
Taking into account the output, after the relay and both libp2p nodes start, both libp2p nodes will discover the bootstrap node (relay) and connect with it. After establishing a connection with the relay, they will discover each other.
This is really useful when running libp2p in constrained environments like a browser. You can run a set of `libp2p-relay-server` nodes that will be responsible for both relaying websocket connections between browser nodes and for discovering other browser peers.
## 4. Where to find other Peer Discovery Mechanisms
There are plenty more Peer Discovery Mechanisms out there, you can:
- Find one in [libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star). Yes, a transport with discovery capabilities! This happens because WebRTC requires a rendezvous point for peers to exchange [SDP](https://tools.ietf.org/html/rfc4317) offer, which means we have one or more points that can introduce peers to each other. Think of it as MulticastDNS for the Web, as in MulticastDNS only works in LAN.
- Any DHT will offer you a discovery capability. You can simple _random-walk_ the routing tables to find other peers to connect to. For example [libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) can be used for peer discovery. An example how to configure it to enable random walks can be found [here](https://github.com/libp2p/js-libp2p/blob/v0.28.4/doc/CONFIGURATION.md#customizing-dht).
- Any DHT will offer you a discovery capability. You can simple _random-walk_ the routing tables to find other peers to connect to. For example [libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) can be used for peer discovery. An example of how to configure it to enable random walks can be found [here](https://github.com/libp2p/js-libp2p/blob/v0.28.4/doc/CONFIGURATION.md#customizing-dht).
- You can create your own Discovery service, a registry, a list, a radio beacon, you name it!

View File

@@ -0,0 +1,13 @@
'use strict'
// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-core/src/runtime/config-nodejs.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'
]
module.exports = bootstrapers

View File

@@ -0,0 +1,42 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pWaitFor = require('p-wait-for')
const 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')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const line = 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)
}
})
await pWaitFor(() => discoveredNodes.length === bootstrapers.length && connectedNodes.length === bootstrapers.length)
proc.kill()
}
module.exports = test

View File

@@ -0,0 +1,35 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pWaitFor = require('p-wait-for')
const uint8ArrayToString = require('uint8arrays/to-string')
const discoveredCopy = 'Discovered:'
async function test() {
const discoveredNodes = []
process.stdout.write('2.js\n')
const proc = execa('node', [path.join(__dirname, '2.js')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const line = uint8ArrayToString(data)
if (line.includes(discoveredCopy)) {
const id = line.trim().split(discoveredCopy)[1]
discoveredNodes.push(id)
}
})
await pWaitFor(() => discoveredNodes.length === 2)
proc.kill()
}
module.exports = test

View File

@@ -0,0 +1,35 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pWaitFor = require('p-wait-for')
const uint8ArrayToString = require('uint8arrays/to-string')
const discoveredCopy = 'discovered:'
async function test() {
let discoverCount = 0
process.stdout.write('3.js\n')
const proc = execa('node', [path.join(__dirname, '3.js')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const line = uint8ArrayToString(data)
// Discovered or Connected
if (line.includes(discoveredCopy)) {
discoverCount++
}
})
await pWaitFor(() => discoverCount === 4)
proc.kill()
}
module.exports = test

View File

@@ -0,0 +1,13 @@
'use strict'
const test1 = require('./test-1')
const test2 = require('./test-2')
const test3 = require('./test-3')
async function test () {
await test1()
await test2()
await test3()
}
module.exports = test

View File

@@ -6,7 +6,7 @@
*/
const PeerId = require('peer-id')
const createLibp2p = require('./libp2p-bundle')
const createLibp2p = require('./libp2p')
const pipe = require('it-pipe')
async function run() {

View File

@@ -6,7 +6,7 @@
*/
const PeerId = require('peer-id')
const createLibp2p = require('./libp2p-bundle')
const createLibp2p = require('./libp2p')
const pipe = require('it-pipe')
async function run() {

61
examples/echo/test.js Normal file
View File

@@ -0,0 +1,61 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pDefer = require('p-defer')
const uint8ArrayToString = require('uint8arrays/to-string')
function startProcess(name) {
return execa('node', [path.join(__dirname, name)], {
cwd: path.resolve(__dirname),
all: true
})
}
async function test () {
const listenerReady = pDefer()
const messageReceived = pDefer()
// Step 1 process
process.stdout.write('node listener.js\n')
const listenerProc = startProcess('src/listener.js')
listenerProc.all.on('data', async (data) => {
process.stdout.write(data)
const s = uint8ArrayToString(data)
if (s.includes('Listener ready, listening on:')) {
listenerReady.resolve()
}
})
await listenerReady.promise
process.stdout.write('==================================================================\n')
// Step 2 process
process.stdout.write('node dialer.js\n')
const dialerProc = startProcess('src/dialer.js')
dialerProc.all.on('data', async (data) => {
process.stdout.write(data)
const s = uint8ArrayToString(data)
if (s.includes('received echo:')) {
messageReceived.resolve()
}
})
await messageReceived.promise
process.stdout.write('echo message received\n')
listenerProc.kill()
dialerProc.kill()
await Promise.all([
listenerProc,
dialerProc
]).catch((err) => {
if (err.signal !== 'SIGTERM') {
throw err
}
})
}
module.exports = test

View File

@@ -4,9 +4,13 @@ This example leverages the [Parcel.js bundler](https://parceljs.org/) to compile
## Setup
In order to run the example, first install the dependencies from same directory as this README:
In order to run the example:
- Install dependencey at the root of the js-libp2p repository (if not already done),
- then, install the dependencies from same directory as this README:
```
npm install
cd ./examples/libp2p-in-the-browser
npm install
```

View File

@@ -2,32 +2,33 @@
"name": "libp2p-in-browser",
"version": "1.0.0",
"description": "A libp2p node running in the browser",
"main": "index.js",
"main": "dist/index.html",
"browserslist": [
"last 2 Chrome versions"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "parcel build index.html",
"start": "parcel index.html"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@babel/preset-env": "^7.8.3",
"@babel/preset-env": "^7.13.0",
"libp2p": "../../",
"libp2p-bootstrap": "^0.12.1",
"libp2p-mplex": "^0.10.0",
"libp2p-noise": "^2.0.0",
"libp2p-webrtc-star": "^0.20.0",
"libp2p-websockets": "^0.14.0"
"libp2p-webrtc-star": "^0.22.0",
"libp2p-websockets": "^0.15.0"
},
"devDependencies": {
"@babel/cli": "^7.8.3",
"@babel/core": "^7.8.3",
"@babel/cli": "^7.13.10",
"@babel/core": "^7.13.0",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-transform-regenerator": "^6.26.0",
"babel-polyfill": "^6.26.0",
"parcel-bundler": "^1.12.4"
"parcel": "next"
}
}

View File

@@ -0,0 +1,52 @@
'use strict'
const execa = require('execa')
const { chromium } = require('playwright');
async function run() {
let url = ''
const proc = execa('parcel', ['./index.html'], {
preferLocal: true,
localDir: __dirname,
cwd: __dirname,
all: true
})
proc.all.on('data', async (chunk) => {
/**@type {string} */
const out = chunk.toString()
if (out.includes('Server running at')) {
url = out.split('Server running at ')[1]
}
if (out.includes('Built in')) {
try {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(url);
await page.waitForFunction(selector => document.querySelector(selector).innerText === 'libp2p started!', '#status')
await page.waitForFunction(
selector => {
const text = document.querySelector(selector).innerText
return text.includes('libp2p id is') &&
text.includes('Found peer') &&
text.includes('Connected to')
},
'#output',
{ timeout: 5000 }
)
await browser.close();
} catch (err) {
console.error(err)
process.exit(1)
} finally {
proc.cancel()
}
}
})
}
module.exports = run

View File

@@ -10,7 +10,12 @@
"dependencies": {
"execa": "^2.1.0",
"fs-extra": "^8.1.0",
"libp2p-pubsub-peer-discovery": "^3.0.0",
"libp2p-relay-server": "^0.1.2",
"p-defer": "^3.0.0",
"which": "^2.0.1"
},
"devDependencies": {
"playwright": "^1.7.1"
}
}

View File

@@ -0,0 +1,36 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pWaitFor = require('p-wait-for')
const 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
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const line = uint8ArrayToString(data)
// Discovered peer
if (!foundIt && line.includes('Found it, multiaddrs are:')) {
foundIt = true
}
addrs.push(line)
})
await pWaitFor(() => addrs.length === 2)
proc.kill()
}
module.exports = test

View File

@@ -0,0 +1,40 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pDefer = require('p-defer')
const 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
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const line = uint8ArrayToString(data)
if (line.includes(providedCopy)) {
providedDefer.resolve()
} else if (line.includes(foundCopy)) {
foundDefer.resolve()
}
})
await Promise.all([
providedDefer.promise,
foundDefer.promise
])
proc.kill()
}
module.exports = test

View File

@@ -0,0 +1,11 @@
'use strict'
const test1 = require('./test-1')
const test2 = require('./test-2')
async function test() {
await test1()
await test2()
}
module.exports = test

30
examples/pnet/test.js Normal file
View File

@@ -0,0 +1,30 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pDefer = require('p-defer')
const uint8ArrayToString = require('uint8arrays/to-string')
async function test () {
const messageReceived = pDefer()
process.stdout.write('index.js\n')
const proc = execa('node', [path.join(__dirname, 'index.js')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const s = uint8ArrayToString(data)
if (s.includes('This message is sent on a private network')) {
messageReceived.resolve()
}
})
await messageReceived.promise
proc.kill()
}
module.exports = test

View File

@@ -0,0 +1,31 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pDefer = require('p-defer')
const uint8ArrayToString = require('uint8arrays/to-string')
async function test() {
const messageDefer = pDefer()
process.stdout.write('1.js\n')
const proc = execa('node', [path.join(__dirname, '1.js')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const line = uint8ArrayToString(data)
if (line.includes('my own protocol, wow!')) {
messageDefer.resolve()
}
})
await messageDefer.promise
proc.kill()
}
module.exports = test

View File

@@ -0,0 +1,38 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pWaitFor = require('p-wait-for')
const uint8ArrayToString = require('uint8arrays/to-string')
const messages = [
'protocol (a)',
'protocol (b)',
'another stream on protocol (b)'
]
async function test() {
process.stdout.write('2.js\n')
let count = 0
const proc = execa('node', [path.join(__dirname, '2.js')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const line = uint8ArrayToString(data)
if (messages.find((m) => line.includes(m))) {
count += 1
}
})
await pWaitFor(() => count === messages.length)
proc.kill()
}
module.exports = test

View File

@@ -0,0 +1,37 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pWaitFor = require('p-wait-for')
const uint8ArrayToString = require('uint8arrays/to-string')
const messages = [
'from 1 to 2',
'from 2 to 1'
]
async function test() {
process.stdout.write('3.js\n')
let count = 0
const proc = execa('node', [path.join(__dirname, '3.js')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const line = uint8ArrayToString(data)
if (messages.find((m) => line.includes(m))) {
count += 1
}
})
await pWaitFor(() => count === messages.length)
proc.kill()
}
module.exports = test

View File

@@ -0,0 +1,13 @@
'use strict'
const test1 = require('./test-1')
const test2 = require('./test-2')
const test3 = require('./test-3')
async function test() {
await test1()
await test2()
await test3()
}
module.exports = test

View File

@@ -0,0 +1,67 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pDefer = require('p-defer')
const uint8ArrayToString = require('uint8arrays/to-string')
const stdout = [
{
topic: 'banana',
messageCount: 2
},
{
topic: 'apple',
messageCount: 2
},
{
topic: 'car',
messageCount: 0
},
{
topic: 'orange',
messageCount: 2
},
]
async function test () {
const defer = pDefer()
let topicCount = 0
let topicMessageCount = 0
process.stdout.write('message-filtering/1.js\n')
const proc = execa('node', [path.join(__dirname, '1.js')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
// End
if (topicCount === stdout.length) {
defer.resolve()
proc.all.removeAllListeners('data')
}
process.stdout.write(data)
const line = uint8ArrayToString(data)
if (stdout[topicCount] && line.includes(stdout[topicCount].topic)) {
// Validate previous number of messages
if (topicCount > 0 && topicMessageCount > stdout[topicCount - 1].messageCount) {
defer.reject()
throw new Error(`topic ${stdout[topicCount - 1].topic} had ${topicMessageCount} messages instead of ${stdout[topicCount - 1].messageCount}`)
}
topicCount++
topicMessageCount = 0
} else {
topicMessageCount++
}
})
await defer.promise
proc.kill()
}
module.exports = test

30
examples/pubsub/test-1.js Normal file
View File

@@ -0,0 +1,30 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pDefer = require('p-defer')
const uint8ArrayToString = require('uint8arrays/to-string')
async function test () {
const defer = pDefer()
process.stdout.write('1.js\n')
const proc = execa('node', [path.join(__dirname, '1.js')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const line = uint8ArrayToString(data)
if (line.includes('node1 received: Bird bird bird, bird is the word!')) {
defer.resolve()
}
})
await defer.promise
proc.kill()
}
module.exports = test

11
examples/pubsub/test.js Normal file
View File

@@ -0,0 +1,11 @@
'use strict'
const test1 = require('./test-1')
const testMessageFiltering = require('./message-filtering/test')
async function test() {
await test1()
await testMessageFiltering()
}
module.exports = test

View File

@@ -22,7 +22,6 @@ async function testExample (dir) {
await installDeps(dir)
await build(dir)
await runTest(dir)
// TODO: add browser test setup
}
async function installDeps (dir) {
@@ -89,7 +88,7 @@ async function runTest (dir) {
return
}
const runTest = require(testFile)
const test = require(testFile)
await runTest()
}
await test()
}

View File

@@ -0,0 +1,38 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pDefer = require('p-defer')
const uint8ArrayToString = require('uint8arrays/to-string')
async function test () {
const deferStarted = pDefer()
const deferListen = pDefer()
process.stdout.write('1.js\n')
const proc = execa('node', [path.join(__dirname, '1.js')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const line = uint8ArrayToString(data)
if (line.includes('node has started (true/false): true')) {
deferStarted.resolve()
} else if (line.includes('p2p')) {
deferListen.resolve()
}
})
await Promise.all([
deferStarted.promise,
deferListen.promise
])
proc.kill()
}
module.exports = test

View File

@@ -0,0 +1,30 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pDefer = require('p-defer')
const uint8ArrayToString = require('uint8arrays/to-string')
async function test () {
const defer = pDefer()
process.stdout.write('2.js\n')
const proc = execa('node', [path.join(__dirname, '2.js')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const line = uint8ArrayToString(data)
if (line.includes('Hello p2p world!')) {
defer.resolve()
}
})
await defer.promise
proc.kill()
}
module.exports = test

View File

@@ -0,0 +1,41 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pDefer = require('p-defer')
const uint8ArrayToString = require('uint8arrays/to-string')
async function test () {
const deferNode1 = pDefer()
const deferNode2 = pDefer()
const deferNode3 = pDefer()
process.stdout.write('3.js\n')
const proc = execa('node', [path.join(__dirname, '3.js')], {
cwd: path.resolve(__dirname),
all: true
})
proc.all.on('data', async (data) => {
process.stdout.write(data)
const line = uint8ArrayToString(data)
if (line.includes('node 1 dialed to node 2 successfully')) {
deferNode1.resolve()
} else if (line.includes('node 2 dialed to node 3 successfully')) {
deferNode2.resolve()
} else if (line.includes('node 3 failed to dial to node 1 with:')) {
deferNode3.resolve()
}
})
await Promise.all([
deferNode1.promise,
deferNode2.promise,
deferNode3.promise
])
proc.kill()
}
module.exports = test

View File

@@ -0,0 +1,13 @@
'use strict'
const test1 = require('./test-1')
const test2 = require('./test-2')
const test3 = require('./test-3')
async function test() {
await test1()
await test2()
await test3()
}
module.exports = test

View File

@@ -0,0 +1,33 @@
### Webrtc-direct example
An example that uses [js-libp2p-webrtc-direct](https://github.com/libp2p/js-libp2p-webrtc-direct) for connecting
nodejs libp2p and browser libp2p clients. To run the example:
## 0. Run a nodejs libp2p listener
When in the root folder of this example, type `node listener.js` in terminal. You should see an address that listens for
incoming connections. Below is just an example of such address. In your case the suffix hash (`peerId`) will be different.
```bash
$ node listener.js
Listening on:
/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/QmUKQCzEUhhhobcNSrXU5uzxTqbvF1BjMCGNGZzZU14Kgd
```
## 1. Prepare a browser libp2p dialer
Confirm that the above address is the same as the field `list` in `public/dialer.js`:
```js
peerDiscovery: {
[Bootstrap.tag]: {
enabled: true,
// paste the address into `list`
list: ['/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/QmUKQCzEUhhhobcNSrXU5uzxTqbvF1BjMCGNGZzZU14Kgd']
}
}
```
## 2. Run a browser libp2p dialer
When in the root folder of this example, type `npm run dev` in terminal. You should see an address where you can browse
the running client. Open this address in your browser. In console
logs you should see logs about successful connection with the node client. In the output of node client you should see
a log message about successful connection as well.

View File

@@ -0,0 +1,57 @@
import 'babel-polyfill'
const Libp2p = require('libp2p')
const WebRTCDirect = require('libp2p-webrtc-direct')
const Mplex = require('libp2p-mplex')
const {NOISE} = require('libp2p-noise')
const Bootstrap = require('libp2p-bootstrap')
document.addEventListener('DOMContentLoaded', async () => {
// use the same peer id as in `listener.js` to avoid copy-pasting of listener's peer id into `peerDiscovery`
const hardcodedPeerId = '12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m'
const libp2p = await Libp2p.create({
modules: {
transport: [WebRTCDirect],
streamMuxer: [Mplex],
connEncryption: [NOISE],
peerDiscovery: [Bootstrap]
},
config: {
peerDiscovery: {
[Bootstrap.tag]: {
enabled: true,
list: [`/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/${hardcodedPeerId}`]
}
}
}
})
const status = document.getElementById('status')
const output = document.getElementById('output')
output.textContent = ''
function log (txt) {
console.info(txt)
output.textContent += `${txt.trim()}\n`
}
// Listen for new peers
libp2p.on('peer:discovery', (peerId) => {
log(`Found peer ${peerId.toB58String()}`)
})
// Listen for new connections to peers
libp2p.connectionManager.on('peer:connect', (connection) => {
log(`Connected to ${connection.remotePeer.toB58String()}`)
})
// Listen for peers disconnecting
libp2p.connectionManager.on('peer:disconnect', (connection) => {
log(`Disconnected from ${connection.remotePeer.toB58String()}`)
})
await libp2p.start()
status.innerText = 'libp2p started!'
log(`libp2p id is ${libp2p.peerId.toB58String()}`)
})

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js-libp2p parcel.js browser example</title>
</head>
<body>
<header>
<h1 id="status">Starting libp2p...</h1>
</header>
<main>
<pre id="output"></pre>
</main>
<script src="./dialer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,44 @@
const Libp2p = require('libp2p')
const Bootstrap = require('libp2p-bootstrap')
const WebRTCDirect = require('libp2p-webrtc-direct')
const Mplex = require('libp2p-mplex')
const {NOISE} = require('libp2p-noise')
const PeerId = require('peer-id')
;(async () => {
// hardcoded peer id to avoid copy-pasting of listener's peer id into the dialer's bootstrap list
// generated with cmd `peer-id --type=ed25519`
const hardcodedPeerId = await PeerId.createFromJSON({
"id": "12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m",
"privKey": "CAESQAG6Ld7ev6nnD0FKPs033/j0eQpjWilhxnzJ2CCTqT0+LfcWoI2Vr+zdc1vwk7XAVdyoCa2nwUR3RJebPWsF1/I=",
"pubKey": "CAESIC33FqCNla/s3XNb8JO1wFXcqAmtp8FEd0SXmz1rBdfy"
})
const node = await Libp2p.create({
peerId: hardcodedPeerId,
addresses: {
listen: ['/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct']
},
modules: {
transport: [WebRTCDirect],
streamMuxer: [Mplex],
connEncryption: [NOISE]
},
config: {
peerDiscovery: {
[Bootstrap.tag]: {
enabled: false,
}
}
}
})
node.connectionManager.on('peer:connect', (connection) => {
console.info(`Connected to ${connection.remotePeer.toB58String()}!`)
})
await node.start()
console.log('Listening on:')
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
})()

View File

@@ -0,0 +1,33 @@
{
"name": "webrtc-direct",
"version": "0.0.1",
"private": true,
"description": "",
"main": "dist/index.html",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "parcel build index.html",
"start": "parcel index.html"
},
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.13.10",
"@babel/core": "^7.13.10",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-transform-regenerator": "^6.26.0",
"babel-polyfill": "^6.26.0",
"parcel-bundler": "1.12.3",
"util": "^0.12.3"
},
"dependencies": {
"libp2p": "../../",
"libp2p-bootstrap": "^0.12.1",
"libp2p-mplex": "^0.10.1",
"libp2p-noise": "^2.0.1",
"libp2p-webrtc-direct": "^0.6.0",
"peer-id": "^0.14.3"
},
"browser": {
"ipfs": "ipfs/dist/index.min.js"
}
}

View File

@@ -0,0 +1,95 @@
'use strict'
const path = require('path')
const execa = require('execa')
const pDefer = require('p-defer')
const uint8ArrayToString = require('uint8arrays/to-string')
const { chromium } = require('playwright');
function startNode (name, args = []) {
return execa('node', [path.join(__dirname, name), ...args], {
cwd: path.resolve(__dirname),
all: true
})
}
function startBrowser (name, args = []) {
return execa('parcel', [path.join(__dirname, name), ...args], {
preferLocal: true,
localDir: __dirname,
cwd: __dirname,
all: true
})
}
async function test () {
// Step 1, listener process
const listenerProcReady = pDefer()
let listenerOutput = ''
process.stdout.write('listener.js\n')
const listenerProc = startNode('listener.js')
listenerProc.all.on('data', async (data) => {
process.stdout.write(data)
listenerOutput += uint8ArrayToString(data)
if (listenerOutput.includes('Listening on:') && listenerOutput.includes('12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m')) {
listenerProcReady.resolve()
}
})
await listenerProcReady.promise
process.stdout.write('==================================================================\n')
// Step 2, dialer process
process.stdout.write('dialer.js\n')
let dialerUrl = ''
const dialerProc = startBrowser('index.html')
dialerProc.all.on('data', async (chunk) => {
/**@type {string} */
const out = chunk.toString()
if (out.includes('Server running at')) {
dialerUrl = out.split('Server running at ')[1]
}
if (out.includes('Built in ')) {
try {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(dialerUrl);
await page.waitForFunction(selector => document.querySelector(selector).innerText === 'libp2p started!', '#status')
await page.waitForFunction(
selector => {
const text = document.querySelector(selector).innerText
return text.includes('libp2p id is') &&
text.includes('Found peer') &&
text.includes('Connected to')
},
'#output',
{ timeout: 10000 }
)
await browser.close();
} catch (err) {
console.error(err)
process.exit(1)
} finally {
dialerProc.cancel()
listenerProc.kill()
}
}
})
await Promise.all([
listenerProc,
dialerProc,
]).catch((err) => {
if (err.signal !== 'SIGTERM') {
throw err
}
})
}
module.exports = test

View File

@@ -1,6 +1,6 @@
{
"name": "libp2p",
"version": "0.30.1",
"version": "0.31.4",
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
"main": "src/index.js",
@@ -20,10 +20,28 @@
"scripts": {
"lint": "aegir lint",
"build": "aegir build",
"test": "npm run test:node && npm run test:browser",
"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: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-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: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-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",
"test:ts": "aegir build --no-bundle && npm run test --prefix test/ts-use",
"test:node": "aegir test -t node -f \"./test/**/*.{node,spec}.js\"",
"test:browser": "aegir test -t browser",
"test:examples": "cd examples && npm run test:all",
"prepare": "aegir build --no-bundle",
"release": "aegir release -t node -t browser",
"release-minor": "aegir release --type minor -t node -t browser",
"release-major": "aegir release --type major -t node -t browser",
@@ -47,96 +65,115 @@
"homepage": "https://libp2p.io",
"license": "MIT",
"engines": {
"node": ">=12.0.0",
"npm": ">=6.0.0"
"node": ">=14.0.0"
},
"browser": {
"@motrix/nat-api": false
},
"eslintConfig": {
"extends": "ipfs",
"ignorePatterns": [
"!.aegir.js",
"test/ts-use"
]
},
"dependencies": {
"@motrix/nat-api": "^0.3.1",
"abort-controller": "^3.0.0",
"aggregate-error": "^3.0.1",
"any-signal": "^1.1.0",
"bignumber.js": "^9.0.0",
"cids": "^1.0.0",
"aggregate-error": "^3.1.0",
"any-signal": "^2.1.1",
"bignumber.js": "^9.0.1",
"cids": "^1.1.5",
"class-is": "^1.1.0",
"debug": "^4.1.1",
"err-code": "^2.0.0",
"events": "^3.1.0",
"debug": "^4.3.1",
"err-code": "^3.0.0",
"es6-promisify": "^6.1.1",
"events": "^3.3.0",
"hashlru": "^2.3.0",
"interface-datastore": "^2.0.0",
"ipfs-utils": "^5.0.1",
"it-all": "^1.0.1",
"interface-datastore": "^4.0.0",
"ipfs-utils": "^7.0.0",
"it-all": "^1.0.4",
"it-buffer": "^0.1.2",
"it-handshake": "^1.0.1",
"it-length-prefixed": "^3.0.1",
"it-drain": "^1.0.3",
"it-filter": "^1.0.1",
"it-first": "^1.0.4",
"it-handshake": "^2.0.0",
"it-length-prefixed": "^5.0.2",
"it-map": "^1.0.4",
"it-merge": "1.0.0",
"it-pipe": "^1.1.0",
"it-protocol-buffers": "^0.2.0",
"libp2p-crypto": "^0.18.0",
"libp2p-interfaces": "^0.8.1",
"libp2p-utils": "^0.2.2",
"mafmt": "^8.0.0",
"merge-options": "^2.0.0",
"moving-average": "^1.0.0",
"multiaddr": "^8.1.0",
"multicodec": "^2.0.0",
"multihashing-async": "^2.0.1",
"multistream-select": "^1.0.0",
"it-take": "1.0.0",
"libp2p-crypto": "^0.19.4",
"libp2p-interfaces": "^0.10.4",
"libp2p-utils": "^0.3.1",
"mafmt": "^9.0.0",
"merge-options": "^3.0.4",
"@vascosantos/moving-average": "^1.1.0",
"multiaddr": "^9.0.1",
"multicodec": "^3.0.1",
"multihashing-async": "^2.1.2",
"multistream-select": "^2.0.0",
"mutable-proxy": "^1.0.0",
"node-forge": "^0.9.1",
"node-forge": "^0.10.0",
"p-any": "^3.0.0",
"p-fifo": "^1.0.0",
"p-settle": "^4.0.1",
"p-retry": "^4.4.0",
"p-settle": "^4.1.1",
"peer-id": "^0.14.2",
"protons": "^2.0.0",
"retimer": "^2.0.0",
"private-ip": "^2.1.0",
"protobufjs": "^6.10.2",
"retimer": "^3.0.0",
"sanitize-filename": "^1.6.3",
"set-delayed-interval": "^1.0.0",
"streaming-iterables": "^5.0.2",
"timeout-abort-controller": "^1.1.1",
"varint": "^5.0.0",
"xsalsa20": "^1.0.2"
"varint": "^6.0.0",
"xsalsa20": "^1.1.0"
},
"devDependencies": {
"@nodeutils/defaults-deep": "^1.1.0",
"@types/es6-promisify": "^6.0.0",
"@types/node-forge": "^0.9.7",
"@types/varint": "^6.0.0",
"abortable-iterator": "^3.0.0",
"aegir": "^29.2.0",
"chai-bytes": "^0.1.2",
"chai-string": "^1.5.0",
"delay": "^4.3.0",
"aegir": "^33.1.1",
"buffer": "^6.0.3",
"delay": "^5.0.0",
"interop-libp2p": "^0.3.0",
"into-stream": "^6.0.0",
"ipfs-http-client": "^47.0.1",
"ipfs-http-client": "^49.0.4",
"it-concat": "^1.0.0",
"it-pair": "^1.0.0",
"it-pushable": "^1.4.0",
"libp2p": ".",
"libp2p-bootstrap": "^0.12.0",
"libp2p-delegated-content-routing": "^0.8.0",
"libp2p-delegated-peer-routing": "^0.8.0",
"libp2p-floodsub": "^0.24.0",
"libp2p-gossipsub": "^0.7.0",
"libp2p-kad-dht": "^0.20.0",
"libp2p-mdns": "^0.15.0",
"libp2p-bootstrap": "^0.12.3",
"libp2p-delegated-content-routing": "^0.10.0",
"libp2p-delegated-peer-routing": "^0.9.0",
"libp2p-floodsub": "^0.25.0",
"libp2p-gossipsub": "^0.9.0",
"libp2p-kad-dht": "^0.22.0",
"libp2p-mdns": "^0.16.0",
"libp2p-mplex": "^0.10.1",
"libp2p-noise": "^2.0.0",
"libp2p-secio": "^0.13.1",
"libp2p-tcp": "^0.15.1",
"libp2p-webrtc-star": "^0.20.0",
"libp2p-websockets": "^0.15.0",
"multihashes": "^3.0.1",
"libp2p-noise": "^3.0.0",
"libp2p-tcp": "^0.15.4",
"libp2p-webrtc-star": "^0.22.2",
"libp2p-websockets": "^0.15.6",
"multihashes": "^4.0.2",
"nock": "^13.0.3",
"p-defer": "^3.0.0",
"p-times": "^3.0.0",
"p-wait-for": "^3.1.0",
"promisify-es6": "^1.0.3",
"p-wait-for": "^3.2.0",
"rimraf": "^3.0.2",
"sinon": "^9.0.2",
"uint8arrays": "^1.1.0"
"sinon": "^10.0.0",
"uint8arrays": "^2.1.3",
"util": "^0.12.3"
},
"contributors": [
"David Dias <daviddias.p@gmail.com>",
"Jacob Heun <jacobheun@gmail.com>",
"Vasco Santos <vasco.santos@moxy.studio>",
"Alan Shaw <alan@tableflip.io>",
"Jacob Heun <jacobheun@gmail.com>",
"Alex Potsides <alex@achingbrain.net>",
"Alan Shaw <alan@tableflip.io>",
"Cayman <caymannava@gmail.com>",
"Pedro Teixeira <i@pgte.me>",
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
@@ -146,38 +183,44 @@
"Volker Mische <volker.mische@gmail.com>",
"Richard Littauer <richard.littauer@gmail.com>",
"a1300 <matthias-knopp@gmx.net>",
"Elven <mon.samuel@qq.com>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
"Andrew Nesbitt <andrewnez@gmail.com>",
"Elven <mon.samuel@qq.com>",
"Giovanni T. Parra <fiatjaf@gmail.com>",
"Ryan Bell <ryan@piing.net>",
"Samlior <samlior@foxmail.com>",
"Thomas Eizinger <thomas@eizinger.io>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
"Didrik Nordström <didrik@betamos.se>",
"Fei Liu <liu.feiwood@gmail.com>",
"Ethan Lam <elmemphis2000@gmail.com>",
"Joel Gustafson <joelg@mit.edu>",
"Julien Bouquillon <contact@revolunet.com>",
"Kevin Kwok <antimatter15@gmail.com>",
"Nuno Nogueira <nunofmn@gmail.com>",
"Kevin Lacker <lacker@gmail.com>",
"Dmitriy Ryajov <dryajov@gmail.com>",
"RasmusErik Voel Jensen <github@solsort.com>",
"Miguel Mota <miguelmota2@gmail.com>",
"Nuno Nogueira <nunofmn@gmail.com>",
"Diogo Silva <fsdiogo@gmail.com>",
"Samlior <samlior@foxmail.com>",
"Philipp Muens <raute1337@gmx.de>",
"RasmusErik Voel Jensen <github@solsort.com>",
"Smite Chow <xiaopengyou@live.com>",
"Soeren <nikorpoulsen@gmail.com>",
"Sönke Hahn <soenkehahn@gmail.com>",
"TJKoury <TJKoury@gmail.com>",
"robertkiel <robert.kiel@validitylabs.org>",
"Tiago Alves <alvesjtiago@gmail.com>",
"Daijiro Wachi <daijiro.wachi@gmail.com>",
"Cindy Wu <ciindy.wu@gmail.com>",
"Yusef Napora <yusef@napora.org>",
"Zane Starr <zcstarr@gmail.com>",
"robertkiel <robert.kiel@validitylabs.org>",
"Cindy Wu <ciindy.wu@gmail.com>",
"Chris Bratlien <chrisbratlien@gmail.com>",
"Bernd Strehl <bernd.strehl@gmail.com>",
"ebinks <elizabethjbinks@gmail.com>",
"Florian-Merle <florian.david.merle@gmail.com>",
"Francis Gulotta <wizard@roborooter.com>",
"Franck Royer <franck@royer.one>",
"Felipe Martins <felipebrasil93@gmail.com>",
"Bernd Strehl <bernd.strehl@gmail.com>",
"Aleksei <vozhdb@gmail.com>",
"Henrique Dias <hacdias@gmail.com>",
"isan_rivkin <isanrivkin@gmail.com>",
"Irakli Gozalishvili <rfobic@gmail.com>"

2
scripts/node-globals.js Normal file
View File

@@ -0,0 +1,2 @@
// @ts-nocheck
export const { Buffer } = require('buffer')

View File

@@ -1,17 +1,19 @@
'use strict'
const multiaddr = require('multiaddr')
/**
* @typedef {import('multiaddr')} Multiaddr
*/
const { EventEmitter } = require('events')
const { Multiaddr } = require('multiaddr')
const PeerId = require('peer-id')
/**
* @typedef {Object} AddressManagerOptions
* @property {string[]} [listen = []] - list of multiaddrs string representation to listen.
* @property {string[]} [announce = []] - list of multiaddrs string representation to announce.
*/
class AddressManager {
/**
* @fires AddressManager#change:addresses Emitted when a addresses change.
*/
class AddressManager extends EventEmitter {
/**
* Responsible for managing the peer addresses.
* Peers can specify their listen and announce addresses.
@@ -19,11 +21,18 @@ class AddressManager {
* while the announce addresses will be used for the peer addresses' to other peers in the network.
*
* @class
* @param {AddressManagerOptions} [options]
* @param {PeerId} peerId - The Peer ID of the node
* @param {object} [options]
* @param {Array<string>} [options.listen = []] - list of multiaddrs string representation to listen.
* @param {Array<string>} [options.announce = []] - list of multiaddrs string representation to announce.
*/
constructor ({ listen = [], announce = [] } = {}) {
this.listen = new Set(listen)
this.announce = new Set(announce)
constructor (peerId, { listen = [], announce = [] } = {}) {
super()
this.peerId = peerId
this.listen = new Set(listen.map(ma => ma.toString()))
this.announce = new Set(announce.map(ma => ma.toString()))
this.observed = new Set()
}
/**
@@ -32,7 +41,7 @@ class AddressManager {
* @returns {Multiaddr[]}
*/
getListenAddrs () {
return Array.from(this.listen).map((a) => multiaddr(a))
return Array.from(this.listen).map((a) => new Multiaddr(a))
}
/**
@@ -41,7 +50,46 @@ class AddressManager {
* @returns {Multiaddr[]}
*/
getAnnounceAddrs () {
return Array.from(this.announce).map((a) => multiaddr(a))
return Array.from(this.announce).map((a) => new Multiaddr(a))
}
/**
* Get observed multiaddrs.
*
* @returns {Array<Multiaddr>}
*/
getObservedAddrs () {
return Array.from(this.observed).map((a) => new Multiaddr(a))
}
/**
* Add peer observed addresses
*
* @param {string | Multiaddr} addr
*/
addObservedAddr (addr) {
let ma = new Multiaddr(addr)
const remotePeer = ma.getPeerId()
// strip our peer id if it has been passed
if (remotePeer) {
const remotePeerId = PeerId.createFromB58String(remotePeer)
// use same encoding for comparison
if (remotePeerId.equals(this.peerId)) {
ma = ma.decapsulate(new Multiaddr(`/p2p/${this.peerId}`))
}
}
const addrString = ma.toString()
// do not trigger the change:addresses event if we already know about this address
if (this.observed.has(addrString)) {
return
}
this.observed.add(addrString)
this.emit('change:addresses')
}
}

View File

@@ -37,7 +37,7 @@ Libp2p circuit configuration can be seen at [Setup with Relay](../../doc/CONFIGU
Once you have a circuit relay node running, you can configure other nodes to use it as a relay as follows:
```js
const multiaddr = require('multiaddr')
const { Multiaddr } = require('multiaddr')
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
@@ -47,7 +47,7 @@ const relayAddr = ...
const node = await Libp2p.create({
addresses: {
listen: [multiaddr(`${relayAddr}/p2p-circuit`)]
listen: [new Multiaddr(`${relayAddr}/p2p-circuit`)]
},
modules: {
transport: [TCP],

View File

@@ -7,7 +7,7 @@ const log = Object.assign(debug('libp2p:auto-relay'), {
const uint8ArrayFromString = require('uint8arrays/from-string')
const uint8ArrayToString = require('uint8arrays/to-string')
const multiaddr = require('multiaddr')
const { Multiaddr } = require('multiaddr')
const PeerId = require('peer-id')
const { relay: multicodec } = require('./multicodec')
@@ -31,6 +31,7 @@ const {
*
* @typedef {Object} AutoRelayOptions
* @property {number} [maxListeners = 1] - maximum number of relays to listen.
* @property {(error: Error, msg?: string) => {}} [onError]
*/
class AutoRelay {
@@ -40,7 +41,7 @@ class AutoRelay {
* @class
* @param {AutoRelayProperties & AutoRelayOptions} props
*/
constructor ({ libp2p, maxListeners = 1 }) {
constructor ({ libp2p, maxListeners = 1, onError }) {
this._libp2p = libp2p
this._peerId = libp2p.peerId
this._peerStore = libp2p.peerStore
@@ -60,6 +61,15 @@ class AutoRelay {
this._peerStore.on('change:protocols', this._onProtocolChange)
this._connectionManager.on('peer:disconnect', this._onPeerDisconnected)
/**
* @param {Error} error
* @param {string} [msg]
*/
this._onError = (error, msg) => {
log.error(msg || error)
onError && onError(error, msg)
}
}
/**
@@ -107,7 +117,7 @@ class AutoRelay {
await this._addListenRelay(connection, id)
}
} catch (err) {
log.error(err)
this._onError(err)
}
}
@@ -157,10 +167,10 @@ class AutoRelay {
// Attempt to listen on relay
try {
await this._transportManager.listen([multiaddr(listenAddr)])
await this._transportManager.listen([new Multiaddr(listenAddr)])
// Announce multiaddrs will update on listen success by TransportManager event being triggered
} catch (err) {
log.error(err)
this._onError(err)
this._listenRelays.delete(id)
}
}
@@ -231,8 +241,7 @@ class AutoRelay {
// Try to listen on known peers that are not connected
for (const peerId of knownHopsToDial) {
const connection = await this._libp2p.dial(peerId)
await this._addListenRelay(connection, peerId.toB58String())
await this._tryToListenOnRelay(peerId)
// Check if already listening on enough relays
if (this._listenRelays.size >= this.maxListeners) {
@@ -247,12 +256,11 @@ class AutoRelay {
if (!provider.multiaddrs.length) {
continue
}
const peerId = provider.id
this._peerStore.addressBook.add(peerId, provider.multiaddrs)
const connection = await this._libp2p.dial(peerId)
await this._addListenRelay(connection, peerId.toB58String())
await this._tryToListenOnRelay(peerId)
// Check if already listening on enough relays
if (this._listenRelays.size >= this.maxListeners) {
@@ -260,7 +268,19 @@ class AutoRelay {
}
}
} catch (err) {
log.error(err)
this._onError(err)
}
}
/**
* @param {PeerId} peerId
*/
async _tryToListenOnRelay (peerId) {
try {
const connection = await this._libp2p.dial(peerId)
await this._addListenRelay(connection, peerId.toB58String())
} catch (err) {
this._onError(err, `could not connect and listen on known hop relay ${peerId.toB58String()}`)
}
}
}

View File

@@ -18,17 +18,17 @@ const { stop } = require('./stop')
const multicodec = require('./../multicodec')
/**
* @typedef {import('../../types').CircuitRequest} CircuitRequest
* @typedef {import('../protocol').ICircuitRelay} ICircuitRelay
* @typedef {import('libp2p-interfaces/src/connection').Connection} Connection
* @typedef {import('./stream-handler')<CircuitRequest>} StreamHandlerT
* @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream
* @typedef {import('../transport')} Transport
*/
/**
* @typedef {Object} HopRequest
* @property {Connection} connection
* @property {CircuitRequest} request
* @property {StreamHandlerT} streamHandler
* @property {ICircuitRelay} request
* @property {StreamHandler} streamHandler
* @property {Transport} circuit
*/
@@ -58,6 +58,11 @@ async function handleHop ({
return log.error('invalid hop request via peer %s', connection.remotePeer.toB58String(), err)
}
if (!request.dstPeer) {
log('HOP request received but we do not receive a dstPeer')
return
}
// Get the connection to the destination (stop) peer
const destinationPeer = new PeerId(request.dstPeer.id)
@@ -113,8 +118,8 @@ async function handleHop ({
*
* @param {object} options
* @param {Connection} options.connection - Connection to the relay
* @param {CircuitRequest} options.request
* @returns {Promise<Connection>}
* @param {ICircuitRelay} options.request
* @returns {Promise<MuxedStream>}
*/
async function hop ({
connection,
@@ -128,6 +133,10 @@ async function hop ({
const response = await streamHandler.read()
if (!response) {
throw errCode(new Error('HOP request had no response'), Errors.ERR_HOP_REQUEST_FAILED)
}
if (response.code === CircuitPB.Status.SUCCESS) {
log('hop request was successful')
return streamHandler.rest()
@@ -159,7 +168,7 @@ async function canHop ({
const response = await streamHandler.read()
await streamHandler.close()
if (response.code !== CircuitPB.Status.SUCCESS) {
if (!response || response.code !== CircuitPB.Status.SUCCESS) {
return false
}
@@ -171,7 +180,7 @@ async function canHop ({
*
* @param {Object} options
* @param {Connection} options.connection
* @param {StreamHandlerT} options.streamHandler
* @param {StreamHandler} options.streamHandler
* @param {Transport} options.circuit
* @private
*/

View File

@@ -13,8 +13,7 @@ const { validateAddrs } = require('./utils')
/**
* @typedef {import('libp2p-interfaces/src/connection').Connection} Connection
* @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream
* @typedef {import('../../types').CircuitRequest} CircuitRequest
* @typedef {import('./stream-handler')<CircuitRequest>} StreamHandlerT
* @typedef {import('../protocol').ICircuitRelay} ICircuitRelay
*/
/**
@@ -23,8 +22,8 @@ const { validateAddrs } = require('./utils')
* @private
* @param {Object} options
* @param {Connection} options.connection
* @param {CircuitRequest} options.request - The CircuitRelay protobuf request (unencoded)
* @param {StreamHandlerT} options.streamHandler
* @param {ICircuitRelay} options.request - The CircuitRelay protobuf request (unencoded)
* @param {StreamHandler} options.streamHandler
* @returns {Promise<MuxedStream>|void} Resolves a duplex iterable
*/
module.exports.handleStop = function handleStop ({
@@ -54,7 +53,7 @@ module.exports.handleStop = function handleStop ({
* @private
* @param {Object} options
* @param {Connection} options.connection
* @param {CircuitRequest} options.request - The CircuitRelay protobuf request (unencoded)
* @param {ICircuitRelay} options.request - The CircuitRelay protobuf request (unencoded)
* @returns {Promise<MuxedStream|void>} Resolves a duplex iterable
*/
module.exports.stop = async function stop ({
@@ -68,6 +67,10 @@ module.exports.stop = async function stop ({
streamHandler.write(request)
const response = await streamHandler.read()
if (!response) {
return streamHandler.close()
}
if (response.code === CircuitPB.Status.SUCCESS) {
log('stop request to %s was successful', connection.remotePeer.toB58String())
return streamHandler.rest()

View File

@@ -6,16 +6,15 @@ const log = Object.assign(debug('libp2p:circuit:stream-handler'), {
})
const lp = require('it-length-prefixed')
// @ts-ignore it-handshake does not export types
const handshake = require('it-handshake')
const { CircuitRelay: CircuitPB } = require('../protocol')
const { CircuitRelay } = require('../protocol')
/**
* @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream
* @typedef {import('../protocol').ICircuitRelay} ICircuitRelay
*/
/**
* @template T
*/
class StreamHandler {
/**
* Create a stream handler for connection
@@ -29,6 +28,7 @@ class StreamHandler {
this.stream = stream
this.shake = handshake(this.stream)
// @ts-ignore options are not optional
this.decoder = lp.decode.fromReader(this.shake.reader, { maxDataLength: maxLength })
}
@@ -36,12 +36,11 @@ class StreamHandler {
* Read and decode message
*
* @async
* @returns {Promise<T|undefined>}
*/
async read () {
const msg = await this.decoder.next()
if (msg.value) {
const value = CircuitPB.decode(msg.value.slice())
const value = CircuitRelay.decode(msg.value.slice())
log('read message type', value.type)
return value
}
@@ -54,13 +53,13 @@ class StreamHandler {
/**
* Encode and write array of buffers
*
* @param {CircuitPB} msg - An unencoded CircuitRelay protobuf message
* @param {ICircuitRelay} msg - An unencoded CircuitRelay protobuf message
* @returns {void}
*/
write (msg) {
log('write message type %s', msg.type)
// @ts-ignore lp.encode expects type type 'Buffer | BufferList', not 'Uint8Array'
this.shake.write(lp.encode.single(CircuitPB.encode(msg)))
this.shake.write(lp.encode.single(CircuitRelay.encode(msg).finish()))
}
/**
@@ -73,6 +72,9 @@ class StreamHandler {
return this.shake.stream
}
/**
* @param {ICircuitRelay} msg - An unencoded CircuitRelay protobuf message
*/
end (msg) {
this.write(msg)
this.close()

View File

@@ -1,18 +1,18 @@
'use strict'
const multiaddr = require('multiaddr')
const { Multiaddr } = require('multiaddr')
const { CircuitRelay } = require('../protocol')
/**
* @typedef {import('./stream-handler')} StreamHandler
* @typedef {import('../../types').CircuitStatus} CircuitStatus
* @typedef {import('../protocol').ICircuitRelay} ICircuitRelay
*/
/**
* Write a response
*
* @param {StreamHandler} streamHandler
* @param {CircuitStatus} status
* @param {import('../protocol').CircuitRelay.Status} status
*/
function writeResponse (streamHandler, status) {
streamHandler.write({
@@ -24,14 +24,16 @@ function writeResponse (streamHandler, status) {
/**
* Validate incomming HOP/STOP message
*
* @param {*} msg - A CircuitRelay unencoded protobuf message
* @param {ICircuitRelay} msg - A CircuitRelay unencoded protobuf message
* @param {StreamHandler} streamHandler
*/
function validateAddrs (msg, streamHandler) {
try {
msg.dstPeer.addrs.forEach((addr) => {
return multiaddr(addr)
})
if (msg.dstPeer && msg.dstPeer.addrs) {
msg.dstPeer.addrs.forEach((addr) => {
return new Multiaddr(addr)
})
}
} catch (err) {
writeResponse(streamHandler, msg.type === CircuitRelay.Type.HOP
? CircuitRelay.Status.HOP_DST_MULTIADDR_INVALID
@@ -40,9 +42,11 @@ function validateAddrs (msg, streamHandler) {
}
try {
msg.srcPeer.addrs.forEach((addr) => {
return multiaddr(addr)
})
if (msg.srcPeer && msg.srcPeer.addrs) {
msg.srcPeer.addrs.forEach((addr) => {
return new Multiaddr(addr)
})
}
} catch (err) {
writeResponse(streamHandler, msg.type === CircuitRelay.Type.HOP
? CircuitRelay.Status.HOP_SRC_MULTIADDR_INVALID

View File

@@ -8,13 +8,12 @@ const log = Object.assign(debug('libp2p:relay'), {
const {
setDelayedInterval,
clearDelayedInterval
// @ts-ignore set-delayed-interval does not export types
} = require('set-delayed-interval')
const AutoRelay = require('./auto-relay')
const { namespaceToCid } = require('./utils')
const {
ADVERTISE_BOOT_DELAY,
ADVERTISE_TTL,
RELAY_RENDEZVOUS_NS
} = require('./constants')
@@ -45,12 +44,6 @@ class Relay {
constructor (libp2p) {
this._libp2p = libp2p
this._options = {
advertise: {
bootDelay: ADVERTISE_BOOT_DELAY,
enabled: true,
ttl: ADVERTISE_TTL,
...libp2p._config.relay.advertise
},
...libp2p._config.relay
}

View File

@@ -1,10 +1,9 @@
'use strict'
const { EventEmitter } = require('events')
const multiaddr = require('multiaddr')
const { Multiaddr } = require('multiaddr')
/**
* @typedef {import('multiaddr')} Multiaddr
* @typedef {import('libp2p-interfaces/src/transport/types').Listener} Listener
*/
@@ -24,7 +23,7 @@ module.exports = (libp2p) => {
async function listen (addr) {
const addrString = String(addr).split('/p2p-circuit').find(a => a !== '')
const relayConn = await libp2p.dial(multiaddr(addrString))
const relayConn = await libp2p.dial(new Multiaddr(addrString))
const relayedAddr = relayConn.remoteAddr.encapsulate('/p2p-circuit')
listeningAddrs.set(relayConn.remotePeer.toB58String(), relayedAddr)

173
src/circuit/protocol/index.d.ts vendored Normal file
View File

@@ -0,0 +1,173 @@
import * as $protobuf from "protobufjs";
/** Properties of a CircuitRelay. */
export interface ICircuitRelay {
/** CircuitRelay type */
type?: (CircuitRelay.Type|null);
/** CircuitRelay srcPeer */
srcPeer?: (CircuitRelay.IPeer|null);
/** CircuitRelay dstPeer */
dstPeer?: (CircuitRelay.IPeer|null);
/** CircuitRelay code */
code?: (CircuitRelay.Status|null);
}
/** Represents a CircuitRelay. */
export class CircuitRelay implements ICircuitRelay {
/**
* Constructs a new CircuitRelay.
* @param [p] Properties to set
*/
constructor(p?: ICircuitRelay);
/** CircuitRelay type. */
public type: CircuitRelay.Type;
/** CircuitRelay srcPeer. */
public srcPeer?: (CircuitRelay.IPeer|null);
/** CircuitRelay dstPeer. */
public dstPeer?: (CircuitRelay.IPeer|null);
/** CircuitRelay code. */
public code: CircuitRelay.Status;
/**
* Encodes the specified CircuitRelay message. Does not implicitly {@link CircuitRelay.verify|verify} messages.
* @param m CircuitRelay message or plain object to encode
* @param [w] Writer to encode to
* @returns Writer
*/
public static encode(m: ICircuitRelay, w?: $protobuf.Writer): $protobuf.Writer;
/**
* Decodes a CircuitRelay message from the specified reader or buffer.
* @param r Reader or buffer to decode from
* @param [l] Message length if known beforehand
* @returns CircuitRelay
* @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): CircuitRelay;
/**
* Creates a CircuitRelay message from a plain object. Also converts values to their respective internal types.
* @param d Plain object
* @returns CircuitRelay
*/
public static fromObject(d: { [k: string]: any }): CircuitRelay;
/**
* Creates a plain object from a CircuitRelay message. Also converts values to other types if specified.
* @param m CircuitRelay
* @param [o] Conversion options
* @returns Plain object
*/
public static toObject(m: CircuitRelay, o?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this CircuitRelay to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
}
export namespace CircuitRelay {
/** Status enum. */
enum Status {
SUCCESS = 100,
HOP_SRC_ADDR_TOO_LONG = 220,
HOP_DST_ADDR_TOO_LONG = 221,
HOP_SRC_MULTIADDR_INVALID = 250,
HOP_DST_MULTIADDR_INVALID = 251,
HOP_NO_CONN_TO_DST = 260,
HOP_CANT_DIAL_DST = 261,
HOP_CANT_OPEN_DST_STREAM = 262,
HOP_CANT_SPEAK_RELAY = 270,
HOP_CANT_RELAY_TO_SELF = 280,
STOP_SRC_ADDR_TOO_LONG = 320,
STOP_DST_ADDR_TOO_LONG = 321,
STOP_SRC_MULTIADDR_INVALID = 350,
STOP_DST_MULTIADDR_INVALID = 351,
STOP_RELAY_REFUSED = 390,
MALFORMED_MESSAGE = 400
}
/** Type enum. */
enum Type {
HOP = 1,
STOP = 2,
STATUS = 3,
CAN_HOP = 4
}
/** Properties of a Peer. */
interface IPeer {
/** Peer id */
id: Uint8Array;
/** Peer addrs */
addrs?: (Uint8Array[]|null);
}
/** Represents a Peer. */
class Peer implements IPeer {
/**
* Constructs a new Peer.
* @param [p] Properties to set
*/
constructor(p?: CircuitRelay.IPeer);
/** Peer id. */
public id: Uint8Array;
/** Peer addrs. */
public addrs: Uint8Array[];
/**
* Encodes the specified Peer message. Does not implicitly {@link CircuitRelay.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: CircuitRelay.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): CircuitRelay.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 }): CircuitRelay.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: CircuitRelay.Peer, o?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this Peer to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
}
}

View File

@@ -1,46 +1,530 @@
'use strict'
const protobuf = require('protons')
/*eslint-disable*/
"use strict";
/** @type {{CircuitRelay: import('../../types').CircuitMessageProto}} */
module.exports = protobuf(`
message CircuitRelay {
var $protobuf = require("protobufjs/minimal");
enum Status {
SUCCESS = 100;
HOP_SRC_ADDR_TOO_LONG = 220;
HOP_DST_ADDR_TOO_LONG = 221;
HOP_SRC_MULTIADDR_INVALID = 250;
HOP_DST_MULTIADDR_INVALID = 251;
HOP_NO_CONN_TO_DST = 260;
HOP_CANT_DIAL_DST = 261;
HOP_CANT_OPEN_DST_STREAM = 262;
HOP_CANT_SPEAK_RELAY = 270;
HOP_CANT_RELAY_TO_SELF = 280;
STOP_SRC_ADDR_TOO_LONG = 320;
STOP_DST_ADDR_TOO_LONG = 321;
STOP_SRC_MULTIADDR_INVALID = 350;
STOP_DST_MULTIADDR_INVALID = 351;
STOP_RELAY_REFUSED = 390;
MALFORMED_MESSAGE = 400;
}
// Common aliases
var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
enum Type { // RPC identifier, either HOP, STOP or STATUS
HOP = 1;
STOP = 2;
STATUS = 3;
CAN_HOP = 4;
}
// Exported root namespace
var $root = $protobuf.roots["libp2p-circuit"] || ($protobuf.roots["libp2p-circuit"] = {});
message Peer {
required bytes id = 1; // peer id
repeated bytes addrs = 2; // peer's known addresses
}
$root.CircuitRelay = (function() {
optional Type type = 1; // Type of the message
/**
* Properties of a CircuitRelay.
* @exports ICircuitRelay
* @interface ICircuitRelay
* @property {CircuitRelay.Type|null} [type] CircuitRelay type
* @property {CircuitRelay.IPeer|null} [srcPeer] CircuitRelay srcPeer
* @property {CircuitRelay.IPeer|null} [dstPeer] CircuitRelay dstPeer
* @property {CircuitRelay.Status|null} [code] CircuitRelay code
*/
optional Peer srcPeer = 2; // srcPeer and dstPeer are used when Type is HOP or STATUS
optional Peer dstPeer = 3;
/**
* Constructs a new CircuitRelay.
* @exports CircuitRelay
* @classdesc Represents a CircuitRelay.
* @implements ICircuitRelay
* @constructor
* @param {ICircuitRelay=} [p] Properties to set
*/
function CircuitRelay(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]];
}
optional Status code = 4; // Status code, used when Type is STATUS
}
`)
/**
* CircuitRelay type.
* @member {CircuitRelay.Type} type
* @memberof CircuitRelay
* @instance
*/
CircuitRelay.prototype.type = 1;
/**
* CircuitRelay srcPeer.
* @member {CircuitRelay.IPeer|null|undefined} srcPeer
* @memberof CircuitRelay
* @instance
*/
CircuitRelay.prototype.srcPeer = null;
/**
* CircuitRelay dstPeer.
* @member {CircuitRelay.IPeer|null|undefined} dstPeer
* @memberof CircuitRelay
* @instance
*/
CircuitRelay.prototype.dstPeer = null;
/**
* CircuitRelay code.
* @member {CircuitRelay.Status} code
* @memberof CircuitRelay
* @instance
*/
CircuitRelay.prototype.code = 100;
/**
* Encodes the specified CircuitRelay message. Does not implicitly {@link CircuitRelay.verify|verify} messages.
* @function encode
* @memberof CircuitRelay
* @static
* @param {ICircuitRelay} m CircuitRelay message or plain object to encode
* @param {$protobuf.Writer} [w] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
CircuitRelay.encode = function encode(m, w) {
if (!w)
w = $Writer.create();
if (m.type != null && Object.hasOwnProperty.call(m, "type"))
w.uint32(8).int32(m.type);
if (m.srcPeer != null && Object.hasOwnProperty.call(m, "srcPeer"))
$root.CircuitRelay.Peer.encode(m.srcPeer, w.uint32(18).fork()).ldelim();
if (m.dstPeer != null && Object.hasOwnProperty.call(m, "dstPeer"))
$root.CircuitRelay.Peer.encode(m.dstPeer, w.uint32(26).fork()).ldelim();
if (m.code != null && Object.hasOwnProperty.call(m, "code"))
w.uint32(32).int32(m.code);
return w;
};
/**
* Decodes a CircuitRelay message from the specified reader or buffer.
* @function decode
* @memberof CircuitRelay
* @static
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
* @param {number} [l] Message length if known beforehand
* @returns {CircuitRelay} CircuitRelay
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
CircuitRelay.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.CircuitRelay();
while (r.pos < c) {
var t = r.uint32();
switch (t >>> 3) {
case 1:
m.type = r.int32();
break;
case 2:
m.srcPeer = $root.CircuitRelay.Peer.decode(r, r.uint32());
break;
case 3:
m.dstPeer = $root.CircuitRelay.Peer.decode(r, r.uint32());
break;
case 4:
m.code = r.int32();
break;
default:
r.skipType(t & 7);
break;
}
}
return m;
};
/**
* Creates a CircuitRelay message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof CircuitRelay
* @static
* @param {Object.<string,*>} d Plain object
* @returns {CircuitRelay} CircuitRelay
*/
CircuitRelay.fromObject = function fromObject(d) {
if (d instanceof $root.CircuitRelay)
return d;
var m = new $root.CircuitRelay();
switch (d.type) {
case "HOP":
case 1:
m.type = 1;
break;
case "STOP":
case 2:
m.type = 2;
break;
case "STATUS":
case 3:
m.type = 3;
break;
case "CAN_HOP":
case 4:
m.type = 4;
break;
}
if (d.srcPeer != null) {
if (typeof d.srcPeer !== "object")
throw TypeError(".CircuitRelay.srcPeer: object expected");
m.srcPeer = $root.CircuitRelay.Peer.fromObject(d.srcPeer);
}
if (d.dstPeer != null) {
if (typeof d.dstPeer !== "object")
throw TypeError(".CircuitRelay.dstPeer: object expected");
m.dstPeer = $root.CircuitRelay.Peer.fromObject(d.dstPeer);
}
switch (d.code) {
case "SUCCESS":
case 100:
m.code = 100;
break;
case "HOP_SRC_ADDR_TOO_LONG":
case 220:
m.code = 220;
break;
case "HOP_DST_ADDR_TOO_LONG":
case 221:
m.code = 221;
break;
case "HOP_SRC_MULTIADDR_INVALID":
case 250:
m.code = 250;
break;
case "HOP_DST_MULTIADDR_INVALID":
case 251:
m.code = 251;
break;
case "HOP_NO_CONN_TO_DST":
case 260:
m.code = 260;
break;
case "HOP_CANT_DIAL_DST":
case 261:
m.code = 261;
break;
case "HOP_CANT_OPEN_DST_STREAM":
case 262:
m.code = 262;
break;
case "HOP_CANT_SPEAK_RELAY":
case 270:
m.code = 270;
break;
case "HOP_CANT_RELAY_TO_SELF":
case 280:
m.code = 280;
break;
case "STOP_SRC_ADDR_TOO_LONG":
case 320:
m.code = 320;
break;
case "STOP_DST_ADDR_TOO_LONG":
case 321:
m.code = 321;
break;
case "STOP_SRC_MULTIADDR_INVALID":
case 350:
m.code = 350;
break;
case "STOP_DST_MULTIADDR_INVALID":
case 351:
m.code = 351;
break;
case "STOP_RELAY_REFUSED":
case 390:
m.code = 390;
break;
case "MALFORMED_MESSAGE":
case 400:
m.code = 400;
break;
}
return m;
};
/**
* Creates a plain object from a CircuitRelay message. Also converts values to other types if specified.
* @function toObject
* @memberof CircuitRelay
* @static
* @param {CircuitRelay} m CircuitRelay
* @param {$protobuf.IConversionOptions} [o] Conversion options
* @returns {Object.<string,*>} Plain object
*/
CircuitRelay.toObject = function toObject(m, o) {
if (!o)
o = {};
var d = {};
if (o.defaults) {
d.type = o.enums === String ? "HOP" : 1;
d.srcPeer = null;
d.dstPeer = null;
d.code = o.enums === String ? "SUCCESS" : 100;
}
if (m.type != null && m.hasOwnProperty("type")) {
d.type = o.enums === String ? $root.CircuitRelay.Type[m.type] : m.type;
}
if (m.srcPeer != null && m.hasOwnProperty("srcPeer")) {
d.srcPeer = $root.CircuitRelay.Peer.toObject(m.srcPeer, o);
}
if (m.dstPeer != null && m.hasOwnProperty("dstPeer")) {
d.dstPeer = $root.CircuitRelay.Peer.toObject(m.dstPeer, o);
}
if (m.code != null && m.hasOwnProperty("code")) {
d.code = o.enums === String ? $root.CircuitRelay.Status[m.code] : m.code;
}
return d;
};
/**
* Converts this CircuitRelay to JSON.
* @function toJSON
* @memberof CircuitRelay
* @instance
* @returns {Object.<string,*>} JSON object
*/
CircuitRelay.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
/**
* Status enum.
* @name CircuitRelay.Status
* @enum {number}
* @property {number} SUCCESS=100 SUCCESS value
* @property {number} HOP_SRC_ADDR_TOO_LONG=220 HOP_SRC_ADDR_TOO_LONG value
* @property {number} HOP_DST_ADDR_TOO_LONG=221 HOP_DST_ADDR_TOO_LONG value
* @property {number} HOP_SRC_MULTIADDR_INVALID=250 HOP_SRC_MULTIADDR_INVALID value
* @property {number} HOP_DST_MULTIADDR_INVALID=251 HOP_DST_MULTIADDR_INVALID value
* @property {number} HOP_NO_CONN_TO_DST=260 HOP_NO_CONN_TO_DST value
* @property {number} HOP_CANT_DIAL_DST=261 HOP_CANT_DIAL_DST value
* @property {number} HOP_CANT_OPEN_DST_STREAM=262 HOP_CANT_OPEN_DST_STREAM value
* @property {number} HOP_CANT_SPEAK_RELAY=270 HOP_CANT_SPEAK_RELAY value
* @property {number} HOP_CANT_RELAY_TO_SELF=280 HOP_CANT_RELAY_TO_SELF value
* @property {number} STOP_SRC_ADDR_TOO_LONG=320 STOP_SRC_ADDR_TOO_LONG value
* @property {number} STOP_DST_ADDR_TOO_LONG=321 STOP_DST_ADDR_TOO_LONG value
* @property {number} STOP_SRC_MULTIADDR_INVALID=350 STOP_SRC_MULTIADDR_INVALID value
* @property {number} STOP_DST_MULTIADDR_INVALID=351 STOP_DST_MULTIADDR_INVALID value
* @property {number} STOP_RELAY_REFUSED=390 STOP_RELAY_REFUSED value
* @property {number} MALFORMED_MESSAGE=400 MALFORMED_MESSAGE value
*/
CircuitRelay.Status = (function() {
var valuesById = {}, values = Object.create(valuesById);
values[valuesById[100] = "SUCCESS"] = 100;
values[valuesById[220] = "HOP_SRC_ADDR_TOO_LONG"] = 220;
values[valuesById[221] = "HOP_DST_ADDR_TOO_LONG"] = 221;
values[valuesById[250] = "HOP_SRC_MULTIADDR_INVALID"] = 250;
values[valuesById[251] = "HOP_DST_MULTIADDR_INVALID"] = 251;
values[valuesById[260] = "HOP_NO_CONN_TO_DST"] = 260;
values[valuesById[261] = "HOP_CANT_DIAL_DST"] = 261;
values[valuesById[262] = "HOP_CANT_OPEN_DST_STREAM"] = 262;
values[valuesById[270] = "HOP_CANT_SPEAK_RELAY"] = 270;
values[valuesById[280] = "HOP_CANT_RELAY_TO_SELF"] = 280;
values[valuesById[320] = "STOP_SRC_ADDR_TOO_LONG"] = 320;
values[valuesById[321] = "STOP_DST_ADDR_TOO_LONG"] = 321;
values[valuesById[350] = "STOP_SRC_MULTIADDR_INVALID"] = 350;
values[valuesById[351] = "STOP_DST_MULTIADDR_INVALID"] = 351;
values[valuesById[390] = "STOP_RELAY_REFUSED"] = 390;
values[valuesById[400] = "MALFORMED_MESSAGE"] = 400;
return values;
})();
/**
* Type enum.
* @name CircuitRelay.Type
* @enum {number}
* @property {number} HOP=1 HOP value
* @property {number} STOP=2 STOP value
* @property {number} STATUS=3 STATUS value
* @property {number} CAN_HOP=4 CAN_HOP value
*/
CircuitRelay.Type = (function() {
var valuesById = {}, values = Object.create(valuesById);
values[valuesById[1] = "HOP"] = 1;
values[valuesById[2] = "STOP"] = 2;
values[valuesById[3] = "STATUS"] = 3;
values[valuesById[4] = "CAN_HOP"] = 4;
return values;
})();
CircuitRelay.Peer = (function() {
/**
* Properties of a Peer.
* @memberof CircuitRelay
* @interface IPeer
* @property {Uint8Array} id Peer id
* @property {Array.<Uint8Array>|null} [addrs] Peer addrs
*/
/**
* Constructs a new Peer.
* @memberof CircuitRelay
* @classdesc Represents a Peer.
* @implements IPeer
* @constructor
* @param {CircuitRelay.IPeer=} [p] Properties to set
*/
function Peer(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]];
}
/**
* Peer id.
* @member {Uint8Array} id
* @memberof CircuitRelay.Peer
* @instance
*/
Peer.prototype.id = $util.newBuffer([]);
/**
* Peer addrs.
* @member {Array.<Uint8Array>} addrs
* @memberof CircuitRelay.Peer
* @instance
*/
Peer.prototype.addrs = $util.emptyArray;
/**
* Encodes the specified Peer message. Does not implicitly {@link CircuitRelay.Peer.verify|verify} messages.
* @function encode
* @memberof CircuitRelay.Peer
* @static
* @param {CircuitRelay.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();
w.uint32(10).bytes(m.id);
if (m.addrs != null && m.addrs.length) {
for (var i = 0; i < m.addrs.length; ++i)
w.uint32(18).bytes(m.addrs[i]);
}
return w;
};
/**
* Decodes a Peer message from the specified reader or buffer.
* @function decode
* @memberof CircuitRelay.Peer
* @static
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
* @param {number} [l] Message length if known beforehand
* @returns {CircuitRelay.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.CircuitRelay.Peer();
while (r.pos < c) {
var t = r.uint32();
switch (t >>> 3) {
case 1:
m.id = r.bytes();
break;
case 2:
if (!(m.addrs && m.addrs.length))
m.addrs = [];
m.addrs.push(r.bytes());
break;
default:
r.skipType(t & 7);
break;
}
}
if (!m.hasOwnProperty("id"))
throw $util.ProtocolError("missing required 'id'", { instance: m });
return m;
};
/**
* Creates a Peer message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof CircuitRelay.Peer
* @static
* @param {Object.<string,*>} d Plain object
* @returns {CircuitRelay.Peer} Peer
*/
Peer.fromObject = function fromObject(d) {
if (d instanceof $root.CircuitRelay.Peer)
return d;
var m = new $root.CircuitRelay.Peer();
if (d.id != null) {
if (typeof d.id === "string")
$util.base64.decode(d.id, m.id = $util.newBuffer($util.base64.length(d.id)), 0);
else if (d.id.length)
m.id = d.id;
}
if (d.addrs) {
if (!Array.isArray(d.addrs))
throw TypeError(".CircuitRelay.Peer.addrs: array expected");
m.addrs = [];
for (var i = 0; i < d.addrs.length; ++i) {
if (typeof d.addrs[i] === "string")
$util.base64.decode(d.addrs[i], m.addrs[i] = $util.newBuffer($util.base64.length(d.addrs[i])), 0);
else if (d.addrs[i].length)
m.addrs[i] = d.addrs[i];
}
}
return m;
};
/**
* Creates a plain object from a Peer message. Also converts values to other types if specified.
* @function toObject
* @memberof CircuitRelay.Peer
* @static
* @param {CircuitRelay.Peer} m Peer
* @param {$protobuf.IConversionOptions} [o] Conversion options
* @returns {Object.<string,*>} Plain object
*/
Peer.toObject = function toObject(m, o) {
if (!o)
o = {};
var d = {};
if (o.arrays || o.defaults) {
d.addrs = [];
}
if (o.defaults) {
if (o.bytes === String)
d.id = "";
else {
d.id = [];
if (o.bytes !== Array)
d.id = $util.newBuffer(d.id);
}
}
if (m.id != null && m.hasOwnProperty("id")) {
d.id = o.bytes === String ? $util.base64.encode(m.id, 0, m.id.length) : o.bytes === Array ? Array.prototype.slice.call(m.id) : m.id;
}
if (m.addrs && m.addrs.length) {
d.addrs = [];
for (var j = 0; j < m.addrs.length; ++j) {
d.addrs[j] = o.bytes === String ? $util.base64.encode(m.addrs[j], 0, m.addrs[j].length) : o.bytes === Array ? Array.prototype.slice.call(m.addrs[j]) : m.addrs[j];
}
}
return d;
};
/**
* Converts this Peer to JSON.
* @function toJSON
* @memberof CircuitRelay.Peer
* @instance
* @returns {Object.<string,*>} JSON object
*/
Peer.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
return Peer;
})();
return CircuitRelay;
})();
module.exports = $root;

View File

@@ -0,0 +1,42 @@
syntax = "proto2";
message CircuitRelay {
enum Status {
SUCCESS = 100;
HOP_SRC_ADDR_TOO_LONG = 220;
HOP_DST_ADDR_TOO_LONG = 221;
HOP_SRC_MULTIADDR_INVALID = 250;
HOP_DST_MULTIADDR_INVALID = 251;
HOP_NO_CONN_TO_DST = 260;
HOP_CANT_DIAL_DST = 261;
HOP_CANT_OPEN_DST_STREAM = 262;
HOP_CANT_SPEAK_RELAY = 270;
HOP_CANT_RELAY_TO_SELF = 280;
STOP_SRC_ADDR_TOO_LONG = 320;
STOP_DST_ADDR_TOO_LONG = 321;
STOP_SRC_MULTIADDR_INVALID = 350;
STOP_DST_MULTIADDR_INVALID = 351;
STOP_RELAY_REFUSED = 390;
MALFORMED_MESSAGE = 400;
}
enum Type { // RPC identifier, either HOP, STOP or STATUS
HOP = 1;
STOP = 2;
STATUS = 3;
CAN_HOP = 4;
}
message Peer {
required bytes id = 1; // peer id
repeated bytes addrs = 2; // peer's known addresses
}
optional Type type = 1; // Type of the message
optional Peer srcPeer = 2; // srcPeer and dstPeer are used when Type is HOP or STATUS
optional Peer dstPeer = 3;
optional Status code = 4; // Status code, used when Type is STATUS
}

View File

@@ -5,10 +5,12 @@ const log = Object.assign(debug('libp2p:circuit'), {
error: debug('libp2p:circuit:err')
})
const errCode = require('err-code')
const mafmt = require('mafmt')
const multiaddr = require('multiaddr')
const { Multiaddr } = require('multiaddr')
const PeerId = require('peer-id')
const { CircuitRelay: CircuitPB } = require('./protocol')
const { codes } = require('../errors')
const toConnection = require('libp2p-utils/src/stream-to-ma-conn')
@@ -21,10 +23,8 @@ const StreamHandler = require('./circuit/stream-handler')
const transportSymbol = Symbol.for('@libp2p/js-libp2p-circuit/circuit')
/**
* @typedef {import('multiaddr')} Multiaddr
* @typedef {import('libp2p-interfaces/src/connection').Connection} Connection
* @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream
* @typedef {import('../types').CircuitRequest} CircuitRequest
*/
class Circuit {
@@ -54,7 +54,7 @@ class Circuit {
* @param {MuxedStream} props.stream
*/
async _onProtocol ({ connection, stream }) {
/** @type {import('./circuit/stream-handler')<CircuitRequest>} */
/** @type {import('./circuit/stream-handler')} */
const streamHandler = new StreamHandler({ stream })
const request = await streamHandler.read()
@@ -96,8 +96,10 @@ class Circuit {
}
if (virtualConnection) {
const remoteAddr = multiaddr(request.dstPeer.addrs[0])
const localAddr = multiaddr(request.srcPeer.addrs[0])
// @ts-ignore dst peer will not be undefined
const remoteAddr = new Multiaddr(request.dstPeer.addrs[0])
// @ts-ignore src peer will not be undefined
const localAddr = new Multiaddr(request.srcPeer.addrs[0])
const maConn = toConnection({
stream: virtualConnection,
remoteAddr,
@@ -123,10 +125,19 @@ class Circuit {
async dial (ma, options) {
// Check the multiaddr to see if it contains a relay and a destination peer
const addrs = ma.toString().split('/p2p-circuit')
const relayAddr = multiaddr(addrs[0])
const destinationAddr = multiaddr(addrs[addrs.length - 1])
const relayPeer = PeerId.createFromCID(relayAddr.getPeerId())
const destinationPeer = PeerId.createFromCID(destinationAddr.getPeerId())
const relayAddr = new Multiaddr(addrs[0])
const destinationAddr = new Multiaddr(addrs[addrs.length - 1])
const relayId = relayAddr.getPeerId()
const destinationId = destinationAddr.getPeerId()
if (!relayId || !destinationId) {
const errMsg = 'Circuit relay dial failed as addresses did not have peer id'
log.error(errMsg)
throw errCode(new Error(errMsg), codes.ERR_RELAYED_DIAL)
}
const relayPeer = PeerId.createFromCID(relayId)
const destinationPeer = PeerId.createFromCID(destinationId)
let disconnectOnFailure = false
let relayConnection = this._connectionManager.get(relayPeer)
@@ -146,7 +157,7 @@ class Circuit {
},
dstPeer: {
id: destinationPeer.toBytes(),
addrs: [multiaddr(destinationAddr).bytes]
addrs: [new Multiaddr(destinationAddr).bytes]
}
}
})

View File

@@ -12,7 +12,7 @@ const TextEncoder = require('ipfs-utils/src/text-encoder')
* @returns {Promise<CID>}
*/
module.exports.namespaceToCid = async (namespace) => {
const bytes = new TextEncoder('utf8').encode(namespace)
const bytes = new TextEncoder().encode(namespace)
const hash = await multihashing(bytes, 'sha2-256')
return new CID(hash)

View File

@@ -1,6 +1,7 @@
'use strict'
const mergeOptions = require('merge-options')
// @ts-ignore no types in multiaddr path
const { dnsaddrResolver } = require('multiaddr/src/resolvers')
const Constants = require('./constants')
@@ -10,11 +11,18 @@ const RelayConstants = require('./circuit/constants')
const { publicAddressesFirst } = require('libp2p-utils/src/address-sort')
const { FaultTolerance } = require('./transport-manager')
/**
* @typedef {import('multiaddr').Multiaddr} Multiaddr
* @typedef {import('.').Libp2pOptions} Libp2pOptions
* @typedef {import('.').constructorOptions} constructorOptions
*/
const DefaultConfig = {
addresses: {
listen: [],
announce: [],
noAnnounce: []
noAnnounce: [],
announceFilter: (/** @type {Multiaddr[]} */ multiaddrs) => multiaddrs
},
connectionManager: {
minConnections: 25
@@ -59,6 +67,16 @@ const DefaultConfig = {
timeout: 10e3
}
},
nat: {
enabled: true,
ttl: 7200,
keepAlive: true,
gateway: null,
externalIp: null,
pmp: {
enabled: false
}
},
peerDiscovery: {
autoDial: true
},
@@ -85,10 +103,15 @@ const DefaultConfig = {
}
}
/**
* @param {Libp2pOptions} opts
* @returns {DefaultConfig & Libp2pOptions & constructorOptions}
*/
module.exports.validate = (opts) => {
opts = mergeOptions(DefaultConfig, opts)
/** @type {DefaultConfig & Libp2pOptions & constructorOptions} */
const resultingOptions = mergeOptions(DefaultConfig, opts)
if (opts.modules.transport.length < 1) throw new Error("'options.modules.transport' must contain at least 1 transport")
if (resultingOptions.modules.transport.length < 1) throw new Error("'options.modules.transport' must contain at least 1 transport")
return opts
return resultingOptions
}

View File

@@ -8,11 +8,10 @@ const log = Object.assign(debug('libp2p:connection-manager'), {
const errcode = require('err-code')
const mergeOptions = require('merge-options')
const LatencyMonitor = require('./latency-monitor')
// @ts-ignore retimer does not have types
const retimer = require('retimer')
/** @typedef {import('../types').EventEmitterFactory} Events */
/** @type Events */
const EventEmitter = require('events')
const { EventEmitter } = require('events')
const PeerId = require('peer-id')
@@ -160,7 +159,7 @@ class ConnectionManager extends EventEmitter {
}
}
await tasks
await Promise.all(tasks)
this.connections.clear()
}
@@ -188,8 +187,10 @@ class ConnectionManager extends EventEmitter {
_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
@@ -362,7 +363,7 @@ class ConnectionManager extends EventEmitter {
*/
_maybeDisconnectOne () {
if (this._options.minConnections < this.connections.size) {
const peerValues = Array.from(this._peerValues).sort(byPeerValue)
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)
const disconnectPeer = peerValues[0]
if (disconnectPeer) {
@@ -381,7 +382,3 @@ class ConnectionManager extends EventEmitter {
}
module.exports = ConnectionManager
function byPeerValue (peerValueEntryA, peerValueEntryB) {
return peerValueEntryA[1] - peerValueEntryB[1]
}

View File

@@ -5,11 +5,7 @@
* This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE)
*/
/* global window */
const globalThis = require('ipfs-utils/src/globalthis')
/** @typedef {import('../types').EventEmitterFactory} Events */
/** @type Events */
const EventEmitter = require('events')
const { EventEmitter } = require('events')
const VisibilityChangeEmitter = require('./visibility-change-emitter')
const debug = require('debug')('latency-monitor:LatencyMonitor')
@@ -61,7 +57,8 @@ class LatencyMonitor extends EventEmitter {
that._latecyCheckMultiply = 2 * (that.latencyRandomPercentage / 100.0) * that.latencyCheckIntervalMs
that._latecyCheckSubtract = that._latecyCheckMultiply / 2
that.dataEmitIntervalMs = (dataEmitIntervalMs === null || dataEmitIntervalMs === 0) ? undefined
that.dataEmitIntervalMs = (dataEmitIntervalMs === null || dataEmitIntervalMs === 0)
? undefined
: dataEmitIntervalMs || 5 * 1000 // 5s
debug('latencyCheckIntervalMs: %s dataEmitIntervalMs: %s',
that.latencyCheckIntervalMs, that.dataEmitIntervalMs)
@@ -74,9 +71,9 @@ class LatencyMonitor extends EventEmitter {
that.asyncTestFn = asyncTestFn // If there is no asyncFn, we measure latency
// If process: use high resolution timer
if (globalThis.process && globalThis.process.hrtime) {
if (globalThis.process && globalThis.process.hrtime) { // eslint-disable-line no-undef
debug('Using process.hrtime for timing')
that.now = globalThis.process.hrtime
that.now = globalThis.process.hrtime // eslint-disable-line no-undef
that.getDeltaMS = (startTime) => {
const hrtime = that.now(startTime)
return (hrtime[0] * 1000) + (hrtime[1] / 1000000)
@@ -176,7 +173,8 @@ class LatencyMonitor extends EventEmitter {
events: this._latencyData.events,
minMs: this._latencyData.minMs,
maxMs: this._latencyData.maxMs,
avgMs: this._latencyData.events ? this._latencyData.totalMs / this._latencyData.events
avgMs: this._latencyData.events
? this._latencyData.totalMs / this._latencyData.events
: Number.POSITIVE_INFINITY,
lengthMs: this.getDeltaMS(this._latencyData.startTime)
}

View File

@@ -6,9 +6,7 @@
*/
'use strict'
/** @typedef {import('../types').EventEmitterFactory} Events */
/** @type Events */
const EventEmitter = require('events')
const { EventEmitter } = require('events')
const debug = require('debug')('latency-monitor:VisibilityChangeEmitter')

View File

@@ -1,15 +1,22 @@
'use strict'
const errCode = require('err-code')
const { messages, codes } = require('./errors')
const { messages, codes } = require('../errors')
const {
storeAddresses,
uniquePeers,
requirePeers,
maybeLimitSource
} = require('./utils')
const all = require('it-all')
const pAny = require('p-any')
const merge = require('it-merge')
const { pipe } = require('it-pipe')
/**
* @typedef {import('peer-id')} PeerId
* @typedef {import('multiaddr')} Multiaddr
* @typedef {import('multiaddr').Multiaddr} Multiaddr
* @typedef {import('cids')} CID
* @typedef {import('libp2p-interfaces/src/content-routing/types').ContentRouting} ContentRoutingModule
*/
/**
@@ -21,22 +28,22 @@ const pAny = require('p-any')
class ContentRouting {
/**
* @class
* @param {import('./')} libp2p
* @param {import('..')} libp2p
*/
constructor (libp2p) {
this.libp2p = libp2p
/** @type {ContentRoutingModule[]} */
this.routers = libp2p._modules.contentRouting || []
this.dht = libp2p._dht
// If we have the dht, make it first
if (this.dht) {
this.routers.unshift(this.dht)
// If we have the dht, add it to the available content routers
if (this.dht && libp2p._config.dht.enabled) {
this.routers.push(this.dht)
}
}
/**
* Iterates over all content routers in series to find providers of the given key.
* Once a content router succeeds, iteration will stop.
* Iterates over all content routers in parallel to find providers of the given key.
*
* @param {CID} key - The CID key of the content to find
* @param {object} [options]
@@ -44,25 +51,20 @@ class ContentRouting {
* @param {number} [options.maxNumProviders] - maximum number of providers to find
* @returns {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>}
*/
async * findProviders (key, options) {
async * findProviders (key, options = {}) {
if (!this.routers.length) {
throw errCode(new Error('No content this.routers available'), 'NO_ROUTERS_AVAILABLE')
}
const result = await pAny(
this.routers.map(async (router) => {
const provs = await all(router.findProviders(key, options))
if (!provs || !provs.length) {
throw errCode(new Error('not found'), 'NOT_FOUND')
}
return provs
})
yield * pipe(
merge(
...this.routers.map(router => router.findProviders(key, options))
),
(source) => storeAddresses(source, this.libp2p.peerStore),
(source) => uniquePeers(source),
(source) => maybeLimitSource(source, options.maxNumProviders),
(source) => requirePeers(source)
)
for (const peer of result) {
yield peer
}
}
/**

View File

@@ -0,0 +1,89 @@
'use strict'
const errCode = require('err-code')
const filter = require('it-filter')
const map = require('it-map')
const take = require('it-take')
/**
* @typedef {import('peer-id')} PeerId
* @typedef {import('multiaddr').Multiaddr} Multiaddr
*/
/**
* Store the multiaddrs from every peer in the passed peer store
*
* @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source
* @param {import('../peer-store')} peerStore
*/
function storeAddresses (source, peerStore) {
return map(source, (peer) => {
// ensure we have the addresses for a given peer
peerStore.addressBook.add(peer.id, peer.multiaddrs)
return peer
})
}
/**
* Filter peers by unique peer id
*
* @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source
*/
function uniquePeers (source) {
/** @type Set<string> */
const seen = new Set()
return filter(source, (peer) => {
// dedupe by peer id
if (seen.has(peer.id.toString())) {
return false
}
seen.add(peer.id.toString())
return true
})
}
/**
* Require at least `min` peers to be yielded from `source`
*
* @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source
* @param {number} min
*/
async function * requirePeers (source, min = 1) {
let seen = 0
for await (const peer of source) {
seen++
yield peer
}
if (seen < min) {
throw errCode(new Error('not found'), 'NOT_FOUND')
}
}
/**
* If `max` is passed, only take that number of peers from the source
* otherwise take all the peers
*
* @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source
* @param {number} [max]
*/
function maybeLimitSource (source, max) {
if (max) {
return take(source, max)
}
return source
}
module.exports = {
storeAddresses,
uniquePeers,
requirePeers,
maybeLimitSource
}

View File

@@ -2,14 +2,15 @@
const errCode = require('err-code')
const AbortController = require('abort-controller').default
const anySignal = require('any-signal')
const { anySignal } = require('any-signal')
// @ts-ignore p-fifo does not export types
const FIFO = require('p-fifo')
const pAny = require('p-any')
/**
* @typedef {import('libp2p-interfaces/src/connection').Connection} Connection
* @typedef {import('./')} Dialer
* @typedef {import('multiaddr')} Multiaddr
* @typedef {import('multiaddr').Multiaddr} Multiaddr
*/
/**
@@ -67,7 +68,7 @@ class DialRequest {
let conn
try {
const signal = dialAbortControllers[i].signal
conn = await this.dialAction(addr, { ...options, signal: anySignal([signal, options.signal]) })
conn = await this.dialAction(addr, { ...options, signal: options.signal ? anySignal([signal, options.signal]) : signal })
// Remove the successful AbortController so it is not aborted
dialAbortControllers.splice(i, 1)
} finally {

View File

@@ -5,9 +5,10 @@ const log = Object.assign(debug('libp2p:dialer'), {
error: debug('libp2p:dialer:err')
})
const errCode = require('err-code')
const multiaddr = require('multiaddr')
const { Multiaddr } = require('multiaddr')
// @ts-ignore timeout-abourt-controles does not export types
const TimeoutController = require('timeout-abort-controller')
const anySignal = require('any-signal')
const { anySignal } = require('any-signal')
const DialRequest = require('./dial-request')
const { publicAddressesFirst } = require('libp2p-utils/src/address-sort')
@@ -22,7 +23,6 @@ const {
/**
* @typedef {import('libp2p-interfaces/src/connection').Connection} Connection
* @typedef {import('multiaddr')} Multiaddr
* @typedef {import('peer-id')} PeerId
* @typedef {import('../peer-store')} PeerStore
* @typedef {import('../peer-store/address-book').Address} Address
@@ -38,9 +38,9 @@ const {
*
* @typedef {Object} DialerOptions
* @property {(addresses: Address[]) => Address[]} [options.addressSorter = publicAddressesFirst] - Sort the known addresses of a peer before trying to dial.
* @property {number} [concurrency = MAX_PARALLEL_DIALS] - Number of max concurrent dials.
* @property {number} [perPeerLimit = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer.
* @property {number} [timeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take.
* @property {number} [maxParallelDials = MAX_PARALLEL_DIALS] - Number of max concurrent dials.
* @property {number} [maxDialsPerPeer = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer.
* @property {number} [dialTimeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take.
* @property {Record<string, Resolver>} [resolvers = {}] - multiaddr resolvers to use when dialing
*
* @typedef DialTarget
@@ -50,7 +50,7 @@ const {
* @typedef PendingDial
* @property {DialRequest} dialRequest
* @property {TimeoutController} controller
* @property {Promise} promise
* @property {Promise<Connection>} promise
* @property {function():void} destroy
*/
@@ -63,22 +63,22 @@ class Dialer {
transportManager,
peerStore,
addressSorter = publicAddressesFirst,
concurrency = MAX_PARALLEL_DIALS,
timeout = DIAL_TIMEOUT,
perPeerLimit = MAX_PER_PEER_DIALS,
maxParallelDials = MAX_PARALLEL_DIALS,
dialTimeout = DIAL_TIMEOUT,
maxDialsPerPeer = MAX_PER_PEER_DIALS,
resolvers = {}
}) {
this.transportManager = transportManager
this.peerStore = peerStore
this.addressSorter = addressSorter
this.concurrency = concurrency
this.timeout = timeout
this.perPeerLimit = perPeerLimit
this.tokens = [...new Array(concurrency)].map((_, index) => index)
this.maxParallelDials = maxParallelDials
this.timeout = dialTimeout
this.maxDialsPerPeer = maxDialsPerPeer
this.tokens = [...new Array(maxParallelDials)].map((_, index) => index)
this._pendingDials = new Map()
for (const [key, value] of Object.entries(resolvers)) {
multiaddr.resolvers.set(key, value)
Multiaddr.resolvers.set(key, value)
}
}
@@ -110,7 +110,7 @@ class Dialer {
const dialTarget = await this._createDialTarget(peer)
if (!dialTarget.addrs.length) {
throw errCode(new Error('The dial request has no addresses'), codes.ERR_NO_VALID_ADDRESSES)
throw errCode(new Error('The dial request has no valid addresses'), codes.ERR_NO_VALID_ADDRESSES)
}
const pendingDial = this._pendingDials.get(dialTarget.id) || this._createPendingDial(dialTarget, options)
@@ -134,6 +134,7 @@ class Dialer {
* Creates a DialTarget. The DialTarget is used to create and track
* the DialRequest to a given peer.
* If a multiaddr is received it should be the first address attempted.
* Multiaddrs not supported by the available transports will be filtered out.
*
* @private
* @param {PeerId|Multiaddr|string} peer - A PeerId or Multiaddr
@@ -150,20 +151,24 @@ class Dialer {
// 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.
if (multiaddr.isMultiaddr(peer)) {
if (Multiaddr.isMultiaddr(peer)) {
knownAddrs = knownAddrs.filter((addr) => !peer.equals(addr))
knownAddrs.unshift(peer)
}
/** @type {Multiaddr[]} */
const addrs = []
for (const a of knownAddrs) {
const resolvedAddrs = await this._resolve(a)
resolvedAddrs.forEach(ra => addrs.push(ra))
}
// Multiaddrs not supported by the available transports will be filtered out.
const supportedAddrs = addrs.filter(a => this.transportManager.transportForMultiaddr(a))
return {
id: id.toB58String(),
addrs
addrs: supportedAddrs
}
}
@@ -177,6 +182,10 @@ class Dialer {
* @returns {PendingDial}
*/
_createPendingDial (dialTarget, options = {}) {
/**
* @param {Multiaddr} addr
* @param {{ signal: { aborted: any; }; }} options
*/
const dialAction = (addr, options) => {
if (options.signal.aborted) throw errCode(new Error('already aborted'), codes.ERR_ALREADY_ABORTED)
return this.transportManager.dial(addr, options)
@@ -207,13 +216,19 @@ class Dialer {
return pendingDial
}
/**
* @param {number} num
*/
getTokens (num) {
const total = Math.min(num, this.perPeerLimit, this.tokens.length)
const total = Math.min(num, this.maxDialsPerPeer, this.tokens.length)
const tokens = this.tokens.splice(0, total)
log('%d tokens request, returning %d, %d remaining', num, total, this.tokens.length)
return tokens
}
/**
* @param {number} token
*/
releaseToken (token) {
// Guard against duplicate releases
if (this.tokens.indexOf(token) > -1) return
@@ -259,7 +274,7 @@ class Dialer {
*/
async _resolveRecord (ma) {
try {
ma = multiaddr(ma.toString()) // Use current multiaddr module
ma = new Multiaddr(ma.toString()) // Use current multiaddr module
const multiaddrs = await ma.resolve()
return multiaddrs
} catch (_) {

View File

@@ -11,11 +11,13 @@ exports.codes = {
PUBSUB_NOT_STARTED: 'ERR_PUBSUB_NOT_STARTED',
DHT_NOT_STARTED: 'ERR_DHT_NOT_STARTED',
CONN_ENCRYPTION_REQUIRED: 'ERR_CONN_ENCRYPTION_REQUIRED',
ERR_INVALID_PROTOCOLS_FOR_STREAM: 'ERR_INVALID_PROTOCOLS_FOR_STREAM',
ERR_CONNECTION_ENDED: 'ERR_CONNECTION_ENDED',
ERR_CONNECTION_FAILED: 'ERR_CONNECTION_FAILED',
ERR_NODE_NOT_STARTED: 'ERR_NODE_NOT_STARTED',
ERR_ALREADY_ABORTED: 'ERR_ALREADY_ABORTED',
ERR_NO_VALID_ADDRESSES: 'ERR_NO_VALID_ADDRESSES',
ERR_RELAYED_DIAL: 'ERR_RELAYED_DIAL',
ERR_DIALED_SELF: 'ERR_DIALED_SELF',
ERR_DISCOVERED_SELF: 'ERR_DISCOVERED_SELF',
ERR_DUPLICATE_TRANSPORT: 'ERR_DUPLICATE_TRANSPORT',

View File

@@ -1,15 +1,11 @@
'use strict'
const PeerId = require('peer-id')
const multiaddr = require('multiaddr')
const { Multiaddr } = require('multiaddr')
const errCode = require('err-code')
const { codes } = require('./errors')
/**
* @typedef {import('multiaddr')} Multiaddr
*/
/**
* Converts the given `peer` to a `Peer` object.
* If a multiaddr is received, the addressBook is updated.
@@ -19,14 +15,23 @@ const { codes } = require('./errors')
*/
function getPeer (peer) {
if (typeof peer === 'string') {
peer = multiaddr(peer)
peer = new Multiaddr(peer)
}
let addr
if (multiaddr.isMultiaddr(peer)) {
if (Multiaddr.isMultiaddr(peer)) {
addr = peer
const idStr = peer.getPeerId()
if (!idStr) {
throw errCode(
new Error(`${peer} does not have a valid peer type`),
codes.ERR_INVALID_MULTIADDR
)
}
try {
peer = PeerId.createFromB58String(peer.getPeerId())
peer = PeerId.createFromB58String(idStr)
} catch (err) {
throw errCode(
new Error(`${peer} is not a valid peer type`),

View File

@@ -5,14 +5,14 @@ const log = Object.assign(debug('libp2p:identify'), {
error: debug('libp2p:identify:err')
})
const errCode = require('err-code')
const pb = require('it-protocol-buffers')
const lp = require('it-length-prefixed')
const { pipe } = require('it-pipe')
const { collect, take, consume } = require('streaming-iterables')
const uint8ArrayFromString = require('uint8arrays/from-string')
const PeerId = require('peer-id')
const multiaddr = require('multiaddr')
const { Multiaddr } = require('multiaddr')
// @ts-ignore it-buffer does not have types
const { toBuffer } = require('it-buffer')
const Message = require('./message')
@@ -23,7 +23,6 @@ const PeerRecord = require('../record/peer-record')
const {
MULTICODEC_IDENTIFY,
MULTICODEC_IDENTIFY_PUSH,
AGENT_VERSION,
PROTOCOL_VERSION
} = require('./consts')
@@ -34,6 +33,11 @@ const { codes } = require('../errors')
* @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream
*/
/**
* @typedef {Object} HostProperties
* @property {string} agentVersion
*/
class IdentifyService {
/**
* @class
@@ -43,6 +47,7 @@ class IdentifyService {
constructor ({ libp2p }) {
this._libp2p = libp2p
this.peerStore = libp2p.peerStore
this.addressManager = libp2p.addressManager
this.connectionManager = libp2p.connectionManager
this.peerId = libp2p.peerId
@@ -50,7 +55,6 @@ class IdentifyService {
// Store self host metadata
this._host = {
agentVersion: AGENT_VERSION,
protocolVersion: PROTOCOL_VERSION,
...libp2p._options.host
}
@@ -93,12 +97,12 @@ class IdentifyService {
const { stream } = await connection.newStream(MULTICODEC_IDENTIFY_PUSH)
await pipe(
[{
[Message.Identify.encode({
listenAddrs,
signedPeerRecord,
protocols
}],
pb.encode(Message),
}).finish()],
lp.encode(),
stream,
consume
)
@@ -159,12 +163,12 @@ class IdentifyService {
let message
try {
message = Message.decode(data)
message = Message.Identify.decode(data)
} catch (err) {
throw errCode(err, codes.ERR_INVALID_MESSAGE)
}
let {
const {
publicKey,
listenAddrs,
protocols,
@@ -179,7 +183,7 @@ class IdentifyService {
}
// Get the observedAddr if there is one
observedAddr = IdentifyService.getCleanMultiaddr(observedAddr)
const cleanObservedAddr = IdentifyService.getCleanMultiaddr(observedAddr)
try {
const envelope = await Envelope.openAndCertify(signedPeerRecord, PeerRecord.DOMAIN)
@@ -193,7 +197,7 @@ class IdentifyService {
// LEGACY: Update peers data in PeerStore
try {
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => new Multiaddr(addr)))
} catch (err) {
log.error('received invalid addrs', err)
}
@@ -201,8 +205,9 @@ class IdentifyService {
this.peerStore.protoBook.set(id, protocols)
this.peerStore.metadataBook.set(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion))
// TODO: Track our observed address so that we can score it
log('received observed address of %s', observedAddr)
// TODO: Add and score our observed addr
log('received observed address of %s', cleanObservedAddr)
// this.addressManager.addObservedAddr(observedAddr)
}
/**
@@ -244,7 +249,7 @@ class IdentifyService {
const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId)
const protocols = this.peerStore.protoBook.get(this.peerId) || []
const message = Message.encode({
const message = Message.Identify.encode({
protocolVersion: this._host.protocolVersion,
agentVersion: this._host.agentVersion,
publicKey,
@@ -252,7 +257,7 @@ class IdentifyService {
signedPeerRecord,
observedAddr: connection.remoteAddr.bytes,
protocols
})
}).finish()
try {
await pipe(
@@ -286,7 +291,7 @@ class IdentifyService {
toBuffer,
collect
)
message = Message.decode(data)
message = Message.Identify.decode(data)
} catch (err) {
return log.error('received invalid message', err)
}
@@ -305,7 +310,8 @@ class IdentifyService {
// LEGACY: Update peers data in PeerStore
try {
this.peerStore.addressBook.set(id, message.listenAddrs.map((addr) => multiaddr(addr)))
this.peerStore.addressBook.set(id,
message.listenAddrs.map((addr) => new Multiaddr(addr)))
} catch (err) {
log.error('received invalid addrs', err)
}
@@ -318,12 +324,12 @@ class IdentifyService {
* Takes the `addr` and converts it to a Multiaddr if possible
*
* @param {Uint8Array | string} addr
* @returns {multiaddr|null}
* @returns {Multiaddr|null}
*/
static getCleanMultiaddr (addr) {
if (addr && addr.length > 0) {
try {
return multiaddr(addr)
return new Multiaddr(addr)
} catch (_) {
return null
}

95
src/identify/message.d.ts vendored Normal file
View File

@@ -0,0 +1,95 @@
import * as $protobuf from "protobufjs";
/** Properties of an Identify. */
export interface IIdentify {
/** Identify protocolVersion */
protocolVersion?: (string|null);
/** Identify agentVersion */
agentVersion?: (string|null);
/** Identify publicKey */
publicKey?: (Uint8Array|null);
/** Identify listenAddrs */
listenAddrs?: (Uint8Array[]|null);
/** Identify observedAddr */
observedAddr?: (Uint8Array|null);
/** Identify protocols */
protocols?: (string[]|null);
/** Identify signedPeerRecord */
signedPeerRecord?: (Uint8Array|null);
}
/** Represents an Identify. */
export class Identify implements IIdentify {
/**
* Constructs a new Identify.
* @param [p] Properties to set
*/
constructor(p?: IIdentify);
/** Identify protocolVersion. */
public protocolVersion: string;
/** Identify agentVersion. */
public agentVersion: string;
/** Identify publicKey. */
public publicKey: Uint8Array;
/** Identify listenAddrs. */
public listenAddrs: Uint8Array[];
/** Identify observedAddr. */
public observedAddr: Uint8Array;
/** Identify protocols. */
public protocols: string[];
/** Identify signedPeerRecord. */
public signedPeerRecord: Uint8Array;
/**
* Encodes the specified Identify message. Does not implicitly {@link Identify.verify|verify} messages.
* @param m Identify message or plain object to encode
* @param [w] Writer to encode to
* @returns Writer
*/
public static encode(m: IIdentify, w?: $protobuf.Writer): $protobuf.Writer;
/**
* Decodes an Identify message from the specified reader or buffer.
* @param r Reader or buffer to decode from
* @param [l] Message length if known beforehand
* @returns Identify
* @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): Identify;
/**
* Creates an Identify message from a plain object. Also converts values to their respective internal types.
* @param d Plain object
* @returns Identify
*/
public static fromObject(d: { [k: string]: any }): Identify;
/**
* Creates a plain object from an Identify message. Also converts values to other types if specified.
* @param m Identify
* @param [o] Conversion options
* @returns Plain object
*/
public static toObject(m: Identify, o?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this Identify to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
}

View File

@@ -1,35 +1,328 @@
'use strict'
/*eslint-disable*/
"use strict";
const protons = require('protons')
const schema = `
message Identify {
// protocolVersion determines compatibility between peers
optional string protocolVersion = 5; // e.g. ipfs/1.0.0
var $protobuf = require("protobufjs/minimal");
// agentVersion is like a UserAgent string in browsers, or client version in bittorrent
// includes the client name and client.
optional string agentVersion = 6; // e.g. go-ipfs/0.1.0
// Common aliases
var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
// publicKey is this node's public key (which also gives its node.ID)
// - may not need to be sent, as secure channel implies it has been sent.
// - then again, if we change / disable secure channel, may still want it.
optional bytes publicKey = 1;
// Exported root namespace
var $root = $protobuf.roots["libp2p-identify"] || ($protobuf.roots["libp2p-identify"] = {});
// listenAddrs are the multiaddrs the sender node listens for open connections on
repeated bytes listenAddrs = 2;
$root.Identify = (function() {
// oservedAddr is the multiaddr of the remote endpoint that the sender node perceives
// this is useful information to convey to the other side, as it helps the remote endpoint
// determine whether its connection to the local peer goes through NAT.
optional bytes observedAddr = 4;
/**
* Properties of an Identify.
* @exports IIdentify
* @interface IIdentify
* @property {string|null} [protocolVersion] Identify protocolVersion
* @property {string|null} [agentVersion] Identify agentVersion
* @property {Uint8Array|null} [publicKey] Identify publicKey
* @property {Array.<Uint8Array>|null} [listenAddrs] Identify listenAddrs
* @property {Uint8Array|null} [observedAddr] Identify observedAddr
* @property {Array.<string>|null} [protocols] Identify protocols
* @property {Uint8Array|null} [signedPeerRecord] Identify signedPeerRecord
*/
repeated string protocols = 3;
/**
* Constructs a new Identify.
* @exports Identify
* @classdesc Represents an Identify.
* @implements IIdentify
* @constructor
* @param {IIdentify=} [p] Properties to set
*/
function Identify(p) {
this.listenAddrs = [];
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]];
}
// signedPeerRecord contains a serialized SignedEnvelope containing a PeerRecord,
// signed by the sending node. It contains the same addresses as the listenAddrs field, but
// in a form that lets us share authenticated addrs with other peers.
optional bytes signedPeerRecord = 8;
}
`
/**
* Identify protocolVersion.
* @member {string} protocolVersion
* @memberof Identify
* @instance
*/
Identify.prototype.protocolVersion = "";
module.exports = protons(schema).Identify
/**
* Identify agentVersion.
* @member {string} agentVersion
* @memberof Identify
* @instance
*/
Identify.prototype.agentVersion = "";
/**
* Identify publicKey.
* @member {Uint8Array} publicKey
* @memberof Identify
* @instance
*/
Identify.prototype.publicKey = $util.newBuffer([]);
/**
* Identify listenAddrs.
* @member {Array.<Uint8Array>} listenAddrs
* @memberof Identify
* @instance
*/
Identify.prototype.listenAddrs = $util.emptyArray;
/**
* Identify observedAddr.
* @member {Uint8Array} observedAddr
* @memberof Identify
* @instance
*/
Identify.prototype.observedAddr = $util.newBuffer([]);
/**
* Identify protocols.
* @member {Array.<string>} protocols
* @memberof Identify
* @instance
*/
Identify.prototype.protocols = $util.emptyArray;
/**
* Identify signedPeerRecord.
* @member {Uint8Array} signedPeerRecord
* @memberof Identify
* @instance
*/
Identify.prototype.signedPeerRecord = $util.newBuffer([]);
/**
* Encodes the specified Identify message. Does not implicitly {@link Identify.verify|verify} messages.
* @function encode
* @memberof Identify
* @static
* @param {IIdentify} m Identify message or plain object to encode
* @param {$protobuf.Writer} [w] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
Identify.encode = function encode(m, w) {
if (!w)
w = $Writer.create();
if (m.publicKey != null && Object.hasOwnProperty.call(m, "publicKey"))
w.uint32(10).bytes(m.publicKey);
if (m.listenAddrs != null && m.listenAddrs.length) {
for (var i = 0; i < m.listenAddrs.length; ++i)
w.uint32(18).bytes(m.listenAddrs[i]);
}
if (m.protocols != null && m.protocols.length) {
for (var i = 0; i < m.protocols.length; ++i)
w.uint32(26).string(m.protocols[i]);
}
if (m.observedAddr != null && Object.hasOwnProperty.call(m, "observedAddr"))
w.uint32(34).bytes(m.observedAddr);
if (m.protocolVersion != null && Object.hasOwnProperty.call(m, "protocolVersion"))
w.uint32(42).string(m.protocolVersion);
if (m.agentVersion != null && Object.hasOwnProperty.call(m, "agentVersion"))
w.uint32(50).string(m.agentVersion);
if (m.signedPeerRecord != null && Object.hasOwnProperty.call(m, "signedPeerRecord"))
w.uint32(66).bytes(m.signedPeerRecord);
return w;
};
/**
* Decodes an Identify message from the specified reader or buffer.
* @function decode
* @memberof Identify
* @static
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
* @param {number} [l] Message length if known beforehand
* @returns {Identify} Identify
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
Identify.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.Identify();
while (r.pos < c) {
var t = r.uint32();
switch (t >>> 3) {
case 5:
m.protocolVersion = r.string();
break;
case 6:
m.agentVersion = r.string();
break;
case 1:
m.publicKey = r.bytes();
break;
case 2:
if (!(m.listenAddrs && m.listenAddrs.length))
m.listenAddrs = [];
m.listenAddrs.push(r.bytes());
break;
case 4:
m.observedAddr = r.bytes();
break;
case 3:
if (!(m.protocols && m.protocols.length))
m.protocols = [];
m.protocols.push(r.string());
break;
case 8:
m.signedPeerRecord = r.bytes();
break;
default:
r.skipType(t & 7);
break;
}
}
return m;
};
/**
* Creates an Identify message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof Identify
* @static
* @param {Object.<string,*>} d Plain object
* @returns {Identify} Identify
*/
Identify.fromObject = function fromObject(d) {
if (d instanceof $root.Identify)
return d;
var m = new $root.Identify();
if (d.protocolVersion != null) {
m.protocolVersion = String(d.protocolVersion);
}
if (d.agentVersion != null) {
m.agentVersion = String(d.agentVersion);
}
if (d.publicKey != null) {
if (typeof d.publicKey === "string")
$util.base64.decode(d.publicKey, m.publicKey = $util.newBuffer($util.base64.length(d.publicKey)), 0);
else if (d.publicKey.length)
m.publicKey = d.publicKey;
}
if (d.listenAddrs) {
if (!Array.isArray(d.listenAddrs))
throw TypeError(".Identify.listenAddrs: array expected");
m.listenAddrs = [];
for (var i = 0; i < d.listenAddrs.length; ++i) {
if (typeof d.listenAddrs[i] === "string")
$util.base64.decode(d.listenAddrs[i], m.listenAddrs[i] = $util.newBuffer($util.base64.length(d.listenAddrs[i])), 0);
else if (d.listenAddrs[i].length)
m.listenAddrs[i] = d.listenAddrs[i];
}
}
if (d.observedAddr != null) {
if (typeof d.observedAddr === "string")
$util.base64.decode(d.observedAddr, m.observedAddr = $util.newBuffer($util.base64.length(d.observedAddr)), 0);
else if (d.observedAddr.length)
m.observedAddr = d.observedAddr;
}
if (d.protocols) {
if (!Array.isArray(d.protocols))
throw TypeError(".Identify.protocols: array expected");
m.protocols = [];
for (var i = 0; i < d.protocols.length; ++i) {
m.protocols[i] = String(d.protocols[i]);
}
}
if (d.signedPeerRecord != null) {
if (typeof d.signedPeerRecord === "string")
$util.base64.decode(d.signedPeerRecord, m.signedPeerRecord = $util.newBuffer($util.base64.length(d.signedPeerRecord)), 0);
else if (d.signedPeerRecord.length)
m.signedPeerRecord = d.signedPeerRecord;
}
return m;
};
/**
* Creates a plain object from an Identify message. Also converts values to other types if specified.
* @function toObject
* @memberof Identify
* @static
* @param {Identify} m Identify
* @param {$protobuf.IConversionOptions} [o] Conversion options
* @returns {Object.<string,*>} Plain object
*/
Identify.toObject = function toObject(m, o) {
if (!o)
o = {};
var d = {};
if (o.arrays || o.defaults) {
d.listenAddrs = [];
d.protocols = [];
}
if (o.defaults) {
if (o.bytes === String)
d.publicKey = "";
else {
d.publicKey = [];
if (o.bytes !== Array)
d.publicKey = $util.newBuffer(d.publicKey);
}
if (o.bytes === String)
d.observedAddr = "";
else {
d.observedAddr = [];
if (o.bytes !== Array)
d.observedAddr = $util.newBuffer(d.observedAddr);
}
d.protocolVersion = "";
d.agentVersion = "";
if (o.bytes === String)
d.signedPeerRecord = "";
else {
d.signedPeerRecord = [];
if (o.bytes !== Array)
d.signedPeerRecord = $util.newBuffer(d.signedPeerRecord);
}
}
if (m.publicKey != null && m.hasOwnProperty("publicKey")) {
d.publicKey = o.bytes === String ? $util.base64.encode(m.publicKey, 0, m.publicKey.length) : o.bytes === Array ? Array.prototype.slice.call(m.publicKey) : m.publicKey;
}
if (m.listenAddrs && m.listenAddrs.length) {
d.listenAddrs = [];
for (var j = 0; j < m.listenAddrs.length; ++j) {
d.listenAddrs[j] = o.bytes === String ? $util.base64.encode(m.listenAddrs[j], 0, m.listenAddrs[j].length) : o.bytes === Array ? Array.prototype.slice.call(m.listenAddrs[j]) : m.listenAddrs[j];
}
}
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.observedAddr != null && m.hasOwnProperty("observedAddr")) {
d.observedAddr = o.bytes === String ? $util.base64.encode(m.observedAddr, 0, m.observedAddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.observedAddr) : m.observedAddr;
}
if (m.protocolVersion != null && m.hasOwnProperty("protocolVersion")) {
d.protocolVersion = m.protocolVersion;
}
if (m.agentVersion != null && m.hasOwnProperty("agentVersion")) {
d.agentVersion = m.agentVersion;
}
if (m.signedPeerRecord != null && m.hasOwnProperty("signedPeerRecord")) {
d.signedPeerRecord = o.bytes === String ? $util.base64.encode(m.signedPeerRecord, 0, m.signedPeerRecord.length) : o.bytes === Array ? Array.prototype.slice.call(m.signedPeerRecord) : m.signedPeerRecord;
}
return d;
};
/**
* Converts this Identify to JSON.
* @function toJSON
* @memberof Identify
* @instance
* @returns {Object.<string,*>} JSON object
*/
Identify.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
return Identify;
})();
module.exports = $root;

View File

@@ -0,0 +1,30 @@
syntax = "proto3";
message Identify {
// protocolVersion determines compatibility between peers
optional string protocolVersion = 5; // e.g. ipfs/1.0.0
// agentVersion is like a UserAgent string in browsers, or client version in bittorrent
// includes the client name and client.
optional string agentVersion = 6; // e.g. go-ipfs/0.1.0
// publicKey is this node's public key (which also gives its node.ID)
// - may not need to be sent, as secure channel implies it has been sent.
// - then again, if we change / disable secure channel, may still want it.
optional bytes publicKey = 1;
// listenAddrs are the multiaddrs the sender node listens for open connections on
repeated bytes listenAddrs = 2;
// oservedAddr is the multiaddr of the remote endpoint that the sender node perceives
// this is useful information to convey to the other side, as it helps the remote endpoint
// determine whether its connection to the local peer goes through NAT.
optional bytes observedAddr = 4;
repeated string protocols = 3;
// signedPeerRecord contains a serialized SignedEnvelope containing a PeerRecord,
// signed by the sending node. It contains the same addresses as the listenAddrs field, but
// in a form that lets us share authenticated addrs with other peers.
optional bytes signedPeerRecord = 8;
}

View File

@@ -4,13 +4,11 @@ const debug = require('debug')
const log = Object.assign(debug('libp2p'), {
error: debug('libp2p:err')
})
/** @typedef {import('./types').EventEmitterFactory} Events */
/** @type Events */
const EventEmitter = require('events')
const globalThis = require('ipfs-utils/src/globalthis')
const { EventEmitter } = require('events')
const errCode = require('err-code')
const PeerId = require('peer-id')
const { Multiaddr } = require('multiaddr')
const PeerRouting = require('./peer-routing')
const ContentRouting = require('./content-routing')
@@ -34,34 +32,67 @@ const Registrar = require('./registrar')
const ping = require('./ping')
const IdentifyService = require('./identify')
const IDENTIFY_PROTOCOLS = IdentifyService.multicodecs
const NatManager = require('./nat-manager')
const { updateSelfPeerRecord } = require('./record/utils')
/**
* @typedef {import('multiaddr')} Multiaddr
* @typedef {import('libp2p-interfaces/src/connection').Connection} Connection
* @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream
* @typedef {import('libp2p-interfaces/src/transport/types').TransportFactory} TransportFactory
* @typedef {import('libp2p-interfaces/src/transport/types').TransportFactory<any, any>} TransportFactory
* @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxerFactory} MuxerFactory
* @typedef {import('libp2p-interfaces/src/content-routing/types').ContentRouting} ContentRoutingModule
* @typedef {import('libp2p-interfaces/src/peer-discovery/types').PeerDiscoveryFactory} PeerDiscoveryFactory
* @typedef {import('libp2p-interfaces/src/peer-routing/types').PeerRouting} PeerRoutingModule
* @typedef {import('libp2p-interfaces/src/crypto/types').Crypto} Crypto
* @typedef {import('libp2p-interfaces/src/pubsub')} Pubsub
* @typedef {import('libp2p-interfaces/src/pubsub').PubsubOptions} PubsubOptions
* @typedef {import('interface-datastore').Datastore} Datastore
* @typedef {import('./pnet')} Protector
*/
/**
* @typedef {Object} HandlerProps
* @property {Connection} connection
* @property {MuxedStream} stream
* @property {string} protocol
*
* @typedef {Object} RandomWalkOptions
* @property {boolean} [enabled = false]
* @property {number} [queriesPerPeriod = 1]
* @property {number} [interval = 300e3]
* @property {number} [timeout = 10e3]
*
* @typedef {Object} DhtOptions
* @property {boolean} [enabled = false]
* @property {number} [kBucketSize = 20]
* @property {RandomWalkOptions} [randomWalk]
* @property {boolean} [clientMode]
* @property {import('libp2p-interfaces/src/types').DhtSelectors} [selectors]
* @property {import('libp2p-interfaces/src/types').DhtValidators} [validators]
*
* @typedef {Object} KeychainOptions
* @property {Datastore} [datastore]
*
* @typedef {Object} PeerStoreOptions
* @property {boolean} persistence
*
* @typedef {Object} PeerDiscoveryOptions
* @property {boolean} autoDial
* @typedef {Object} PubsubLocalOptions
* @property {boolean} enabled
*
* @typedef {Object} MetricsOptions
* @property {boolean} enabled
*
* @typedef {Object} RelayOptions
* @property {boolean} enabled
* @property {import('./circuit').RelayAdvertiseOptions} advertise
* @property {import('./circuit').HopOptions} hop
* @property {import('./circuit').AutoRelayOptions} autoRelay
* @property {boolean} [enabled = true]
* @property {import('./circuit').RelayAdvertiseOptions} [advertise]
* @property {import('./circuit').HopOptions} [hop]
* @property {import('./circuit').AutoRelayOptions} [autoRelay]
*
* @typedef {Object} Libp2pConfig
* @property {Object} [dht] dht module options
* @property {PeerDiscoveryOptions} [peerDiscovery]
* @property {Pubsub} [pubsub] pubsub module options
* @property {DhtOptions} [dht] dht module options
* @property {import('./nat-manager').NatManagerOptions} [nat]
* @property {Record<string, Object|boolean>} [peerDiscovery]
* @property {PubsubLocalOptions & PubsubOptions} [pubsub] pubsub module options
* @property {RelayOptions} [relay]
* @property {Record<string, Object>} [transport] transport options indexed by transport key
*
@@ -69,21 +100,32 @@ const IDENTIFY_PROTOCOLS = IdentifyService.multicodecs
* @property {TransportFactory[]} transport
* @property {MuxerFactory[]} streamMuxer
* @property {Crypto[]} connEncryption
* @property {PeerDiscoveryFactory[]} [peerDiscovery]
* @property {PeerRoutingModule[]} [peerRouting]
* @property {ContentRoutingModule[]} [contentRouting]
* @property {Object} [dht]
* @property {{new(...args: any[]): Pubsub}} [pubsub]
* @property {Protector} [connProtector]
*
* @typedef {Object} Libp2pOptions
* @property {Libp2pModules} modules libp2p modules to use
* @property {import('./address-manager').AddressManagerOptions} [addresses]
* @property {import('./connection-manager').ConnectionManagerOptions} [connectionManager]
* @property {Datastore} [datastore]
* @property {import('./dialer').DialerOptions} [dialer]
* @property {import('./metrics').MetricsOptions} [metrics]
* @property {Object} [keychain]
* @property {import('./transport-manager').TransportManagerOptions} [transportManager]
* @property {import('./identify/index').HostProperties} [host] libp2p host
* @property {KeychainOptions & import('./keychain/index').KeychainOptions} [keychain]
* @property {MetricsOptions & import('./metrics').MetricsOptions} [metrics]
* @property {import('./peer-routing').PeerRoutingOptions} [peerRouting]
* @property {PeerStoreOptions & import('./peer-store/persistent').PersistentPeerStoreOptions} [peerStore]
* @property {import('./transport-manager').TransportManagerOptions} [transportManager]
* @property {Libp2pConfig} [config]
*
* @typedef {Object} constructorOptions
* @property {PeerId} peerId
*
* @typedef {Object} CreateOptions
* @property {PeerId} peerId
* @property {PeerId} [peerId]
*
* @extends {EventEmitter}
* @fires Libp2p#error Emitted when an error occurs
@@ -99,12 +141,14 @@ class Libp2p extends EventEmitter {
*/
static async create (options) {
if (options.peerId) {
// @ts-ignore 'Libp2pOptions & CreateOptions' is not assignable to 'Libp2pOptions & constructorOptions'
return new Libp2p(options)
}
const peerId = await PeerId.create()
options.peerId = peerId
// @ts-ignore 'Libp2pOptions & CreateOptions' is not assignable to 'Libp2pOptions & constructorOptions'
return new Libp2p(options)
}
@@ -112,7 +156,7 @@ class Libp2p extends EventEmitter {
* Libp2p node.
*
* @class
* @param {Libp2pOptions} _options
* @param {Libp2pOptions & constructorOptions} _options
*/
constructor (_options) {
super()
@@ -134,7 +178,14 @@ class Libp2p extends EventEmitter {
// Addresses {listen, announce, noAnnounce}
this.addresses = this._options.addresses
this.addressManager = new AddressManager(this._options.addresses)
this.addressManager = new AddressManager(this.peerId, this._options.addresses)
// when addresses change, update our peer record
this.addressManager.on('change:addresses', () => {
updateSelfPeerRecord(this).catch(err => {
log.error('Error updating self peer record', err)
})
})
this._modules = this._options.modules
this._config = this._options.config
@@ -142,9 +193,6 @@ class Libp2p extends EventEmitter {
this._discovery = new Map() // Discovery service instances/references
// Create the Connection Manager
if (this._options.connectionManager.minPeers) { // Remove in 0.29
this._options.connectionManager.minConnections = this._options.connectionManager.minPeers
}
this.connectionManager = new ConnectionManager(this, {
autoDial: this._config.peerDiscovery.autoDial,
...this._options.connectionManager
@@ -165,7 +213,6 @@ class Libp2p extends EventEmitter {
const keychainOpts = Keychain.generateOptions()
this.keychain = new Keychain(this._options.keychain.datastore, {
passPhrase: this._options.keychain.pass,
...keychainOpts,
...this._options.keychain
})
@@ -188,6 +235,15 @@ class Libp2p extends EventEmitter {
faultTolerance: this._options.transportManager.faultTolerance
})
// Create the Nat Manager
this.natManager = new NatManager({
peerId: this.peerId,
addressManager: this.addressManager,
transportManager: this.transportManager,
// @ts-ignore Nat typedef is not understood as Object
...this._options.config.nat
})
// Create the Registrar
this.registrar = new Registrar({
peerStore: this.peerStore,
@@ -209,11 +265,7 @@ class Libp2p extends EventEmitter {
this.dialer = new Dialer({
transportManager: this.transportManager,
peerStore: this.peerStore,
concurrency: this._options.dialer.maxParallelDials,
perPeerLimit: this._options.dialer.maxDialsPerPeer,
timeout: this._options.dialer.dialTimeout,
resolvers: this._options.dialer.resolvers,
addressSorter: this._options.dialer.addressSorter
...this._options.dialer
})
this._modules.transport.forEach((Transport) => {
@@ -243,13 +295,14 @@ class Libp2p extends EventEmitter {
// Attach private network protector
if (this._modules.connProtector) {
this.upgrader.protector = this._modules.connProtector
} else if (globalThis.process !== undefined && globalThis.process.env && globalThis.process.env.LIBP2P_FORCE_PNET) {
} else if (globalThis.process !== undefined && globalThis.process.env && globalThis.process.env.LIBP2P_FORCE_PNET) { // eslint-disable-line no-undef
throw new Error('Private network is enforced, but no protector was provided')
}
// dht provided components (peerRouting, contentRouting, dht)
if (this._modules.dht) {
const DHT = this._modules.dht
// @ts-ignore Object is not constructable
this._dht = new DHT({
libp2p: this,
dialer: this.dialer,
@@ -351,6 +404,7 @@ class Libp2p extends EventEmitter {
this.metrics && this.metrics.stop()
])
await this.natManager.stop()
await this.transportManager.close()
ping.unmount(this)
@@ -407,22 +461,36 @@ class Libp2p extends EventEmitter {
* @returns {Promise<Connection>}
*/
dial (peer, options) {
return this.dialProtocol(peer, [], options)
return this._dial(peer, options)
}
/**
* Dials to the provided peer and handshakes with the given protocol.
* Dials to the provided peer and tries to handshake with the given protocols in order.
* If successful, the known metadata of the peer will be added to the nodes `peerStore`,
* and the `Connection` will be returned
* and the `MuxedStream` will be returned together with the successful negotiated protocol.
*
* @async
* @param {PeerId|Multiaddr|string} peer - The peer to dial
* @param {string[]|string} protocols
* @param {object} [options]
* @param {AbortSignal} [options.signal]
* @returns {Promise<Connection|*>}
*/
async dialProtocol (peer, protocols, options) {
if (!protocols || !protocols.length) {
throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM)
}
const connection = await this._dial(peer, options)
return connection.newStream(protocols)
}
/**
* @async
* @param {PeerId|Multiaddr|string} peer - The peer to dial
* @param {object} [options]
* @returns {Promise<Connection>}
*/
async _dial (peer, options) {
const { id, multiaddrs } = getPeer(peer)
if (id.equals(this.peerId)) {
@@ -437,31 +505,36 @@ class Libp2p extends EventEmitter {
this.peerStore.addressBook.add(id, multiaddrs)
}
// If a protocol was provided, create a new stream
if (protocols && protocols.length) {
return connection.newStream(protocols)
}
return connection
}
/**
* Get peer advertising multiaddrs by concating the addresses used
* by transports to listen with the announce addresses.
* Duplicated addresses and noAnnounce addresses are filtered out.
* Get a deduplicated list of peer advertising multiaddrs by concatenating
* the listen addresses used by transports with any configured
* announce addresses as well as observed addresses reported by peers.
*
* If Announce addrs are specified, configured listen addresses will be
* ignored though observed addresses will still be included.
*
* @returns {Multiaddr[]}
*/
get multiaddrs () {
const announceAddrs = this.addressManager.getAnnounceAddrs()
if (announceAddrs.length) {
return announceAddrs
let addrs = this.addressManager.getAnnounceAddrs().map(ma => ma.toString())
if (!addrs.length) {
// no configured announce addrs, add configured listen addresses
addrs = this.transportManager.getAddrs().map(ma => ma.toString())
}
const announceFilter = this._options.addresses.announceFilter || ((multiaddrs) => multiaddrs)
addrs = addrs.concat(this.addressManager.getObservedAddrs().map(ma => ma.toString()))
const announceFilter = this._options.addresses.announceFilter
// dedupe multiaddrs
const addrSet = new Set(addrs)
// Create advertising list
return announceFilter(this.transportManager.getAddrs())
return announceFilter(Array.from(addrSet).map(str => new Multiaddr(str)))
}
/**
@@ -507,7 +580,7 @@ class Libp2p extends EventEmitter {
* Registers the `handler` for each protocol
*
* @param {string[]|string} protocols
* @param {({ connection: Connection, stream: MuxedStream, protocol: string }) => void} handler
* @param {(props: HandlerProps) => void} handler
*/
handle (protocols, handler) {
protocols = Array.isArray(protocols) ? protocols : [protocols]
@@ -540,6 +613,9 @@ class Libp2p extends EventEmitter {
const addrs = this.addressManager.getListenAddrs()
await this.transportManager.listen(addrs)
// Manage your NATs
this.natManager.start()
// Start PeerStore
await this.peerStore.start()
@@ -624,7 +700,7 @@ class Libp2p extends EventEmitter {
try {
await this.dialer.connectToPeer(peerId)
} catch (err) {
log.error('could not connect to discovered peer', err)
log.error(`could not connect to discovered peer ${peerId.toB58String()} with ${err}`)
}
}
}
@@ -637,6 +713,9 @@ class Libp2p extends EventEmitter {
* @private
*/
async _setupPeerDiscovery () {
/**
* @param {PeerDiscoveryFactory} DiscoveryService
*/
const setupService = (DiscoveryService) => {
let config = {
enabled: true // on by default
@@ -645,6 +724,7 @@ class Libp2p extends EventEmitter {
if (DiscoveryService.tag &&
this._config.peerDiscovery &&
this._config.peerDiscovery[DiscoveryService.tag]) {
// @ts-ignore PeerDiscovery not understood as an Object for spread
config = { ...config, ...this._config.peerDiscovery[DiscoveryService.tag] }
}
@@ -653,6 +733,7 @@ class Libp2p extends EventEmitter {
let discoveryService
if (typeof DiscoveryService === 'function') {
// @ts-ignore DiscoveryService has no constructor type inferred
discoveryService = new DiscoveryService(Object.assign({}, config, {
peerId: this.peerId,
libp2p: this

View File

@@ -4,6 +4,7 @@ const debug = require('debug')
const log = Object.assign(debug('libp2p:plaintext'), {
error: debug('libp2p:plaintext:err')
})
// @ts-ignore it-handshake do not export types
const handshake = require('it-handshake')
const lp = require('it-length-prefixed')
const PeerId = require('peer-id')
@@ -16,8 +17,12 @@ const protocol = '/plaintext/2.0.0'
* @typedef {import('libp2p-interfaces/src/connection').Connection} Connection
*/
/**
* @param {import('./proto').IExchange} exchange
*/
function lpEncodeExchange (exchange) {
const pb = Exchange.encode(exchange)
const pb = Exchange.encode(exchange).finish()
// @ts-ignore TODO: Uint8Array not assignable to Buffer
return lp.encode.single(pb)
}
@@ -68,12 +73,23 @@ async function encrypt (localId, conn, remoteId) {
}
}
module.exports = {
protocol,
secureInbound: (localId, conn, remoteId) => {
return encrypt(localId, conn, remoteId)
},
secureOutbound: (localId, conn, remoteId) => {
return encrypt(localId, conn, remoteId)
}
}
module.exports =
{
protocol,
/**
* @param {PeerId} localId
* @param {Connection} conn
* @param {PeerId | undefined} remoteId
*/
secureInbound: (localId, conn, remoteId) => {
return encrypt(localId, conn, remoteId)
},
/**
* @param {PeerId} localId
* @param {Connection} conn
* @param {PeerId | undefined} remoteId
*/
secureOutbound: (localId, conn, remoteId) => {
return encrypt(localId, conn, remoteId)
}
}

128
src/insecure/proto.d.ts vendored Normal file
View File

@@ -0,0 +1,128 @@
import * as $protobuf from "protobufjs";
/** Properties of an Exchange. */
export interface IExchange {
/** Exchange id */
id?: (Uint8Array|null);
/** Exchange pubkey */
pubkey?: (IPublicKey|null);
}
/** Represents an Exchange. */
export class Exchange implements IExchange {
/**
* Constructs a new Exchange.
* @param [p] Properties to set
*/
constructor(p?: IExchange);
/** Exchange id. */
public id: Uint8Array;
/** Exchange pubkey. */
public pubkey?: (IPublicKey|null);
/**
* Encodes the specified Exchange message. Does not implicitly {@link Exchange.verify|verify} messages.
* @param m Exchange message or plain object to encode
* @param [w] Writer to encode to
* @returns Writer
*/
public static encode(m: IExchange, w?: $protobuf.Writer): $protobuf.Writer;
/**
* Decodes an Exchange message from the specified reader or buffer.
* @param r Reader or buffer to decode from
* @param [l] Message length if known beforehand
* @returns Exchange
* @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): Exchange;
/**
* Creates an Exchange message from a plain object. Also converts values to their respective internal types.
* @param d Plain object
* @returns Exchange
*/
public static fromObject(d: { [k: string]: any }): Exchange;
/**
* Creates a plain object from an Exchange message. Also converts values to other types if specified.
* @param m Exchange
* @param [o] Conversion options
* @returns Plain object
*/
public static toObject(m: Exchange, o?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this Exchange to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
}
/** KeyType enum. */
export enum KeyType {
RSA = 0,
Ed25519 = 1,
Secp256k1 = 2,
ECDSA = 3
}
/** Represents a PublicKey. */
export class PublicKey implements IPublicKey {
/**
* Constructs a new PublicKey.
* @param [p] Properties to set
*/
constructor(p?: IPublicKey);
/** PublicKey Type. */
public Type: KeyType;
/** PublicKey Data. */
public Data: Uint8Array;
/**
* Encodes the specified PublicKey message. Does not implicitly {@link PublicKey.verify|verify} messages.
* @param m PublicKey message or plain object to encode
* @param [w] Writer to encode to
* @returns Writer
*/
public static encode(m: IPublicKey, w?: $protobuf.Writer): $protobuf.Writer;
/**
* Decodes a PublicKey message from the specified reader or buffer.
* @param r Reader or buffer to decode from
* @param [l] Message length if known beforehand
* @returns PublicKey
* @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): PublicKey;
/**
* Creates a PublicKey message from a plain object. Also converts values to their respective internal types.
* @param d Plain object
* @returns PublicKey
*/
public static fromObject(d: { [k: string]: any }): PublicKey;
/**
* Creates a plain object from a PublicKey message. Also converts values to other types if specified.
* @param m PublicKey
* @param [o] Conversion options
* @returns Plain object
*/
public static toObject(m: PublicKey, o?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this PublicKey to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
}

View File

@@ -1,22 +1,371 @@
'use strict'
/*eslint-disable*/
"use strict";
const protobuf = require('protons')
var $protobuf = require("protobufjs/minimal");
module.exports = protobuf(`
message Exchange {
optional bytes id = 1;
optional PublicKey pubkey = 2;
}
// Common aliases
var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
enum KeyType {
RSA = 0;
Ed25519 = 1;
Secp256k1 = 2;
ECDSA = 3;
}
// Exported root namespace
var $root = $protobuf.roots["libp2p-plaintext"] || ($protobuf.roots["libp2p-plaintext"] = {});
message PublicKey {
required KeyType Type = 1;
required bytes Data = 2;
}
`)
$root.Exchange = (function() {
/**
* Properties of an Exchange.
* @exports IExchange
* @interface IExchange
* @property {Uint8Array|null} [id] Exchange id
* @property {IPublicKey|null} [pubkey] Exchange pubkey
*/
/**
* Constructs a new Exchange.
* @exports Exchange
* @classdesc Represents an Exchange.
* @implements IExchange
* @constructor
* @param {IExchange=} [p] Properties to set
*/
function Exchange(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]];
}
/**
* Exchange id.
* @member {Uint8Array} id
* @memberof Exchange
* @instance
*/
Exchange.prototype.id = $util.newBuffer([]);
/**
* Exchange pubkey.
* @member {IPublicKey|null|undefined} pubkey
* @memberof Exchange
* @instance
*/
Exchange.prototype.pubkey = null;
/**
* Encodes the specified Exchange message. Does not implicitly {@link Exchange.verify|verify} messages.
* @function encode
* @memberof Exchange
* @static
* @param {IExchange} m Exchange message or plain object to encode
* @param {$protobuf.Writer} [w] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
Exchange.encode = function encode(m, w) {
if (!w)
w = $Writer.create();
if (m.id != null && Object.hasOwnProperty.call(m, "id"))
w.uint32(10).bytes(m.id);
if (m.pubkey != null && Object.hasOwnProperty.call(m, "pubkey"))
$root.PublicKey.encode(m.pubkey, w.uint32(18).fork()).ldelim();
return w;
};
/**
* Decodes an Exchange message from the specified reader or buffer.
* @function decode
* @memberof Exchange
* @static
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
* @param {number} [l] Message length if known beforehand
* @returns {Exchange} Exchange
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
Exchange.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.Exchange();
while (r.pos < c) {
var t = r.uint32();
switch (t >>> 3) {
case 1:
m.id = r.bytes();
break;
case 2:
m.pubkey = $root.PublicKey.decode(r, r.uint32());
break;
default:
r.skipType(t & 7);
break;
}
}
return m;
};
/**
* Creates an Exchange message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof Exchange
* @static
* @param {Object.<string,*>} d Plain object
* @returns {Exchange} Exchange
*/
Exchange.fromObject = function fromObject(d) {
if (d instanceof $root.Exchange)
return d;
var m = new $root.Exchange();
if (d.id != null) {
if (typeof d.id === "string")
$util.base64.decode(d.id, m.id = $util.newBuffer($util.base64.length(d.id)), 0);
else if (d.id.length)
m.id = d.id;
}
if (d.pubkey != null) {
if (typeof d.pubkey !== "object")
throw TypeError(".Exchange.pubkey: object expected");
m.pubkey = $root.PublicKey.fromObject(d.pubkey);
}
return m;
};
/**
* Creates a plain object from an Exchange message. Also converts values to other types if specified.
* @function toObject
* @memberof Exchange
* @static
* @param {Exchange} m Exchange
* @param {$protobuf.IConversionOptions} [o] Conversion options
* @returns {Object.<string,*>} Plain object
*/
Exchange.toObject = function toObject(m, o) {
if (!o)
o = {};
var d = {};
if (o.defaults) {
if (o.bytes === String)
d.id = "";
else {
d.id = [];
if (o.bytes !== Array)
d.id = $util.newBuffer(d.id);
}
d.pubkey = null;
}
if (m.id != null && m.hasOwnProperty("id")) {
d.id = o.bytes === String ? $util.base64.encode(m.id, 0, m.id.length) : o.bytes === Array ? Array.prototype.slice.call(m.id) : m.id;
}
if (m.pubkey != null && m.hasOwnProperty("pubkey")) {
d.pubkey = $root.PublicKey.toObject(m.pubkey, o);
}
return d;
};
/**
* Converts this Exchange to JSON.
* @function toJSON
* @memberof Exchange
* @instance
* @returns {Object.<string,*>} JSON object
*/
Exchange.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
return Exchange;
})();
/**
* KeyType enum.
* @exports KeyType
* @enum {number}
* @property {number} RSA=0 RSA value
* @property {number} Ed25519=1 Ed25519 value
* @property {number} Secp256k1=2 Secp256k1 value
* @property {number} ECDSA=3 ECDSA value
*/
$root.KeyType = (function() {
var valuesById = {}, values = Object.create(valuesById);
values[valuesById[0] = "RSA"] = 0;
values[valuesById[1] = "Ed25519"] = 1;
values[valuesById[2] = "Secp256k1"] = 2;
values[valuesById[3] = "ECDSA"] = 3;
return values;
})();
$root.PublicKey = (function() {
/**
* Properties of a PublicKey.
* @exports IPublicKey
* @interface IPublicKey
* @property {KeyType|null} [Type] PublicKey Type
* @property {Uint8Array|null} [Data] PublicKey Data
*/
/**
* Constructs a new PublicKey.
* @exports PublicKey
* @classdesc Represents a PublicKey.
* @implements IPublicKey
* @constructor
* @param {IPublicKey=} [p] Properties to set
*/
function PublicKey(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]];
}
/**
* PublicKey Type.
* @member {KeyType} Type
* @memberof PublicKey
* @instance
*/
PublicKey.prototype.Type = 0;
/**
* PublicKey Data.
* @member {Uint8Array} Data
* @memberof PublicKey
* @instance
*/
PublicKey.prototype.Data = $util.newBuffer([]);
/**
* Encodes the specified PublicKey message. Does not implicitly {@link PublicKey.verify|verify} messages.
* @function encode
* @memberof PublicKey
* @static
* @param {IPublicKey} m PublicKey message or plain object to encode
* @param {$protobuf.Writer} [w] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
PublicKey.encode = function encode(m, w) {
if (!w)
w = $Writer.create();
if (m.Type != null && Object.hasOwnProperty.call(m, "Type"))
w.uint32(8).int32(m.Type);
if (m.Data != null && Object.hasOwnProperty.call(m, "Data"))
w.uint32(18).bytes(m.Data);
return w;
};
/**
* Decodes a PublicKey message from the specified reader or buffer.
* @function decode
* @memberof PublicKey
* @static
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
* @param {number} [l] Message length if known beforehand
* @returns {PublicKey} PublicKey
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
PublicKey.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.PublicKey();
while (r.pos < c) {
var t = r.uint32();
switch (t >>> 3) {
case 1:
m.Type = r.int32();
break;
case 2:
m.Data = r.bytes();
break;
default:
r.skipType(t & 7);
break;
}
}
return m;
};
/**
* Creates a PublicKey message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof PublicKey
* @static
* @param {Object.<string,*>} d Plain object
* @returns {PublicKey} PublicKey
*/
PublicKey.fromObject = function fromObject(d) {
if (d instanceof $root.PublicKey)
return d;
var m = new $root.PublicKey();
switch (d.Type) {
case "RSA":
case 0:
m.Type = 0;
break;
case "Ed25519":
case 1:
m.Type = 1;
break;
case "Secp256k1":
case 2:
m.Type = 2;
break;
case "ECDSA":
case 3:
m.Type = 3;
break;
}
if (d.Data != null) {
if (typeof d.Data === "string")
$util.base64.decode(d.Data, m.Data = $util.newBuffer($util.base64.length(d.Data)), 0);
else if (d.Data.length)
m.Data = d.Data;
}
return m;
};
/**
* Creates a plain object from a PublicKey message. Also converts values to other types if specified.
* @function toObject
* @memberof PublicKey
* @static
* @param {PublicKey} m PublicKey
* @param {$protobuf.IConversionOptions} [o] Conversion options
* @returns {Object.<string,*>} Plain object
*/
PublicKey.toObject = function toObject(m, o) {
if (!o)
o = {};
var d = {};
if (o.defaults) {
d.Type = o.enums === String ? "RSA" : 0;
if (o.bytes === String)
d.Data = "";
else {
d.Data = [];
if (o.bytes !== Array)
d.Data = $util.newBuffer(d.Data);
}
}
if (m.Type != null && m.hasOwnProperty("Type")) {
d.Type = o.enums === String ? $root.KeyType[m.Type] : m.Type;
}
if (m.Data != null && m.hasOwnProperty("Data")) {
d.Data = o.bytes === String ? $util.base64.encode(m.Data, 0, m.Data.length) : o.bytes === Array ? Array.prototype.slice.call(m.Data) : m.Data;
}
return d;
};
/**
* Converts this PublicKey to JSON.
* @function toJSON
* @memberof PublicKey
* @instance
* @returns {Object.<string,*>} JSON object
*/
PublicKey.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
return PublicKey;
})();
module.exports = $root;

18
src/insecure/proto.proto Normal file
View File

@@ -0,0 +1,18 @@
syntax = "proto3";
message Exchange {
optional bytes id = 1;
optional PublicKey pubkey = 2;
}
enum KeyType {
RSA = 0;
Ed25519 = 1;
Secp256k1 = 2;
ECDSA = 3;
}
message PublicKey {
KeyType Type = 1;
bytes Data = 2;
}

View File

@@ -1,7 +1,10 @@
'use strict'
// @ts-ignore node-forge types not exported
require('node-forge/lib/pkcs7')
// @ts-ignore node-forge types not exported
require('node-forge/lib/pbe')
// @ts-ignore node-forge types not exported
const forge = require('node-forge/lib/forge')
const { certificateForKey, findAsync } = require('./util')
const errcode = require('err-code')
@@ -85,6 +88,7 @@ class CMS {
try {
const buf = forge.util.createBuffer(uint8ArrayToString(cmsData, 'ascii'))
const obj = forge.asn1.fromDer(buf)
// @ts-ignore not defined
cms = forge.pkcs7.messageFromAsn1(obj)
} catch (err) {
throw errcode(new Error('Invalid CMS: ' + err.message), 'ERR_INVALID_CMS')
@@ -93,11 +97,15 @@ class CMS {
// Find a recipient whose key we hold. We only deal with recipient certs
// issued by ipfs (O=ipfs).
const recipients = cms.recipients
// @ts-ignore cms types not defined
.filter(r => r.issuer.find(a => a.shortName === 'O' && a.value === 'ipfs'))
// @ts-ignore cms types not defined
.filter(r => r.issuer.find(a => a.shortName === 'CN'))
// @ts-ignore cms types not defined
.map(r => {
return {
recipient: r,
// @ts-ignore cms types not defined
keyId: r.issuer.find(a => a.shortName === 'CN').value
}
})
@@ -113,6 +121,7 @@ class CMS {
})
if (!r) {
// @ts-ignore cms types not defined
const missingKeys = recipients.map(r => r.keyId)
throw errcode(new Error('Decryption needs one of the key(s): ' + missingKeys.join(', ')), 'ERR_MISSING_KEYS', {
missingKeys

View File

@@ -4,18 +4,38 @@
const sanitize = require('sanitize-filename')
const mergeOptions = require('merge-options')
const crypto = require('libp2p-crypto')
const Datastore = require('interface-datastore')
const { Key } = require('interface-datastore')
const CMS = require('./cms')
const errcode = require('err-code')
const { Number } = require('ipfs-utils/src/globalthis')
const uint8ArrayToString = require('uint8arrays/to-string')
const uint8ArrayFromString = require('uint8arrays/from-string')
// @ts-ignore node-forge sha512 types not exported
require('node-forge/lib/sha512')
/**
* @typedef {import('peer-id')} PeerId
* @typedef {import('interface-datastore/src/key')} Key
* @typedef {import('interface-datastore').Datastore} Datastore
*/
/**
* @typedef {Object} DekOptions
* @property {string} hash
* @property {string} salt
* @property {number} iterationCount
* @property {number} keyLength
*
* @typedef {Object} KeychainOptions
* @property {string} [pass]
* @property {DekOptions} [dek]
*/
/**
* Information about a key.
*
* @typedef {Object} KeyInfo
* @property {string} id - The universally unique key id.
* @property {string} name - The local key name.
*/
const keyPrefix = '/pkcs8/'
@@ -39,6 +59,9 @@ const defaultOptions = {
}
}
/**
* @param {string} name
*/
function validateKeyName (name) {
if (!name) return false
if (typeof name !== 'string') return false
@@ -72,7 +95,7 @@ async function throwDelayed (err) {
* @private
*/
function DsName (name) {
return new Datastore.Key(keyPrefix + name)
return new Key(keyPrefix + name)
}
/**
@@ -83,17 +106,9 @@ function DsName (name) {
* @private
*/
function DsInfoName (name) {
return new Datastore.Key(infoPrefix + name)
return new Key(infoPrefix + name)
}
/**
* Information about a key.
*
* @typedef {Object} KeyInfo
* @property {string} id - The universally unique key id.
* @property {string} name - The local key name.
*/
/**
* Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8.
*
@@ -107,7 +122,7 @@ class Keychain {
* Creates a new instance of a key chain.
*
* @param {Datastore} store - where the key are.
* @param {object} options
* @param {KeychainOptions} options
* @class
*/
constructor (store, options) {
@@ -119,8 +134,8 @@ class Keychain {
this.opts = mergeOptions(defaultOptions, options)
// Enforce NIST SP 800-132
if (this.opts.passPhrase && this.opts.passPhrase.length < 20) {
throw new Error('passPhrase must be least 20 characters')
if (this.opts.pass && this.opts.pass.length < 20) {
throw new Error('pass must be least 20 characters')
}
if (this.opts.dek.keyLength < NIST.minKeyLength) {
throw new Error(`dek.keyLength must be least ${NIST.minKeyLength} bytes`)
@@ -132,12 +147,14 @@ class Keychain {
throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
}
const dek = this.opts.passPhrase ? crypto.pbkdf2(
this.opts.passPhrase,
this.opts.dek.salt,
this.opts.dek.iterationCount,
this.opts.dek.keyLength,
this.opts.dek.hash) : ''
const dek = this.opts.pass
? crypto.pbkdf2(
this.opts.pass,
this.opts.dek.salt,
this.opts.dek.iterationCount,
this.opts.dek.keyLength,
this.opts.dek.hash)
: ''
privates.set(this, { dek })
}

View File

@@ -4,7 +4,6 @@
require('node-forge/lib/x509')
const forge = require('node-forge/lib/forge')
const pki = forge.pki
exports = module.exports
/**
* Gets a self-signed X.509 certificate for the key.
@@ -17,7 +16,7 @@ exports = module.exports
* @param {RsaPrivateKey} privateKey - The naked key
* @returns {Uint8Array}
*/
exports.certificateForKey = (key, privateKey) => {
const certificateForKey = (key, privateKey) => {
const publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e)
const cert = pki.createCertificate()
cert.publicKey = publicKey
@@ -87,4 +86,7 @@ async function findAsync (array, asyncCompare) {
return array[index]
}
module.exports.findAsync = findAsync
module.exports = {
certificateForKey,
findAsync
}

View File

@@ -1,13 +1,15 @@
// @ts-nocheck
'use strict'
/** @typedef {import('../types').EventEmitterFactory} Events */
/** @type Events */
const EventEmitter = require('events')
const Big = require('bignumber.js')
const MovingAverage = require('moving-average')
const { EventEmitter } = require('events')
const { BigNumber: Big } = require('bignumber.js')
const MovingAverage = require('@vascosantos/moving-average')
const retimer = require('retimer')
/**
* @typedef {import('@vascosantos/moving-average').IMovingAverage} IMovingAverage
*/
class Stats extends EventEmitter {
/**
* A queue based manager for stat processing
@@ -21,24 +23,30 @@ class Stats extends EventEmitter {
this._options = options
this._queue = []
this._stats = {}
/** @type {{ dataReceived: Big, dataSent: Big }} */
this._stats = {
dataReceived: Big(0),
dataSent: Big(0)
}
this._frequencyLastTime = Date.now()
this._frequencyAccumulators = {}
/** @type {{ dataReceived: IMovingAverage[], dataSent: IMovingAverage[] }} */
this._movingAverages = {}
this._update = this._update.bind(this)
const intervals = this._options.movingAverageIntervals
for (var i = 0; i < initialCounters.length; i++) {
var key = initialCounters[i]
for (let i = 0; i < initialCounters.length; i++) {
const key = initialCounters[i]
this._stats[key] = Big(0)
this._movingAverages[key] = {}
for (var k = 0; k < intervals.length; k++) {
var interval = intervals[k]
var ma = this._movingAverages[key][interval] = MovingAverage(interval)
for (let k = 0; k < intervals.length; k++) {
const interval = intervals[k]
const ma = this._movingAverages[key][interval] = MovingAverage(interval)
ma.push(this._frequencyLastTime, 0)
}
}
@@ -72,8 +80,6 @@ class Stats extends EventEmitter {
/**
* Returns a clone of the current stats.
*
* @returns {Object}
*/
get snapshot () {
return Object.assign({}, this._stats)
@@ -81,8 +87,6 @@ class Stats extends EventEmitter {
/**
* Returns a clone of the internal movingAverages
*
* @returns {MovingAverage}
*/
get movingAverages () {
return Object.assign({}, this._movingAverages)
@@ -219,9 +223,9 @@ class Stats extends EventEmitter {
const intervals = this._options.movingAverageIntervals
for (var i = 0; i < intervals.length; i++) {
var movingAverageInterval = intervals[i]
var movingAverage = movingAverages[movingAverageInterval]
for (let i = 0; i < intervals.length; i++) {
const movingAverageInterval = intervals[i]
let movingAverage = movingAverages[movingAverageInterval]
if (!movingAverage) {
movingAverage = movingAverages[movingAverageInterval] = MovingAverage(movingAverageInterval)
}

197
src/nat-manager.js Normal file
View File

@@ -0,0 +1,197 @@
'use strict'
// @ts-ignore nat-api does not export types
const NatAPI = require('@motrix/nat-api')
const debug = require('debug')
const { promisify } = require('es6-promisify')
const { Multiaddr } = require('multiaddr')
const log = Object.assign(debug('libp2p:nat'), {
error: debug('libp2p:nat:err')
})
const { isBrowser } = require('ipfs-utils/src/env')
const retry = require('p-retry')
// @ts-ignore private-api does not export types
const isPrivateIp = require('private-ip')
const pkg = require('../package.json')
const errcode = require('err-code')
const {
codes: { ERR_INVALID_PARAMETERS }
} = require('./errors')
const isLoopback = require('libp2p-utils/src/multiaddr/is-loopback')
const DEFAULT_TTL = 7200
/**
* @typedef {import('peer-id')} PeerId
* @typedef {import('./transport-manager')} TransportManager
* @typedef {import('./address-manager')} AddressManager
*/
/**
* @typedef {Object} NatManagerProperties
* @property {PeerId} peerId - The peer ID of the current node
* @property {TransportManager} transportManager - A transport manager
* @property {AddressManager} addressManager - An address manager
*
* @typedef {Object} NatManagerOptions
* @property {boolean} enabled - Whether to enable the NAT manager
* @property {string} [externalIp] - Pass a value to use instead of auto-detection
* @property {string} [description] - A string value to use for the port mapping description on the gateway
* @property {number} [ttl = DEFAULT_TTL] - How long UPnP port mappings should last for in seconds (minimum 1200)
* @property {boolean} [keepAlive] - Whether to automatically refresh UPnP port mappings when their TTL is reached
* @property {string} [gateway] - Pass a value to use instead of auto-detection
* @property {object} [pmp] - PMP options
* @property {boolean} [pmp.enabled] - Whether to enable PMP as well as UPnP
*/
function highPort (min = 1024, max = 65535) {
return Math.floor(Math.random() * (max - min + 1) + min)
}
class NatManager {
/**
* @class
* @param {NatManagerProperties & NatManagerOptions} options
*/
constructor ({ peerId, addressManager, transportManager, ...options }) {
this._peerId = peerId
this._addressManager = addressManager
this._transportManager = transportManager
this._enabled = options.enabled
this._externalIp = options.externalIp
this._options = {
description: options.description || `${pkg.name}@${pkg.version} ${this._peerId}`,
ttl: options.ttl || DEFAULT_TTL,
autoUpdate: options.keepAlive || true,
gateway: options.gateway,
enablePMP: Boolean(options.pmp && options.pmp.enabled)
}
if (this._options.ttl < DEFAULT_TTL) {
throw errcode(new Error(`NatManager ttl should be at least ${DEFAULT_TTL} seconds`), ERR_INVALID_PARAMETERS)
}
}
/**
* Starts the NAT manager
*/
start () {
if (isBrowser || !this._enabled) {
return
}
// done async to not slow down startup
this._start().catch((err) => {
// hole punching errors are non-fatal
log.error(err)
})
}
async _start () {
const addrs = this._transportManager.getAddrs()
for (const addr of addrs) {
// try to open uPnP ports for each thin waist address
const { family, host, port, transport } = addr.toOptions()
if (!addr.isThinWaistAddress() || transport !== 'tcp') {
// only bare tcp addresses
// eslint-disable-next-line no-continue
continue
}
if (isLoopback(addr)) {
// eslint-disable-next-line no-continue
continue
}
if (family !== 4) {
// ignore ipv6
// eslint-disable-next-line no-continue
continue
}
const client = this._getClient()
const publicIp = this._externalIp || await client.externalIp()
if (isPrivateIp(publicIp)) {
throw new Error(`${publicIp} is private - please set config.nat.externalIp to an externally routable IP or ensure you are not behind a double NAT`)
}
const publicPort = highPort()
log(`opening uPnP connection from ${publicIp}:${publicPort} to ${host}:${port}`)
await client.map({
publicPort,
privatePort: port,
protocol: transport.toUpperCase()
})
this._addressManager.addObservedAddr(Multiaddr.fromNodeAddress({
family: 4,
address: publicIp,
port: publicPort
}, transport))
}
}
_getClient () {
if (this._client) {
return this._client
}
const client = new NatAPI(this._options)
/** @type {(...any: any) => any} */
const map = promisify(client.map.bind(client))
/** @type {(...any: any) => any} */
const destroy = promisify(client.destroy.bind(client))
/** @type {(...any: any) => any} */
const externalIp = promisify(client.externalIp.bind(client))
// these are all network operations so add a retry
this._client = {
/**
* @param {...any} args
* @returns {Promise<void>}
*/
map: (...args) => retry(() => map(...args), { onFailedAttempt: log.error, unref: true }),
/**
* @param {...any} args
* @returns {Promise<void>}
*/
destroy: (...args) => retry(() => destroy(...args), { onFailedAttempt: log.error, unref: true }),
/**
* @param {...any} args
* @returns {Promise<string>}
*/
externalIp: (...args) => retry(() => externalIp(...args), { onFailedAttempt: log.error, unref: true })
}
return this._client
}
/**
* Stops the NAT manager
*
* @async
*/
async stop () {
if (isBrowser || !this._client) {
return
}
try {
await this._client.destroy()
this._client = null
} catch (err) {
log.error(err)
}
}
}
module.exports = NatManager

View File

@@ -5,18 +5,39 @@ const log = Object.assign(debug('libp2p:peer-routing'), {
error: debug('libp2p:peer-routing:err')
})
const errCode = require('err-code')
const {
storeAddresses,
uniquePeers,
requirePeers
} = require('./content-routing/utils')
const all = require('it-all')
const pAny = require('p-any')
const merge = require('it-merge')
const { pipe } = require('it-pipe')
const first = require('it-first')
const drain = require('it-drain')
const filter = require('it-filter')
const {
setDelayedInterval,
clearDelayedInterval
// @ts-ignore module with no types
} = require('set-delayed-interval')
/**
* @typedef {import('peer-id')} PeerId
* @typedef {import('multiaddr')} Multiaddr
* @typedef {import('multiaddr').Multiaddr} Multiaddr
* @typedef {import('libp2p-interfaces/src/peer-routing/types').PeerRouting} PeerRoutingModule
*/
/**
* @typedef {Object} RefreshManagerOptions
* @property {boolean} [enabled = true] - Whether to enable the Refresh manager
* @property {number} [bootDelay = 6e5] - Boot delay to start the Refresh Manager (in ms)
* @property {number} [interval = 10e3] - Interval between each Refresh Manager run (in ms)
*
* @typedef {Object} PeerRoutingOptions
* @property {RefreshManagerOptions} [refreshManager]
*/
class PeerRouting {
/**
* @class
@@ -25,11 +46,12 @@ class PeerRouting {
constructor (libp2p) {
this._peerId = libp2p.peerId
this._peerStore = libp2p.peerStore
/** @type {PeerRoutingModule[]} */
this._routers = libp2p._modules.peerRouting || []
// If we have the dht, make it first
if (libp2p._dht) {
this._routers.unshift(libp2p._dht)
// If we have the dht, add it to the available peer routers
if (libp2p._dht && libp2p._config.dht.enabled) {
this._routers.push(libp2p._dht)
}
this._refreshManagerOptions = libp2p._options.peerRouting.refreshManager
@@ -55,9 +77,8 @@ class PeerRouting {
*/
async _findClosestPeersTask () {
try {
for await (const { id, multiaddrs } of this.getClosestPeers(this._peerId.id)) {
this._peerStore.addressBook.add(id, multiaddrs)
}
// nb getClosestPeers adds the addresses to the address book
await drain(this.getClosestPeers(this._peerId.id))
} catch (err) {
log.error(err)
}
@@ -71,7 +92,7 @@ class PeerRouting {
}
/**
* Iterates over all peer routers in series to find the given peer.
* Iterates over all peer routers in parallel to find the given peer.
*
* @param {PeerId} id - The id of the peer to find
* @param {object} [options]
@@ -83,16 +104,25 @@ class PeerRouting {
throw errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE')
}
return pAny(this._routers.map(async (router) => {
const result = await router.findPeer(id, options)
if (id.toB58String() === this._peerId.toB58String()) {
throw errCode(new Error('Should not try to find self'), 'ERR_FIND_SELF')
}
// If we don't have a result, we need to provide an error to keep trying
if (!result || Object.keys(result).length === 0) {
throw errCode(new Error('not found'), 'NOT_FOUND')
}
const output = await pipe(
merge(
...this._routers.map(router => [router.findPeer(id, options)])
),
(source) => filter(source, Boolean),
// @ts-ignore findPeer resolves a Promise
(source) => storeAddresses(source, this._peerStore),
(source) => first(source)
)
return result
}))
if (output) {
return output
}
throw errCode(new Error('not found'), 'NOT_FOUND')
}
/**
@@ -108,20 +138,14 @@ class PeerRouting {
throw errCode(new Error('No peer routers available'), 'NO_ROUTERS_AVAILABLE')
}
const result = await pAny(
this._routers.map(async (router) => {
const peers = await all(router.getClosestPeers(key, options))
if (!peers || !peers.length) {
throw errCode(new Error('not found'), 'NOT_FOUND')
}
return peers
})
yield * pipe(
merge(
...this._routers.map(router => router.getClosestPeers(key, options))
),
(source) => storeAddresses(source, this._peerStore),
(source) => uniquePeers(source),
(source) => requirePeers(source)
)
for (const peer of result) {
yield peer
}
}
}

View File

@@ -6,7 +6,7 @@ const log = Object.assign(debug('libp2p:peer-store:address-book'), {
})
const errcode = require('err-code')
const multiaddr = require('multiaddr')
const { Multiaddr } = require('multiaddr')
const PeerId = require('peer-id')
const Book = require('./book')
@@ -18,7 +18,6 @@ const {
const Envelope = require('../record/envelope')
/**
* @typedef {import('multiaddr')} Multiaddr
* @typedef {import('./')} PeerStore
*/
@@ -60,7 +59,7 @@ class AddressBook extends Book {
if (!data.addresses) {
return []
}
return data.addresses.map((address) => address.multiaddr)
return data.addresses.map((/** @type {Address} */ address) => address.multiaddr)
}
})
@@ -230,6 +229,11 @@ class AddressBook extends Book {
const addresses = this._toAddresses(multiaddrs)
const id = peerId.toB58String()
// No addresses to be added
if (!addresses.length) {
return this
}
const entry = this.data.get(id)
if (entry && entry.addresses) {
@@ -295,17 +299,21 @@ class AddressBook extends Book {
}
// create Address for each address
/** @type {Address[]} */
const addresses = []
multiaddrs.forEach((addr) => {
if (!multiaddr.isMultiaddr(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)
}
addresses.push({
multiaddr: addr,
isCertified
})
// Guarantee no replicates
if (!addresses.find((a) => a.multiaddr.equals(addr))) {
addresses.push({
multiaddr: addr,
isCertified
})
}
})
return addresses

View File

@@ -7,6 +7,9 @@ const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../errors')
/**
* @param {any} data
*/
const passthrough = data => data
/**

View File

@@ -2,9 +2,7 @@
const errcode = require('err-code')
/** @typedef {import('../types').EventEmitterFactory} Events */
/** @type Events */
const EventEmitter = require('events')
const { EventEmitter } = require('events')
const PeerId = require('peer-id')
const AddressBook = require('./address-book')

View File

@@ -81,6 +81,9 @@ class MetadataBook extends Book {
* Set data into the datastructure
*
* @override
* @param {PeerId} peerId
* @param {string} key
* @param {Uint8Array} value
*/
_setValue (peerId, key, value, { emit = true } = {}) {
const id = peerId.toB58String()

View File

@@ -5,7 +5,7 @@ const log = Object.assign(debug('libp2p:persistent-peer-store'), {
error: debug('libp2p:persistent-peer-store:err')
})
const { Key } = require('interface-datastore')
const multiaddr = require('multiaddr')
const { Multiaddr } = require('multiaddr')
const PeerId = require('peer-id')
const PeerStore = require('..')
@@ -18,13 +18,18 @@ const {
NAMESPACE_PROTOCOL
} = require('./consts')
const Addresses = require('./pb/address-book.proto')
const Protocols = require('./pb/proto-book.proto')
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 {any} datastore
* @property {import('interface-datastore').Datastore} datastore
*
* @typedef {Object} PersistentPeerStoreOptions
* @property {number} [threshold = 5] - Number of dirty peers allowed before commit data.
@@ -214,7 +219,7 @@ class PersistentPeerStore extends PeerStore {
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
* @param {Batch} batch
*/
_batchAddressBook (peerId, batch) {
const b32key = peerId.toString()
@@ -234,11 +239,13 @@ class PersistentPeerStore extends PeerStore {
multiaddr: address.multiaddr.bytes,
isCertified: address.isCertified
})),
certified_record: entry.record ? {
seq: entry.record.seqNumber,
raw: entry.record.raw
} : undefined
})
certifiedRecord: entry.record
? {
seq: entry.record.seqNumber,
raw: entry.record.raw
}
: undefined
}).finish()
batch.put(key, encodedData)
} catch (err) {
@@ -251,7 +258,7 @@ class PersistentPeerStore extends PeerStore {
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
* @param {Batch} batch
*/
_batchKeyBook (peerId, batch) {
const b32key = peerId.toString()
@@ -277,14 +284,14 @@ class PersistentPeerStore extends PeerStore {
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
* @param {Batch} batch
*/
_batchMetadataBook (peerId, batch) {
const b32key = peerId.toString()
const dirtyMetada = this._dirtyMetadata.get(peerId.toB58String()) || []
try {
dirtyMetada.forEach((dirtyKey) => {
dirtyMetada.forEach((/** @type {string} */ dirtyKey) => {
const key = new Key(`${NAMESPACE_METADATA}${b32key}/${dirtyKey}`)
const dirtyValue = this.metadataBook.getValue(peerId, dirtyKey)
@@ -304,7 +311,7 @@ class PersistentPeerStore extends PeerStore {
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
* @param {Batch} batch
*/
_batchProtoBook (peerId, batch) {
const b32key = peerId.toString()
@@ -319,7 +326,7 @@ class PersistentPeerStore extends PeerStore {
return
}
const encodedData = Protocols.encode({ protocols })
const encodedData = Protocols.encode({ protocols }).finish()
batch.put(key, encodedData)
} catch (err) {
@@ -351,13 +358,15 @@ class PersistentPeerStore extends PeerStore {
peerId,
{
addresses: decoded.addrs.map((address) => ({
multiaddr: multiaddr(address.multiaddr),
multiaddr: new Multiaddr(address.multiaddr),
isCertified: Boolean(address.isCertified)
})),
record: decoded.certified_record ? {
raw: decoded.certified_record.raw,
seqNumber: decoded.certified_record.seq
} : undefined
record: decoded.certifiedRecord
? {
raw: decoded.certifiedRecord.raw,
seqNumber: decoded.certifiedRecord.seq
}
: undefined
},
{ emit: false })
break

View File

@@ -0,0 +1,198 @@
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 };
}
}

Some files were not shown because too many files have changed in this diff Show More