Compare commits

...

78 Commits

Author SHA1 Message Date
Pavel Murygin
9c76cd9413 make things work 2022-10-04 23:53:28 +04:00
Pavel Murygin
a9021e4c40 Remove nat port manager 2022-10-04 13:50:41 +04:00
github-actions[bot]
6ac62da025 chore: release 0.39.3 (#1392)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-10-03 15:07:18 +01:00
dependabot[bot]
806804ac88 chore(deps-dev): bump @libp2p/bootstrap from 2.0.1 to 3.0.0 (#1391)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-28 13:51:16 +01:00
Alex Potsides
0ecc02b2a4 docs: update delegated routing example readme (#1390) 2022-09-23 09:34:54 +01:00
Alex Potsides
ec02351e65 fix: when creating dial targets, encapsulate PeerIds last (#1389)
It turns out because `Multiaddr.encapsulate` stringifies the `Multiaddr`
it's a [suprisingly expensive operation](https://github.com/multiformats/js-multiaddr/pull/275#issuecomment-1254981709)
so here we switch the order of our `Multiaddr` pipeline around so
we filter undialable addresses (e.g. unsupported transports etc) before
encapsulating the `PeerId` onto a `Multiaddr` we'd then just ignore.
2022-09-23 10:33:42 +02:00
Alex Potsides
3f57edaf3b fix: yield only final peers from dht getClosestPeers (#1380)
* fix: yield final peers from dht getClosestPeers

`PEER_RESPONSE` is an intermediate event, we should only yield from `FINAL_PEER` events as we'll only get `K` of those.

* chore: fix test
2022-09-23 10:31:04 +02:00
Saul
62198414b3 docs: fix examples in documentation (#1379)
This PR fixes all the errors in the code snippets in the readme files contained in the examples directory and it fixes the doc/GETTING_STARTED.md file.

I have also updated the old package names, e.g. `libp2p-websockets` -> `@libp2p/websockets`.

Resolves #1367 and resolves #1361

Co-authored-by: saul <saul@organicdesign.nz>
2022-09-22 18:40:33 +01:00
Christoph Müller
a11260c753 docs: update noise package name in instructions (#1383)
libp2p-noise has been moved to @chainsafe/libp2p-noise
2022-09-22 18:36:55 +01:00
github-actions[bot]
a4ac534252 chore: release 0.39.2 (#1388)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-09-21 14:57:07 +01:00
Alex Potsides
633d4a9740 fix: remove ipfs dependency and upgrade multiaddr (#1387)
- Upgrades @multiformats/multiaddr to 11.0.0
- Removes ipfs-http-client and delegate router dependencies
- Test delegation using interface stubs instead of implementations
2022-09-21 14:41:20 +01:00
libp2p-mgmt-read-write[bot]
a4566ede92 chore: Update .github/workflows/stale.yml [skip ci] 2022-09-19 13:40:57 +00:00
github-actions[bot]
111e75d05e chore: release 0.39.1 (#1376)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-09-11 09:04:59 +01:00
Alex Potsides
0218acfae2 fix: report dialer metrics (#1377)
Converts the dialer to a component so it can access metrics
2022-09-09 19:00:11 +01:00
Cayman
b87632f97f fix: add yamux interop tests (#1290)
Test stream compatibility with https://www.npmjs.com/package/@chainsafe/libp2p-yamux

Co-authored-by: achingbrain <alex@achingbrain.net>
2022-09-09 16:24:59 +01:00
dependabot[bot]
dd14f82ed5 deps(dev): bump ipfs-http-client from 57.0.3 to 58.0.0 (#1369)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-08 12:47:31 +01:00
Alex Potsides
43b0418998 fix: overwrite stream fields after handshake (#1305)
Instead of creating a new stream objects instead just overwrite the duplex fields with ones from the mss stream.
2022-09-08 12:47:03 +01:00
github-actions[bot]
d63e08115b chore: release 0.39.0 (#1359)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-09-07 10:58:36 +01:00
tabcat
57ef75493d docs: re-add talks section (#1368)
The libp2p <3 ethereum video seems to have been moved to https://archive.devcon.org/archive/watch/2/libp2p-devp2p-ipfs-and-ethereum-networking/
unfortunately couldn't find the slides and demos previously linked
2022-09-07 10:57:39 +01:00
Marin Petrunić
d281a60dac fix: discovery mechanism examples not working (#1365)
Co-authored-by: achingbrain <alex@achingbrain.net>- fixed tests that were passing even though the example isn't working
- added timeouts to avoid infinite wait

Fixes #1229
2022-09-05 15:17:35 +01:00
Alex Potsides
fc2224a1e8 deps: update tcp (#1366) 2022-09-05 11:21:31 +01:00
Alex Potsides
0e7096d527 docs: update message filtering example (#1362)
Updates the example to use the new pubsub `addEventListener`-style API along with the README.

Also updates the test to actually test that the relevant messages were received.

Fixes https://github.com/libp2p/js-libp2p/issues/1288
2022-08-30 09:33:39 +01:00
Alex Potsides
1f38ab7ac8 fix!: load self key into keychain on startup if not present (#1357)
To prevent triggering keychain attack prevention on startup, refactor the `KeyChain` class to load the current PeerId as the `'self'` key on startup.

Fixes #1315

BREAKING CHANGE: the `loadKeychain` method has been removed as it is no longer necessary
2022-08-17 21:34:57 +01:00
github-actions[bot]
29c803a63e chore: release 0.38.0 (#1253)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-08-17 20:39:29 +01:00
Alex Potsides
41990421bf deps: update node-forge to 1.3.1 to slience automated warnings (#1358)
Fixes #1351
2022-08-17 20:27:57 +01:00
Alex Potsides
509e56a603 fix: prepend connection addr to circuit relay address (#1355)
Otherwise the reported remote addr is not valid
2022-08-15 09:20:05 +01:00
Alex Potsides
886759b7fb fix: catch errors when reconnecting old peers (#1352)
Since we don't wait for successful reconnection to existing peers,
the peerstore can be closed while we're accessing it since we might
be shut down right after we've started up, e.g. during test runs so
just log any reconnect errors instead of throwing.
2022-08-14 11:14:18 +01:00
Alex Potsides
2262f81924 deps: update wherearewe@2.0.0 (#1350) 2022-08-13 18:27:12 +01:00
Alex Potsides
6630cb19b9 deps: update interface-datastore and datastore-core (#1347) 2022-08-12 13:29:34 +01:00
Cayman
8880eefa8f fix: add successful stream peer to protobook (#1341)
* fix: add successful stream peer to protobook

If a protocol stream has been successfully negotiated and is to be
psased to the application, the peerstore should ensure that the peer is
registerd with that protocol.

* fix: upgrader test fix
2022-08-11 10:48:16 -05:00
Alex Potsides
f439d9b589 deps!: update all deps to support no-copy operations (#1335)
Updates all deps needed to support passing lists of byte arrays where they have been created from multiple input buffers.

When reading multiplexed data, all messages arrive in length-prefixed buffers, which means the first few bytes tell the consumer how many bytes long next chunk will be.

One length prefixed chunk can be delivered in several payloads from the underlying network transport. The first payload can also include the length prefix and some or all of the data, so we stitch these together in a `Uint8ArrayList` to avoid having to concatenate `Uint8Array`s together.

Previously once we'd received enough bytes to satisfy the length prefix we'd concatenate the bytes together, but this is a potentially expensive operation where transports have small message sizes so instead just pass the `Uint8ArrayList` to the consumer and let them decide wether to concatenate or not as some consumers will be smart enough to operate on lists of `Uint8Array`s instead of always requiring a contiguous block of memory.

BREAKING CHANGE: Streams are now `Duplex<Uint8ArrayList, Uint8ArrayList | Uint8Array>`
2022-08-11 13:21:04 +01:00
Alex Potsides
564f4b8aa7 deps: update it-length-prefix, uint8arraylist etc (#1317)
In order to support no-copy operations in streams, update all deps
to support streaming Uint8ArrayLists.
2022-08-03 14:15:35 +01:00
Alex Potsides
05e8e7ead9 fix: remove mplex prefix from muxer errors (#1304)
Muxer errors are now standard across implementations so do not
depend on the `"MPLEX_"` prefix
2022-07-25 13:37:31 +01:00
Alex Potsides
3c0fb13bab fix: close streams when protocol limits are reached (#1301)
- If a stream is opened that exceeds inbound/outbound limits, reset that stream (if it is incoming) or abort and throw (if it is outgoing)
- Make the error message more helpful (say which protocol has breached the limit)
- Increase the default stream limits so we don't trigger this by accident when a remote dials us with a protocol we don't support
2022-07-22 13:57:01 +01:00
Roshan Singh
54450d4342 docs: removed talks section (#1300)
Removed "Talks" section as links are broken.
2022-07-21 13:46:39 +01:00
Alex Potsides
627b8bf87c fix: MaxListenersExceeded warning (#1297)
Where we create signals that are passed down the stack, increase the max listeners to prevent warnings in the console.
2022-07-17 08:25:21 +00:00
Alex Potsides
ba56c64662 fix: add timeout for circuit relay (#1294)
Make sure we don't potentially wait forever during incoming circuit relay handshakes.

Adds a timeout option to the hop config to control how long we will wait.
2022-07-15 16:36:31 +00:00
Alex Potsides
0bb1b802c8 feat: programmatically set agentVersion for use in identify (#1296)
If no `agentVersion` is provided for the Identify protocol, the default `AGENT_VERSION` will now be set to
* `js-libp2p/<libp2p.version> UserAgent=<process.version>` when running in Node.js
* `js-libp2p/<libp2p.version> UserAgent=<navigator.userAgent>` when running in the browser (also when running in a webworker)

Fixes #686
Supersedes #1240

Co-authored-by: Kevin Westphal <westphal@consider-it.de>
Co-authored-by: Kevin <56823591+6d7a@users.noreply.github.com>
2022-07-15 16:35:52 +00:00
Konosuke Kachi
6eaab2e3ee docs: update transport example (#1268) 2022-07-15 15:27:26 +00:00
Alex Potsides
750ed9c35f fix: add timeout for incoming connections and build-in protocols (#1292)
Ensure that we don't wait forever for upgrading an inbound connection
to occur.

Note that transports should return an AbortableSource when passed an
AbortSignal so outbound connections to not need the same fix.

Also adds default timeouts for the ping, fetch, and identify protocols.
2022-07-14 13:35:12 +00:00
Cayman
b1b91398e2 fix: update muxer behavior (#1289)
- use `direction` in `muxerFactory.createStreamMuxer`
- use `muxer.close` instead of `muxer.streams.forEach(s => s.close())`
2022-07-14 12:03:06 +00:00
Alex Potsides
e6f646ed36 chore: update deps (#1285) 2022-07-01 18:33:17 +02:00
Alex Potsides
5af93883ce chore: update deps (#1284)
Update libp2p deps
2022-06-29 06:59:51 +01:00
Alex Potsides
2836acc90f fix: use keep-alive tag to reconnect to peers on startup (#1278)
Instead of trying to connect to every peer in the peer store when
we start a node, only connect to the peers that have been marked
with a `keep-alive` tag.
2022-06-28 08:05:16 +01:00
Alex Potsides
b1b2b216da feat!: use tag values to choose which connections to close (#1276)
Uses peer tag values to select low-value connections to prune when we have too many connections open.

BREAKING CHANGE: `connectionManager.peerValue` has been removed, use `peerStore.tagPeer` instead
2022-06-27 15:34:03 +01:00
Alex Potsides
ceb44f9e98 docs: add build step to docs (#1275)
Now that we need to build ts to js a build step is necessary.

Add it to the instructions where we are telling people to clone the repo first.

Refs: #1273
2022-06-24 16:28:56 +01:00
dependabot[bot]
b270527c8f chore(deps-dev): bump @libp2p/webrtc-star from 2.0.1 to 3.0.0 (#1266)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-24 13:33:37 +01:00
Vasco Santos
676cee2947 docs: update websockets import var (#1274) 2022-06-24 14:33:28 +02:00
dependabot[bot]
a5077cbc6b chore(deps-dev): bump @chainsafe/libp2p-noise from 6.2.0 to 7.0.1 (#1272)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-24 13:16:32 +01:00
Alex Potsides
de30c2cec7 feat!: limit protocol streams per-connection (#1255)
* feat: limit protocol streams per-connection

Uses the `maxInboundStreams` and `maxOutboundStreams` of the `registrar.handle`
opts to limit the number of concurrent streams open on each connection
on a per-protocol basis.

Both values default to 1 so some tuning will be necessary to set
appropriate values for some protocols.

* chore: make error codes consistent

* chore: fix up examples
2022-06-17 15:46:31 +02:00
Alex Potsides
5371729646 fix: specify max stream args separately (#1254) 2022-06-16 08:37:58 +01:00
Alex Potsides
d4dd664071 feat!: update libp2p interfaces (#1252)
BREAKING CHANGE: uses new single-issue libp2p interface modules
2022-06-15 18:30:39 +01:00
github-actions[bot]
13d95b413c chore: release 0.37.3 (#1237)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-06-10 07:49:49 +01:00
Alex Potsides
b0472686d2 fix: wait for peer stats to be updated during test (#1238)
The peer stats update interval doesn't always align with the timing
in the test so make sure it's elapsed before asserting on the results.

Fixes #1219
2022-06-08 10:47:41 -05:00
Alex Potsides
f9073ecd21 fix: connection pruning (#1235)
Actually prune connections when we reach the connection limit
2022-06-08 16:20:56 +01:00
Alex Potsides
eee256db8a fix: ensure streams are closed when protocol negotiation fails (#1236)
If an error is thrown during the initial stages of setting up a multiplexed stream, ensure we close the stream to free up any resources associated with it.
2022-06-08 08:29:32 +01:00
Alex Potsides
3babbbd75a chore: update ipfs-http-client (#1234)
Updates to ESM version of ipfs-http-client
2022-06-06 15:04:00 +01:00
github-actions[bot]
893f8c280f chore: release 0.37.2 (#1232)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-05-31 17:45:54 +01:00
Alex Potsides
824720fb8f fix: reduce identify message size limit (#1230)
Adds a config option to specify a maximum message size we'll accept
for an Identify message.

The default is 8KB, the same as go-libp2p - previously we fell back
to the default `maxMessageLength` option of `it-length-prefixed`
which is 4MB.

Also adds a default timeout for reading responses to identify
requests which is used if an AbortSignal is not passed in.

The default timeout also aligns with go-libp2p.
2022-05-31 17:10:40 +01:00
github-actions[bot]
4c0c2c6d3e chore: release 0.37.1 (#1220)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-05-26 08:30:50 +01:00
Alex Potsides
a1220d22f5 fix: time out slow reads (#1227)
There are a few places in the codebase where we send/receive data from the network without timeouts/abort controllers which means the user has to wait for the underlying socket to timeout which can take a long time depending on the platform, if at all.

This change ensures we can time out while running identify (both flavours), ping and fetch and adds tests to ensure there are no regressions.
2022-05-25 18:15:21 +01:00
Rakesh Shrestha
5934b13cce Protocol muxing docs explanation (#1165)
* update examples

* prettier to single quotes

* protocol-muxing bidirectional code explanation

Co-authored-by: aomini daiki <rakesh.shrestha@hazesoft.co>
2022-05-24 10:34:22 -05:00
Alex Potsides
b09eb8fc53 fix: explicitly close streams when connnections close (#1221)
Make sure we don't leave streams open.

Updates all deps to close multiplexed streams when closing connections.
2022-05-23 13:45:20 +01:00
Robert Kiel
35f9c0c793 fix: fix unintended aborts in dialer (#1185)
Fix a bug where `DialRequest` can abort wrong dial attempts.

Co-authored-by: Robert Kiel <robert.kiel@hoprnet.io>
2022-05-18 14:53:58 +01:00
Alex Potsides
d5386df684 fix: do upnp hole punch after startup (#1217)
The transport manager configures it's addresses during the `start`
phase so access them during `afterStart` so they'll be ready for use.
2022-05-18 13:19:31 +01:00
Steve Loeppky
1f5d5c2de1 Added pubsub to the TOC (#1215) 2022-05-16 13:22:41 -05:00
github-actions[bot]
7678156cf3 chore: release 0.37.0 (#1178)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-05-16 12:10:31 +01:00
Alex Potsides
f2fd4e30ff docs: add migration guide (#1214)
Adds migration guide for `libp2p@0.37.x`
2022-05-16 10:58:56 +01:00
Alex Potsides
31480603f3 fix: simplify pnet exports (#1213)
Export the key generator from `libp2p/pnet`
2022-05-16 10:07:32 +01:00
Alex Potsides
4837430d8b fix: encode enums correctly (#1210)
Updates protons and regenerates protobuf code to encode enums correctly
2022-05-10 12:35:33 +01:00
Alex Potsides
da3d19b309 fix: update interfaces (#1207)
Update to the latest interfaces version
2022-05-04 16:03:43 +01:00
Alex Potsides
a15254fdd4 fix: update to new interfaces (#1206)
Notably removes the `Dialer` interface as the `ConnectionManager` is now in charge of managing connections.
2022-05-04 10:19:04 +01:00
tuyennhv
d16817ca44 fix: emit peer:connect after all (#1171)
**Motivation**

In lodestar, when we handle "peer:connect" event, we dial the peer which gives another "peer:connect" event and it causes other issues

**Motivation**

In `onConnect` function, "peer:connect" event should be emitted after we add connection to the `connections` map so that when app dial the peer in "peer:connect" event handler, it uses the same/existing connection
2022-04-22 20:56:47 +01:00
Alex Potsides
fab4f1385c fix: update pubsub interfaces (#1194)
Update to latest version of pubsub interface
2022-04-22 20:49:35 +01:00
Alex Potsides
5397137c65 fix: use placeholder dht/pubsub (#1193)
Instead of making the `.dht` and `.pubsub` properties optional, use dummy implementations that throw exceptions if they are not configured.

This way we don't have to null guard everywhere they are accessed.
2022-04-21 15:46:06 +01:00
Alex Potsides
147304449e fix: expose getPublicKey (#1188)
This is used externally by IPFS so expose the method
2022-04-14 18:00:21 +01:00
Alex Potsides
1b9bab68ed chore: update deps (#1190) 2022-04-14 08:05:43 +01:00
Alex Potsides
c64a586a20 chore: update aegir to the latest version (#1186)
Removes boilerplate config that is no longer necessary
2022-04-09 09:26:25 +01:00
171 changed files with 5929 additions and 5353 deletions

View File

@@ -1,21 +1,21 @@
'use strict'
import { WebSockets } from '@libp2p/websockets'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
import { pipe } from 'it-pipe'
import { createFromJSON } from '@libp2p/peer-id-factory'
/** @type {import('aegir').PartialOptions} */
module.exports = {
export default {
build: {
bundlesizeMax: '253kB'
bundlesizeMax: '147kB'
},
test: {
before: async () => {
// use dynamic import because we only want to reference these files during the test run, e.g. after building
const { createLibp2p } = await import('./dist/src/index.js')
const { MULTIADDRS_WEBSOCKETS } = await import('./dist/test/fixtures/browser.js')
const { default: Peers } = await import('./dist/test/fixtures/peers.js')
const { WebSockets } = await import('@libp2p/websockets')
const { Mplex } = await import('@libp2p/mplex')
const { NOISE } = await import('@chainsafe/libp2p-noise')
const { Plaintext } = await import('./dist/src/insecure/index.js')
const { pipe } = await import('it-pipe')
const { createFromJSON } = await import('@libp2p/peer-id-factory')
const { default: Peers } = await import('./dist/test/fixtures/peers.js')
// Use the last peer
const peerId = await createFromJSON(Peers[Peers.length - 1])
@@ -31,7 +31,7 @@ module.exports = {
new Mplex()
],
connectionEncryption: [
NOISE,
new Noise(),
new Plaintext()
],
relay: {

26
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Close and mark stale issue
on:
schedule:
- cron: '0 0 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Oops, seems like we needed more information for this issue, please comment with more details or this issue will be closed in 7 days.'
close-issue-message: 'This issue was closed because it is missing author input.'
stale-issue-label: 'kind/stale'
any-of-labels: 'need/author-input'
exempt-issue-labels: 'need/triage,need/community-input,need/maintainer-input,need/maintainers-input,need/analysis,status/blocked,status/in-progress,status/ready,status/deferred,status/inactive'
days-before-issue-stale: 6
days-before-issue-close: 7
enable-statistics: true

2
.gitignore vendored
View File

@@ -10,7 +10,7 @@ test/repo-tests*
logs
*.log
coverage
.coverage
.nyc_output
# Runtime data

View File

@@ -10,6 +10,131 @@
### [0.39.3](https://www.github.com/libp2p/js-libp2p/compare/v0.39.2...v0.39.3) (2022-09-28)
### Bug Fixes
* when creating dial targets, encapsulate PeerIds last ([#1389](https://www.github.com/libp2p/js-libp2p/issues/1389)) ([ec02351](https://www.github.com/libp2p/js-libp2p/commit/ec02351e65d0627872e6a53894c060a593b9e66e))
* yield only final peers from dht getClosestPeers ([#1380](https://www.github.com/libp2p/js-libp2p/issues/1380)) ([3f57eda](https://www.github.com/libp2p/js-libp2p/commit/3f57edaf3b472daf8ea6e914f38ff9ad6cf9b49c))
### [0.39.2](https://www.github.com/libp2p/js-libp2p/compare/v0.39.1...v0.39.2) (2022-09-21)
### Bug Fixes
* remove ipfs dependency and upgrade multiaddr ([#1387](https://www.github.com/libp2p/js-libp2p/issues/1387)) ([633d4a9](https://www.github.com/libp2p/js-libp2p/commit/633d4a9740ea02e32c0bb290c0a3958b68f181e9))
### [0.39.1](https://www.github.com/libp2p/js-libp2p/compare/v0.39.0...v0.39.1) (2022-09-09)
### Bug Fixes
* add yamux interop tests ([#1290](https://www.github.com/libp2p/js-libp2p/issues/1290)) ([b87632f](https://www.github.com/libp2p/js-libp2p/commit/b87632f97f44aecf583df06aed865bc4e087391a))
* overwrite stream fields after handshake ([#1305](https://www.github.com/libp2p/js-libp2p/issues/1305)) ([43b0418](https://www.github.com/libp2p/js-libp2p/commit/43b04189987f11a7729b522d1e1dbdc1caceb874))
* report dialer metrics ([#1377](https://www.github.com/libp2p/js-libp2p/issues/1377)) ([0218acf](https://www.github.com/libp2p/js-libp2p/commit/0218acfae26fa69475b2ce0678b1c754c7eda605))
## [0.39.0](https://www.github.com/libp2p/js-libp2p/compare/v0.38.0...v0.39.0) (2022-09-05)
### ⚠ BREAKING CHANGES
* the `loadKeychain` method has been removed as it is no longer necessary
### Bug Fixes
* discovery mechanism examples not working ([#1365](https://www.github.com/libp2p/js-libp2p/issues/1365)) ([d281a60](https://www.github.com/libp2p/js-libp2p/commit/d281a60dac973eeb0c842ffd70cd8bad3ae1156a)), closes [#1229](https://www.github.com/libp2p/js-libp2p/issues/1229)
* load self key into keychain on startup if not present ([#1357](https://www.github.com/libp2p/js-libp2p/issues/1357)) ([1f38ab7](https://www.github.com/libp2p/js-libp2p/commit/1f38ab7ac8380c9501b252d076bb356662978882)), closes [#1315](https://www.github.com/libp2p/js-libp2p/issues/1315)
## [0.38.0](https://www.github.com/libp2p/js-libp2p/compare/v0.37.3...v0.38.0) (2022-08-17)
### ⚠ BREAKING CHANGES
* Streams are now `Duplex<Uint8ArrayList, Uint8ArrayList | Uint8Array>`
* `connectionManager.peerValue` has been removed, use `peerStore.tagPeer` instead
* limit protocol streams per-connection (#1255)
* uses new single-issue libp2p interface modules
### Features
* limit protocol streams per-connection ([#1255](https://www.github.com/libp2p/js-libp2p/issues/1255)) ([de30c2c](https://www.github.com/libp2p/js-libp2p/commit/de30c2cec79d1e9d758cbcddc11d315b17843343))
* programmatically set agentVersion for use in identify ([#1296](https://www.github.com/libp2p/js-libp2p/issues/1296)) ([0bb1b80](https://www.github.com/libp2p/js-libp2p/commit/0bb1b802c8fc2f32eaef10efbc88005dce6c6020)), closes [#686](https://www.github.com/libp2p/js-libp2p/issues/686) [#1240](https://www.github.com/libp2p/js-libp2p/issues/1240)
* update libp2p interfaces ([#1252](https://www.github.com/libp2p/js-libp2p/issues/1252)) ([d4dd664](https://www.github.com/libp2p/js-libp2p/commit/d4dd664071476e3d22f53e02e7d66099f3265f6c))
* use tag values to choose which connections to close ([#1276](https://www.github.com/libp2p/js-libp2p/issues/1276)) ([b1b2b21](https://www.github.com/libp2p/js-libp2p/commit/b1b2b216daf12caccd67503dfd7b296b191c5b83))
### Bug Fixes
* add successful stream peer to protobook ([#1341](https://www.github.com/libp2p/js-libp2p/issues/1341)) ([8880eef](https://www.github.com/libp2p/js-libp2p/commit/8880eefa8ffeff1203cdf5053a17dbf45f43cc3d))
* add timeout for circuit relay ([#1294](https://www.github.com/libp2p/js-libp2p/issues/1294)) ([ba56c64](https://www.github.com/libp2p/js-libp2p/commit/ba56c6466232ad4aa5025e2db084c5c9ccd4e5d0))
* add timeout for incoming connections and build-in protocols ([#1292](https://www.github.com/libp2p/js-libp2p/issues/1292)) ([750ed9c](https://www.github.com/libp2p/js-libp2p/commit/750ed9c35f095aa6e136a801ccd792f2190f38a1))
* catch errors when reconnecting old peers ([#1352](https://www.github.com/libp2p/js-libp2p/issues/1352)) ([886759b](https://www.github.com/libp2p/js-libp2p/commit/886759b7fb3c14f243d4e74b1714930424bb7453))
* close streams when protocol limits are reached ([#1301](https://www.github.com/libp2p/js-libp2p/issues/1301)) ([3c0fb13](https://www.github.com/libp2p/js-libp2p/commit/3c0fb13babe295c8e5284345080bd4434f39efa7))
* MaxListenersExceeded warning ([#1297](https://www.github.com/libp2p/js-libp2p/issues/1297)) ([627b8bf](https://www.github.com/libp2p/js-libp2p/commit/627b8bf87c775762dd6a9de69b77852e48ebcf26))
* prepend connection addr to circuit relay address ([#1355](https://www.github.com/libp2p/js-libp2p/issues/1355)) ([509e56a](https://www.github.com/libp2p/js-libp2p/commit/509e56a60359f98ec435f8519c6a499641cce212))
* remove mplex prefix from muxer errors ([#1304](https://www.github.com/libp2p/js-libp2p/issues/1304)) ([05e8e7e](https://www.github.com/libp2p/js-libp2p/commit/05e8e7ead96d494bdd7dfa5d6430155670066767))
* specify max stream args separately ([#1254](https://www.github.com/libp2p/js-libp2p/issues/1254)) ([5371729](https://www.github.com/libp2p/js-libp2p/commit/53717296468ef17fdc3e0dda9d5908b15d2772a1))
* update muxer behavior ([#1289](https://www.github.com/libp2p/js-libp2p/issues/1289)) ([b1b9139](https://www.github.com/libp2p/js-libp2p/commit/b1b91398e27d0b8852a74a87f0d8ccc5f34340b4))
* use keep-alive tag to reconnect to peers on startup ([#1278](https://www.github.com/libp2p/js-libp2p/issues/1278)) ([2836acc](https://www.github.com/libp2p/js-libp2p/commit/2836acc90f8eafd2106539a80ac7d3b307c0bd02))
### deps
* update all deps to support no-copy operations ([#1335](https://www.github.com/libp2p/js-libp2p/issues/1335)) ([f439d9b](https://www.github.com/libp2p/js-libp2p/commit/f439d9b589a0a6544b61aca3736e920943ce38b5))
### [0.37.3](https://www.github.com/libp2p/js-libp2p/compare/v0.37.2...v0.37.3) (2022-06-08)
### Bug Fixes
* connection pruning ([#1235](https://www.github.com/libp2p/js-libp2p/issues/1235)) ([f9073ec](https://www.github.com/libp2p/js-libp2p/commit/f9073ecd215e119b7a864e2ad31fe7067322c754))
* ensure streams are closed when protocol negotiation fails ([#1236](https://www.github.com/libp2p/js-libp2p/issues/1236)) ([eee256d](https://www.github.com/libp2p/js-libp2p/commit/eee256db8ab65cea7228b1683403417edfdb1367))
* wait for peer stats to be updated during test ([#1238](https://www.github.com/libp2p/js-libp2p/issues/1238)) ([b047268](https://www.github.com/libp2p/js-libp2p/commit/b0472686d29a4f295360d3f15a50c86c981892f7)), closes [#1219](https://www.github.com/libp2p/js-libp2p/issues/1219)
### [0.37.2](https://www.github.com/libp2p/js-libp2p/compare/v0.37.1...v0.37.2) (2022-05-31)
### Bug Fixes
* reduce identify message size limit ([#1230](https://www.github.com/libp2p/js-libp2p/issues/1230)) ([824720f](https://www.github.com/libp2p/js-libp2p/commit/824720fb8f21f868ed88e881fbc3ce6b9459600d))
### [0.37.1](https://www.github.com/libp2p/js-libp2p/compare/v0.37.0...v0.37.1) (2022-05-25)
### Bug Fixes
* do upnp hole punch after startup ([#1217](https://www.github.com/libp2p/js-libp2p/issues/1217)) ([d5386df](https://www.github.com/libp2p/js-libp2p/commit/d5386df68478a71ac269acb2d00d36a7a5c9ebc5))
* explicitly close streams when connnections close ([#1221](https://www.github.com/libp2p/js-libp2p/issues/1221)) ([b09eb8f](https://www.github.com/libp2p/js-libp2p/commit/b09eb8fc53ec1d8f6280d681c9ca6a467ec259b5))
* fix unintended aborts in dialer ([#1185](https://www.github.com/libp2p/js-libp2p/issues/1185)) ([35f9c0c](https://www.github.com/libp2p/js-libp2p/commit/35f9c0c79387232465848b450a47cafe841405e7))
* time out slow reads ([#1227](https://www.github.com/libp2p/js-libp2p/issues/1227)) ([a1220d2](https://www.github.com/libp2p/js-libp2p/commit/a1220d22f5affb64e64dec0cd6a92cd8241b26df))
## [0.37.0](https://www.github.com/libp2p/js-libp2p/compare/v0.36.2...v0.37.0) (2022-05-16)
### ⚠ BREAKING CHANGES
* types are no longer hand crafted, this module is now ESM only
### Features
* convert to typescript ([#1172](https://www.github.com/libp2p/js-libp2p/issues/1172)) ([199395d](https://www.github.com/libp2p/js-libp2p/commit/199395de4d8139cc77d0b408626f37c9b8520d28))
### Bug Fixes
* add transport manager to exports map and fix docs ([#1182](https://www.github.com/libp2p/js-libp2p/issues/1182)) ([cc60cfd](https://www.github.com/libp2p/js-libp2p/commit/cc60cfde1a0907ca68f658f6de5362a708189222))
* emit peer:connect after all ([#1171](https://www.github.com/libp2p/js-libp2p/issues/1171)) ([d16817c](https://www.github.com/libp2p/js-libp2p/commit/d16817ca443443e88803ee8096d45debb14af91b))
* encode enums correctly ([#1210](https://www.github.com/libp2p/js-libp2p/issues/1210)) ([4837430](https://www.github.com/libp2p/js-libp2p/commit/4837430d8bcdbee0865eeba6fe694bc71fc6c9bb))
* expose getPublicKey ([#1188](https://www.github.com/libp2p/js-libp2p/issues/1188)) ([1473044](https://www.github.com/libp2p/js-libp2p/commit/147304449e5f8d3acb8b00bdd9588b56830667c6))
* expose metrics and registrar, use dht for peer discovery ([#1183](https://www.github.com/libp2p/js-libp2p/issues/1183)) ([64bfcee](https://www.github.com/libp2p/js-libp2p/commit/64bfcee5093b368df0b381f78afc2ddff3d339a9))
* simplify pnet exports ([#1213](https://www.github.com/libp2p/js-libp2p/issues/1213)) ([3148060](https://www.github.com/libp2p/js-libp2p/commit/31480603f3e17d838d2685573995218a1e678e7a))
* update deps ([#1181](https://www.github.com/libp2p/js-libp2p/issues/1181)) ([8cca8e4](https://www.github.com/libp2p/js-libp2p/commit/8cca8e4bfc6a339e58b5a5efa8a84fd891aa08ee))
* update interfaces ([#1207](https://www.github.com/libp2p/js-libp2p/issues/1207)) ([da3d19b](https://www.github.com/libp2p/js-libp2p/commit/da3d19b30977fd2c7e77d92aa8914b13e3179aaa))
* update pubsub interfaces ([#1194](https://www.github.com/libp2p/js-libp2p/issues/1194)) ([fab4f13](https://www.github.com/libp2p/js-libp2p/commit/fab4f1385cf61b7b16719b9aacdfe03146a3f260))
* update to new interfaces ([#1206](https://www.github.com/libp2p/js-libp2p/issues/1206)) ([a15254f](https://www.github.com/libp2p/js-libp2p/commit/a15254fdd478a336edf1e1196b721dc56888b2ea))
* use placeholder dht/pubsub ([#1193](https://www.github.com/libp2p/js-libp2p/issues/1193)) ([5397137](https://www.github.com/libp2p/js-libp2p/commit/5397137c654dfdec431e0c9ba4b1ff9dee19abf1))
### [0.36.2](https://www.github.com/libp2p/js-libp2p/compare/v0.36.1...v0.36.2) (2022-01-26)

View File

@@ -67,7 +67,7 @@ We are in the process of writing better documentation, blog posts, tutorials and
- [Specification (WIP)](https://github.com/libp2p/specs)
- [Discussion Forums](https://discuss.libp2p.io)
- Talks
- [`libp2p <3 ethereum` at DEVCON2](https://ethereumfoundation.org/devcon/?session=libp2p) [📼 video](https://www.youtube.com/watch?v=HxueJbeMVG4) [slides](https://ethereumfoundation.org/devcon/wp-content/uploads/2016/10/libp2p-HEART-devp2p-IPFS-PLUS-Ethereum-networking.pdf) [📼 demo-1](https://ethereumfoundation.org/devcon/wp-content/uploads/2016/10/libp2p_demo1-1.mp4) [📼 demo-2](https://ethereumfoundation.org/devcon/wp-content/uploads/2016/10/libp2p_demo2-1.mp4)
- [`libp2p <3 ethereum` at DEVCON2](https://archive.devcon.org/archive/watch/2/libp2p-devp2p-ipfs-and-ethereum-networking/)
- Articles
- [The overview of libp2p](https://github.com/libp2p/libp2p#description)
@@ -105,6 +105,7 @@ You can find multiple examples on the [examples folder](./examples) that will gu
> git clone https://github.com/libp2p/js-libp2p.git
> cd js-libp2p
> npm install
> npm run build
```
### Tests

View File

@@ -46,6 +46,9 @@
* [`peerStore.delete`](#peerstoredelete)
* [`peerStore.get`](#peerstoreget)
* [`peerStore.peers`](#peerstorepeers)
* [`peerStore.tagPeer`](#peerstoretagpeer)
* [`peerStore.unTagPeer`](#peerstoreuntagpeer)
* [`peerStore.getTags`](#peerstoregettags)
* [`pubsub.getSubscribers`](#pubsubgetsubscribers)
* [`pubsub.getTopics`](#pubsubgettopics)
* [`pubsub.publish`](#pubsubpublish)
@@ -56,7 +59,6 @@
* [`pubsub.topicValidators.set`](#pubsubtopicvalidatorsset)
* [`pubsub.topicValidators.delete`](#pubsubtopicvalidatorsdelete)
* [`connectionManager.get`](#connectionmanagerget)
* [`connectionManager.setPeerValue`](#connectionmanagersetpeervalue)
* [`connectionManager.size`](#connectionmanagersize)
* [`keychain.createKey`](#keychaincreatekey)
* [`keychain.renameKey`](#keychainrenamekey)
@@ -97,7 +99,9 @@ Creates an instance of Libp2p.
| options.modules | [`Array<object>`](./CONFIGURATION.md#modules) | libp2p [modules](./CONFIGURATION.md#modules) to use |
| [options.addresses] | `{ listen: Array<string>, announce: Array<string>, announceFilter: (ma: Array<multiaddr>) => Array<multiaddr> }` | Addresses for transport listening and to advertise to the network |
| [options.config] | `object` | libp2p modules configuration and core configuration |
| [options.host] | `{ agentVersion: string }` | libp2p host options |
| [options.identify] | `{ protocolPrefix: string, host: { agentVersion: string }, timeout: number, maxIdentifyMessageSize: number }` | libp2p identify protocol options |
| [options.ping] | `{ protocolPrefix: string }` | libp2p ping protocol options |
| [options.fetch] | `{ protocolPrefix: string }` | libp2p fetch protocol options |
| [options.connectionManager] | [`object`](./CONFIGURATION.md#configuring-connection-manager) | libp2p Connection Manager [configuration](./CONFIGURATION.md#configuring-connection-manager) |
| [options.transportManager] | [`object`](./CONFIGURATION.md#configuring-transport-manager) | libp2p transport manager [configuration](./CONFIGURATION.md#configuring-transport-manager) |
| [options.datastore] | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) (in memory datastore will be used if not provided) |
@@ -181,36 +185,6 @@ Required keys in the `options` object:
## Libp2p Instance Methods
### loadKeychain
Load keychain keys from the datastore, importing the private key as 'self', if needed.
`libp2p.loadKeychain()`
#### Returns
| Type | Description |
|------|-------------|
| `Promise` | Promise resolves when the keychain is ready |
#### Example
```js
import { createLibp2p } from 'libp2p'
// ...
const libp2p = await createLibp2p({
// ...
keychain: {
pass: '0123456789pass1234567890'
}
})
// load keychain
await libp2p.loadKeychain()
```
### start
Starts the libp2p node.
@@ -387,7 +361,7 @@ await libp2p.hangUp(remotePeerId)
Sets up [multistream-select routing](https://github.com/multiformats/multistream-select) of protocols to their application handlers. Whenever a stream is opened on one of the provided protocols, the handler will be called. `handle` must be called in order to register a handler and support for a given protocol. This also informs other peers of the protocols you support.
`libp2p.handle(protocols, handler)`
`libp2p.handle(protocols, handler, options)`
In the event of a new handler for the same protocol being added, the first one is discarded.
@@ -397,6 +371,7 @@ In the event of a new handler for the same protocol being added, the first one i
|------|------|-------------|
| protocols | `Array<string>|string` | protocols to register |
| handler | `function({ connection:*, stream:*, protocol:string })` | handler to call |
| options | `StreamHandlerOptions` | Options including protocol stream limits |
#### Example
@@ -407,7 +382,10 @@ const handler = ({ connection, stream, protocol }) => {
// use stream or connection according to the needs
}
libp2p.handle('/echo/1.0.0', handler)
libp2p.handle('/echo/1.0.0', handler, {
maxInboundStreams: 5,
maxOutboundStreams: 5
})
```
### unhandle
@@ -1393,6 +1371,81 @@ for (let [peerIdString, peer] of peerStore.peers.entries()) {
}
```
### peerStore.tagPeer
Tags a peer with the specified tag and optional value/expiry time
`peerStore.tagPeer(peerId, tag, options)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | `PeerId` | The peer to tag |
| tag | `string` | The name of the tag to add |
| options | `{ value?: number, ttl?: number }` | An optional value (1-100) and an optional ttl after which the tag will expire (ms) |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<void>` | Promise resolves once the tag is stored |
#### Example
```js
await peerStore.tagPeer(peerId, 'my-tag', { value: 100, ttl: Date.now() + 60000 })
```
### peerStore.unTagPeer
Remove the tag from the specified peer
`peerStore.unTagPeer(peerId, tag)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | `PeerId` | The peer to untag |
| tag | `string` | The name of the tag to remove |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<void>` | Promise resolves once the tag has been removed |
#### Example
```js
await peerStore.unTagPeer(peerId, 'my-tag')
```
### peerStore.getTags
Remove the tag from the specified peer
`peerStore.getTags(peerId)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | `PeerId` | The peer to get the tags for |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<Array<{ name: string, value: number }>>` | The promise resolves to the list of tags for the passed peer |
#### Example
```js
await peerStore.getTags(peerId)
```
### pubsub.getSubscribers
Gets a list of the peer-ids that are subscribed to one topic.
@@ -1666,32 +1719,6 @@ Get a connection with a given peer, if it exists.
libp2p.connectionManager.get(peerId)
```
### connectionManager.setPeerValue
Enables users to change the value of certain peers in a range of 0 to 1. Peers with the lowest values will have their Connections pruned first, if any Connection Manager limits are exceeded. See [./CONFIGURATION.md#configuring-connection-manager](./CONFIGURATION.md#configuring-connection-manager) for details on how to configure these limits.
`libp2p.connectionManager.setPeerValue(peerId, value)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | [`PeerId`][peer-id] | The peer to set the value for |
| value | `number` | The value of the peer from 0 to 1 |
#### Returns
| Type | Description |
|------|-------------|
| `void` | |
#### Example
```js
libp2p.connectionManager.setPeerValue(highPriorityPeerId, 1)
libp2p.connectionManager.setPeerValue(lowPriorityPeerId, 0)
```
### connectionManager.size
Getter for obtaining the current number of open connections.

View File

@@ -247,7 +247,7 @@ import { GossipSub } from 'libp2p-gossipsub'
const node = await createLibp2p({
transports: [
new TCP(),
new WS()
new WebSockets()
],
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
@@ -323,7 +323,7 @@ import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
import { GossipSub } from 'libp2p-gossipsub'
import { SignaturePolicy } from '@libp2p/interfaces/pubsub'
import { SignaturePolicy } from '@libp2p/interface-pubsub'
const node = await createLibp2p({
transports: [
@@ -494,8 +494,6 @@ const node = await createLibp2p({
datastore: dsInstant,
}
})
await node.loadKeychain()
```
#### Configuring Dialing
@@ -510,6 +508,7 @@ Dialing in libp2p can be configured to limit the rate of dialing, and how long d
| dialTimeout | `number` | Second dial timeout per peer in ms. |
| resolvers | `object` | Dial [Resolvers](https://github.com/multiformats/js-multiaddr/blob/master/src/resolvers/index.js) for resolving multiaddrs |
| addressSorter | `(Array<Address>) => Array<Address>` | Sort the known addresses of a peer before trying to dial. |
| startupReconnectTimeout | `number` | When a node is restarted, we try to connect to any peers marked with the `keep-alive` tag up until to this timeout in ms is reached (default: 60000) |
The below configuration example shows how the dialer should be configured, with the current defaults:
@@ -556,7 +555,6 @@ const node = await createLibp2p({
maxConnections: Infinity,
minConnections: 0,
pollInterval: 2000,
defaultPeerValue: 1,
// The below values will only be taken into account when Metrics are enabled
maxData: Infinity,
maxSentData: Infinity,
@@ -885,7 +883,12 @@ Changing the protocol name prefix can isolate default public network (IPFS) for
```js
const node = await createLibp2p({
protocolPrefix: 'ipfs' // default
identify: {
protocolPrefix: 'ipfs' // default
},
ping: {
protocolPrefix: 'ipfs' // default
}
})
/*
protocols: [

View File

@@ -17,4 +17,3 @@ The following is a list of available options for setting limits for the Connecti
- `maxEventLoopDelay`: sets the maximum event loop delay (measured in milliseconds) this node is willing to endure before it starts disconnecting peers. Defaults to `Infinity`.
- `pollInterval`: sets the poll interval (in milliseconds) for assessing the current state and determining if this peer needs to force a disconnect. Defaults to `2000` (2 seconds).
- `movingAverageInterval`: the interval used to calculate moving averages (in milliseconds). Defaults to `60000` (1 minute). This must be an available interval configured in `Metrics`
- `defaultPeerValue`: number between 0 and 1. Defaults to 1.

View File

@@ -34,12 +34,12 @@ Now that we have libp2p installed, let's configure the minimum needed to get you
Libp2p uses Transports to establish connections between peers over the network. Transports are the components responsible for performing the actual exchange of data between libp2p nodes. You can configure any number of Transports, but you only need 1 to start with. Supporting more Transports will improve the ability of your node to speak to a larger number of nodes on the network, as matching Transports are required for two nodes to communicate with one another.
You should select Transports according to the runtime of your application; Node.js or the browser. You can see a list of some of the available Transports in the [configuration readme](./CONFIGURATION.md#transport). For this guide let's install `libp2p-websockets`, as it can be used in both Node.js and the browser.
You should select Transports according to the runtime of your application; Node.js or the browser. You can see a list of some of the available Transports in the [configuration readme](./CONFIGURATION.md#transport). For this guide let's install `@libp2p/websockets`, as it can be used in both Node.js and the browser.
Start by installing `libp2p-websockets`:
Start by installing `@libp2p/websockets`:
```sh
npm install libp2p-websockets
npm install @libp2p/websockets
```
Now that we have the module installed, let's configure libp2p to use the Transport. We'll use the [`Libp2p.create`](./API.md#create) method, which takes a single configuration object as its only parameter. We can add the Transport by passing it into the `modules.transport` array:
@@ -66,13 +66,13 @@ If you want to know more about libp2p transports, you should read the following
Encryption is an important part of communicating on the libp2p network. Every connection must be encrypted to help ensure security for everyone. As such, Connection Encryption (Crypto) is a required component of libp2p.
There are a growing number of Crypto modules being developed for libp2p. As those are released they will be tracked in the [Connection Encryption section of the configuration readme](./CONFIGURATION.md#connection-encryption). For now, we are going to configure our node to use the `libp2p-noise` module.
There are a growing number of Crypto modules being developed for libp2p. As those are released they will be tracked in the [Connection Encryption section of the configuration readme](./CONFIGURATION.md#connection-encryption). For now, we are going to configure our node to use the `@chainsafe/libp2p-noise` module.
```sh
npm install libp2p-noise
npm install @chainsafe/libp2p-noise
```
With `libp2p-noise` installed, we can add it to our existing configuration by importing it and adding it to the `modules.connEncryption` array:
With `@chainsafe/libp2p-noise` installed, we can add it to our existing configuration by importing it and adding it to the `modules.connEncryption` array:
```js
import { createLibp2p } from 'libp2p'
@@ -96,12 +96,12 @@ If you want to know more about libp2p connection encryption, you should read the
While multiplexers are not strictly required, they are highly recommended as they improve the effectiveness and efficiency of connections for the various protocols libp2p runs. Adding a multiplexer to your configuration will allow libp2p to run several of its internal protocols, like Identify, as well as allow your application to easily run any number of protocols over a single connection.
Looking at the [available stream multiplexing](./CONFIGURATION.md#stream-multiplexing) modules, js-libp2p currently only supports `libp2p-mplex`, so we will use that here. Bear in mind that future libp2p Transports might have `multiplexing` capabilities already built-in (such as `QUIC`).
Looking at the [available stream multiplexing](./CONFIGURATION.md#stream-multiplexing) modules, js-libp2p currently only supports `@libp2p/mplex`, so we will use that here. Bear in mind that future libp2p Transports might have `multiplexing` capabilities already built-in (such as `QUIC`).
You can install `libp2p-mplex` and add it to your libp2p node as follows in the next example.
You can install `@libp2p/mplex` and add it to your libp2p node as follows in the next example.
```sh
npm install libp2p-mplex
npm install @libp2p/mplex
```
```js
@@ -148,12 +148,9 @@ const node = await createLibp2p({
await node.start()
console.log('libp2p has started')
const listenAddrs = node.transportManager.getAddrs()
const listenAddrs = node.getMultiaddrs()
console.log('libp2p is listening on the following addresses: ', listenAddrs)
const advertiseAddrs = node.multiaddrs
console.log('libp2p is advertising the following addresses: ', advertiseAddrs)
// stop libp2p
await node.stop()
console.log('libp2p has stopped')
@@ -170,17 +167,17 @@ Peer discovery is an important part of creating a well connected libp2p node. A
For each discovered peer libp2p will emit a `peer:discovery` event which includes metadata about that peer. You can read the [Events](./API.md#events) in the API doc to learn more.
Looking at the [available peer discovery](./CONFIGURATION.md#peer-discovery) protocols, there are several options to be considered:
- If you already know the addresses of some other network peers, you should consider using `libp2p-bootstrap` as this is the easiest way of getting your peer into the network.
- If it is likely that you will have other peers on your local network, `libp2p-mdns` is a must if you're node is not running in the browser. It allows peers to discover each other when on the same local network.
- If your application is browser based you can use the `libp2p-webrtc-star` Transport, which includes a rendezvous based peer sharing service.
- A random walk approach can be used via `libp2p-kad-dht`, to crawl the network and find new peers along the way.
- If you already know the addresses of some other network peers, you should consider using `@libp2p/bootstrap` as this is the easiest way of getting your peer into the network.
- If it is likely that you will have other peers on your local network, `@libp2p/mdns` is a must if you're node is not running in the browser. It allows peers to discover each other when on the same local network.
- If your application is browser based you can use the `@libp2p/webrtc-star` Transport, which includes a rendezvous based peer sharing service.
- A random walk approach can be used via `@libp2p/kad-dht`, to crawl the network and find new peers along the way.
For this guide we will configure `libp2p-bootstrap` as this is useful for joining the public network.
For this guide we will configure `@libp2p/bootstrap` as this is useful for joining the public network.
Let's install `libp2p-bootstrap`.
Let's install `@libp2p/bootstrap`.
```sh
npm install libp2p-bootstrap
npm install @libp2p/bootstrap
```
We can provide specific configurations for each protocol within a `config.peerDiscovery` property in the options as shown below.
@@ -221,12 +218,12 @@ const node = await createLibp2p({
}
})
node.on('peer:discovery', (peer) => {
console.log('Discovered %s', peer.id.toB58String()) // Log discovered peer
node.addEventListener('peer:discovery', (evt) => {
console.log('Discovered %s', evt.detail.id.toString()) // Log discovered peer
})
node.connectionManager.on('peer:connect', (connection) => {
console.log('Connected to %s', connection.remotePeer.toB58String()) // Log connected peer
node.connectionManager.addEventListener('peer:connect', (evt) => {
console.log('Connected to %s', evt.detail.remotePeer.toString()) // Log connected peer
})
// start libp2p

View File

@@ -0,0 +1,262 @@
<!--Specify versions for migration below-->
# Migrating to libp2p@37 <!-- omit in toc -->
A migration guide for refactoring your application code from libp2p v0.36.x to v0.37.0.
## Table of Contents <!-- omit in toc -->
- [ESM](#esm)
- [TypeScript](#typescript)
- [Config](#config)
- [Bundled modules](#bundled-modules)
- [Events](#events)
- [Pubsub](#pubsub)
## ESM
The biggest change to `libp2p@0.37.0` is that the module is now [ESM-only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).
ESM is the module system for JavaScript, it allows us to structure our code in separate files without polluting a global namespace.
Other systems have tried to fill this gap, notably CommonJS, AMD, RequireJS and others, but ESM is [the official standard format](https://tc39.es/ecma262/#sec-modules) to package JavaScript code for reuse.
## TypeScript
The core `libp2p` module and all supporting modules have now been ported to TypeScript in a complete ground-up rewrite. This will not have a huge impact on most application code, but those that are type-aware, either by being written in TypeScript themselves or using JSDoc comments will notice full type completion and better error message when coding against the libp2p API.
To reflect the updated nature of these modules, all ecosystem modules have been moved to the `@libp2p` org on npm, so `libp2p-tcp` has become `@libp2p/tcp`, `libp2p-mplex` has become `@libp2p/mplex` and so on. `@chainsafe/libp2p-noise` and `libp2p-gossipsub` are unaffected.
## Config
Because libp2p is now fully typed it was necessary to refactor the configuration object passed to the libp2p constructor. The reason being, it previously accepted config objects to pass to the constructors of the various modules - to type those we'd need to know the types of all possible modules in advance which isn't possible.
The following changes have been made to the configuration object:
1. It now takes instances of modules rather than their classes
2. Keys from the `config` and `modules` objects have been migrated to the root of the object
3. Use of the `enabled` flag has been removed - if you don't want a particular feature enabled, don't pass a module implementing that feature
4. Some keys have been renamed = `transport` -> `transports`, `streamMuxer` -> `streamMuxers`, `connEncryption` -> `connectionEncryption`, etc
5. Keys from `config.dialer` have been moved to `config.connectionManager` as the connection manager is now responsible for managing connections
6. The `protocolPrefix` configuration option is now passed on a per-protocol basis for `identify`, `fetch` and `ping`
**Before**
```js
import Libp2p from 'libp2p'
import TCP from 'libp2p-tcp'
import Mplex from 'libp2p-mplex'
import { NOISE } from '@chainsafe/libp2p-noise'
import Gossipsub from 'libp2p-gossipsub'
import KadDHT from 'libp2p-kad-dht'
import Bootstrap from 'libp2p-bootstrap'
import MulticastDNS from 'libp2p-mdns'
const node = await Libp2p.create({
addresses: {
listen: ['/ip4/127.0.0.1/tcp/8000']
},
modules: {
transport: [
TCP
],
streamMuxer: [
Mplex
],
connEncryption: [
NOISE
],
dht: KadDHT,
pubsub: Gossipsub,
peerDiscovery: [
Bootstrap,
MulticastDNS
]
},
protocolPrefix: 'ipfs',
config: {
peerDiscovery: {
autoDial: true,
[MulticastDNS.tag]: {
interval: 1000,
enabled: true
},
[Bootstrap.tag]: {
list: [
// .. multiaddrs here
],
interval: 2000,
enabled: true
}
},
dialer: {
dialTimeout: 60000
}
}
})
```
**After**
```js
import { createLibp2p } from 'libp2p'
import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
import Gossipsub from '@chainsafe/libp2p-gossipsub'
import { KadDHT } from '@libp2p/kad-dht'
import { Bootstrap } from '@libp2p/bootstrap'
import { MulticastDNS } from '@libp2p/mdns'
const node = await createLibp2p({
addresses: {
listen: ['/ip4/127.0.0.1/tcp/8000']
},
addressManager: {
autoDial: true
},
connectionManager: {
dialTimeout: 60000
},
transports: [
new TCP()
],
streamMuxers: [
new Mplex()
],
connectionEncryption: [
new Noise()
],
dht: new KadDHT(),
pubsub: new Gossipsub(),
peerDiscovery: [
new Bootstrap({
list: [
// .. multiaddrs here
],
interval: 2000
}),
new MulticastDNS({
interval: 1000
})
],
identify: {
protocolPrefix: 'ipfs'
}
})
```
## Bundled modules
Previously you'd have to use deep import paths to get at bundled modules such as the private network module.
Access to these modules is now controlled by the package.json export map so your import paths will need to be updated:
**Before**
```js
import plaintext from 'libp2p/src/insecure/plaintext.js'
import Protector from 'libp2p/src/pnet/index.js'
import generateKey from 'libp2p/src/pnet/key-generator.js'
import TransportManager from 'libp2p/src/transport-manager.js'
```
**After**
```js
import { Plaintext } from 'libp2p/insecure'
import { PreSharedKeyConnectionProtector, generateKey } from 'libp2p/pnet'
import { TransportManager } from 'libp2p/transport-manager'
```
## Events
To reduce our dependency on Node.js internals, use of [EventEmitter](https://nodejs.org/api/events.html#class-eventemitter) has been replaced with the standard [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget).
The EventTarget API is very similar to [HTML DOM Events](https://developer.mozilla.org/en-US/docs/Web/API/Event) used by the browser.
All events are instances of the [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) class. Event-specific information can be accessed via the `.detail` property of the passed event.
They type of event emitted can be inferred from the types for each event emitter.
**Before**
```js
const handler = (peerInfo) => {
//...
}
// listen for event
libp2p.on('peer:discovery', handler)
// stop listening for event
libp2p.removeListener('peer:discovery', handler)
libp2p.off('peer:discovery', handler)
```
**After**
```js
const handler = (event) => {
const peerInfo = event.detail
//...
}
// listen for event
libp2p.addEventListener('peer:discovery', handler)
// stop listening for event
libp2p.removeEventListener('peer:discovery', handler)
```
## Pubsub
Similar to the events refactor above, pubsub is now driven by the standard [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) API.
You can still subscribe to events without a listener with `.subscribe` but all other uses now use the standard API.
Similar to the other events emitted by libp2p the event type is [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). This is part of the js language but at the time of writing Node.js [does not support](https://github.com/nodejs/node/issues/40678) `CustomEvent`, so a polyfill is supplied as part of the `@libp2p/interfaces`
**Before**
```js
const handler = (message: Message) => {
const topic = message.topic
//...
}
// listen for event
libp2p.pubsub.subscribe('my-topic')
libp2p.pubsub.on('my-topic', handler)
// send event
libp2p.pubsub.emit('my-topic', Uint8Array.from([0, 1, 2, 3]))
// stop listening for event
libp2p.unsubscribe('my-topic', handler)
libp2p.pubsub.off('my-topic', handler)
```
**After**
```js
import type { Message } from '@libp2p/interface-pubsub'
const handler = (event: CustomEvent<Message>) => {
const message = event.detail
const topic = message.topic
//...
}
// listen for event
libp2p.pubsub.subscribe('my-topic')
libp2p.pubsub.addEventListener('message', handler)
// send event
libp2p.pubsub.publish('my-topic', Uint8Array.from([0, 1, 2, 3]))
// stop listening for event
libp2p.pubsub.unsubscribe('my-topic')
libp2p.pubsub.removeEventListener('message', handler)
```

View File

@@ -5,7 +5,7 @@ While direct connections to nodes are preferable, it's not always possible to do
## 0. Setup the example
Before moving into the examples, you should run `npm install` on the top level `js-libp2p` folder, in order to install all the dependencies needed for this example. Once the install finishes, you should move into the example folder with `cd examples/auto-relay`.
Before moving into the examples, you should run `npm install` and `npm run build` on the top level `js-libp2p` folder, in order to install all the dependencies needed for this example. Once the install finishes, you should move into the example folder with `cd examples/auto-relay`.
This example comes with 3 main files. A `relay.js` file to be used in the first step, a `listener.js` file to be used in the second step and a `dialer.js` file to be used on the third step. All of these scripts will run their own libp2p node, which will interact with the previous ones. All nodes must be running in order for you to proceed.
@@ -24,7 +24,7 @@ import { Mplex } from '@libp2p/mplex'
const node = await createLibp2p({
transports: [new WebSockets()],
connectionEncryption: [new Noise()],
streamMuxers: [new Mplex()]
streamMuxers: [new Mplex()],
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0/ws']
// TODO check "What is next?" section
@@ -43,9 +43,9 @@ const node = await createLibp2p({
await node.start()
console.log(`Node started with id ${node.peerId.toB58String()}`)
console.log(`Node started with id ${node.peerId.toString()}`)
console.log('Listening on:')
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
node.getMultiaddrs().forEach((ma) => console.log(ma.toString()))
```
The Relay HOP advertise functionality is **NOT** required to be enabled. However, if you are interested in advertising on the network that this node is available to be used as a HOP Relay you can enable it. A content router module or Rendezvous needs to be configured to leverage this option.
@@ -94,17 +94,17 @@ const node = await createLibp2p({
})
await node.start()
console.log(`Node started with id ${node.peerId.toB58String()}`)
console.log(`Node started with id ${node.peerId.toString()}`)
const conn = await node.dial(relayAddr)
console.log(`Connected to the HOP relay ${conn.remotePeer.toString()}`)
// Wait for connection and relay to be bind for the example purpose
node.peerStore.on('change:multiaddrs', ({ peerId }) => {
node.peerStore.addEventListener('change:multiaddrs', (evt) => {
// Updated self multiaddrs?
if (peerId.equals(node.peerId)) {
console.log(`Advertising with a relay address of ${node.multiaddrs[0].toString()}/p2p/${node.peerId.toB58String()}`)
if (evt.detail.peerId.equals(node.peerId)) {
console.log(`Advertising with a relay address of ${node.getMultiaddrs()[0].toString()}`)
}
})
```
@@ -151,7 +151,7 @@ const node = await createLibp2p({
})
await node.start()
console.log(`Node started with id ${node.peerId.toB58String()}`)
console.log(`Node started with id ${node.peerId.toString()}`)
const conn = await node.dial(autoRelayNodeAddr)
console.log(`Connected to the auto relay node via ${conn.remoteAddr.toString()}`)
@@ -176,4 +176,4 @@ As you can see from the output, the remote address of the established connection
Before moving into production, there are a few things that you should take into account.
A relay node should not advertise its private address in a real world scenario, as the node would not be reachable by others. You should provide an array of public addresses in the libp2p `addresses.announce` option. If you are using websockets, bear in mind that due to browsers security policies you cannot establish unencrypted connection from secure context. The simplest solution is to setup SSL with nginx and proxy to the node and setup a domain name for the certificate.
A relay node should not advertise its private address in a real world scenario, as the node would not be reachable by others. You should provide an array of public addresses in the libp2p `addresses.announce` option. If you are using websockets, bear in mind that due to browsers security policies you cannot establish unencrypted connection from secure context. The simplest solution is to setup SSL with nginx and proxy to the node and setup a domain name for the certificate.

View File

@@ -1,5 +1,5 @@
import path from 'path'
import execa from 'execa'
import { execa } from 'execa'
import pDefer from 'p-defer'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { fileURLToPath } from 'url'

View File

@@ -3,7 +3,7 @@
This example creates a simple chat app in your terminal.
## Setup
1. Install the modules in the libp2p root directory, `npm install`.
1. Install the modules in the libp2p root directory, `npm install` and `npm run build`.
2. Open 2 terminal windows in the `./examples/chat/src` directory.
## Running

View File

@@ -1,6 +1,6 @@
/* eslint-disable no-console */
import { Multiaddr } from '@multiformats/multiaddr'
import { multiaddr } from '@multiformats/multiaddr'
import { createLibp2p } from './libp2p.js'
import { stdinToStream, streamToConsole } from './stream.js'
import { createFromJSON } from '@libp2p/peer-id-factory'
@@ -31,8 +31,8 @@ async function run () {
})
// Dial to the remote peer (the "listener")
const listenerMa = new Multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toString()}`)
const { stream } = await nodeDialer.dialProtocol(listenerMa, '/chat/1.0.0')
const listenerMa = multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toString()}`)
const stream = await nodeDialer.dialProtocol(listenerMa, '/chat/1.0.0')
console.log('Dialer dialed to listener on protocol: /chat/1.0.0')
console.log('Type a message and see what happens')

View File

@@ -28,7 +28,7 @@ export function streamToConsole(stream) {
// Decode length-prefixed data
lp.decode(),
// Turn buffers into strings
(source) => map(source, (buf) => uint8ArrayToString(buf)),
(source) => map(source, (buf) => uint8ArrayToString(buf.subarray())),
// Sink function
async function (source) {
// For each chunk of data

View File

@@ -1,5 +1,5 @@
import path from 'path'
import execa from 'execa'
import { execa } from 'execa'
import pDefer from 'p-defer'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { fileURLToPath } from 'url'

View File

@@ -34,13 +34,13 @@ const createNode = async () => {
stream,
async function (source) {
for await (const msg of source) {
console.log(uint8ArrayToString(msg))
console.log(uint8ArrayToString(msg.subarray()))
}
}
)
})
const { stream } = await node1.dialProtocol(node2.peerId, '/a-protocol')
const stream = await node1.dialProtocol(node2.peerId, '/a-protocol')
await pipe(
[uint8ArrayFromString('This information is sent out encrypted to the other peer')],

View File

@@ -1,31 +1,23 @@
❗❗Outdated: This example is still not refactored with the `0.27.*` release.
WIP on [libp2p/js-libp2p#507](https://github.com/libp2p/js-libp2p/pull/507)
======
# Delegated Routing with Libp2p and IPFS
This example shows how to use delegated peer and content routing. The [Peer and Content Routing Example](../peer-and-content-routing) focuses
on the DHT implementation. This example takes that a step further and introduces delegated routing. Delegated routing is
especially useful when your libp2p node will have limited resources, making running a DHT impractical. It's
also highly useful if your node is generating content, but can't reliably be on the network. You can use delegate nodes
to provide content on your behalf.
This example shows how to use delegated peer and content routing. The [Peer and Content Routing Example](../peer-and-content-routing) focuses on the DHT implementation. This example takes that a step further and introduces delegated routing. Delegated routing is especially useful when your libp2p node will have limited resources, making running a DHT impractical. It's also highly useful if your node is generating content, but can't reliably be on the network. You can use delegate nodes to provide content on your behalf.
The starting [Libp2p Bundle](./src/libp2p-bundle.js) in this example starts by disabling the DHT and adding the Delegated Peer and Content Routers.
Once you've completed the example, you should try enabled the DHT and see what kind of results you get! You can also enable the
various Peer Discovery modules and see the impact it has on your Peer count.
Once you've completed the example, you should try enabled the DHT and see what kind of results you get! You can also enable the various Peer Discovery modules and see the impact it has on your Peer count.
## Prerequisite
**NOTE**: This example is currently dependent on a clone of the [delegated routing support branch of go-ipfs](https://github.com/ipfs/go-ipfs/pull/4595).
This example uses a publicly known delegated routing node. This aims to ease experimentation, but you should not rely on this in production.
## Running this example
1. Install IPFS locally if you dont already have it. [Install Guide](https://docs.ipfs.io/introduction/install/)
2. Run the IPFS daemon: `ipfs daemon`
3. The daemon will output a line about its API address, like `API server listening on /ip4/127.0.0.1/tcp/8080`
4. In another window output the addresses of the node: `ipfs id`. Make note of the websocket address, it will contain `/ws/` in the address.
5. In `./src/libp2p-bundle.js` check if the host and port of your node are correct, according to the previous step. If they are different, replace them.
6. In `./src/App.js` replace `BootstrapNode` with your nodes Websocket address from step 4.
7. Start this example:
3. In another window output the addresses of the node: `ipfs id`. Make note of the websocket address, it will contain `/ws/` in the address.
- If there is no websocket address, you will need to add it in the ipfs config file (`~/.ipfs/config`)
- Add to Swarm Addresses something like: `"/ip4/127.0.0.1/tcp/4010/ws"`
4. In `./src/App.js` replace `BootstrapNode` with your nodes Websocket address from the step above.
5. Start this example
```sh
npm install
@@ -34,7 +26,7 @@ npm start
This should open your browser to http://localhost:3000. If it does not, go ahead and do that now.
8. Your browser should show you connected to at least 1 peer.
6. Your browser should show you connected to at least 1 peer.
### Finding Content via the Delegate
1. Add a file to your IPFS node. From this example root you can do `ipfs add ./README.md` to add the example readme.

View File

@@ -3,15 +3,15 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@chainsafe/libp2p-noise": "^6.0.1",
"ipfs-core": "^0.14.1",
"@chainsafe/libp2p-noise": "^8.0.1",
"ipfs-core": "^0.15.4",
"libp2p": "../../",
"@libp2p/delegated-content-routing": "^1.0.1",
"@libp2p/delegated-peer-routing": "^1.0.1",
"@libp2p/kad-dht": "^1.0.1",
"@libp2p/mplex": "^1.0.2",
"@libp2p/webrtc-star": "^1.0.6",
"@libp2p/websockets": "^1.0.3",
"@libp2p/delegated-content-routing": "^2.0.1",
"@libp2p/delegated-peer-routing": "^2.0.1",
"@libp2p/kad-dht": "^3.0.0",
"@libp2p/mplex": "^5.2.3",
"@libp2p/webrtc-star": "^3.0.3",
"@libp2p/websockets": "^3.0.4",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "5.0.0"

View File

@@ -4,7 +4,7 @@ import { createLibp2p } from 'libp2p'
import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
import { Gossipsub } from '@achingbrain/libp2p-gossipsub'
import { FloodSub } from '@libp2p/floodsub'
import { Bootstrap } from '@libp2p/bootstrap'
import { PubSubPeerDiscovery } from '@libp2p/pubsub-peer-discovery'
@@ -16,7 +16,7 @@ const createNode = async (bootstrappers) => {
transports: [new TCP()],
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
pubsub: new Gossipsub(),
pubsub: new FloodSub(),
peerDiscovery: [
new Bootstrap({
list: bootstrappers
@@ -40,7 +40,7 @@ const createNode = async (bootstrappers) => {
transports: [new TCP()],
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
pubsub: new Gossipsub(),
pubsub: new FloodSub(),
peerDiscovery: [
new PubSubPeerDiscovery({
interval: 1000
@@ -67,7 +67,7 @@ const createNode = async (bootstrappers) => {
const peer = evt.detail
console.log(`Peer ${node1.peerId.toString()} discovered: ${peer.id.toString()}`)
})
node2.addEventListener('peer:discovery',(evt) => {
node2.addEventListener('peer:discovery', (evt) => {
const peer = evt.detail
console.log(`Peer ${node2.peerId.toString()} discovered: ${peer.id.toString()}`)
})
@@ -77,4 +77,4 @@ const createNode = async (bootstrappers) => {
node1.start(),
node2.start()
])
})();
})()

View File

@@ -15,6 +15,9 @@ First, we create our libp2p node.
```JavaScript
import { createLibp2p } from 'libp2p'
import { Bootstrap } from '@libp2p/bootstrap'
import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
const node = await createLibp2p({
transports: [
@@ -52,7 +55,6 @@ Now, once we create and start the node, we can listen for events such as `peer:d
```JavaScript
const node = await createLibp2p({
peerId,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
@@ -73,13 +75,13 @@ const node = await createLibp2p({
]
})
node.connectionManager.on('peer:connect', (connection) => {
console.log('Connection established to:', connection.remotePeer.toB58String()) // Emitted when a new connection has been created
node.connectionManager.addEventListener('peer:connect', (evt) => {
console.log('Connection established to:', evt.detail.remotePeer.toString()) // Emitted when a new connection has been created
})
node.on('peer:discovery', (peerId) => {
node.addEventListener('peer:discovery', (evt) => {
// No need to dial, autoDial is on
console.log('Discovered:', peerId.toB58String())
console.log('Discovered:', evt.detail.id.toString())
})
await node.start()
@@ -105,16 +107,19 @@ Connection established to: QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb
## 2. MulticastDNS to find other peers in the network
For this example, we need `libp2p-mdns`, go ahead and `npm install` it. You can find the complete solution at [2.js](./2.js).
For this example, we need `@libp2p/mdns`, go ahead and `npm install` it. You can find the complete solution at [2.js](./2.js).
Update your libp2p configuration to include MulticastDNS.
```JavaScript
import { createLibp2p } from 'libp2p'
import { MulticastDNS } from '@libp2p/mdns'
import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
const createNode = () => {
return Libp2p.create({
return createLibp2p({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
@@ -144,8 +149,8 @@ const [node1, node2] = await Promise.all([
createNode()
])
node1.on('peer:discovery', (peer) => console.log('Discovered:', peerId.toB58String()))
node2.on('peer:discovery', (peer) => console.log('Discovered:', peerId.toB58String()))
node1.addEventListener('peer:discovery', (evt) => console.log('Discovered:', evt.detail.id.toString()))
node2.addEventListener('peer:discovery', (evt) => console.log('Discovered:', evt.detail.id.toString()))
await Promise.all([
node1.start(),
@@ -163,7 +168,7 @@ Discovered: QmRcXXhtG8vTqwVBRonKWtV4ovDoC1Fe56WYtcrw694eiJ
## 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.
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).
@@ -174,9 +179,9 @@ import { createLibp2p } from 'libp2p'
import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
import { Gossipsub } from 'libp2p-gossipsub'
import { GossipSub } from '@chainsafe/libp2p-gossipsub'
import { Bootstrap } from '@libp2p/bootstrap'
const PubsubPeerDiscovery from 'libp2p-pubsub-peer-discovery')
import { PubSubPeerDiscovery } from '@libp2p/pubsub-peer-discovery'
const createNode = async (bootstrapers) => {
const node = await createLibp2p({
@@ -184,23 +189,25 @@ const createNode = async (bootstrapers) => {
listen: ['/ip4/0.0.0.0/tcp/0']
},
transports: [
new TCP()
],
streamMuxers: [
new Mplex()
],
connectionEncryption: [
new Noise()
],
peerDiscovery: [
new Bootstrap({
interval: 60e3,
list: bootstrapers
}),
new PubsubPeerDiscovery({
interval: 1000
})
])
new TCP()
],
streamMuxers: [
new Mplex()
],
connectionEncryption: [
new Noise()
],
pubsub: new GossipSub({ allowPublishToZeroPeers: true }),
peerDiscovery: [
new Bootstrap({
interval: 60e3,
list: bootstrapers
}),
new PubSubPeerDiscovery({
interval: 1000
})
]
})
return node
}
@@ -209,28 +216,49 @@ const createNode = async (bootstrapers) => {
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({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
}
const relay = await createLibp2p({
addresses: {
listen: [
'/ip4/0.0.0.0/tcp/0'
]
},
transports: [new TCP()],
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
pubsub: new GossipSub({ allowPublishToZeroPeers: true }),
peerDiscovery: [
new PubSubPeerDiscovery({
interval: 1000
})
],
relay: {
enabled: true, // Allows you to dial and accept relayed connections. Does not make you a relay.
hop: {
enabled: true // Allows you to be a relay for other peers
}
}
})
console.log(`libp2p relay starting with id: ${relay.peerId.toB58String()}`)
console.log(`libp2p relay starting with id: ${relay.peerId.toString()}`)
await relay.start()
const relayMultiaddrs = relay.multiaddrs.map((m) => `${m.toString()}/p2p/${relay.peerId.toB58String()}`)
const relayMultiaddrs = relay.getMultiaddrs()
const [node1, node2] = await Promise.all([
createNode(relayMultiaddrs),
createNode(relayMultiaddrs)
])
node1.on('peer:discovery', (peerId) => {
console.log(`Peer ${node1.peerId.toB58String()} discovered: ${peerId.toB58String()}`)
node1.addEventListener('peer:discovery', (evt) => {
console.log(`Peer ${node1.peerId.toString()} discovered: ${evt.detail.id.toString()}`)
})
node2.on('peer:discovery', (peerId) => {
console.log(`Peer ${node2.peerId.toB58String()} discovered: ${peerId.toB58String()}`)
node2.addEventListener('peer:discovery', (evt) => {
console.log(`Peer ${node2.peerId.toString()} discovered: ${evt.detail.id.toString()}`)
})
;[node1, node2].forEach((node, index) => console.log(`Node ${index} starting with id: ${node.peerId.toB58String()}`))
;[node1, node2].forEach((node, index) => console.log(`Node ${index} starting with id: ${node.peerId.toString()}`))
await Promise.all([
node1.start(),
node2.start()
@@ -258,6 +286,6 @@ This is really useful when running libp2p in constrained environments like a bro
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 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).
- 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 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

@@ -1,5 +1,5 @@
import path from 'path'
import execa from 'execa'
import { execa } from 'execa'
import pWaitFor from 'p-wait-for'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { fileURLToPath } from 'url'
@@ -27,7 +27,7 @@ export async function test () {
})
})
await pWaitFor(() => discoveredNodes > 1)
await pWaitFor(() => discoveredNodes > 1, 600000)
proc.kill()
}

View File

@@ -1,5 +1,5 @@
import path from 'path'
import execa from 'execa'
import { execa } from 'execa'
import pWaitFor from 'p-wait-for'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { fileURLToPath } from 'url'
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export async function test () {
let discoveredNodes = 0
const discoveredPeers = []
process.stdout.write('3.js\n')
@@ -19,15 +19,20 @@ export async function test () {
proc.all.on('data', async (data) => {
process.stdout.write(data)
const str = uint8ArrayToString(data)
const discoveredPeersRegex = /Peer\s+(?<Peer1>[^\s]+)\s+discovered:\s+(?<Peer2>[^\s]+)/
str.split('\n').forEach(line => {
if (line.includes('discovered:')) {
discoveredNodes++
const peers = line.match(discoveredPeersRegex)
if (peers != null) {
// sort so we don't count reversed pair twice
const match = [peers.groups.Peer1, peers.groups.Peer2].sort().join(',')
if (!discoveredPeers.includes(match)) {
discoveredPeers.push(match)
}
}
})
})
await pWaitFor(() => discoveredNodes > 3)
await pWaitFor(() => discoveredPeers.length > 2, 600000)
proc.kill()
}

View File

@@ -3,7 +3,7 @@
This example performs a simple echo from the listener to the dialer.
## Setup
1. Install the modules from libp2p root, `npm install`.
1. Install the modules from libp2p root, `npm install` and `npm run build`.
2. Open 2 terminal windows in the `./src` directory.
## Running

View File

@@ -37,7 +37,7 @@ async function run() {
// Dial the listener node
console.log('Dialing to peer:', listenerMultiaddr)
const { stream } = await dialerNode.dialProtocol(listenerMultiaddr, '/echo/1.0.0')
const stream = await dialerNode.dialProtocol(listenerMultiaddr, '/echo/1.0.0')
console.log('nodeA dialed to nodeB on protocol: /echo/1.0.0')
@@ -51,7 +51,7 @@ async function run() {
// For each chunk of data
for await (const data of source) {
// Output the data
console.log('received echo:', uint8ArrayToString(data))
console.log('received echo:', uint8ArrayToString(data.subarray()))
}
}
)

View File

@@ -1,5 +1,5 @@
import path from 'path'
import execa from 'execa'
import { execa } from 'execa'
import pDefer from 'p-defer'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { fileURLToPath } from 'url'

View File

@@ -1,23 +1,24 @@
# libp2p in the browser
This example leverages the [Parcel.js bundler](https://parceljs.org/) to compile and serve the libp2p code in the browser. Parcel uses [Babel](https://babeljs.io/) to handle transpilation of the code. You can use other bundlers such as Webpack or Browserify, but we will not be covering them here.
This example leverages the [vite bundler](https://vitejs.dev/) to compile and serve the libp2p code in the browser. You can use other bundlers such as Webpack, but we will not be covering them here.
## Setup
In order to run the example:
- Install dependencey at the root of the js-libp2p repository (if not already done),
- 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
npm run build
cd ./examples/libp2p-in-the-browser
npm install
```
## Running the examples
Start by running the Parcel server:
Start by running the vite server:
```
npm start
@@ -29,7 +30,7 @@ The output should look something like this:
$ npm start
> libp2p-in-browser@1.0.0 start
> parcel index.html
> vite index.html
Server running at http://localhost:1234
✨ Built in 1000ms.
@@ -39,7 +40,7 @@ This will compile the code and start a server listening on port [http://localhos
Now, if you open a second browser tab to `http://localhost:1234`, you should discover your node from the previous tab. This is due to the fact that the `libp2p-webrtc-star` transport also acts as a Peer Discovery interface. Your node will be notified of any peer that connects to the same signaling server you are connected to. Once libp2p discovers this new peer, it will attempt to establish a direct WebRTC connection.
**Note**: In the example we assign libp2p to `window.libp2p`, in case you would like to play around with the API directly in the browser. You can of course make changes to `index.js` and Parcel will automatically rebuild and reload the browser tabs.
**Note**: In the example we assign libp2p to `window.libp2p`, in case you would like to play around with the API directly in the browser. You can of course make changes to `index.js` and vite will automatically rebuild and reload the browser tabs.
## Going to production?

View File

@@ -4,7 +4,7 @@
<head>
<meta charset="utf-8">
<title>js-libp2p parcel.js browser example</title>
<title>js-libp2p vite browser example</title>
</head>
<body>

View File

@@ -3,20 +3,17 @@
"version": "1.0.0",
"description": "A libp2p node running in the browser",
"type": "module",
"browserslist": [
"last 2 Chrome versions"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "vite"
},
"license": "ISC",
"dependencies": {
"@chainsafe/libp2p-noise": "^6.0.1",
"@libp2p/bootstrap": "^1.0.1",
"@libp2p/mplex": "^1.0.2",
"@libp2p/webrtc-star": "^1.0.6",
"@libp2p/websockets": "^1.0.3",
"@chainsafe/libp2p-noise": "^8.0.1",
"@libp2p/bootstrap": "^2.0.1",
"@libp2p/mplex": "^5.2.3",
"@libp2p/webrtc-star": "^3.0.3",
"@libp2p/websockets": "^3.0.4",
"libp2p": "../../"
},
"devDependencies": {

View File

@@ -1,4 +1,4 @@
import execa from 'execa'
import { execa } from 'execa'
import { chromium } from 'playwright'
import path from 'path'
import { fileURLToPath } from 'url'

View File

@@ -0,0 +1,5 @@
export default {
build: {
target: 'es2020'
}
}

View File

@@ -9,12 +9,13 @@
},
"license": "MIT",
"dependencies": {
"@achingbrain/libp2p-gossipsub": "^0.13.5",
"@libp2p/pubsub-peer-discovery": "^5.0.1",
"execa": "^2.1.0",
"fs-extra": "^8.1.0",
"@libp2p/pubsub-peer-discovery": "^6.0.2",
"@libp2p/floodsub": "^3.0.3",
"@nodeutils/defaults-deep": "^1.1.0",
"execa": "^6.1.0",
"fs-extra": "^10.1.0",
"libp2p": "../",
"p-defer": "^3.0.0",
"p-defer": "^4.0.0",
"uint8arrays": "^3.0.0",
"which": "^2.0.1"
},

View File

@@ -40,7 +40,7 @@ const createNode = async () => {
])
// Wait for onConnect handlers in the DHT
await delay(100)
await delay(1000)
const cid = CID.parse('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL')
await node1.contentRouting.provide(cid)

View File

@@ -8,41 +8,44 @@ Content Routing is the category of modules that offer a way to find where conten
# 1. Using Peer Routing to find other peers
This example builds on top of the [Protocol and Stream Muxing](../protocol-and-stream-muxing). We need to install `libp2p-kad-dht`, go ahead and `npm install libp2p-kad-dht`. If you want to see the final version, open [1.js](./1.js).
This example builds on top of the [Protocol and Stream Muxing](../protocol-and-stream-muxing). We need to install `@libp2p/kad-dht`, go ahead and `npm install @libp2p/kad-dht`. If you want to see the final version, open [1.js](./1.js).
First, let's update our config to support Peer Routing and Content Routing.
```JavaScript
import { createLibp2p } from 'libp2p'
import { KadDHT } from '@libp2p/kad-dht'
import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
const node = await createLibp2p({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
transports: [
new TCP()
],
streamMuxers: [
new Mplex()
],
connEncryption: [
new Noise()
],
// we add the DHT module that will enable Peer and Content Routing
dht: KadDHT
})
const createNode = async () => {
const node = await createLibp2p({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
transports: [new TCP()],
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
dht: new KadDHT()
})
await node.start()
return node
}
```
Once that is done, we can use the createNode function we developed in the previous example to create 3 nodes. Connect node 1 to node 2 and node 2 to node 3. We will use node 2 as a way to find the whereabouts of node 3
```JavaScript
const node1 = nodes[0]
const node2 = nodes[1]
const node3 = nodes[2]
const [node1, node2, node3] = await Promise.all([
createNode(),
createNode(),
createNode()
])
await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
await node2.peerStore.addressBook.set(node3.peerId, node3.getMultiaddrs())
await Promise.all([
node1.dial(node2.peerId),
@@ -50,12 +53,12 @@ await Promise.all([
])
// Set up of the cons might take time
await delay(100)
await new Promise(resolve => setTimeout(resolve, 100))
const peer = await node1.peerRouting.findPeer(node3.peerId)
console.log('Found it, multiaddrs are:')
peer.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${peer.id.toB58String()}`))
peer.multiaddrs.forEach((ma) => console.log(ma.toString()))
```
You should see the output being something like:
@@ -78,12 +81,17 @@ You can find this example completed in [2.js](./2.js), however as you will see i
Instead of calling `peerRouting.findPeer`, we will use `contentRouting.provide` and `contentRouting.findProviders`.
```JavaScript
import { CID } from 'multiformats/cid'
import all from 'it-all'
const cid = CID.parse('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL')
await node1.contentRouting.provide(cid)
console.log('Node %s is providing %s', node1.peerId.toB58String(), cid.toString())
const provs = await all(node3.contentRouting.findProviders(cid, { timeout: 5000 }))
console.log('Node %s is providing %s', node1.peerId.toString(), cid.toString())
console.log('Found provider:', providers[0].id.toB58String())
const providers = await all(node3.contentRouting.findProviders(cid, { timeout: 5000 }))
console.log('Found provider:', providers[0].id.toString())
```
The output of your program should look like:

View File

@@ -2,7 +2,7 @@
This example shows how to set up a private network of libp2p nodes.
## Setup
1. Install the modules in the libp2p root directory, `npm install`.
1. Install the modules in the libp2p root directory, `npm install` and `npm run build`.
## Run
Running the example will cause two nodes with the same swarm key to be started and exchange basic information.

View File

@@ -1,6 +1,6 @@
/* eslint no-console: ["off"] */
import { generate } from 'libp2p/pnet/generate'
import { generateKey } from 'libp2p/pnet'
import { privateLibp2pNode } from './libp2p-node.js'
import { pipe } from 'it-pipe'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
@@ -8,11 +8,11 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
// Create a Uint8Array and write the swarm key to it
const swarmKey = new Uint8Array(95)
generate(swarmKey)
generateKey(swarmKey)
// This key is for testing a different key not working
const otherSwarmKey = new Uint8Array(95)
generate(otherSwarmKey)
generateKey(otherSwarmKey)
;(async () => {
const node1 = await privateLibp2pNode(swarmKey)
@@ -37,13 +37,13 @@ generate(otherSwarmKey)
stream,
async function (source) {
for await (const msg of source) {
console.log(uint8ArrayToString(msg))
console.log(uint8ArrayToString(msg.subarray()))
}
}
)
})
const { stream } = await node1.dialProtocol(node2.peerId, '/private')
const stream = await node1.dialProtocol(node2.peerId, '/private')
await pipe(
[uint8ArrayFromString('This message is sent on a private network')],

View File

@@ -36,7 +36,7 @@ const createNode = async () => {
stream,
async function (source) {
for await (const msg of source) {
console.log(uint8ArrayToString(msg))
console.log(uint8ArrayToString(msg.subarray()))
}
}
)
@@ -60,14 +60,14 @@ const createNode = async () => {
})
*/
const { stream } = await node1.dialProtocol(node2.peerId, ['/your-protocol'])
const stream = await node1.dialProtocol(node2.peerId, ['/your-protocol'])
await pipe(
[uint8ArrayFromString('my own protocol, wow!')],
stream
)
/*
const { stream } = node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0'])
const stream = node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0'])
await pipe(
['my own protocol, wow!'],

View File

@@ -35,25 +35,28 @@ const createNode = async () => {
stream,
async function (source) {
for await (const msg of source) {
console.log(`from: ${protocol}, msg: ${uint8ArrayToString(msg)}`)
console.log(`from: ${protocol}, msg: ${uint8ArrayToString(msg.subarray())}`)
}
}
)
).finally(() => {
// clean up resources
stream.close()
})
})
const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/a'])
const stream1 = await node1.dialProtocol(node2.peerId, ['/a'])
await pipe(
[uint8ArrayFromString('protocol (a)')],
stream1
)
const { stream: stream2 } = await node1.dialProtocol(node2.peerId, ['/b'])
const stream2 = await node1.dialProtocol(node2.peerId, ['/b'])
await pipe(
[uint8ArrayFromString('protocol (b)')],
stream2
)
const { stream: stream3 } = await node1.dialProtocol(node2.peerId, ['/b'])
const stream3 = await node1.dialProtocol(node2.peerId, ['/b'])
await pipe(
[uint8ArrayFromString('another stream on protocol (b)')],
stream3

View File

@@ -37,7 +37,7 @@ const createNode = async () => {
stream,
async function (source) {
for await (const msg of source) {
console.log(uint8ArrayToString(msg))
console.log(uint8ArrayToString(msg.subarray()))
}
}
)
@@ -48,19 +48,19 @@ const createNode = async () => {
stream,
async function (source) {
for await (const msg of source) {
console.log(uint8ArrayToString(msg))
console.log(uint8ArrayToString(msg.subarray()))
}
}
)
})
const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2'])
const stream1 = await node1.dialProtocol(node2.peerId, ['/node-2'])
await pipe(
[uint8ArrayFromString('from 1 to 2')],
stream1
)
const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1'])
const stream2 = await node2.dialProtocol(node1.peerId, ['/node-1'])
await pipe(
[uint8ArrayFromString('from 2 to 1')],
stream2

View File

@@ -6,21 +6,25 @@ The feature of agreeing on a protocol over an established connection is what we
# 1. Handle multiple protocols
Let's see _protocol multiplexing_ in action! You will need the following modules for this example: `libp2p`, `libp2p-tcp`, `peer-id`, `it-pipe`, `it-buffer` and `streaming-iterables`. This example reuses the base left by the [Transports](../transports) example. You can see the complete solution at [1.js](./1.js).
Let's see _protocol multiplexing_ in action! You will need the following modules for this example: `libp2p`, `@libp2p/tcp`, `@libp2p/peer-id`, `it-pipe`, `it-buffer` and `streaming-iterables`. This example reuses the base left by the [Transports](../transports) example. You can see the complete solution at [1.js](./1.js).
After creating the nodes, we need to tell libp2p which protocols to handle.
```JavaScript
import { pipe } from 'it-pipe'
const { map } from 'streaming-iterables')
const { toBuffer } from 'it-buffer')
import { map } from 'streaming-iterables'
import { toBuffer } from 'it-buffer'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
// ...
const node1 = nodes[0]
const node2 = nodes[1]
const [node1, node2] = await Promise.all([
createNode(),
createNode()
])
// Add node's 2 data to the PeerStore
await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
// Here we are telling libp2p that if someone dials this node to talk with the `/your-protocol`
// multicodec, the protocol identifier, please call this handler and give it the stream
@@ -30,7 +34,7 @@ node2.handle('/your-protocol', ({ stream }) => {
stream,
source => (async function () {
for await (const msg of source) {
console.log(msg.toString())
console.log(uint8ArrayToString(msg.subarray()))
}
})()
)
@@ -40,10 +44,10 @@ node2.handle('/your-protocol', ({ stream }) => {
After the protocol is _handled_, now we can dial to it.
```JavaScript
const { stream } = await node1.dialProtocol(node2.peerId, ['/your-protocol'])
const stream = await node1.dialProtocol(node2.peerId, ['/your-protocol'])
await pipe(
['my own protocol, wow!'],
[uint8ArrayFromString('my own protocol, wow!')],
stream
)
```
@@ -56,16 +60,16 @@ node2.handle('/another-protocol/1.0.1', ({ stream }) => {
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
console.log(uint8ArrayToString(msg.subarray()))
}
}
)
})
// ...
const { stream } = await node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0'])
const stream = await node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0'])
await pipe(
['my own protocol, wow!'],
[uint8ArrayFromString('my own protocol, wow!')],
stream
)
```
@@ -75,8 +79,8 @@ This feature is super power for network protocols. It works in the same way as v
There is still one last feature, you can provide multiple protocols for the same handler. If you have a backwards incompatible change, but it only requires minor changes to the code, you may prefer to do protocol checking instead of having multiple handlers
```JavaScript
node2.handle(['/another-protocol/1.0.0', '/another-protocol/2.0.0'], ({ protocol, stream }) => {
if (protocol === '/another-protocol/2.0.0') {
node2.handle(['/another-protocol/1.0.0', '/another-protocol/2.0.0'], ({ stream }) => {
if (stream.stat.protocol === '/another-protocol/2.0.0') {
// handle backwards compatibility
}
@@ -84,7 +88,7 @@ node2.handle(['/another-protocol/1.0.0', '/another-protocol/2.0.0'], ({ protocol
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
console.log(uint8ArrayToString(msg.subarray()))
}
}
)
@@ -107,47 +111,47 @@ import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
//...
const createNode = () => {
return Libp2p.create({
transports: [
new TCP()
],
streamMuxers: [
new Mplex()
]
})
}
createLibp2p({
//...
transports: [
new TCP()
],
streamMuxers: [
new Mplex()
]
})
```
With this, we can dial as many times as we want to a peer and always reuse the same established underlying connection.
```JavaScript
node2.handle(['/a', '/b'], ({ protocol, stream }) => {
node2.handle(['/a', '/b'], ({ stream }) => {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(`from: ${protocol}, msg: ${msg.toString()}`)
console.log(`from: ${stream.stat.protocol}, msg: ${uint8ArrayToString(msg.subarray())}`)
}
}
)
})
const { stream } = await node1.dialProtocol(node2.peerId, ['/a'])
const stream = await node1.dialProtocol(node2.peerId, ['/a'])
await pipe(
['protocol (a)'],
[uint8ArrayFromString('protocol (a)')],
stream
)
const { stream: stream2 } = await node1.dialProtocol(node2.peerId, ['/b'])
const stream2 = await node1.dialProtocol(node2.peerId, ['/b'])
await pipe(
['protocol (b)'],
[uint8ArrayFromString('protocol (b)')],
stream2
)
const { stream: stream3 } = await node1.dialProtocol(node2.peerId, ['/b'])
const stream3 = await node1.dialProtocol(node2.peerId, ['/b'])
await pipe(
['another stream on protocol (b)'],
[uint8ArrayFromString('another stream on protocol (b)')],
stream3
)
```
@@ -167,10 +171,103 @@ There is one last trick on _protocol and stream multiplexing_ that libp2p uses t
With the aid of both mechanisms, we can reuse an incomming connection to dial streams out too, this is specially useful when you are behind tricky NAT, firewalls or if you are running in a browser, where you can't have listening addrs, but you can dial out. By dialing out, you enable other peers to talk with you in Protocols that they want, simply by opening a new multiplexed stream.
You can see this working on example [3.js](./3.js). The result should look like the following:
You can see this working on example [3.js](./3.js).
As we've seen earlier, we can create our node with this createNode function.
```js
const createNode = async () => {
const node = await createLibp2p({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
transports: [new TCP()],
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
})
await node.start()
return node
}
```
We can now create our two nodes for this example.
```js
const [node1, node2] = await Promise.all([
createNode(),
createNode()
])
```
Since, we want to connect these nodes `node1` & `node2`, we add our `node2` multiaddr in key-value pair in `node1` peer store.
```js
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
```
You may notice that we are only adding `node2` to `node1` peer store. This is because we want to dial up a bidirectional connection between these two nodes.
Finally, let's create protocols for `node1` & `node2` and dial those protocols.
```js
node1.handle('/node-1', ({ stream }) => {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(uint8ArrayToString(msg.subarray()))
}
}
)
})
node2.handle('/node-2', ({ stream }) => {
pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(uint8ArrayToString(msg.subarray()))
}
}
)
})
// Dialing node2 from node1
const stream1 = await node1.dialProtocol(node2.peerId, ['/node-2'])
await pipe(
[uint8ArrayFromString('from 1 to 2')],
stream1
)
// Dialing node1 from node2
const stream2 = await node2.dialProtocol(node1.peerId, ['/node-1'])
await pipe(
[uint8ArrayFromString('from 2 to 1')],
stream2
)
```
If we run this code, the result should look like the following:
```Bash
> node 3.js
from 1 to 2
from 2 to 1
```
So, we have successfully set up a bidirectional connection with protocol muxing. But you should be aware that we were able to dial from `node2` to `node1` even we haven't added the `node1` peerId to node2 address book is because we dialed node2 from node1 first. Then, we just dialed back our stream out from `node2` to `node1`. So, if we dial from `node2` to `node1` before dialing from `node1` to `node2` we will get an error.
The code below will result into an error as `the dial address is not valid`.
```js
// Dialing from node2 to node1
const stream2 = await node2.dialProtocol(node1.peerId, ['/node-1'])
await pipe(
[uint8ArrayFromString('from 2 to 1')],
stream2
)
// Dialing from node1 to node2
const stream1 = await node1.dialProtocol(node2.peerId, ['/node-2'])
await pipe(
[uint8ArrayFromString('from 1 to 2')],
stream1
)
```

View File

@@ -4,10 +4,9 @@ import { createLibp2p } from 'libp2p'
import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
import { Gossipsub } from '@achingbrain/libp2p-gossipsub'
import { FloodSub } from '@libp2p/floodsub'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { CustomEvent } from '@libp2p/interfaces'
const createNode = async () => {
const node = await createLibp2p({
@@ -17,7 +16,7 @@ const createNode = async () => {
transports: [new TCP()],
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
pubsub: new Gossipsub()
pubsub: new FloodSub()
})
await node.start()
@@ -36,17 +35,21 @@ const createNode = async () => {
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
await node1.dial(node2.peerId)
node1.pubsub.addEventListener(topic, (evt) => {
console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)}`)
node1.pubsub.subscribe(topic)
node1.pubsub.addEventListener('message', (evt) => {
console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`)
})
// Will not receive own published messages by default
node2.pubsub.addEventListener(topic, (evt) => {
console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)}`)
node2.pubsub.subscribe(topic)
node2.pubsub.addEventListener('message', (evt) => {
console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`)
})
// node2 publishes "news" every second
setInterval(() => {
node2.pubsub.dispatchEvent(new CustomEvent(topic, { detail: uint8ArrayFromString('Bird bird bird, bird is the word!') }))
node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')).catch(err => {
console.error(err)
})
}, 1000)
})()

View File

@@ -1,6 +1,6 @@
# Publish Subscribe
Publish Subscribe is also included on the stack. Currently, we have two PubSub implementation available [libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub) and [libp2p-gossipsub](https://github.com/ChainSafe/js-libp2p-gossipsub), with many more being researched at [research-pubsub](https://github.com/libp2p/research-pubsub).
Publish Subscribe is also included on the stack. Currently, we have two PubSub implementation available [@libp2p/floodsub](https://github.com/libp2p/js-libp2p-floodsub) and [@chainsafe/libp2p-gossipsub](https://github.com/ChainSafe/js-libp2p-gossipsub), with many more being researched at [research-pubsub](https://github.com/libp2p/research-pubsub).
We've seen many interesting use cases appear with this, here are some highlights:
@@ -10,7 +10,7 @@ We've seen many interesting use cases appear with this, here are some highlights
## 0. Set up the example
Before moving into the examples, you should run `npm install` on the top level `js-libp2p` folder, in order to install all the dependencies needed for this example. In addition, you will need to install the example related dependencies by doing `cd examples && npm install`. Once the install finishes, you should move into the example folder with `cd pubsub`.
Before moving into the examples, you should run `npm install` and `npm run build` on the top level `js-libp2p` folder, in order to install all the dependencies needed for this example. In addition, you will need to install the example related dependencies by doing `cd examples && npm install`. Once the install finishes, you should move into the example folder with `cd pubsub`.
## 1. Setting up a simple PubSub network on top of libp2p
@@ -22,54 +22,62 @@ First, let's update our libp2p configuration with a pubsub implementation.
```JavaScript
import { createLibp2p } from 'libp2p'
import { Gossipsub } from 'libp2p-gossipsub'
import { GossipSub } from '@chainsafe/libp2p-gossipsub'
import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
const node = await createLibp2p({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
transports: [
new TCP()
],
streamMuxers: [
new Mplex()
],
connectionEncryption: [
new Noise()
],
// we add the Pubsub module we want
pubsub: new Gossipsub()
})
const createNode = async () => {
const node = await createLibp2p({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
transports: [new TCP()],
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
// we add the Pubsub module we want
pubsub: new GossipSub({ allowPublishToZeroPeers: true })
})
await node.start()
return node
}
```
Once that is done, we only need to create a few libp2p nodes, connect them and everything is ready to start using pubsub.
```JavaScript
const { fromString } from 'uint8arrays/from-string')
const { toString } from 'uint8arrays/to-string')
import { fromString as uint8ArrayFromString } from "uint8arrays/from-string";
import { toString as uint8ArrayToString } from "uint8arrays/to-string";
const topic = 'news'
const node1 = nodes[0]
const node2 = nodes[1]
const [node1, node2] = await Promise.all([
createNode(),
createNode()
])
// Add node's 2 data to the PeerStore
await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
await node1.dial(node2.peerId)
node1.pubsub.on(topic, (msg) => {
console.log(`node1 received: ${toString(msg.data)}`)
node1.pubsub.addEventListener("message", (evt) => {
console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`)
})
await node1.pubsub.subscribe(topic)
// Will not receive own published messages by default
node2.pubsub.on(topic, (msg) => {
console.log(`node2 received: ${toString(msg.data)}`)
node2.pubsub.addEventListener("message", (evt) => {
console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`)
})
await node2.pubsub.subscribe(topic)
// node2 publishes "news" every second
setInterval(() => {
node2.pubsub.publish(topic, fromString('Bird bird bird, bird is the word!'))
node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')).catch(err => {
console.error(err)
})
}, 1000)
```
@@ -85,14 +93,7 @@ node1 received: Bird bird bird, bird is the word!
You can change the pubsub `emitSelf` option if you want the publishing node to receive its own messages.
```JavaScript
const defaults = {
config: {
pubsub: {
enabled: true,
emitSelf: true
}
}
}
new GossipSub({ allowPublishToZeroPeers: true, emitSelf: true })
```
The output of the program should look like:

View File

@@ -4,10 +4,9 @@ import { createLibp2p } from 'libp2p'
import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
import { Gossipsub } from '@achingbrain/libp2p-gossipsub'
import { FloodSub } from '@libp2p/floodsub'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { CustomEvent } from '@libp2p/interfaces'
const createNode = async () => {
const node = await createLibp2p({
@@ -17,7 +16,7 @@ const createNode = async () => {
transports: [new TCP()],
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
pubsub: new Gossipsub()
pubsub: new FloodSub()
})
await node.start()
@@ -41,24 +40,42 @@ const createNode = async () => {
await node2.dial(node3.peerId)
//subscribe
node1.pubsub.addEventListener(topic, (evt) => {
node1.pubsub.addEventListener('message', (evt) => {
if (evt.detail.topic !== topic) {
return
}
// Will not receive own published messages by default
console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)}`)
})
await node1.pubsub.subscribe(topic)
node1.pubsub.subscribe(topic)
node2.pubsub.addEventListener('message', (evt) => {
if (evt.detail.topic !== topic) {
return
}
node2.pubsub.addEventListener(topic, (evt) => {
console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)}`)
})
node2.pubsub.subscribe(topic)
node3.pubsub.addEventListener('message', (evt) => {
if (evt.detail.topic !== topic) {
return
}
node3.pubsub.addEventListener(topic, (evt) => {
console.log(`node3 received: ${uint8ArrayToString(evt.detail.data)}`)
})
node3.pubsub.subscribe(topic)
// wait for subscriptions to propagate
await delay(1000)
const validateFruit = (msgTopic, msg) => {
const fruit = uint8ArrayToString(msg.data)
const validFruit = ['banana', 'apple', 'orange']
// car is not a fruit !
if (!validFruit.includes(fruit)) {
throw new Error('no valid fruit received')
}
@@ -69,16 +86,19 @@ const createNode = async () => {
node2.pubsub.topicValidators.set(topic, validateFruit)
node3.pubsub.topicValidators.set(topic, validateFruit)
// node1 publishes "fruits" every five seconds
var count = 0;
const myFruits = ['banana', 'apple', 'car', 'orange'];
// car is not a fruit !
setInterval(() => {
console.log('############## fruit ' + myFruits[count] + ' ##############')
node1.pubsub.dispatchEvent(new CustomEvent<Uint8Array>(topic, { detail: uint8ArrayFromString(myFruits[count]) }))
count++
if (count == myFruits.length) {
count = 0
}
}, 5000)
// node1 publishes "fruits"
for (const fruit of ['banana', 'apple', 'car', 'orange']) {
console.log('############## fruit ' + fruit + ' ##############')
await node1.pubsub.publish(topic, uint8ArrayFromString(fruit))
}
// wait a few seconds for messages to be received
await delay(5000)
console.log('############## all messages sent ##############')
})()
async function delay (ms) {
await new Promise((resolve) => {
setTimeout(() => resolve(), ms)
})
}

View File

@@ -8,23 +8,27 @@ First, let's update our libp2p configuration with a pubsub implementation.
```JavaScript
import { createLibp2p } from 'libp2p'
import { Gossipsub } from 'libp2p-gossipsub'
import { GossipSub } from '@chainsafe/libp2p-gossipsub'
import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
const node = await createLibp2p({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
transports: [
new TCP()
],
streamMuxers: [
new Mplex()
],
connectionEncryption: [
new Noise()
],
pubsub: new Gossipsub()
})
const createNode = async () => {
const node = await createLibp2p({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
transports: [new TCP()],
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
// we add the Pubsub module we want
pubsub: new GossipSub({ allowPublishToZeroPeers: true })
})
await node.start()
return node
}
```
Then, create three nodes and connect them together. In this example, we will connect the nodes in series. Node 1 connected with node 2 and node 2 connected with node 3.
@@ -36,30 +40,45 @@ const [node1, node2, node3] = await Promise.all([
createNode(),
])
await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
await node1.dial(node2.peerId)
await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await node2.peerStore.addressBook.set(node3.peerId, node3.getMultiaddrs())
await node2.dial(node3.peerId)
```
Now we' can subscribe to the fruit topic and log incoming messages.
```JavaScript
import { fromString as uint8ArrayFromString } from "uint8arrays/from-string";
import { toString as uint8ArrayToString } from "uint8arrays/to-string";
const topic = 'fruit'
node1.pubsub.on(topic, (msg) => {
node1.pubsub.addEventListener('message', (msg) => {
if (msg.detail.topic !== topic) {
return
}
console.log(`node1 received: ${uint8ArrayToString(msg.data)}`)
})
await node1.pubsub.subscribe(topic)
node2.pubsub.on(topic, (msg) => {
node2.pubsub.addEventListener('message', (msg) => {
if (msg.detail.topic !== topic) {
return
}
console.log(`node2 received: ${uint8ArrayToString(msg.data)}`)
})
await node2.pubsub.subscribe(topic)
node3.pubsub.on(topic, (msg) => {
console.log(`node3 received: ${uint8ArrayToString(msg.data)}`)
node3.pubsub.addEventListener('message', (msg) => {
if (msg.detail.topic !== topic) {
return
}
console.log(`node3 received: ${uint8ArrayToString(msg.data)}`)
})
await node3.pubsub.subscribe(topic)
```
@@ -83,17 +102,10 @@ node3.pubsub.topicValidators.set(topic, validateFruit)
In this example, node one has an outdated version of the system, or is a malicious node. When it tries to publish fruit, the messages are re-shared and all the nodes share the message. However, when it tries to publish a vehicle the message is not re-shared.
```JavaScript
var count = 0;
const myFruits = ['banana', 'apple', 'car', 'orange'];
setInterval(() => {
console.log('############## fruit ' + myFruits[count] + ' ##############')
node1.pubsub.publish(topic, new TextEncoder().encode(myFruits[count]))
count++
if (count == myFruits.length) {
count = 0
}
}, 5000)
for (const fruit of ['banana', 'apple', 'car', 'orange']) {
console.log('############## fruit ' + fruit + ' ##############')
await node1.pubsub.publish(topic, uint8ArrayFromString(fruit))
}
```
Result
@@ -111,4 +123,4 @@ node3 received: apple
node1 received: orange
node2 received: orange
node3 received: orange
```
```

View File

@@ -1,34 +1,16 @@
import path from 'path'
import execa from 'execa'
import { execa } from 'execa'
import pDefer from 'p-defer'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const stdout = [
{
topic: 'banana',
messageCount: 2
},
{
topic: 'apple',
messageCount: 2
},
{
topic: 'car',
messageCount: 0
},
{
topic: 'orange',
messageCount: 2
},
]
// holds messages received by peers
const messages = {}
export async function test () {
const defer = pDefer()
let topicCount = 0
let topicMessageCount = 0
process.stdout.write('message-filtering/1.js\n')
@@ -38,26 +20,27 @@ export async function test () {
})
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}`)
// End
if (line.includes('all messages sent')) {
if (messages.car > 0) {
defer.reject(new Error('Message validation failed - peers failed to filter car messages'))
}
topicCount++
topicMessageCount = 0
} else {
topicMessageCount++
for (const fruit of ['banana', 'apple', 'orange']) {
if (messages[fruit] !== 2) {
defer.reject(new Error(`Not enough ${fruit} messages - received ${messages[fruit] ?? 0}, expected 2`))
}
}
defer.resolve()
}
if (line.includes('received:')) {
const fruit = line.split('received:')[1].trim()
messages[fruit] = (messages[fruit] ?? 0) + 1
}
})

View File

@@ -1,5 +1,5 @@
import path from 'path'
import execa from 'execa'
import { execa } from 'execa'
import pDefer from 'p-defer'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { fileURLToPath } from 'url'

View File

@@ -3,7 +3,7 @@ process.env.CI = true // needed for some "clever" build tools
import fs from 'fs-extra'
import path from 'path'
import execa from 'execa'
import { execa } from 'execa'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -36,7 +36,8 @@ async function installDeps (dir) {
return
}
const proc = execa.command('npm install', {
const proc = execa('npm', ['install'], {
all: true,
cwd: dir
})
proc.all.on('data', (data) => {
@@ -71,6 +72,7 @@ async function build (dir) {
}
const proc = execa('npm', ['run', build], {
all: true,
cwd: dir
})
proc.all.on('data', (data) => {

View File

@@ -42,13 +42,18 @@ function printAddrs (node, number) {
node2.handle('/print', async ({ stream }) => {
const result = await pipe(
stream,
async function * (source) {
for await (const list of source) {
yield list.subarray()
}
},
toBuffer
)
console.log(uint8ArrayToString(result))
})
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
const stream = await node1.dialProtocol(node2.peerId, '/print')
await pipe(
['Hello', ' ', 'p2p', ' ', 'world', '!'].map(str => uint8ArrayFromString(str)),

View File

@@ -37,13 +37,13 @@ function print ({ stream }) {
stream,
async function (source) {
for await (const msg of source) {
console.log(uint8ArrayToString(msg))
console.log(uint8ArrayToString(msg.subarray()))
}
}
)
}
;(async () => {
(async () => {
const [node1, node2, node3] = await Promise.all([
createNode([new TCP()], '/ip4/0.0.0.0/tcp/0'),
createNode([new TCP(), new WebSockets()], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']),
@@ -63,14 +63,14 @@ function print ({ stream }) {
await node3.peerStore.addressBook.set(node1.peerId, node1.getMultiaddrs())
// node 1 (TCP) dials to node 2 (TCP+WebSockets)
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
const stream = await node1.dialProtocol(node2.peerId, '/print')
await pipe(
[uint8ArrayFromString('node 1 dialed to node 2 successfully')],
stream
)
// node 2 (TCP+WebSockets) dials to node 2 (WebSockets)
const { stream: stream2 } = await node2.dialProtocol(node3.peerId, '/print')
// node 2 (TCP+WebSockets) dials to node 3 (WebSockets)
const stream2 = await node2.dialProtocol(node3.peerId, '/print')
await pipe(
[uint8ArrayFromString('node 2 dialed to node 3 successfully')],
stream2

View File

@@ -57,7 +57,7 @@ function print ({ stream }) {
stream,
async function (source) {
for await (const msg of source) {
console.log(uint8ArrayToString(msg))
console.log(uint8ArrayToString(msg.subarray()))
}
}
)
@@ -78,7 +78,7 @@ function print ({ stream }) {
const targetAddr = node1.getMultiaddrs()[0];
// node 2 (Secure WebSockets) dials to node 1 (Secure Websockets)
const { stream } = await node2.dialProtocol(targetAddr, '/print')
const stream = await node2.dialProtocol(targetAddr, '/print')
await pipe(
[uint8ArrayFromString('node 2 dialed to node 1 successfully')],
stream

View File

@@ -13,7 +13,7 @@ When using libp2p, you need properly configure it, that is, pick your set of mod
You will need 4 dependencies total, so go ahead and install all of them with:
```bash
> npm install libp2p libp2p-tcp @chainsafe/libp2p-noise
> npm install libp2p @libp2p/tcp @chainsafe/libp2p-noise
```
Then, in your favorite text editor create a file with the `.js` extension. I've called mine `1.js`.
@@ -58,7 +58,7 @@ console.log('node has started (true/false):', node.isStarted())
// 0, which means "listen in any network interface and pick
// a port for me
console.log('listening on:')
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
node.getMultiaddrs().forEach((ma) => console.log(ma.toString()))
```
Running this should result in something like:
@@ -80,15 +80,15 @@ Now that we have our `createNode` function, let's create two nodes and make them
For this step, we will need some more dependencies.
```bash
> npm install it-pipe it-to-buffer @libp2p/mplex
> npm install it-pipe it-all @libp2p/mplex
```
And we also need to import the modules on our .js file:
```js
import { pipe } from 'it-pipe'
import toBuffer from 'it-to-buffer'
import { Mplex } from '@libp2p/mplex'
import all from 'it-all'
```
We are going to reuse the `createNode` function from step 1, but this time add a stream multiplexer from `libp2p-mplex`.
@@ -114,38 +114,39 @@ We will also make things simpler by creating another function to print the multi
```JavaScript
function printAddrs (node, number) {
console.log('node %s is listening on:', number)
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
node.getMultiaddrs().forEach((ma) => console.log(ma.toString()))
}
```
Then add,
```js
;(async () => {
const [node1, node2] = await Promise.all([
createNode(),
createNode()
])
import { fromString as uint8ArrayFromString } from "uint8arrays/from-string";
import { toString as uint8ArrayToString } from "uint8arrays/to-string";
printAddrs(node1, '1')
printAddrs(node2, '2')
const [node1, node2] = await Promise.all([
createNode(),
createNode()
])
node2.handle('/print', async ({ stream }) => {
const result = await pipe(
stream,
toBuffer
)
console.log(result.toString())
})
printAddrs(node1, '1')
printAddrs(node2, '2')
await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
await pipe(
['Hello', ' ', 'p2p', ' ', 'world', '!'],
stream
node2.handle('/print', async ({ stream }) => {
const result = await pipe(
stream,
all
)
})();
console.log(result.map(buf => uint8ArrayToString(buf.subarray())).join(""))
})
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
const stream = await node1.dialProtocol(node2.peerId, '/print')
await pipe(
['Hello', ' ', 'p2p', ' ', 'world', '!'].map(str => uint8ArrayFromString(str)),
stream
)
```
For more information refer to the [docs](https://github.com/libp2p/js-libp2p/blob/master/doc/API.md).
@@ -168,10 +169,10 @@ Next, we want nodes to have multiple transports available to increase their chan
What we are going to do in this step is to create 3 nodes: one with TCP, another with TCP+WebSockets and another one with just WebSockets. The full solution can be found on [3.js](./3.js).
In this example, we will need to also install `libp2p-websockets`:
In this example, we will need to also install `@libp2p/websockets`:
```bash
> npm install libp2p-websockets
> npm install @libp2p/websockets
```
We want to create 3 nodes: one with TCP, one with TCP+WebSockets and one with just WebSockets. We need to update our `createNode` function to accept WebSocket connections as well. Moreover, let's upgrade our function to enable us to pick the addresses over which a node will start a listener:
@@ -188,7 +189,7 @@ const createNode = async (transports, addresses = []) => {
addresses: {
listen: addresses
},
transport: transports,
transports: transports,
connectionEncryption: [new Noise()],
streamMuxers: [new Mplex()]
})
@@ -207,9 +208,9 @@ import { WebSockets } from '@libp2p/websockets'
import { TCP } from '@libp2p/tcp'
const [node1, node2, node3] = await Promise.all([
createNode([TCP], '/ip4/0.0.0.0/tcp/0'),
createNode([TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']),
createNode([WebSockets], '/ip4/127.0.0.1/tcp/20000/ws')
createNode([new TCP()], '/ip4/0.0.0.0/tcp/0'),
createNode([new TCP(), new WebSockets()], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']),
createNode([new WebSockets()], '/ip4/127.0.0.1/tcp/20000/ws')
])
printAddrs(node1, '1')
@@ -220,21 +221,21 @@ node1.handle('/print', print)
node2.handle('/print', print)
node3.handle('/print', print)
await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs)
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
await node2.peerStore.addressBook.set(node3.peerId, node3.getMultiaddrs())
await node3.peerStore.addressBook.set(node1.peerId, node1.getMultiaddrs())
// node 1 (TCP) dials to node 2 (TCP+WebSockets)
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
const stream = await node1.dialProtocol(node2.peerId, '/print')
await pipe(
['node 1 dialed to node 2 successfully'],
['node 1 dialed to node 2 successfully'].map(str => uint8ArrayFromString(str)),
stream
)
// node 2 (TCP+WebSockets) dials to node 2 (WebSockets)
const { stream: stream2 } = await node2.dialProtocol(node3.peerId, '/print')
// node 2 (TCP+WebSockets) dials to node 3 (WebSockets)
const stream2 = await node2.dialProtocol(node3.peerId, '/print')
await pipe(
['node 2 dialed to node 3 successfully'],
['node 2 dialed to node 3 successfully'].map(str => uint8ArrayFromString(str)),
stream2
)
@@ -254,7 +255,7 @@ function print ({ stream }) {
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
console.log(uint8ArrayToString(msg.subarray()))
}
}
)

View File

@@ -1,4 +1,4 @@
import execa from 'execa'
import { execa } from 'execa'
import fs from 'fs-extra'
import which from 'which'
@@ -26,7 +26,10 @@ export async function waitForOutput (expectedOutput, command, args = [], opts =
command = 'node'
}
const proc = execa(command, args, opts)
const proc = execa(command, args, {
...opts,
all: true
})
let output = ''
let time = 600000

View File

@@ -1,6 +1,6 @@
### Webrtc-direct example
An example that uses [js-libp2p-webrtc-direct](https://github.com/libp2p/js-libp2p-webrtc-direct) for connecting
An example that uses [@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

View File

@@ -1,5 +1,5 @@
import { createLibp2p } from 'libp2p'
import { WebRTCDirect } from '@achingbrain/webrtc-direct'
import { WebRTCDirect } from '@libp2p/webrtc-direct'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
import { Bootstrap } from '@libp2p/bootstrap'

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js-libp2p parcel.js browser example</title>
<title>js-libp2p vite browser example</title>
</head>
<body>

View File

@@ -1,5 +1,5 @@
import { createLibp2p } from 'libp2p'
import { WebRTCDirect } from '@achingbrain/webrtc-direct'
import { WebRTCDirect } from '@libp2p/webrtc-direct'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
import { createFromJSON } from '@libp2p/peer-id-factory'

View File

@@ -3,19 +3,16 @@
"version": "0.0.1",
"private": true,
"type": "module",
"browserslist": [
"last 2 Chrome versions"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "vite"
},
"license": "ISC",
"dependencies": {
"@libp2p/webrtc-direct": "^1.0.0",
"@chainsafe/libp2p-noise": "^6.0.1",
"@libp2p/bootstrap": "^1.0.1",
"@libp2p/mplex": "^1.0.2",
"@libp2p/webrtc-direct": "^2.0.0",
"@chainsafe/libp2p-noise": "^8.0.1",
"@libp2p/bootstrap": "^2.0.1",
"@libp2p/mplex": "^5.2.3",
"libp2p": "../../",
"wrtc": "^0.4.7"
},

View File

@@ -1,5 +1,5 @@
import path from 'path'
import execa from 'execa'
import { execa } from 'execa'
import pDefer from 'p-defer'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { chromium } from 'playwright'
@@ -60,8 +60,8 @@ export async function test () {
selector => {
const text = document.querySelector(selector).innerText
return text.includes('libp2p id is') &&
text.includes('Found peer') &&
text.includes('Connected to')
text.includes('Found peer 12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m') &&
text.includes('Connected to 12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m')
},
'#output',
{ timeout: 10000 }

View File

@@ -0,0 +1,5 @@
export default {
build: {
target: 'es2020'
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "libp2p",
"version": "0.36.2",
"version": "0.39.3",
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
"license": "Apache-2.0 OR MIT",
"homepage": "https://github.com/libp2p/js-libp2p#readme",
@@ -49,18 +49,19 @@
],
"exports": {
".": {
"types": "./src/index.d.ts",
"import": "./dist/src/index.js"
},
"./insecure": {
"types": "./dist/src/insecure/index.d.ts",
"import": "./dist/src/insecure/index.js"
},
"./pnet": {
"types": "./dist/src/pnet/index.d.ts",
"import": "./dist/src/pnet/index.js"
},
"./pnet/generate": {
"import": "./dist/src/pnet/key-generator.js"
},
"./transport-manager": {
"types": "./dist/src/transport-manager.d.ts",
"import": "./dist/src/transport-manager.js"
}
},
@@ -76,126 +77,135 @@
]
},
"scripts": {
"prepare": "npm run build",
"clean": "aegir clean",
"lint": "aegir lint",
"build": "tsc",
"postbuild": "mkdirp dist/src/circuit/pb dist/src/fetch/pb dist/src/identify/pb dist/src/insecure/pb && cp src/circuit/pb/*.js src/circuit/pb/*.d.ts dist/src/circuit/pb && cp src/fetch/pb/*.js src/fetch/pb/*.d.ts dist/src/fetch/pb && cp src/identify/pb/*.js src/identify/pb/*.d.ts dist/src/identify/pb && cp src/insecure/pb/*.js src/insecure/pb/*.d.ts dist/src/insecure/pb",
"generate": "run-s generate:proto:* generate:proto-types:*",
"generate:proto:circuit": "pbjs -t static-module -w es6 -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",
"generate:proto:fetch": "pbjs -t static-module -w es6 -r libp2p-fetch --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/fetch/proto.js ./src/fetch/proto.proto",
"generate:proto:identify": "pbjs -t static-module -w es6 -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",
"generate:proto:plaintext": "pbjs -t static-module -w es6 -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",
"generate:proto-types:circuit": "pbts -o src/circuit/protocol/index.d.ts src/circuit/protocol/index.js",
"generate:proto-types:fetch": "pbts -o src/fetch/proto.d.ts src/fetch/proto.js",
"generate:proto-types:identify": "pbts -o src/identify/message.d.ts src/identify/message.js",
"generate:proto-types:plaintext": "pbts -o src/insecure/proto.d.ts src/insecure/proto.js",
"pretest": "npm run build",
"dep-check": "aegir dep-check",
"prepublishOnly": "node scripts/update-version.js",
"build": "aegir build",
"generate": "run-s generate:proto:*",
"generate:proto:circuit": "protons ./src/circuit/pb/index.proto",
"generate:proto:fetch": "protons ./src/fetch/pb/proto.proto",
"generate:proto:identify": "protons ./src/identify/pb/message.proto",
"generate:proto:plaintext": "protons ./src/insecure/pb/proto.proto",
"test": "aegir test",
"test:node": "npm run test -- -t node -f \"./dist/test/**/*.{node,spec}.js\" --cov",
"test:chrome": "npm run test -- -t browser -f \"./dist/test/**/*.spec.js\" --cov",
"test:chrome-webworker": "npm run test -- -t webworker -f \"./dist/test/**/*.spec.js\"",
"test:firefox": "npm run test -- -t browser -f \"./dist/test/**/*.spec.js\" -- --browser firefox",
"test:firefox-webworker": "npm run test -- -t webworker -f \"./dist/test/**/*.spec.js\" -- --browser firefox",
"test:node": "aegir test -t node -f \"./dist/test/**/*.{node,spec}.js\" --cov",
"test:chrome": "aegir test -t browser -f \"./dist/test/**/*.spec.js\" --cov",
"test:chrome-webworker": "aegir test -t webworker -f \"./dist/test/**/*.spec.js\"",
"test:firefox": "aegir test -t browser -f \"./dist/test/**/*.spec.js\" -- --browser firefox",
"test:firefox-webworker": "aegir test -t webworker -f \"./dist/test/**/*.spec.js\" -- --browser firefox",
"test:examples": "cd examples && npm run test:all",
"test:interop": "npm run test -- -t node -f dist/test/interop.js"
"test:interop": "aegir test -t node -f dist/test/interop.js"
},
"dependencies": {
"@achingbrain/nat-port-mapper": "^1.0.0",
"@libp2p/connection": "^1.1.4",
"@libp2p/crypto": "^0.22.9",
"@libp2p/interfaces": "^1.3.17",
"@libp2p/multistream-select": "^1.0.3",
"@libp2p/peer-id": "^1.1.8",
"@libp2p/peer-id-factory": "^1.0.8",
"@libp2p/peer-store": "^1.0.6",
"@libp2p/utils": "^1.0.9",
"@achingbrain/nat-port-mapper": "^1.0.3",
"@libp2p/components": "^2.0.3",
"@libp2p/connection": "^4.0.1",
"@libp2p/crypto": "^1.0.3",
"@libp2p/interface-address-manager": "^1.0.2",
"@libp2p/interface-connection": "^3.0.1",
"@libp2p/interface-connection-encrypter": "^2.0.1",
"@libp2p/interface-connection-manager": "^1.1.1",
"@libp2p/interface-content-routing": "^1.0.2",
"@libp2p/interface-dht": "^1.0.1",
"@libp2p/interface-metrics": "^3.0.0",
"@libp2p/interface-peer-discovery": "^1.0.1",
"@libp2p/interface-peer-id": "^1.0.4",
"@libp2p/interface-peer-info": "^1.0.3",
"@libp2p/interface-peer-routing": "^1.0.1",
"@libp2p/interface-peer-store": "^1.2.2",
"@libp2p/interface-pubsub": "^2.1.0",
"@libp2p/interface-registrar": "^2.0.3",
"@libp2p/interface-stream-muxer": "^2.0.2",
"@libp2p/interface-transport": "^1.0.4",
"@libp2p/interfaces": "^3.0.3",
"@libp2p/logger": "^2.0.1",
"@libp2p/multistream-select": "^3.0.0",
"@libp2p/peer-collections": "^2.0.0",
"@libp2p/peer-id": "^1.1.15",
"@libp2p/peer-id-factory": "^1.0.18",
"@libp2p/peer-record": "^4.0.3",
"@libp2p/peer-store": "^3.1.5",
"@libp2p/tracked-map": "^2.0.1",
"@libp2p/utils": "^3.0.2",
"@multiformats/mafmt": "^11.0.2",
"@multiformats/multiaddr": "^10.1.8",
"@multiformats/multiaddr": "^11.0.0",
"abortable-iterator": "^4.0.2",
"aggregate-error": "^4.0.0",
"any-signal": "^3.0.0",
"bignumber.js": "^9.0.1",
"class-is": "^1.1.0",
"datastore-core": "^7.0.0",
"debug": "^4.3.3",
"datastore-core": "^8.0.1",
"err-code": "^3.0.1",
"events": "^3.3.0",
"hashlru": "^2.3.0",
"interface-datastore": "^6.1.0",
"interface-datastore": "^7.0.0",
"it-all": "^1.0.6",
"it-drain": "^1.0.5",
"it-filter": "^1.0.3",
"it-first": "^1.0.6",
"it-foreach": "^0.1.1",
"it-handshake": "^3.0.1",
"it-length-prefixed": "^7.0.1",
"it-handshake": "^4.1.2",
"it-length-prefixed": "^8.0.2",
"it-map": "^1.0.6",
"it-merge": "^1.0.3",
"it-pair": "^2.0.2",
"it-pipe": "^2.0.3",
"it-sort": "^1.0.1",
"it-stream-types": "^1.0.4",
"it-take": "^1.0.2",
"it-to-buffer": "^2.0.2",
"merge-options": "^3.0.4",
"mortice": "^3.0.0",
"multiformats": "^9.6.3",
"mutable-proxy": "^1.0.0",
"node-forge": "^1.2.1",
"node-forge": "^1.3.1",
"p-fifo": "^1.0.0",
"p-retry": "^5.0.0",
"p-settle": "^5.0.0",
"private-ip": "^2.3.3",
"protobufjs": "^6.11.2",
"protons-runtime": "^3.0.1",
"retimer": "^3.0.0",
"sanitize-filename": "^1.6.3",
"set-delayed-interval": "^1.0.0",
"streaming-iterables": "^6.0.0",
"timeout-abort-controller": "^3.0.0",
"uint8arraylist": "^2.3.2",
"uint8arrays": "^3.0.0",
"varint": "^6.0.0",
"wherearewe": "^1.0.0",
"wherearewe": "^2.0.0",
"xsalsa20": "^1.1.0"
},
"devDependencies": {
"@achingbrain/libp2p-gossipsub": "^0.13.5",
"@chainsafe/libp2p-noise": "^6.0.1",
"@libp2p/bootstrap": "^1.0.2",
"@libp2p/daemon-client": "^1.0.0",
"@libp2p/daemon-server": "^1.0.0",
"@libp2p/delegated-content-routing": "^1.0.2",
"@libp2p/delegated-peer-routing": "^1.0.2",
"@libp2p/floodsub": "^1.0.2",
"@libp2p/interface-compliance-tests": "^1.1.20",
"@libp2p/interop": "^1.0.0",
"@libp2p/kad-dht": "^1.0.3",
"@libp2p/mdns": "^1.0.3",
"@libp2p/mplex": "^1.0.1",
"@libp2p/tcp": "^1.0.6",
"@libp2p/tracked-map": "^1.0.4",
"@libp2p/webrtc-star": "^1.0.3",
"@libp2p/websockets": "^1.0.3",
"@nodeutils/defaults-deep": "^1.1.0",
"@types/node": "^16.11.26",
"@chainsafe/libp2p-noise": "^8.0.1",
"@chainsafe/libp2p-yamux": "^1.0.0",
"@libp2p/bootstrap": "^3.0.0",
"@libp2p/daemon-client": "^3.0.1",
"@libp2p/daemon-server": "^3.0.1",
"@libp2p/floodsub": "^3.0.0",
"@libp2p/interface-compliance-tests": "^3.0.2",
"@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.2",
"@libp2p/interface-mocks": "^4.0.3",
"@libp2p/interop": "^3.0.1",
"@libp2p/kad-dht": "^3.0.5",
"@libp2p/mdns": "^3.0.1",
"@libp2p/mplex": "^5.2.3",
"@libp2p/pubsub": "^3.1.3",
"@libp2p/tcp": "^3.1.1",
"@libp2p/topology": "^3.0.1",
"@libp2p/webrtc-star": "^3.0.3",
"@libp2p/websockets": "^3.0.4",
"@types/node-forge": "^1.0.0",
"@types/p-fifo": "^1.0.0",
"@types/varint": "^6.0.0",
"@types/xsalsa20": "^1.1.0",
"aegir": "^36.1.3",
"buffer": "^6.0.3",
"aegir": "^37.3.0",
"cborg": "^1.8.1",
"delay": "^5.0.0",
"execa": "^6.1.0",
"go-libp2p": "^0.0.6",
"into-stream": "^7.0.0",
"ipfs-http-client": "^56.0.1",
"it-pair": "^2.0.2",
"it-pushable": "^2.0.1",
"nock": "^13.0.3",
"it-pushable": "^3.0.0",
"it-to-buffer": "^2.0.2",
"npm-run-all": "^4.1.5",
"p-defer": "^4.0.0",
"p-event": "^5.0.1",
"p-times": "^4.0.0",
"p-wait-for": "^4.1.0",
"p-wait-for": "^5.0.0",
"protons": "^5.0.0",
"rimraf": "^3.0.2",
"sinon": "^13.0.1",
"sinon": "^14.0.0",
"ts-sinon": "^2.0.2"
},
"browser": {

14
scripts/update-version.js Normal file
View File

@@ -0,0 +1,14 @@
import { readFile, writeFile } from 'fs/promises'
const pkg = JSON.parse(
await readFile(
new URL('../package.json', import.meta.url)
)
)
await writeFile(
new URL('../src/version.ts', import.meta.url),
`export const version = '${pkg.version}'
export const name = '${pkg.name}'
`
)

View File

@@ -1,7 +1,9 @@
import { AddressManagerEvents, CustomEvent, EventEmitter } from '@libp2p/interfaces'
import { Multiaddr } from '@multiformats/multiaddr'
import type { AddressManagerEvents } from '@libp2p/interface-address-manager'
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
import type { Multiaddr } from '@multiformats/multiaddr'
import { multiaddr } from '@multiformats/multiaddr'
import { peerIdFromString } from '@libp2p/peer-id'
import type { Components } from '@libp2p/interfaces/components'
import type { Components } from '@libp2p/components'
export interface AddressManagerInit {
announceFilter?: AddressFilter
@@ -57,28 +59,28 @@ export class DefaultAddressManager extends EventEmitter<AddressManagerEvents> {
* Get peer listen multiaddrs
*/
getListenAddrs (): Multiaddr[] {
return Array.from(this.listen).map((a) => new Multiaddr(a))
return Array.from(this.listen).map((a) => multiaddr(a))
}
/**
* Get peer announcing multiaddrs
*/
getAnnounceAddrs (): Multiaddr[] {
return Array.from(this.announce).map((a) => new Multiaddr(a))
return Array.from(this.announce).map((a) => multiaddr(a))
}
/**
* Get observed multiaddrs
*/
getObservedAddrs (): Multiaddr[] {
return Array.from(this.observed).map((a) => new Multiaddr(a))
return Array.from(this.observed).map((a) => multiaddr(a))
}
/**
* Add peer observed addresses
*/
addObservedAddr (addr: string | Multiaddr): void {
let ma = new Multiaddr(addr)
let ma = multiaddr(addr)
const remotePeer = ma.getPeerId()
// strip our peer id if it has been passed
@@ -87,7 +89,7 @@ export class DefaultAddressManager extends EventEmitter<AddressManagerEvents> {
// use same encoding for comparison
if (remotePeerId.equals(this.components.getPeerId())) {
ma = ma.decapsulate(new Multiaddr(`/p2p/${this.components.getPeerId().toString()}`))
ma = ma.decapsulate(multiaddr(`/p2p/${this.components.getPeerId().toString()}`))
}
}
@@ -117,7 +119,7 @@ export class DefaultAddressManager extends EventEmitter<AddressManagerEvents> {
// Create advertising list
return this.announceFilter(Array.from(addrSet)
.map(str => new Multiaddr(str)))
.map(str => multiaddr(str)))
.map(ma => {
if (ma.getPeerId() === this.components.getPeerId().toString()) {
return ma

View File

@@ -37,17 +37,17 @@ 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
import { Multiaddr } from '@multiformats/multiaddr'
import { multiaddr } from '@multiformats/multiaddr'
import Libp2p from 'libp2p'
import { TCP } from '@libp2p/tcp'
import { Mplex } from '@libp2p/mplex'
import { NOISE } from '@chainsafe/libp2p-noise'
import { Noise } from '@chainsafe/libp2p-noise'
const relayAddr = ...
const node = await createLibp2p({
addresses: {
listen: [new Multiaddr(`${relayAddr}/p2p-circuit`)]
listen: [multiaddr(`${relayAddr}/p2p-circuit`)]
},
transports: [
new TCP()
@@ -56,7 +56,7 @@ const node = await createLibp2p({
new Mplex()
],
connectionEncryption: [
NOISE
new Noise()
]
},
config: {

View File

@@ -10,10 +10,10 @@ import {
HOP_METADATA_VALUE,
RELAY_RENDEZVOUS_NS
} from './constants.js'
import type { PeerId } from '@libp2p/interfaces/peer-id'
import type { AddressSorter, PeerProtocolsChangeData } from '@libp2p/interfaces/peer-store'
import type { Connection } from '@libp2p/interfaces/connection'
import type { Components } from '@libp2p/interfaces/components'
import type { PeerId } from '@libp2p/interface-peer-id'
import type { AddressSorter, PeerProtocolsChangeData } from '@libp2p/interface-peer-store'
import type { Connection } from '@libp2p/interface-connection'
import type { Components } from '@libp2p/components'
import sort from 'it-sort'
import all from 'it-all'
import { pipe } from 'it-pipe'
@@ -85,12 +85,14 @@ export class AutoRelay {
// If protocol, check if can hop, store info in the metadataBook and listen on it
try {
const connection = this.components.getConnectionManager().getConnection(peerId)
const connections = this.components.getConnectionManager().getConnections(peerId)
if (connection == null) {
if (connections.length === 0) {
return
}
const connection = connections[0]
// Do not hop on a relayed connection
if (connection.remoteAddr.protoCodes().includes(CIRCUIT_PROTO_CODE)) {
log(`relayed connection to ${id} will not be used to hop on`)
@@ -223,15 +225,15 @@ export class AutoRelay {
continue
}
const connection = this.components.getConnectionManager().getConnection(id)
const connections = this.components.getConnectionManager().getConnections(id)
// If not connected, store for possible later use.
if (connection == null) {
if (connections.length === 0) {
knownHopsToDial.push(id)
continue
}
await this._addListenRelay(connection, idStr)
await this._addListenRelay(connections[0], idStr)
// Check if already listening on enough relays
if (this.listenRelays.size >= this.maxListeners) {
@@ -258,6 +260,12 @@ export class AutoRelay {
}
const peerId = provider.id
if (peerId.equals(this.components.getPeerId())) {
// Skip the provider if it's us as dialing will fail
continue
}
await this.components.getPeerStore().addressBook.add(peerId, provider.multiaddrs)
await this._tryToListenOnRelay(peerId)
@@ -274,7 +282,7 @@ export class AutoRelay {
async _tryToListenOnRelay (peerId: PeerId) {
try {
const connection = await this.components.getDialer().dial(peerId)
const connection = await this.components.getConnectionManager().openConnection(peerId)
await this._addListenRelay(connection, peerId.toString())
} catch (err: any) {
log.error('Could not use %p as relay', peerId, err)

View File

@@ -2,28 +2,30 @@ import { logger } from '@libp2p/logger'
import errCode from 'err-code'
import { validateAddrs } from './utils.js'
import { StreamHandler } from './stream-handler.js'
import { CircuitRelay as CircuitPB, ICircuitRelay } from '../pb/index.js'
import { CircuitRelay as CircuitPB } from '../pb/index.js'
import { pipe } from 'it-pipe'
import { codes as Errors } from '../../errors.js'
import { stop } from './stop.js'
import { RELAY_CODEC } from '../multicodec.js'
import type { Connection } from '@libp2p/interfaces/connection'
import type { Connection } from '@libp2p/interface-connection'
import { peerIdFromBytes } from '@libp2p/peer-id'
import type { Duplex } from 'it-stream-types'
import type { Circuit } from '../transport.js'
import type { ConnectionManager } from '@libp2p/interfaces/registrar'
import type { ConnectionManager } from '@libp2p/interface-connection-manager'
import type { AbortOptions } from '@libp2p/interfaces'
import type { Uint8ArrayList } from 'uint8arraylist'
const log = logger('libp2p:circuit:hop')
export interface HopRequest {
connection: Connection
request: ICircuitRelay
request: CircuitPB
streamHandler: StreamHandler
circuit: Circuit
connectionManager: ConnectionManager
}
export async function handleHop (hopRequest: HopRequest) {
export async function handleHop (hopRequest: HopRequest): Promise<void> {
const {
connection,
request,
@@ -58,8 +60,8 @@ export async function handleHop (hopRequest: HopRequest) {
// Get the connection to the destination (stop) peer
const destinationPeer = peerIdFromBytes(request.dstPeer.id)
const destinationConnection = connectionManager.getConnection(destinationPeer)
if (destinationConnection == null && !circuit.hopActive()) {
const destinationConnections = connectionManager.getConnections(destinationPeer)
if (destinationConnections.length === 0 && !circuit.hopActive()) {
log('HOP request received but we are not connected to the destination peer')
return streamHandler.end({
type: CircuitPB.Type.STATUS,
@@ -68,7 +70,7 @@ export async function handleHop (hopRequest: HopRequest) {
}
// TODO: Handle being an active relay
if (destinationConnection == null) {
if (destinationConnections.length === 0) {
log('did not have connection to remote peer')
return streamHandler.end({
type: CircuitPB.Type.STATUS,
@@ -83,11 +85,11 @@ export async function handleHop (hopRequest: HopRequest) {
srcPeer: request.srcPeer
}
let destinationStream: Duplex<Uint8Array>
let destinationStream: Duplex<Uint8ArrayList>
try {
log('performing STOP request')
const result = await stop({
connection: destinationConnection,
connection: destinationConnections[0],
request: stopRequest
})
@@ -118,23 +120,26 @@ export async function handleHop (hopRequest: HopRequest) {
)
}
export interface HopConfig {
export interface HopConfig extends AbortOptions {
connection: Connection
request: ICircuitRelay
request: CircuitPB
}
/**
* Performs a HOP request to a relay peer, to request a connection to another
* peer. A new, virtual, connection will be created between the two via the relay.
*/
export async function hop (options: HopConfig): Promise<Duplex<Uint8Array>> {
export async function hop (options: HopConfig): Promise<Duplex<Uint8ArrayList, Uint8ArrayList | Uint8Array>> {
const {
connection,
request
request,
signal
} = options
// Create a new stream to the relay
const { stream } = await connection.newStream(RELAY_CODEC)
const stream = await connection.newStream(RELAY_CODEC, {
signal
})
// Send the HOP request
const streamHandler = new StreamHandler({ stream })
streamHandler.write(request)
@@ -147,16 +152,17 @@ export async function hop (options: HopConfig): Promise<Duplex<Uint8Array>> {
if (response.code === CircuitPB.Status.SUCCESS) {
log('hop request was successful')
return streamHandler.rest()
}
log('hop request failed with code %d, closing stream', response.code)
streamHandler.close()
throw errCode(new Error(`HOP request failed with code ${response.code}`), Errors.ERR_HOP_REQUEST_FAILED)
throw errCode(new Error(`HOP request failed with code "${response.code ?? 'unknown'}"`), Errors.ERR_HOP_REQUEST_FAILED)
}
export interface CanHopOptions {
export interface CanHopOptions extends AbortOptions {
connection: Connection
}
@@ -165,11 +171,14 @@ export interface CanHopOptions {
*/
export async function canHop (options: CanHopOptions) {
const {
connection
connection,
signal
} = options
// Create a new stream to the relay
const { stream } = await connection.newStream(RELAY_CODEC)
const stream = await connection.newStream(RELAY_CODEC, {
signal
})
// Send the HOP request
const streamHandler = new StreamHandler({ stream })

View File

@@ -1,23 +1,25 @@
import { logger } from '@libp2p/logger'
import { CircuitRelay as CircuitPB, ICircuitRelay } from '../pb/index.js'
import { CircuitRelay as CircuitPB } from '../pb/index.js'
import { RELAY_CODEC } from '../multicodec.js'
import { StreamHandler } from './stream-handler.js'
import { validateAddrs } from './utils.js'
import type { Connection } from '@libp2p/interfaces/connection'
import type { Connection } from '@libp2p/interface-connection'
import type { Duplex } from 'it-stream-types'
import type { AbortOptions } from '@libp2p/interfaces'
import type { Uint8ArrayList } from 'uint8arraylist'
const log = logger('libp2p:circuit:stop')
export interface HandleStopOptions {
connection: Connection
request: ICircuitRelay
request: CircuitPB
streamHandler: StreamHandler
}
/**
* Handles incoming STOP requests
*/
export function handleStop (options: HandleStopOptions): Duplex<Uint8Array> | undefined {
export function handleStop (options: HandleStopOptions): Duplex<Uint8ArrayList, Uint8ArrayList | Uint8Array> | undefined {
const {
connection,
request,
@@ -42,9 +44,9 @@ export function handleStop (options: HandleStopOptions): Duplex<Uint8Array> | un
return streamHandler.rest()
}
export interface StopOptions {
export interface StopOptions extends AbortOptions {
connection: Connection
request: ICircuitRelay
request: CircuitPB
}
/**
@@ -53,10 +55,13 @@ export interface StopOptions {
export async function stop (options: StopOptions) {
const {
connection,
request
request,
signal
} = options
const { stream } = await connection.newStream([RELAY_CODEC])
const stream = await connection.newStream(RELAY_CODEC, {
signal
})
log('starting stop request to %p', connection.remotePeer)
const streamHandler = new StreamHandler({ stream })

View File

@@ -1,9 +1,10 @@
import { logger } from '@libp2p/logger'
import * as lp from 'it-length-prefixed'
import { Handshake, handshake } from 'it-handshake'
import { CircuitRelay, ICircuitRelay } from '../pb/index.js'
import type { Stream } from '@libp2p/interfaces/connection'
import { CircuitRelay } from '../pb/index.js'
import type { Stream } from '@libp2p/interface-connection'
import type { Source } from 'it-stream-types'
import type { Uint8ArrayList } from 'uint8arraylist'
const log = logger('libp2p:circuit:stream-handler')
@@ -21,8 +22,8 @@ export interface StreamHandlerOptions {
export class StreamHandler {
private readonly stream: Stream
private readonly shake: Handshake
private readonly decoder: Source<Uint8Array>
private readonly shake: Handshake<Uint8ArrayList | Uint8Array>
private readonly decoder: Source<Uint8ArrayList>
constructor (options: StreamHandlerOptions) {
const { stream, maxLength = 4096 } = options
@@ -40,7 +41,7 @@ export class StreamHandler {
const msg = await this.decoder.next()
if (msg.value != null) {
const value = CircuitRelay.decode(msg.value.slice())
const value = CircuitRelay.decode(msg.value)
log('read message type', value.type)
return value
}
@@ -53,10 +54,9 @@ export class StreamHandler {
/**
* Encode and write array of buffers
*/
write (msg: ICircuitRelay) {
write (msg: CircuitRelay) {
log('write message type %s', msg.type)
// @ts-expect-error lp.encode expects type type 'Buffer | BufferList', not 'Uint8Array'
this.shake.write(lp.encode.single(CircuitRelay.encode(msg).finish()))
this.shake.write(lp.encode.single(CircuitRelay.encode(msg)))
}
/**
@@ -68,9 +68,9 @@ export class StreamHandler {
}
/**
* @param {ICircuitRelay} msg - An unencoded CircuitRelay protobuf message
* @param {CircuitRelay} msg - An unencoded CircuitRelay protobuf message
*/
end (msg: ICircuitRelay) {
end (msg: CircuitRelay) {
this.write(msg)
this.close()
}

View File

@@ -1,5 +1,5 @@
import { Multiaddr } from '@multiformats/multiaddr'
import { CircuitRelay, ICircuitRelay } from '../pb/index.js'
import { multiaddr } from '@multiformats/multiaddr'
import { CircuitRelay } from '../pb/index.js'
import type { StreamHandler } from './stream-handler.js'
/**
@@ -15,11 +15,11 @@ function writeResponse (streamHandler: StreamHandler, status: CircuitRelay.Statu
/**
* Validate incomming HOP/STOP message
*/
export function validateAddrs (msg: ICircuitRelay, streamHandler: StreamHandler) {
export function validateAddrs (msg: CircuitRelay, streamHandler: StreamHandler) {
try {
if (msg.dstPeer?.addrs != null) {
msg.dstPeer.addrs.forEach((addr) => {
return new Multiaddr(addr)
return multiaddr(addr)
})
}
} catch (err: any) {
@@ -32,7 +32,7 @@ export function validateAddrs (msg: ICircuitRelay, streamHandler: StreamHandler)
try {
if (msg.srcPeer?.addrs != null) {
msg.srcPeer.addrs.forEach((addr) => {
return new Multiaddr(addr)
return multiaddr(addr)
})
}
} catch (err: any) {

View File

@@ -10,9 +10,10 @@ import { namespaceToCid } from './utils.js'
import {
RELAY_RENDEZVOUS_NS
} from './constants.js'
import type { AddressSorter } from '@libp2p/interfaces/peer-store'
import type { Startable } from '@libp2p/interfaces'
import type { Components } from '@libp2p/interfaces/components'
import type { AddressSorter } from '@libp2p/interface-peer-store'
import type { Startable } from '@libp2p/interfaces/startable'
import type { Components } from '@libp2p/components'
import type { RelayConfig } from '../index.js'
const log = logger('libp2p:relay')
@@ -22,11 +23,6 @@ export interface RelayAdvertiseConfig {
ttl?: number
}
export interface HopConfig {
enabled?: boolean
active?: boolean
}
export interface AutoRelayConfig {
enabled?: boolean
@@ -36,13 +32,8 @@ export interface AutoRelayConfig {
maxListeners: number
}
export interface RelayInit {
export interface RelayInit extends RelayConfig {
addressSorter?: AddressSorter
maxListeners?: number
onError?: (error: Error, msg?: string) => void
hop: HopConfig
advertise: RelayAdvertiseConfig
autoRelay: AutoRelayConfig
}
export class Relay implements Startable {

View File

@@ -1,11 +1,13 @@
import { CustomEvent, EventEmitter } from '@libp2p/interfaces'
import type { ConnectionManager } from '@libp2p/interfaces/registrar'
import type { Dialer } from '@libp2p/interfaces/dialer'
import type { Listener } from '@libp2p/interfaces/transport'
import { Multiaddr } from '@multiformats/multiaddr'
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
import type { ConnectionManager } from '@libp2p/interface-connection-manager'
import type { PeerStore } from '@libp2p/interface-peer-store'
import type { Listener } from '@libp2p/interface-transport'
import { peerIdFromString } from '@libp2p/peer-id'
import type { Multiaddr } from '@multiformats/multiaddr'
import { multiaddr } from '@multiformats/multiaddr'
export interface ListenerOptions {
dialer: Dialer
peerStore: PeerStore
connectionManager: ConnectionManager
}
@@ -17,7 +19,19 @@ export function createListener (options: ListenerOptions): Listener {
*/
async function listen (addr: Multiaddr): Promise<void> {
const addrString = addr.toString().split('/p2p-circuit').find(a => a !== '')
const relayConn = await options.dialer.dial(new Multiaddr(addrString))
const ma = multiaddr(addrString)
const relayPeerStr = ma.getPeerId()
if (relayPeerStr == null) {
throw new Error('Could not determine relay peer from multiaddr')
}
const relayPeerId = peerIdFromString(relayPeerStr)
await options.peerStore.addressBook.add(relayPeerId, [ma])
const relayConn = await options.connectionManager.openConnection(relayPeerId)
const relayedAddr = relayConn.remoteAddr.encapsulate('/p2p-circuit')
listeningAddrs.set(relayConn.remotePeer.toString(), relayedAddr)

View File

@@ -1,173 +0,0 @@
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,528 +0,0 @@
/*eslint-disable*/
import $protobuf from "protobufjs/minimal.js";
// Common aliases
const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
// Exported root namespace
const $root = $protobuf.roots["libp2p-circuit"] || ($protobuf.roots["libp2p-circuit"] = {});
export const CircuitRelay = $root.CircuitRelay = (() => {
/**
* 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
*/
/**
* 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]];
}
/**
* 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() {
const 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() {
const 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;
})();
export { $root as default };

View File

@@ -1,4 +1,4 @@
syntax = "proto2";
syntax = "proto3";
message CircuitRelay {

235
src/circuit/pb/index.ts Normal file
View File

@@ -0,0 +1,235 @@
/* eslint-disable import/export */
/* eslint-disable @typescript-eslint/no-namespace */
import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime'
import type { Uint8ArrayList } from 'uint8arraylist'
import type { Codec } from 'protons-runtime'
export interface CircuitRelay {
type?: CircuitRelay.Type
srcPeer?: CircuitRelay.Peer
dstPeer?: CircuitRelay.Peer
code?: CircuitRelay.Status
}
export namespace CircuitRelay {
export enum Status {
SUCCESS = 'SUCCESS',
HOP_SRC_ADDR_TOO_LONG = 'HOP_SRC_ADDR_TOO_LONG',
HOP_DST_ADDR_TOO_LONG = 'HOP_DST_ADDR_TOO_LONG',
HOP_SRC_MULTIADDR_INVALID = 'HOP_SRC_MULTIADDR_INVALID',
HOP_DST_MULTIADDR_INVALID = 'HOP_DST_MULTIADDR_INVALID',
HOP_NO_CONN_TO_DST = 'HOP_NO_CONN_TO_DST',
HOP_CANT_DIAL_DST = 'HOP_CANT_DIAL_DST',
HOP_CANT_OPEN_DST_STREAM = 'HOP_CANT_OPEN_DST_STREAM',
HOP_CANT_SPEAK_RELAY = 'HOP_CANT_SPEAK_RELAY',
HOP_CANT_RELAY_TO_SELF = 'HOP_CANT_RELAY_TO_SELF',
STOP_SRC_ADDR_TOO_LONG = 'STOP_SRC_ADDR_TOO_LONG',
STOP_DST_ADDR_TOO_LONG = 'STOP_DST_ADDR_TOO_LONG',
STOP_SRC_MULTIADDR_INVALID = 'STOP_SRC_MULTIADDR_INVALID',
STOP_DST_MULTIADDR_INVALID = 'STOP_DST_MULTIADDR_INVALID',
STOP_RELAY_REFUSED = 'STOP_RELAY_REFUSED',
MALFORMED_MESSAGE = 'MALFORMED_MESSAGE'
}
enum __StatusValues {
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
}
export namespace Status {
export const codec = () => {
return enumeration<Status>(__StatusValues)
}
}
export enum Type {
HOP = 'HOP',
STOP = 'STOP',
STATUS = 'STATUS',
CAN_HOP = 'CAN_HOP'
}
enum __TypeValues {
HOP = 1,
STOP = 2,
STATUS = 3,
CAN_HOP = 4
}
export namespace Type {
export const codec = () => {
return enumeration<Type>(__TypeValues)
}
}
export interface Peer {
id: Uint8Array
addrs: Uint8Array[]
}
export namespace Peer {
let _codec: Codec<Peer>
export const codec = (): Codec<Peer> => {
if (_codec == null) {
_codec = message<Peer>((obj, writer, opts = {}) => {
if (opts.lengthDelimited !== false) {
writer.fork()
}
if (obj.id != null) {
writer.uint32(10)
writer.bytes(obj.id)
} else {
throw new Error('Protocol error: required field "id" was not found in object')
}
if (obj.addrs != null) {
for (const value of obj.addrs) {
writer.uint32(18)
writer.bytes(value)
}
} else {
throw new Error('Protocol error: required field "addrs" was not found in object')
}
if (opts.lengthDelimited !== false) {
writer.ldelim()
}
}, (reader, length) => {
const obj: any = {}
const end = length == null ? reader.len : reader.pos + length
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1:
obj.id = reader.bytes()
break
case 2:
obj.addrs = obj.addrs ?? []
obj.addrs.push(reader.bytes())
break
default:
reader.skipType(tag & 7)
break
}
}
obj.addrs = obj.addrs ?? []
if (obj.id == null) {
throw new Error('Protocol error: value for required field "id" was not found in protobuf')
}
if (obj.addrs == null) {
throw new Error('Protocol error: value for required field "addrs" was not found in protobuf')
}
return obj
})
}
return _codec
}
export const encode = (obj: Peer): Uint8Array => {
return encodeMessage(obj, Peer.codec())
}
export const decode = (buf: Uint8Array | Uint8ArrayList): Peer => {
return decodeMessage(buf, Peer.codec())
}
}
let _codec: Codec<CircuitRelay>
export const codec = (): Codec<CircuitRelay> => {
if (_codec == null) {
_codec = message<CircuitRelay>((obj, writer, opts = {}) => {
if (opts.lengthDelimited !== false) {
writer.fork()
}
if (obj.type != null) {
writer.uint32(8)
CircuitRelay.Type.codec().encode(obj.type, writer)
}
if (obj.srcPeer != null) {
writer.uint32(18)
CircuitRelay.Peer.codec().encode(obj.srcPeer, writer)
}
if (obj.dstPeer != null) {
writer.uint32(26)
CircuitRelay.Peer.codec().encode(obj.dstPeer, writer)
}
if (obj.code != null) {
writer.uint32(32)
CircuitRelay.Status.codec().encode(obj.code, writer)
}
if (opts.lengthDelimited !== false) {
writer.ldelim()
}
}, (reader, length) => {
const obj: any = {}
const end = length == null ? reader.len : reader.pos + length
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1:
obj.type = CircuitRelay.Type.codec().decode(reader)
break
case 2:
obj.srcPeer = CircuitRelay.Peer.codec().decode(reader, reader.uint32())
break
case 3:
obj.dstPeer = CircuitRelay.Peer.codec().decode(reader, reader.uint32())
break
case 4:
obj.code = CircuitRelay.Status.codec().decode(reader)
break
default:
reader.skipType(tag & 7)
break
}
}
return obj
})
}
return _codec
}
export const encode = (obj: CircuitRelay): Uint8Array => {
return encodeMessage(obj, CircuitRelay.codec())
}
export const decode = (buf: Uint8Array | Uint8ArrayList): CircuitRelay => {
return decodeMessage(buf, CircuitRelay.codec())
}
}

View File

@@ -1,7 +1,8 @@
import { logger } from '@libp2p/logger'
import errCode from 'err-code'
import * as mafmt from '@multiformats/mafmt'
import { Multiaddr } from '@multiformats/multiaddr'
import type { Multiaddr } from '@multiformats/multiaddr'
import { multiaddr } from '@multiformats/multiaddr'
import { CircuitRelay as CircuitPB } from './pb/index.js'
import { codes } from '../errors.js'
import { streamToMaConnection } from '@libp2p/utils/stream-to-ma-conn'
@@ -10,19 +11,30 @@ import { createListener } from './listener.js'
import { handleCanHop, handleHop, hop } from './circuit/hop.js'
import { handleStop } from './circuit/stop.js'
import { StreamHandler } from './circuit/stream-handler.js'
import { symbol } from '@libp2p/interfaces/transport'
import { symbol } from '@libp2p/interface-transport'
import { peerIdFromString } from '@libp2p/peer-id'
import { Components, Initializable } from '@libp2p/interfaces/components'
import { Components, Initializable } from '@libp2p/components'
import type { AbortOptions } from '@libp2p/interfaces'
import type { IncomingStreamData } from '@libp2p/interfaces/registrar'
import type { Listener, Transport, CreateListenerOptions, ConnectionHandler } from '@libp2p/interfaces/transport'
import type { Connection } from '@libp2p/interfaces/connection'
import type { IncomingStreamData } from '@libp2p/interface-registrar'
import type { Listener, Transport, CreateListenerOptions, ConnectionHandler } from '@libp2p/interface-transport'
import type { Connection } from '@libp2p/interface-connection'
import type { RelayConfig } from '../index.js'
import { abortableDuplex } from 'abortable-iterator'
import { TimeoutController } from 'timeout-abort-controller'
import { setMaxListeners } from 'events'
import type { Uint8ArrayList } from 'uint8arraylist'
import type { Duplex } from 'it-stream-types'
const log = logger('libp2p:circuit')
export class Circuit implements Transport, Initializable {
private handler?: ConnectionHandler
private components: Components = new Components()
private readonly _init: RelayConfig
constructor (init: RelayConfig) {
this._init = init
}
init (components: Components): void {
this.components = components
@@ -49,54 +61,30 @@ export class Circuit implements Transport, Initializable {
}
get [Symbol.toStringTag] () {
return this.constructor.name
return 'libp2p/circuit-relay-v1'
}
async _onProtocol (data: IncomingStreamData) {
const { connection, stream } = data
const streamHandler = new StreamHandler({ stream })
const request = await streamHandler.read()
const controller = new TimeoutController(this._init.hop.timeout)
if (request == null) {
log('request was invalid, could not read from stream')
streamHandler.write({
type: CircuitPB.Type.STATUS,
code: CircuitPB.Status.MALFORMED_MESSAGE
try {
// fails on node < 15.4
setMaxListeners?.(Infinity, controller.signal)
} catch {}
try {
const source = abortableDuplex(stream, controller.signal)
const streamHandler = new StreamHandler({
stream: {
...stream,
...source
}
})
streamHandler.close()
return
}
const request = await streamHandler.read()
let virtualConnection
switch (request.type) {
case CircuitPB.Type.CAN_HOP: {
log('received CAN_HOP request from %p', connection.remotePeer)
await handleCanHop({ circuit: this, connection, streamHandler })
break
}
case CircuitPB.Type.HOP: {
log('received HOP request from %p', connection.remotePeer)
virtualConnection = await handleHop({
connection,
request,
streamHandler,
circuit: this,
connectionManager: this.components.getConnectionManager()
})
break
}
case CircuitPB.Type.STOP: {
log('received STOP request from %p', connection.remotePeer)
virtualConnection = await handleStop({
connection,
request,
streamHandler
})
break
}
default: {
log('Request of type %s not supported', request.type)
if (request == null) {
log('request was invalid, could not read from stream')
streamHandler.write({
type: CircuitPB.Type.STATUS,
code: CircuitPB.Status.MALFORMED_MESSAGE
@@ -104,27 +92,68 @@ export class Circuit implements Transport, Initializable {
streamHandler.close()
return
}
}
if (virtualConnection != null) {
// @ts-expect-error dst peer will not be undefined
const remoteAddr = new Multiaddr(request.dstPeer.addrs[0])
// @ts-expect-error dst peer will not be undefined
const localAddr = new Multiaddr(request.srcPeer.addrs[0])
const maConn = streamToMaConnection({
stream: virtualConnection,
remoteAddr,
localAddr
})
const type = request.type === CircuitPB.Type.HOP ? 'relay' : 'inbound'
log('new %s connection %s', type, maConn.remoteAddr)
let virtualConnection: Duplex<Uint8ArrayList, Uint8ArrayList | Uint8Array> | undefined
const conn = await this.components.getUpgrader().upgradeInbound(maConn)
log('%s connection %s upgraded', type, maConn.remoteAddr)
if (this.handler != null) {
this.handler(conn)
switch (request.type) {
case CircuitPB.Type.CAN_HOP: {
log('received CAN_HOP request from %p', connection.remotePeer)
await handleCanHop({ circuit: this, connection, streamHandler })
break
}
case CircuitPB.Type.HOP: {
log('received HOP request from %p', connection.remotePeer)
await handleHop({
connection,
request,
streamHandler,
circuit: this,
connectionManager: this.components.getConnectionManager()
})
break
}
case CircuitPB.Type.STOP: {
log('received STOP request from %p', connection.remotePeer)
virtualConnection = await handleStop({
connection,
request,
streamHandler
})
break
}
default: {
log('Request of type %s not supported', request.type)
streamHandler.write({
type: CircuitPB.Type.STATUS,
code: CircuitPB.Status.MALFORMED_MESSAGE
})
streamHandler.close()
return
}
}
if (virtualConnection != null) {
const remoteAddr = connection.remoteAddr
.encapsulate('/p2p-circuit')
.encapsulate(multiaddr(request.dstPeer?.addrs[0]))
const localAddr = multiaddr(request.srcPeer?.addrs[0])
const maConn = streamToMaConnection({
stream: virtualConnection,
remoteAddr,
localAddr
})
const type = request.type === CircuitPB.Type.HOP ? 'relay' : 'inbound'
log('new %s connection %s', type, maConn.remoteAddr)
const conn = await this.components.getUpgrader().upgradeInbound(maConn)
log('%s connection %s upgraded', type, maConn.remoteAddr)
if (this.handler != null) {
this.handler(conn)
}
}
} finally {
controller.clear()
}
}
@@ -134,8 +163,8 @@ export class Circuit implements Transport, Initializable {
async dial (ma: Multiaddr, options: AbortOptions = {}): Promise<Connection> {
// Check the multiaddr to see if it contains a relay and a destination peer
const addrs = ma.toString().split('/p2p-circuit')
const relayAddr = new Multiaddr(addrs[0])
const destinationAddr = new Multiaddr(addrs[addrs.length - 1])
const relayAddr = multiaddr(addrs[0])
const destinationAddr = multiaddr(addrs[addrs.length - 1])
const relayId = relayAddr.getPeerId()
const destinationId = destinationAddr.getPeerId()
@@ -149,14 +178,18 @@ export class Circuit implements Transport, Initializable {
const destinationPeer = peerIdFromString(destinationId)
let disconnectOnFailure = false
let relayConnection = this.components.getConnectionManager().getConnection(relayPeer)
const relayConnections = this.components.getConnectionManager().getConnections(relayPeer)
let relayConnection = relayConnections[0]
if (relayConnection == null) {
relayConnection = await this.components.getDialer().dial(relayAddr, options)
await this.components.getPeerStore().addressBook.add(relayPeer, [relayAddr])
relayConnection = await this.components.getConnectionManager().openConnection(relayPeer, options)
disconnectOnFailure = true
}
try {
const virtualConnection = await hop({
...options,
connection: relayConnection,
request: {
type: CircuitPB.Type.HOP,
@@ -166,7 +199,7 @@ export class Circuit implements Transport, Initializable {
},
dstPeer: {
id: destinationPeer.toBytes(),
addrs: [new Multiaddr(destinationAddr).bytes]
addrs: [multiaddr(destinationAddr).bytes]
}
}
})
@@ -195,8 +228,8 @@ export class Circuit implements Transport, Initializable {
this.handler = options.handler
return createListener({
dialer: this.components.getDialer(),
connectionManager: this.components.getConnectionManager()
connectionManager: this.components.getConnectionManager(),
peerStore: this.components.getPeerStore()
})
}

View File

@@ -10,6 +10,7 @@ import type { Libp2pInit } from './index.js'
import { codes, messages } from './errors.js'
import errCode from 'err-code'
import type { RecursivePartial } from '@libp2p/interfaces'
import { isNode, isBrowser, isWebWorker, isElectronMain, isElectronRenderer, isReactNative } from 'wherearewe'
const DefaultConfig: Partial<Libp2pInit> = {
addresses: {
@@ -21,24 +22,20 @@ const DefaultConfig: Partial<Libp2pInit> = {
connectionManager: {
maxConnections: 300,
minConnections: 50,
autoDial: true,
autoDialInterval: 10000,
autoDial: true
},
connectionGater: {},
transportManager: {
faultTolerance: FaultTolerance.FATAL_ALL
},
dialer: {
maxParallelDials: Constants.MAX_PARALLEL_DIALS,
maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS,
dialTimeout: Constants.DIAL_TIMEOUT,
inboundUpgradeTimeout: Constants.INBOUND_UPGRADE_TIMEOUT,
resolvers: {
dnsaddr: dnsaddrResolver
},
addressSorter: publicAddressesFirst
},
host: {
agentVersion: AGENT_VERSION
connectionGater: {},
transportManager: {
faultTolerance: FaultTolerance.FATAL_ALL
},
metrics: {
enabled: false,
@@ -58,7 +55,6 @@ const DefaultConfig: Partial<Libp2pInit> = {
bootDelay: 10e3
}
},
protocolPrefix: 'ipfs',
nat: {
enabled: true,
ttl: 7200,
@@ -73,12 +69,37 @@ const DefaultConfig: Partial<Libp2pInit> = {
},
hop: {
enabled: false,
active: false
active: false,
timeout: 30000
},
autoRelay: {
enabled: false,
maxListeners: 2
}
},
identify: {
protocolPrefix: 'ipfs',
host: {
agentVersion: AGENT_VERSION
},
// https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L48
timeout: 60000,
maxInboundStreams: 1,
maxOutboundStreams: 1,
maxPushIncomingStreams: 1,
maxPushOutgoingStreams: 1
},
ping: {
protocolPrefix: 'ipfs',
maxInboundStreams: 1,
maxOutboundStreams: 1,
timeout: 10000
},
fetch: {
protocolPrefix: 'libp2p',
maxInboundStreams: 1,
maxOutboundStreams: 1,
timeout: 10000
}
}
@@ -97,5 +118,14 @@ export function validateConfig (opts: RecursivePartial<Libp2pInit>): Libp2pInit
throw errCode(new Error(messages.ERR_PROTECTOR_REQUIRED), codes.ERR_PROTECTOR_REQUIRED)
}
// Append user agent version to default AGENT_VERSION depending on the environment
if (resultingOptions.identify.host.agentVersion === AGENT_VERSION) {
if (isNode || isElectronMain) {
resultingOptions.identify.host.agentVersion += ` UserAgent=${globalThis.process.version}`
} else if (isBrowser || isWebWorker || isElectronRenderer || isReactNative) {
resultingOptions.identify.host.agentVersion += ` UserAgent=${globalThis.navigator.userAgent}`
}
}
return resultingOptions
}

View File

@@ -6,8 +6,8 @@ import all from 'it-all'
import { pipe } from 'it-pipe'
import filter from 'it-filter'
import sort from 'it-sort'
import type { Startable } from '@libp2p/interfaces'
import type { Components } from '@libp2p/interfaces/components'
import type { Startable } from '@libp2p/interfaces/startable'
import type { Components } from '@libp2p/components'
const log = logger('libp2p:connection-manager:auto-dialler')
@@ -102,7 +102,7 @@ export class AutoDialler implements Startable {
const minConnections = this.options.minConnections
// Already has enough connections
if (this.components.getConnectionManager().getConnectionList().length >= minConnections) {
if (this.components.getConnectionManager().getConnections().length >= minConnections) {
this.autoDialTimeout = retimer(this._autoDial, this.options.autoDialInterval)
return
@@ -126,7 +126,7 @@ export class AutoDialler implements Startable {
async (source) => await all(source)
)
for (let i = 0; this.running && i < peers.length && this.components.getConnectionManager().getConnectionList().length < minConnections; i++) {
for (let i = 0; this.running && i < peers.length && this.components.getConnectionManager().getConnections().length < minConnections; i++) {
// Connection Manager was stopped during async dial
if (!this.running) {
return
@@ -134,10 +134,10 @@ export class AutoDialler implements Startable {
const peer = peers[i]
if (this.components.getConnectionManager().getConnection(peer.id) == null) {
if (this.components.getConnectionManager().getConnections(peer.id).length === 0) {
log('connecting to a peerStore stored peer %p', peer.id)
try {
await this.components.getDialer().dial(peer.id)
await this.components.getConnectionManager().openConnection(peer.id)
} catch (err: any) {
log.error('could not connect to peerStore stored peer', err)
}

View File

@@ -0,0 +1,65 @@
import type { PeerInfo } from '@libp2p/interface-peer-info'
import { logger } from '@libp2p/logger'
import type { Components } from '@libp2p/components'
import { TimeoutController } from 'timeout-abort-controller'
import { setMaxListeners } from 'events'
const log = logger('libp2p:dialer:auto-dialer')
export interface AutoDialerInit {
enabled: boolean
minConnections: number
dialTimeout: number
}
export class AutoDialer {
private readonly components: Components
private readonly enabled: boolean
private readonly minConnections: number
private readonly dialTimeout: number
constructor (components: Components, init: AutoDialerInit) {
this.components = components
this.enabled = init.enabled
this.minConnections = init.minConnections
this.dialTimeout = init.dialTimeout
}
public handle (evt: CustomEvent<PeerInfo>) {
const { detail: peer } = evt
if (!this.enabled) {
return
}
const connections = this.components.getConnectionManager().getConnections(peer.id)
// If auto dialing is on and we have no connection to the peer, check if we should dial
if (connections.length === 0) {
const minConnections = this.minConnections ?? 0
const allConnections = this.components.getConnectionManager().getConnections()
if (minConnections > allConnections.length) {
log('auto-dialing discovered peer %p with timeout %d', peer.id, this.dialTimeout)
const controller = new TimeoutController(this.dialTimeout)
try {
// fails on node < 15.4
setMaxListeners?.(Infinity, controller.signal)
} catch {}
void this.components.getConnectionManager().openConnection(peer.id, {
signal: controller.signal
})
.catch(err => {
log.error('could not connect to discovered peer %p with %o', peer.id, err)
})
.finally(() => {
controller.clear()
})
}
}
}
}

View File

@@ -1,14 +1,13 @@
import errCode from 'err-code'
import { anySignal } from 'any-signal'
import FIFO from 'p-fifo'
// @ts-expect-error setMaxListeners is missing from the node 16 types
import { setMaxListeners } from 'events'
import { codes } from '../errors.js'
import { codes } from '../../errors.js'
import { logger } from '@libp2p/logger'
import type { Multiaddr } from '@multiformats/multiaddr'
import type { Connection } from '@libp2p/interfaces/connection'
import type { Connection } from '@libp2p/interface-connection'
import type { AbortOptions } from '@libp2p/interfaces'
import type { DefaultDialer } from './index.js'
import type { Dialer } from '@libp2p/interface-connection-manager'
const log = logger('libp2p:dialer:dial-request')
@@ -19,12 +18,12 @@ export interface DialAction {
export interface DialRequestOptions {
addrs: Multiaddr[]
dialAction: DialAction
dialer: DefaultDialer
dialer: Dialer
}
export class DialRequest {
private readonly addrs: Multiaddr[]
private readonly dialer: DefaultDialer
private readonly dialer: Dialer
private readonly dialAction: DialAction
/**
@@ -62,7 +61,7 @@ export class DialRequest {
})
}
const dialAbortControllers = this.addrs.map(() => {
const dialAbortControllers: Array<(AbortController | undefined)> = this.addrs.map(() => {
const controller = new AbortController()
try {
// fails on node < 15.4
@@ -80,16 +79,27 @@ export class DialRequest {
}
let completedDials = 0
let done = false
try {
return await Promise.any(this.addrs.map(async (addr, i) => {
const token = await tokenHolder.shift() // get token
// End attempt once another attempt succeeded
if (done) {
this.dialer.releaseToken(tokens.splice(tokens.indexOf(token), 1)[0])
throw errCode(new Error('dialAction already succeeded'), codes.ERR_ALREADY_SUCCEEDED)
}
const controller = dialAbortControllers[i]
if (controller == null) {
throw errCode(new Error('dialAction did not come with an AbortController'), codes.ERR_INVALID_PARAMETERS)
}
let conn
try {
const signal = dialAbortControllers[i].signal
const signal = controller.signal
conn = await this.dialAction(addr, { ...options, signal: (options.signal != null) ? anySignal([signal, options.signal]) : signal })
// Remove the successful AbortController so it is not aborted
dialAbortControllers.splice(i, 1)
dialAbortControllers[i] = undefined
} finally {
completedDials++
// If we have more or equal dials remaining than tokens, recycle the token, otherwise release it
@@ -102,10 +112,25 @@ export class DialRequest {
}
}
if (conn == null) {
// Notify Promise.any that attempt was not successful
// to prevent from returning undefined despite there
// were successful dial attempts
throw errCode(new Error('dialAction led to empty object'), codes.ERR_TRANSPORT_DIAL_FAILED)
} else {
// This dial succeeded, don't attempt anything else
done = true
}
return conn
}))
} finally {
dialAbortControllers.map(c => c.abort()) // success/failure happened, abort everything else
// success/failure happened, abort everything else
dialAbortControllers.forEach(c => {
if (c !== undefined) {
c.abort()
}
})
tokens.forEach(token => this.dialer.releaseToken(token)) // release tokens back to the dialer
}
}

View File

@@ -3,31 +3,33 @@ import all from 'it-all'
import filter from 'it-filter'
import { pipe } from 'it-pipe'
import errCode from 'err-code'
import { Multiaddr } from '@multiformats/multiaddr'
import type { Multiaddr, Resolver } from '@multiformats/multiaddr'
import { multiaddr, resolvers } from '@multiformats/multiaddr'
import { TimeoutController } from 'timeout-abort-controller'
import { AbortError } from '@libp2p/interfaces/errors'
import { anySignal } from 'any-signal'
// @ts-expect-error setMaxListeners is missing from the node 16 types
import { setMaxListeners } from 'events'
import { DialAction, DialRequest } from './dial-request.js'
import { publicAddressesFirst } from '@libp2p/utils/address-sort'
import { trackedMap } from '@libp2p/tracked-map'
import { codes } from '../errors.js'
import { codes } from '../../errors.js'
import {
DIAL_TIMEOUT,
MAX_PARALLEL_DIALS,
MAX_PER_PEER_DIALS,
MAX_ADDRS_TO_DIAL
} from '../constants.js'
import type { Connection } from '@libp2p/interfaces/connection'
import type { AbortOptions, Startable } from '@libp2p/interfaces'
import type { PeerId } from '@libp2p/interfaces/peer-id'
import { getPeer } from '../get-peer.js'
} from '../../constants.js'
import type { Connection } from '@libp2p/interface-connection'
import type { AbortOptions } from '@libp2p/interfaces'
import type { Startable } from '@libp2p/interfaces/startable'
import type { PeerId } from '@libp2p/interface-peer-id'
import { getPeer } from '../../get-peer.js'
import sort from 'it-sort'
import type { Components } from '@libp2p/interfaces/components'
import type { Dialer, DialerInit } from '@libp2p/interfaces/dialer'
import type { Components } from '@libp2p/components'
import map from 'it-map'
import type { AddressSorter } from '@libp2p/interfaces/peer-store'
import type { AddressSorter } from '@libp2p/interface-peer-store'
import type { ComponentMetricsTracker } from '@libp2p/interface-metrics'
import type { Dialer } from '@libp2p/interface-connection-manager'
const log = logger('libp2p:dialer')
@@ -52,7 +54,40 @@ export interface PendingDialTarget {
reject: (err: Error) => void
}
export class DefaultDialer implements Dialer, Startable {
export interface DialerInit {
/**
* Sort the known addresses of a peer before trying to dial
*/
addressSorter?: AddressSorter
/**
* Number of max concurrent dials
*/
maxParallelDials?: number
/**
* Number of max addresses to dial for a given peer
*/
maxAddrsToDial?: number
/**
* How long a dial attempt is allowed to take
*/
dialTimeout?: number
/**
* Number of max concurrent dials per peer
*/
maxDialsPerPeer?: number
/**
* Multiaddr resolvers to use when dialing
*/
resolvers?: Record<string, Resolver>
metrics?: ComponentMetricsTracker
}
export class DefaultDialer implements Startable, Dialer {
private readonly components: Components
private readonly addressSorter: AddressSorter
private readonly maxAddrsToDial: number
@@ -64,13 +99,13 @@ export class DefaultDialer implements Dialer, Startable {
private started: boolean
constructor (components: Components, init: DialerInit = {}) {
this.components = components
this.started = false
this.addressSorter = init.addressSorter ?? publicAddressesFirst
this.maxAddrsToDial = init.maxAddrsToDial ?? MAX_ADDRS_TO_DIAL
this.timeout = init.dialTimeout ?? DIAL_TIMEOUT
this.maxDialsPerPeer = init.maxDialsPerPeer ?? MAX_PER_PEER_DIALS
this.tokens = [...new Array(init.maxParallelDials ?? MAX_PARALLEL_DIALS)].map((_, index) => index)
this.components = components
this.pendingDials = trackedMap({
component: METRICS_COMPONENT,
metric: METRICS_PENDING_DIALS,
@@ -79,11 +114,11 @@ export class DefaultDialer implements Dialer, Startable {
this.pendingDialTargets = trackedMap({
component: METRICS_COMPONENT,
metric: METRICS_PENDING_DIAL_TARGETS,
metrics: init.metrics
metrics: components.getMetrics()
})
for (const [key, value] of Object.entries(init.resolvers ?? {})) {
Multiaddr.resolvers.set(key, value)
resolvers.set(key, value)
}
}
@@ -139,19 +174,9 @@ export class DefaultDialer implements Dialer, Startable {
throw errCode(new Error('The dial request is blocked by gater.allowDialPeer'), codes.ERR_PEER_DIAL_INTERCEPTED)
}
log('dial to %p', id)
const existingConnection = this.components.getConnectionManager().getConnection(id)
if (existingConnection != null) {
log('had an existing connection to %p', id)
return existingConnection
}
log('creating dial target for %p', id)
const dialTarget = await this._createCancellableDialTarget(id)
const dialTarget = await this._createCancellableDialTarget(id, options)
if (dialTarget.addrs.length === 0) {
throw errCode(new Error('The dial request has no valid addresses'), codes.ERR_NO_VALID_ADDRESSES)
@@ -176,28 +201,12 @@ export class DefaultDialer implements Dialer, Startable {
}
}
async dialProtocol (peer: PeerId | Multiaddr, protocols: string | string[], options: AbortOptions = {}) {
if (protocols == null) {
throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM)
}
protocols = Array.isArray(protocols) ? protocols : [protocols]
if (protocols.length === 0) {
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 await connection.newStream(protocols)
}
/**
* Connects to a given `peer` by dialing all of its known addresses.
* The dial to the first address that is successfully able to upgrade a connection
* will be used.
*/
async _createCancellableDialTarget (peer: PeerId): Promise<DialTarget> {
async _createCancellableDialTarget (peer: PeerId, options: AbortOptions): Promise<DialTarget> {
// Make dial target promise cancellable
const id = `${(parseInt(String(Math.random() * 1e9), 10)).toString()}${Date.now()}`
const cancellablePromise = new Promise<DialTarget>((resolve, reject) => {
@@ -206,7 +215,7 @@ export class DefaultDialer implements Dialer, Startable {
try {
const dialTarget = await Promise.race([
this._createDialTarget(peer),
this._createDialTarget(peer, options),
cancellablePromise
])
@@ -222,16 +231,24 @@ export class DefaultDialer implements Dialer, Startable {
* If a multiaddr is received it should be the first address attempted.
* Multiaddrs not supported by the available transports will be filtered out.
*/
async _createDialTarget (peer: PeerId): Promise<DialTarget> {
const knownAddrs = await pipe(
async _createDialTarget (peer: PeerId, options: AbortOptions): Promise<DialTarget> {
const _resolve = this._resolve.bind(this)
const addrs = await pipe(
await this.components.getPeerStore().addressBook.get(peer),
(source) => filter(source, async (address) => {
return !(await this.components.getConnectionGater().denyDialMultiaddr(peer, address.multiaddr))
}),
// Sort addresses so, for example, we try certified public address first
(source) => sort(source, this.addressSorter),
(source) => map(source, (address) => {
const ma = address.multiaddr
async function * resolve (source) {
for await (const a of source) {
yield * await _resolve(a.multiaddr, options)
}
},
// Multiaddrs not supported by the available transports will be filtered out.
(source) => filter(source, (ma) => Boolean(this.components.getTransportManager().transportForMultiaddr(ma))),
(source) => map(source, (ma) => {
if (peer.toString() === ma.getPeerId()) {
return ma
}
@@ -241,26 +258,14 @@ export class DefaultDialer implements Dialer, Startable {
async (source) => await all(source)
)
const addrs: Multiaddr[] = []
for (const a of knownAddrs) {
const resolvedAddrs = await this._resolve(a)
log('resolved %s to %s', a, resolvedAddrs)
resolvedAddrs.forEach(ra => addrs.push(ra))
}
// Multiaddrs not supported by the available transports will be filtered out.
const supportedAddrs = addrs.filter(a => this.components.getTransportManager().transportForMultiaddr(a))
if (supportedAddrs.length > this.maxAddrsToDial) {
if (addrs.length > this.maxAddrsToDial) {
await this.components.getPeerStore().delete(peer)
throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES)
}
return {
id: peer.toString(),
addrs: supportedAddrs
addrs
}
}
@@ -277,7 +282,10 @@ export class DefaultDialer implements Dialer, Startable {
throw errCode(new Error('already aborted'), codes.ERR_ALREADY_ABORTED)
}
return await this.components.getTransportManager().dial(addr, options)
return await this.components.getTransportManager().dial(addr, options).catch(err => {
log.error('dial to %s failed', addr, err)
throw err
})
}
const dialRequest = new DialRequest({
@@ -334,7 +342,7 @@ export class DefaultDialer implements Dialer, Startable {
/**
* Resolve multiaddr recursively
*/
async _resolve (ma: Multiaddr): Promise<Multiaddr[]> {
async _resolve (ma: Multiaddr, options: AbortOptions): Promise<Multiaddr[]> {
// TODO: recursive logic should live in multiaddr once dns4/dns6 support is in place
// Now only supporting resolve for dnsaddr
const resolvableProto = ma.protoNames().includes('dnsaddr')
@@ -344,9 +352,9 @@ export class DefaultDialer implements Dialer, Startable {
return [ma]
}
const resolvedMultiaddrs = await this._resolveRecord(ma)
const resolvedMultiaddrs = await this._resolveRecord(ma, options)
const recursiveMultiaddrs = await Promise.all(resolvedMultiaddrs.map(async (nm) => {
return await this._resolve(nm)
return await this._resolve(nm, options)
}))
const addrs = recursiveMultiaddrs.flat()
@@ -361,10 +369,10 @@ export class DefaultDialer implements Dialer, Startable {
/**
* Resolve a given multiaddr. If this fails, an empty array will be returned
*/
async _resolveRecord (ma: Multiaddr): Promise<Multiaddr[]> {
async _resolveRecord (ma: Multiaddr, options: AbortOptions): Promise<Multiaddr[]> {
try {
ma = new Multiaddr(ma.toString()) // Use current multiaddr module
const multiaddrs = await ma.resolve()
ma = multiaddr(ma.toString()) // Use current multiaddr module
const multiaddrs = await ma.resolve(options)
return multiaddrs
} catch (err) {
log.error(`multiaddr ${ma.toString()} could not be resolved`, err)

View File

@@ -4,16 +4,21 @@ import mergeOptions from 'merge-options'
import { LatencyMonitor, SummaryObject } from './latency-monitor.js'
// @ts-expect-error retimer does not have types
import retimer from 'retimer'
import { CustomEvent, EventEmitter, Startable } from '@libp2p/interfaces'
import { trackedMap } from '@libp2p/tracked-map'
import type { AbortOptions } from '@libp2p/interfaces'
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
import type { Startable } from '@libp2p/interfaces/startable'
import { codes } from '../errors.js'
import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id'
// @ts-expect-error setMaxListeners is missing from the node 16 types
import { isPeerId, PeerId } from '@libp2p/interface-peer-id'
import { setMaxListeners } from 'events'
import type { Connection } from '@libp2p/interfaces/connection'
import type { ConnectionManager } from '@libp2p/interfaces/registrar'
import type { Components } from '@libp2p/interfaces/components'
import * as STATUS from '@libp2p/interfaces/connection/status'
import type { Connection } from '@libp2p/interface-connection'
import type { ConnectionManager } from '@libp2p/interface-connection-manager'
import { Components, Initializable } from '@libp2p/components'
import * as STATUS from '@libp2p/interface-connection/status'
import type { AddressSorter } from '@libp2p/interface-peer-store'
import type { Resolver } from '@multiformats/multiaddr'
import { PeerMap } from '@libp2p/peer-collections'
import { TimeoutController } from 'timeout-abort-controller'
import { KEEP_ALIVE } from '@libp2p/interface-peer-store/tags'
const log = logger('libp2p:connection-manager')
@@ -26,29 +31,23 @@ const defaultOptions: Partial<ConnectionManagerInit> = {
maxEventLoopDelay: Infinity,
pollInterval: 2000,
autoDialInterval: 10000,
movingAverageInterval: 60000,
defaultPeerValue: 1
movingAverageInterval: 60000
}
const METRICS_SYSTEM = 'libp2p'
const METRICS_COMPONENT = 'connection-manager'
const METRICS_PEER_CONNECTIONS = 'peer-connections'
const METRICS_PEER_VALUES = 'peer-values'
export interface ConnectionManagerEvents {
'peer:connect': CustomEvent<PeerId>
'peer:disconnect': CustomEvent<PeerId>
}
const STARTUP_RECONNECT_TIMEOUT = 60000
export interface ConnectionManagerInit {
/**
* The maximum number of connections allowed
* The maximum number of connections to keep open
*/
maxConnections?: number
maxConnections: number
/**
* The minimum number of connections to avoid pruning
* The minimum number of connections to keep open
*/
minConnections?: number
minConnections: number
/**
* The max data (in and out), per average interval to allow
@@ -81,64 +80,95 @@ export interface ConnectionManagerInit {
movingAverageInterval?: number
/**
* The value of the peer
*/
defaultPeerValue?: number
/**
* Should preemptively guarantee connections are above the low watermark
* If true, try to connect to all discovered peers up to the connection manager limit
*/
autoDial?: boolean
/**
* How often, in milliseconds, it should preemptively guarantee connections are above the low watermark
* How long to wait between attempting to keep our number of concurrent connections
* above minConnections
*/
autoDialInterval?: number
autoDialInterval: number
/**
* Sort the known addresses of a peer before trying to dial
*/
addressSorter?: AddressSorter
/**
* Number of max concurrent dials
*/
maxParallelDials?: number
/**
* Number of max addresses to dial for a given peer
*/
maxAddrsToDial?: number
/**
* How long a dial attempt is allowed to take, including DNS resolution
* of the multiaddr, opening a socket and upgrading it to a Connection.
*/
dialTimeout?: number
/**
* When a new inbound connection is opened, the upgrade process (e.g. protect,
* encrypt, multiplex etc) must complete within this number of ms.
*/
inboundUpgradeTimeout: number
/**
* Number of max concurrent dials per peer
*/
maxDialsPerPeer?: number
/**
* Multiaddr resolvers to use when dialing
*/
resolvers?: Record<string, Resolver>
/**
* On startup we try to dial any peer that has previously been
* tagged with KEEP_ALIVE up to this timeout in ms. (default: 60000)
*/
startupReconnectTimeout?: number
}
export interface ConnectionManagerEvents {
'peer:connect': CustomEvent<PeerId>
'peer:disconnect': CustomEvent<PeerId>
}
/**
* Responsible for managing known connections.
*/
export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEvents> implements ConnectionManager, Startable {
private readonly components: Components
private readonly init: Required<ConnectionManagerInit>
private readonly peerValues: Map<string, number>
// @ts-expect-error
export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEvents> implements ConnectionManager, Startable, Initializable {
private components = new Components()
private readonly opts: Required<ConnectionManagerInit>
private readonly connections: Map<string, Connection[]>
private started: boolean
private timer?: ReturnType<retimer>
private readonly latencyMonitor: LatencyMonitor
private readonly startupReconnectTimeout: number
private connectOnStartupController?: TimeoutController
private readonly dialTimeout: number
constructor (components: Components, init: ConnectionManagerInit = {}) {
constructor (init: ConnectionManagerInit) {
super()
this.components = components
this.init = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, init)
this.opts = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, init)
if (this.init.maxConnections < this.init.minConnections) {
if (this.opts.maxConnections < this.opts.minConnections) {
throw errCode(new Error('Connection Manager maxConnections must be greater than minConnections'), codes.ERR_INVALID_PARAMETERS)
}
log('options: %o', this.init)
/**
* Map of peer identifiers to their peer value for pruning connections.
*
* @type {Map<string, number>}
*/
this.peerValues = trackedMap({
component: METRICS_COMPONENT,
metric: METRICS_PEER_VALUES,
metrics: this.components.getMetrics()
})
log('options: %o', this.opts)
/**
* Map of connections per peer
*/
this.connections = trackedMap({
component: METRICS_COMPONENT,
metric: METRICS_PEER_CONNECTIONS,
metrics: this.components.getMetrics()
})
this.connections = new Map()
this.started = false
this._checkMetrics = this._checkMetrics.bind(this)
@@ -153,12 +183,103 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
setMaxListeners?.(Infinity, this)
} catch {}
this.components.getUpgrader().addEventListener('connection', (evt) => {
void this.onConnect(evt).catch(err => {
log.error(err)
})
this.onConnect = this.onConnect.bind(this)
this.onDisconnect = this.onDisconnect.bind(this)
this.startupReconnectTimeout = init.startupReconnectTimeout ?? STARTUP_RECONNECT_TIMEOUT
this.dialTimeout = init.dialTimeout ?? 30000
}
init (components: Components): void {
this.components = components
// track inbound/outbound connections
this.components.getMetrics()?.updateComponentMetric({
system: METRICS_SYSTEM,
component: METRICS_COMPONENT,
metric: 'connections',
label: 'direction',
value: () => {
const metric = {
inbound: 0,
outbound: 0
}
for (const conns of this.connections.values()) {
for (const conn of conns) {
if (conn.stat.direction === 'inbound') {
metric.inbound++
} else {
metric.outbound++
}
}
}
return metric
}
})
// track total number of streams per protocol
this.components.getMetrics()?.updateComponentMetric({
system: METRICS_SYSTEM,
component: METRICS_COMPONENT,
metric: 'protocol-streams-total',
label: 'protocol',
value: () => {
const metric: Record<string, number> = {}
for (const conns of this.connections.values()) {
for (const conn of conns) {
for (const stream of conn.streams) {
const key = `${stream.stat.direction} ${stream.stat.protocol ?? 'unnegotiated'}`
metric[key] = (metric[key] ?? 0) + 1
}
}
}
return metric
}
})
// track 90th percentile of streams per protocol
this.components.getMetrics()?.updateComponentMetric({
system: METRICS_SYSTEM,
component: METRICS_COMPONENT,
metric: 'protocol-streams-per-connection-90th-percentile',
label: 'protocol',
value: () => {
const allStreams: Record<string, number[]> = {}
for (const conns of this.connections.values()) {
for (const conn of conns) {
const streams: Record<string, number> = {}
for (const stream of conn.streams) {
const key = `${stream.stat.direction} ${stream.stat.protocol ?? 'unnegotiated'}`
streams[key] = (streams[key] ?? 0) + 1
}
for (const [protocol, count] of Object.entries(streams)) {
allStreams[protocol] = allStreams[protocol] ?? []
allStreams[protocol].push(count)
}
}
}
const metric: Record<string, number> = {}
for (let [protocol, counts] of Object.entries(allStreams)) {
counts = counts.sort((a, b) => a - b)
const index = Math.floor(counts.length * 0.9)
metric[protocol] = counts[index]
}
return metric
}
})
this.components.getUpgrader().addEventListener('connectionEnd', this.onDisconnect.bind(this))
}
isStarted () {
@@ -171,7 +292,7 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
*/
async start () {
if (this.components.getMetrics() != null) {
this.timer = this.timer ?? retimer(this._checkMetrics, this.init.pollInterval)
this.timer = this.timer ?? retimer(this._checkMetrics, this.opts.pollInterval)
}
// latency monitor
@@ -183,6 +304,58 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
log('started')
}
async afterStart () {
this.components.getUpgrader().addEventListener('connection', this.onConnect)
this.components.getUpgrader().addEventListener('connectionEnd', this.onDisconnect)
// re-connect to any peers with the KEEP_ALIVE tag
void Promise.resolve()
.then(async () => {
const keepAlivePeers: PeerId[] = []
for (const peer of await this.components.getPeerStore().all()) {
const tags = await this.components.getPeerStore().getTags(peer.id)
const hasKeepAlive = tags.filter(tag => tag.name === KEEP_ALIVE).length > 0
if (hasKeepAlive) {
keepAlivePeers.push(peer.id)
}
}
this.connectOnStartupController?.clear()
this.connectOnStartupController = new TimeoutController(this.startupReconnectTimeout)
try {
// fails on node < 15.4
setMaxListeners?.(Infinity, this.connectOnStartupController.signal)
} catch {}
await Promise.all(
keepAlivePeers.map(async peer => {
await this.openConnection(peer, {
signal: this.connectOnStartupController?.signal
})
.catch(err => {
log.error(err)
})
})
)
})
.catch(err => {
log.error(err)
})
.finally(() => {
this.connectOnStartupController?.clear()
})
}
async beforeStop () {
// if we are still dialing KEEP_ALIVE peers, abort those dials
this.connectOnStartupController?.abort()
this.components.getUpgrader().removeEventListener('connection', this.onConnect)
this.components.getUpgrader().removeEventListener('connectionEnd', this.onDisconnect)
}
/**
* Stops the Connection Manager
*/
@@ -202,10 +375,16 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
*/
async _close () {
// Close all connections we're tracking
const tasks = []
const tasks: Array<Promise<void>> = []
for (const connectionList of this.connections.values()) {
for (const connection of connectionList) {
tasks.push(connection.close())
tasks.push((async () => {
try {
await connection.close()
} catch (err) {
log.error(err)
}
})())
}
}
@@ -214,18 +393,6 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
this.connections.clear()
}
/**
* Sets the value of the given peer. Peers with lower values
* will be disconnected first.
*/
setPeerValue (peerId: PeerId, value: number) {
if (value < 0 || value > 1) {
throw new Error('value should be a number between 0 and 1')
}
this.peerValues.set(peerId.toString(), value)
}
/**
* Checks the libp2p metrics to determine if any values have exceeded
* the configured maximums.
@@ -238,23 +405,29 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
if (metrics != null) {
try {
const movingAverages = metrics.getGlobal().getMovingAverages()
const received = movingAverages.dataReceived[this.init.movingAverageInterval].movingAverage
const received = movingAverages.dataReceived[this.opts.movingAverageInterval].movingAverage
await this._checkMaxLimit('maxReceivedData', received)
const sent = movingAverages.dataSent[this.init.movingAverageInterval].movingAverage
const sent = movingAverages.dataSent[this.opts.movingAverageInterval].movingAverage
await this._checkMaxLimit('maxSentData', sent)
const total = received + sent
await this._checkMaxLimit('maxData', total)
log('metrics update', total)
log.trace('metrics update', total)
} finally {
this.timer = retimer(this._checkMetrics, this.init.pollInterval)
this.timer = retimer(this._checkMetrics, this.opts.pollInterval)
}
}
}
onConnect (evt: CustomEvent<Connection>) {
void this._onConnect(evt).catch(err => {
log.error(err)
})
}
/**
* Tracks the incoming connection and check the connection limit
*/
async onConnect (evt: CustomEvent<Connection>) {
async _onConnect (evt: CustomEvent<Connection>) {
const { detail: connection } = evt
if (!this.started) {
@@ -267,8 +440,6 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
const peerIdStr = peerId.toString()
const storedConns = this.connections.get(peerIdStr)
this.dispatchEvent(new CustomEvent<Connection>('peer:connect', { detail: connection }))
if (storedConns != null) {
storedConns.push(connection)
} else {
@@ -279,11 +450,11 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
await this.components.getPeerStore().keyBook.set(peerId, peerId.publicKey)
}
if (!this.peerValues.has(peerIdStr)) {
this.peerValues.set(peerIdStr, this.init.defaultPeerValue)
}
const numConnections = this.getConnections().length
const toPrune = numConnections - this.opts.maxConnections
await this._checkMaxLimit('maxConnections', this.getConnectionList().length)
await this._checkMaxLimit('maxConnections', numConnections, toPrune)
this.dispatchEvent(new CustomEvent<Connection>('peer:connect', { detail: connection }))
}
/**
@@ -305,42 +476,88 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
this.connections.set(peerId, storedConn)
} else if (storedConn != null) {
this.connections.delete(peerId)
this.peerValues.delete(connection.remotePeer.toString())
this.dispatchEvent(new CustomEvent<Connection>('peer:disconnect', { detail: connection }))
this.components.getMetrics()?.onPeerDisconnected(connection.remotePeer)
}
}
getConnectionMap (): Map<string, Connection[]> {
return this.connections
}
getConnectionList (): Connection[] {
let output: Connection[] = []
for (const connections of this.connections.values()) {
output = output.concat(connections)
getConnections (peerId?: PeerId): Connection[] {
if (peerId != null) {
return this.connections.get(peerId.toString()) ?? []
}
return output
}
let conns: Connection[] = []
getConnections (peerId: PeerId): Connection[] {
return this.connections.get(peerId.toString()) ?? []
}
/**
* Get a connection with a peer
*/
getConnection (peerId: PeerId): Connection | undefined {
const connections = this.getAll(peerId)
if (connections.length > 0) {
return connections[0]
for (const c of this.connections.values()) {
conns = conns.concat(c)
}
return undefined
return conns
}
async openConnection (peerId: PeerId, options: AbortOptions = {}): Promise<Connection> {
log('dial to %p', peerId)
const existingConnections = this.getConnections(peerId)
if (existingConnections.length > 0) {
log('had an existing connection to %p', peerId)
return existingConnections[0]
}
let timeoutController: TimeoutController | undefined
if (options?.signal == null) {
timeoutController = new TimeoutController(this.dialTimeout)
options.signal = timeoutController.signal
try {
// fails on node < 15.4
setMaxListeners?.(Infinity, timeoutController.signal)
} catch {}
}
try {
const connection = await this.components.getDialer().dial(peerId, options)
let peerConnections = this.connections.get(peerId.toString())
if (peerConnections == null) {
peerConnections = []
this.connections.set(peerId.toString(), peerConnections)
}
// we get notified of connections via the Upgrader emitting "connection"
// events, double check we aren't already tracking this connection before
// storing it
let trackedConnection = false
for (const conn of peerConnections) {
if (conn.id === connection.id) {
trackedConnection = true
}
}
if (!trackedConnection) {
peerConnections.push(connection)
}
return connection
} finally {
if (timeoutController != null) {
timeoutController.clear()
}
}
}
async closeConnections (peerId: PeerId): Promise<void> {
const connections = this.connections.get(peerId.toString()) ?? []
await Promise.all(
connections.map(async connection => {
return await connection.close()
})
)
}
/**
@@ -368,7 +585,7 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
_onLatencyMeasure (evt: CustomEvent<SummaryObject>) {
const { detail: summary } = evt
this._checkMaxLimit('maxEventLoopDelay', summary.avgMs)
this._checkMaxLimit('maxEventLoopDelay', summary.avgMs, 1)
.catch(err => {
log.error(err)
})
@@ -377,46 +594,86 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
/**
* If the `value` of `name` has exceeded its limit, maybe close a connection
*/
async _checkMaxLimit (name: keyof ConnectionManagerInit, value: number) {
const limit = this.init[name]
async _checkMaxLimit (name: keyof ConnectionManagerInit, value: number, toPrune: number = 1) {
const limit = this.opts[name]
log.trace('checking limit of %s. current value: %d of %d', name, value, limit)
if (value > limit) {
log('%s: limit exceeded: %p, %d', this.components.getPeerId(), name, value)
await this._maybeDisconnectOne()
log('%s: limit exceeded: %p, %d/%d, pruning %d connection(s)', this.components.getPeerId(), name, value, limit, toPrune)
await this._maybePruneConnections(toPrune)
}
}
/**
* If we have more connections than our maximum, close a connection
* to the lowest valued peer.
* If we have more connections than our maximum, select some excess connections
* to prune based on peer value
*/
async _maybeDisconnectOne () {
if (this.init.minConnections < this.connections.size) {
const peerValues = Array.from(new Map([...this.peerValues.entries()].sort((a, b) => a[1] - b[1])))
async _maybePruneConnections (toPrune: number) {
const connections = this.getConnections()
log('%p: sorted peer values: %j', this.components.getPeerId(), peerValues)
const disconnectPeer = peerValues[0]
if (connections.length <= this.opts.minConnections || toPrune < 1) {
return
}
if (disconnectPeer != null) {
const peerId = disconnectPeer[0]
log('%p: lowest value peer is %s', this.components.getPeerId(), peerId)
log('%p: closing a connection to %j', this.components.getPeerId(), peerId)
const peerValues = new PeerMap<number>()
for (const connections of this.connections.values()) {
if (connections[0].remotePeer.toString() === peerId) {
void connections[0].close()
.catch(err => {
log.error(err)
})
// work out peer values
for (const connection of connections) {
const remotePeer = connection.remotePeer
// TODO: should not need to invoke this manually
this.onDisconnect(new CustomEvent<Connection>('connectionEnd', {
detail: connections[0]
}))
break
}
}
if (peerValues.has(remotePeer)) {
continue
}
const tags = await this.components.getPeerStore().getTags(remotePeer)
// sum all tag values
peerValues.set(remotePeer, tags.reduce((acc, curr) => {
return acc + curr.value
}, 0))
}
// sort by value, lowest to highest
const sortedConnections = connections.sort((a, b) => {
const peerAValue = peerValues.get(a.remotePeer) ?? 0
const peerBValue = peerValues.get(b.remotePeer) ?? 0
if (peerAValue > peerBValue) {
return 1
}
if (peerAValue < peerBValue) {
return -1
}
return 0
})
// close some connections
const toClose = []
for (const connection of sortedConnections) {
log('too many connections open - closing a connection to %p', connection.remotePeer)
toClose.push(connection)
if (toClose.length === toPrune) {
break
}
}
// close connections
await Promise.all(
toClose.map(async connection => {
try {
await connection.close()
} catch (err) {
log.error(err)
}
// TODO: should not need to invoke this manually
this.onDisconnect(new CustomEvent<Connection>('connectionEnd', {
detail: connection
}))
})
)
}
}

View File

@@ -2,7 +2,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)
*/
import { CustomEvent, EventEmitter } from '@libp2p/interfaces'
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
import { VisibilityChangeEmitter } from './visibility-change-emitter.js'
import { logger } from '@libp2p/logger'

View File

@@ -2,7 +2,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)
*/
import { CustomEvent, EventEmitter } from '@libp2p/interfaces'
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
import { logger } from '@libp2p/logger'
const log = logger('libp2p:connection-manager:latency-monitor:visibility-change-emitter')

View File

@@ -4,6 +4,11 @@
*/
export const DIAL_TIMEOUT = 30e3
/**
* How long in ms an inbound connection upgrade is allowed to take
*/
export const INBOUND_UPGRADE_TIMEOUT = 30e3
/**
* Maximum allowed concurrent dials
*/

View File

@@ -8,10 +8,11 @@ import {
import drain from 'it-drain'
import merge from 'it-merge'
import { pipe } from 'it-pipe'
import type { ContentRouting } from '@libp2p/interfaces/content-routing'
import type { AbortOptions, Startable } from '@libp2p/interfaces'
import type { ContentRouting } from '@libp2p/interface-content-routing'
import type { AbortOptions } from '@libp2p/interfaces'
import type { Startable } from '@libp2p/interfaces/startable'
import type { CID } from 'multiformats/cid'
import type { Components } from '@libp2p/interfaces/components'
import type { Components } from '@libp2p/components'
export interface CompoundContentRoutingInit {
routers: ContentRouting[]

View File

@@ -2,8 +2,8 @@ import errCode from 'err-code'
import filter from 'it-filter'
import map from 'it-map'
import type { Source } from 'it-stream-types'
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
import type { PeerStore } from '@libp2p/interfaces/peer-store'
import type { PeerInfo } from '@libp2p/interface-peer-info'
import type { PeerStore } from '@libp2p/interface-peer-store'
/**
* Store the multiaddrs from every peer in the passed peer store

View File

@@ -1,7 +1,7 @@
import drain from 'it-drain'
import errCode from 'err-code'
import type { DHT } from '@libp2p/interfaces/dht'
import type { ContentRouting } from '@libp2p/interfaces/content-routing'
import type { DHT } from '@libp2p/interface-dht'
import type { ContentRouting } from '@libp2p/interface-content-routing'
import type { CID } from 'multiformats/cid'
import type { AbortOptions } from '@libp2p/interfaces'

View File

@@ -1,8 +1,8 @@
import errCode from 'err-code'
import { messages, codes } from '../errors.js'
import type { PeerRouting } from '@libp2p/interfaces/peer-routing'
import type { DHT } from '@libp2p/interfaces/dht'
import type { PeerId } from '@libp2p/interfaces/peer-id'
import type { PeerRouting } from '@libp2p/interface-peer-routing'
import type { DHT } from '@libp2p/interface-dht'
import type { PeerId } from '@libp2p/interface-peer-id'
import type { AbortOptions } from '@libp2p/interfaces'
/**
@@ -27,8 +27,8 @@ export class DHTPeerRouting implements PeerRouting {
async * getClosestPeers (key: Uint8Array, options: AbortOptions = {}) {
for await (const event of this.dht.getClosestPeers(key, options)) {
if (event.name === 'PEER_RESPONSE') {
yield * event.closer
if (event.name === 'FINAL_PEER') {
yield event.peer
}
}
}

60
src/dht/dummy-dht.ts Normal file
View File

@@ -0,0 +1,60 @@
import type { DualDHT, QueryEvent, SingleDHT } from '@libp2p/interface-dht'
import type { PeerDiscoveryEvents } from '@libp2p/interface-peer-discovery'
import errCode from 'err-code'
import { messages, codes } from '../errors.js'
import { EventEmitter } from '@libp2p/interfaces/events'
import { symbol } from '@libp2p/interface-peer-discovery'
export class DummyDHT extends EventEmitter<PeerDiscoveryEvents> implements DualDHT {
get [symbol] (): true {
return true
}
get [Symbol.toStringTag] () {
return '@libp2p/dummy-dht'
}
get wan (): SingleDHT {
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
}
get lan (): SingleDHT {
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
}
get (): AsyncIterable<QueryEvent> {
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
}
findProviders (): AsyncIterable<QueryEvent> {
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
}
findPeer (): AsyncIterable<QueryEvent> {
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
}
getClosestPeers (): AsyncIterable<QueryEvent> {
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
}
provide (): AsyncIterable<QueryEvent> {
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
}
put (): AsyncIterable<QueryEvent> {
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
}
async getMode (): Promise<'client' | 'server'> {
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
}
async setMode (): Promise<void> {
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
}
async refreshRoutingTable (): Promise<void> {
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
}
}

View File

@@ -1,40 +0,0 @@
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
import { logger } from '@libp2p/logger'
import type { Components } from '@libp2p/interfaces/components'
const log = logger('libp2p:dialer:auto-dialer')
export interface AutoDialerInit {
enabled: boolean
minConnections: number
}
export class AutoDialer {
private readonly components: Components
private readonly enabled: boolean
private readonly minConnections: number
constructor (components: Components, init: AutoDialerInit) {
this.components = components
this.enabled = init.enabled
this.minConnections = init.minConnections
}
public handle (evt: CustomEvent<PeerInfo>) {
const { detail: peer } = evt
// If auto dialing is on and we have no connection to the peer, check if we should dial
if (this.enabled && this.components.getConnectionManager().getConnection(peer.id) == null) {
const minConnections = this.minConnections ?? 0
if (minConnections > this.components.getConnectionManager().getConnectionList().length) {
log('auto-dialing discovered peer %p', peer.id)
void this.components.getDialer().dial(peer.id)
.catch(err => {
log.error('could not connect to discovered peer %p with %o', peer.id, err)
})
}
}
}
}

View File

@@ -1,6 +1,7 @@
export enum messages {
NOT_STARTED_YET = 'The libp2p node is not started yet',
DHT_DISABLED = 'DHT is not available',
PUBSUB_DISABLED = 'PubSub is not available',
CONN_ENCRYPTION_REQUIRED = 'At least one connection encryption module is required',
ERR_TRANSPORTS_REQUIRED = 'At least one transport module is required',
ERR_PROTECTOR_REQUIRED = 'Private network is enforced, but no protector was provided',
@@ -9,6 +10,7 @@ export enum messages {
export enum codes {
DHT_DISABLED = 'ERR_DHT_DISABLED',
ERR_PUBSUB_DISABLED = 'ERR_PUBSUB_DISABLED',
PUBSUB_NOT_STARTED = 'ERR_PUBSUB_NOT_STARTED',
DHT_NOT_STARTED = 'ERR_DHT_NOT_STARTED',
CONN_ENCRYPTION_REQUIRED = 'ERR_CONN_ENCRYPTION_REQUIRED',
@@ -67,5 +69,9 @@ export enum codes {
ERR_INVALID_PASS_LENGTH = 'ERR_INVALID_PASS_LENGTH',
ERR_NOT_IMPLEMENTED = 'ERR_NOT_IMPLEMENTED',
ERR_WRONG_PING_ACK = 'ERR_WRONG_PING_ACK',
ERR_INVALID_RECORD = 'ERR_INVALID_RECORD'
ERR_INVALID_RECORD = 'ERR_INVALID_RECORD',
ERR_ALREADY_SUCCEEDED = 'ERR_ALREADY_SUCCEEDED',
ERR_NO_HANDLER_FOR_PROTOCOL = 'ERR_NO_HANDLER_FOR_PROTOCOL',
ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS',
ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS'
}

View File

@@ -1,3 +1,4 @@
// https://github.com/libp2p/specs/tree/master/fetch#wire-protocol
export const PROTOCOL = '/libp2p/fetch/0.0.1'
export const PROTOCOL_VERSION = '0.0.1'
export const PROTOCOL_NAME = 'fetch'

View File

@@ -3,18 +3,30 @@ import errCode from 'err-code'
import { codes } from '../errors.js'
import * as lp from 'it-length-prefixed'
import { FetchRequest, FetchResponse } from './pb/proto.js'
import { handshake } from 'it-handshake'
import { PROTOCOL } from './constants.js'
import type { PeerId } from '@libp2p/interfaces/peer-id'
import type { Startable } from '@libp2p/interfaces'
import type { Stream } from '@libp2p/interfaces/connection'
import type { IncomingStreamData } from '@libp2p/interfaces/registrar'
import type { Components } from '@libp2p/interfaces/components'
import { PROTOCOL_NAME, PROTOCOL_VERSION } from './constants.js'
import type { PeerId } from '@libp2p/interface-peer-id'
import type { Startable } from '@libp2p/interfaces/startable'
import type { Stream } from '@libp2p/interface-connection'
import type { IncomingStreamData } from '@libp2p/interface-registrar'
import type { Components } from '@libp2p/components'
import type { AbortOptions } from '@libp2p/interfaces'
import { abortableDuplex } from 'abortable-iterator'
import { pipe } from 'it-pipe'
import first from 'it-first'
import { TimeoutController } from 'timeout-abort-controller'
import { setMaxListeners } from 'events'
const log = logger('libp2p:fetch')
export interface FetchInit {
export interface FetchServiceInit {
protocolPrefix: string
maxInboundStreams: number
maxOutboundStreams: number
/**
* How long we should wait for a remote peer to send any data
*/
timeout: number
}
export interface HandleMessageOptions {
@@ -33,24 +45,33 @@ export interface LookupFunction {
* by a fixed prefix that all keys that should be routed to that lookup function will start with.
*/
export class FetchService implements Startable {
public readonly protocol: string
private readonly components: Components
private readonly lookupFunctions: Map<string, LookupFunction>
private readonly protocol: string
private started: boolean
private readonly init: FetchServiceInit
constructor (components: Components, init: FetchInit) {
constructor (components: Components, init: FetchServiceInit) {
this.started = false
this.components = components
this.protocol = PROTOCOL
this.protocol = `/${init.protocolPrefix ?? 'libp2p'}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`
this.lookupFunctions = new Map() // Maps key prefix to value lookup function
this.handleMessage = this.handleMessage.bind(this)
this.init = init
}
async start () {
await this.components.getRegistrar().handle(this.protocol, (data) => {
void this.handleMessage(data).catch(err => {
log.error(err)
})
void this.handleMessage(data)
.catch(err => {
log.error(err)
})
.finally(() => {
data.stream.close()
})
}, {
maxInboundStreams: this.init.maxInboundStreams,
maxOutboundStreams: this.init.maxOutboundStreams
})
this.started = true
}
@@ -67,33 +88,73 @@ export class FetchService implements Startable {
/**
* Sends a request to fetch the value associated with the given key from the given peer
*/
async fetch (peer: PeerId, key: string): Promise<Uint8Array | null> {
async fetch (peer: PeerId, key: string, options: AbortOptions = {}): Promise<Uint8Array | null> {
log('dialing %s to %p', this.protocol, peer)
const connection = await this.components.getDialer().dial(peer)
const { stream } = await connection.newStream([this.protocol])
const shake = handshake(stream)
const connection = await this.components.getConnectionManager().openConnection(peer, options)
let timeoutController
let signal = options.signal
let stream: Stream | undefined
// send message
const request = new FetchRequest({ identifier: key })
shake.write(lp.encode.single(FetchRequest.encode(request).finish()).slice())
// create a timeout if no abort signal passed
if (signal == null) {
timeoutController = new TimeoutController(this.init.timeout)
signal = timeoutController.signal
// read response
// @ts-expect-error fromReader returns a Source which has no .next method
const response = FetchResponse.decode((await lp.decode.fromReader(shake.reader).next()).value.slice())
switch (response.status) {
case (FetchResponse.StatusCode.OK): {
return response.data
try {
// fails on node < 15.4
setMaxListeners?.(Infinity, timeoutController.signal)
} catch {}
}
try {
stream = await connection.newStream([this.protocol], {
signal
})
// make stream abortable
const source = abortableDuplex(stream, signal)
const result = await pipe(
[FetchRequest.encode({ identifier: key })],
lp.encode(),
source,
lp.decode(),
async function (source) {
const buf = await first(source)
if (buf == null) {
throw errCode(new Error('No data received'), codes.ERR_INVALID_MESSAGE)
}
const response = FetchResponse.decode(buf)
switch (response.status) {
case (FetchResponse.StatusCode.OK): {
return response.data
}
case (FetchResponse.StatusCode.NOT_FOUND): {
return null
}
case (FetchResponse.StatusCode.ERROR): {
const errmsg = (new TextDecoder()).decode(response.data)
throw errCode(new Error('Error in fetch protocol response: ' + errmsg), codes.ERR_INVALID_PARAMETERS)
}
default: {
throw errCode(new Error('Unknown response status'), codes.ERR_INVALID_MESSAGE)
}
}
}
)
return result ?? null
} finally {
if (timeoutController != null) {
timeoutController.clear()
}
case (FetchResponse.StatusCode.NOT_FOUND): {
return null
}
case (FetchResponse.StatusCode.ERROR): {
const errmsg = (new TextDecoder()).decode(response.data)
throw errCode(new Error('Error in fetch protocol response: ' + errmsg), codes.ERR_INVALID_PARAMETERS)
}
default: {
throw errCode(new Error('Unknown response status'), codes.ERR_INVALID_MESSAGE)
if (stream != null) {
stream.close()
}
}
}
@@ -105,25 +166,40 @@ export class FetchService implements Startable {
*/
async handleMessage (data: IncomingStreamData) {
const { stream } = data
const shake = handshake(stream)
// @ts-expect-error fromReader returns a Source which has no .next method
const request = FetchRequest.decode((await lp.decode.fromReader(shake.reader).next()).value.slice())
const self = this
let response
const lookup = this._getLookupFunction(request.identifier)
if (lookup != null) {
const data = await lookup(request.identifier)
if (data != null) {
response = new FetchResponse({ status: FetchResponse.StatusCode.OK, data })
} else {
response = new FetchResponse({ status: FetchResponse.StatusCode.NOT_FOUND })
}
} else {
const errmsg = (new TextEncoder()).encode('No lookup function registered for key: ' + request.identifier)
response = new FetchResponse({ status: FetchResponse.StatusCode.ERROR, data: errmsg })
}
await pipe(
stream,
lp.decode(),
async function * (source) {
const buf = await first(source)
shake.write(lp.encode.single(FetchResponse.encode(response).finish()).slice())
if (buf == null) {
throw errCode(new Error('No data received'), codes.ERR_INVALID_MESSAGE)
}
// for await (const buf of source) {
const request = FetchRequest.decode(buf)
let response: FetchResponse
const lookup = self._getLookupFunction(request.identifier)
if (lookup != null) {
const data = await lookup(request.identifier)
if (data != null) {
response = { status: FetchResponse.StatusCode.OK, data }
} else {
response = { status: FetchResponse.StatusCode.NOT_FOUND, data: new Uint8Array(0) }
}
} else {
const errmsg = (new TextEncoder()).encode('No lookup function registered for key: ' + request.identifier)
response = { status: FetchResponse.StatusCode.ERROR, data: errmsg }
}
yield FetchResponse.encode(response)
},
lp.encode(),
stream
)
}
/**

View File

@@ -1,134 +0,0 @@
import * as $protobuf from "protobufjs";
/** Properties of a FetchRequest. */
export interface IFetchRequest {
/** FetchRequest identifier */
identifier?: (string|null);
}
/** Represents a FetchRequest. */
export class FetchRequest implements IFetchRequest {
/**
* Constructs a new FetchRequest.
* @param [p] Properties to set
*/
constructor(p?: IFetchRequest);
/** FetchRequest identifier. */
public identifier: string;
/**
* Encodes the specified FetchRequest message. Does not implicitly {@link FetchRequest.verify|verify} messages.
* @param m FetchRequest message or plain object to encode
* @param [w] Writer to encode to
* @returns Writer
*/
public static encode(m: IFetchRequest, w?: $protobuf.Writer): $protobuf.Writer;
/**
* Decodes a FetchRequest message from the specified reader or buffer.
* @param r Reader or buffer to decode from
* @param [l] Message length if known beforehand
* @returns FetchRequest
* @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): FetchRequest;
/**
* Creates a FetchRequest message from a plain object. Also converts values to their respective internal types.
* @param d Plain object
* @returns FetchRequest
*/
public static fromObject(d: { [k: string]: any }): FetchRequest;
/**
* Creates a plain object from a FetchRequest message. Also converts values to other types if specified.
* @param m FetchRequest
* @param [o] Conversion options
* @returns Plain object
*/
public static toObject(m: FetchRequest, o?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this FetchRequest to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
}
/** Properties of a FetchResponse. */
export interface IFetchResponse {
/** FetchResponse status */
status?: (FetchResponse.StatusCode|null);
/** FetchResponse data */
data?: (Uint8Array|null);
}
/** Represents a FetchResponse. */
export class FetchResponse implements IFetchResponse {
/**
* Constructs a new FetchResponse.
* @param [p] Properties to set
*/
constructor(p?: IFetchResponse);
/** FetchResponse status. */
public status: FetchResponse.StatusCode;
/** FetchResponse data. */
public data: Uint8Array;
/**
* Encodes the specified FetchResponse message. Does not implicitly {@link FetchResponse.verify|verify} messages.
* @param m FetchResponse message or plain object to encode
* @param [w] Writer to encode to
* @returns Writer
*/
public static encode(m: IFetchResponse, w?: $protobuf.Writer): $protobuf.Writer;
/**
* Decodes a FetchResponse message from the specified reader or buffer.
* @param r Reader or buffer to decode from
* @param [l] Message length if known beforehand
* @returns FetchResponse
* @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): FetchResponse;
/**
* Creates a FetchResponse message from a plain object. Also converts values to their respective internal types.
* @param d Plain object
* @returns FetchResponse
*/
public static fromObject(d: { [k: string]: any }): FetchResponse;
/**
* Creates a plain object from a FetchResponse message. Also converts values to other types if specified.
* @param m FetchResponse
* @param [o] Conversion options
* @returns Plain object
*/
public static toObject(m: FetchResponse, o?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this FetchResponse to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
}
export namespace FetchResponse {
/** StatusCode enum. */
enum StatusCode {
OK = 0,
NOT_FOUND = 1,
ERROR = 2
}
}

View File

@@ -1,331 +0,0 @@
/*eslint-disable*/
import $protobuf from "protobufjs/minimal.js";
// Common aliases
const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
// Exported root namespace
const $root = $protobuf.roots["libp2p-fetch"] || ($protobuf.roots["libp2p-fetch"] = {});
export const FetchRequest = $root.FetchRequest = (() => {
/**
* Properties of a FetchRequest.
* @exports IFetchRequest
* @interface IFetchRequest
* @property {string|null} [identifier] FetchRequest identifier
*/
/**
* Constructs a new FetchRequest.
* @exports FetchRequest
* @classdesc Represents a FetchRequest.
* @implements IFetchRequest
* @constructor
* @param {IFetchRequest=} [p] Properties to set
*/
function FetchRequest(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]];
}
/**
* FetchRequest identifier.
* @member {string} identifier
* @memberof FetchRequest
* @instance
*/
FetchRequest.prototype.identifier = "";
/**
* Encodes the specified FetchRequest message. Does not implicitly {@link FetchRequest.verify|verify} messages.
* @function encode
* @memberof FetchRequest
* @static
* @param {IFetchRequest} m FetchRequest message or plain object to encode
* @param {$protobuf.Writer} [w] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
FetchRequest.encode = function encode(m, w) {
if (!w)
w = $Writer.create();
if (m.identifier != null && Object.hasOwnProperty.call(m, "identifier"))
w.uint32(10).string(m.identifier);
return w;
};
/**
* Decodes a FetchRequest message from the specified reader or buffer.
* @function decode
* @memberof FetchRequest
* @static
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
* @param {number} [l] Message length if known beforehand
* @returns {FetchRequest} FetchRequest
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
FetchRequest.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.FetchRequest();
while (r.pos < c) {
var t = r.uint32();
switch (t >>> 3) {
case 1:
m.identifier = r.string();
break;
default:
r.skipType(t & 7);
break;
}
}
return m;
};
/**
* Creates a FetchRequest message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof FetchRequest
* @static
* @param {Object.<string,*>} d Plain object
* @returns {FetchRequest} FetchRequest
*/
FetchRequest.fromObject = function fromObject(d) {
if (d instanceof $root.FetchRequest)
return d;
var m = new $root.FetchRequest();
if (d.identifier != null) {
m.identifier = String(d.identifier);
}
return m;
};
/**
* Creates a plain object from a FetchRequest message. Also converts values to other types if specified.
* @function toObject
* @memberof FetchRequest
* @static
* @param {FetchRequest} m FetchRequest
* @param {$protobuf.IConversionOptions} [o] Conversion options
* @returns {Object.<string,*>} Plain object
*/
FetchRequest.toObject = function toObject(m, o) {
if (!o)
o = {};
var d = {};
if (o.defaults) {
d.identifier = "";
}
if (m.identifier != null && m.hasOwnProperty("identifier")) {
d.identifier = m.identifier;
}
return d;
};
/**
* Converts this FetchRequest to JSON.
* @function toJSON
* @memberof FetchRequest
* @instance
* @returns {Object.<string,*>} JSON object
*/
FetchRequest.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
return FetchRequest;
})();
export const FetchResponse = $root.FetchResponse = (() => {
/**
* Properties of a FetchResponse.
* @exports IFetchResponse
* @interface IFetchResponse
* @property {FetchResponse.StatusCode|null} [status] FetchResponse status
* @property {Uint8Array|null} [data] FetchResponse data
*/
/**
* Constructs a new FetchResponse.
* @exports FetchResponse
* @classdesc Represents a FetchResponse.
* @implements IFetchResponse
* @constructor
* @param {IFetchResponse=} [p] Properties to set
*/
function FetchResponse(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]];
}
/**
* FetchResponse status.
* @member {FetchResponse.StatusCode} status
* @memberof FetchResponse
* @instance
*/
FetchResponse.prototype.status = 0;
/**
* FetchResponse data.
* @member {Uint8Array} data
* @memberof FetchResponse
* @instance
*/
FetchResponse.prototype.data = $util.newBuffer([]);
/**
* Encodes the specified FetchResponse message. Does not implicitly {@link FetchResponse.verify|verify} messages.
* @function encode
* @memberof FetchResponse
* @static
* @param {IFetchResponse} m FetchResponse message or plain object to encode
* @param {$protobuf.Writer} [w] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
FetchResponse.encode = function encode(m, w) {
if (!w)
w = $Writer.create();
if (m.status != null && Object.hasOwnProperty.call(m, "status"))
w.uint32(8).int32(m.status);
if (m.data != null && Object.hasOwnProperty.call(m, "data"))
w.uint32(18).bytes(m.data);
return w;
};
/**
* Decodes a FetchResponse message from the specified reader or buffer.
* @function decode
* @memberof FetchResponse
* @static
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
* @param {number} [l] Message length if known beforehand
* @returns {FetchResponse} FetchResponse
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
FetchResponse.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.FetchResponse();
while (r.pos < c) {
var t = r.uint32();
switch (t >>> 3) {
case 1:
m.status = r.int32();
break;
case 2:
m.data = r.bytes();
break;
default:
r.skipType(t & 7);
break;
}
}
return m;
};
/**
* Creates a FetchResponse message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof FetchResponse
* @static
* @param {Object.<string,*>} d Plain object
* @returns {FetchResponse} FetchResponse
*/
FetchResponse.fromObject = function fromObject(d) {
if (d instanceof $root.FetchResponse)
return d;
var m = new $root.FetchResponse();
switch (d.status) {
case "OK":
case 0:
m.status = 0;
break;
case "NOT_FOUND":
case 1:
m.status = 1;
break;
case "ERROR":
case 2:
m.status = 2;
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 FetchResponse message. Also converts values to other types if specified.
* @function toObject
* @memberof FetchResponse
* @static
* @param {FetchResponse} m FetchResponse
* @param {$protobuf.IConversionOptions} [o] Conversion options
* @returns {Object.<string,*>} Plain object
*/
FetchResponse.toObject = function toObject(m, o) {
if (!o)
o = {};
var d = {};
if (o.defaults) {
d.status = o.enums === String ? "OK" : 0;
if (o.bytes === String)
d.data = "";
else {
d.data = [];
if (o.bytes !== Array)
d.data = $util.newBuffer(d.data);
}
}
if (m.status != null && m.hasOwnProperty("status")) {
d.status = o.enums === String ? $root.FetchResponse.StatusCode[m.status] : m.status;
}
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 FetchResponse to JSON.
* @function toJSON
* @memberof FetchResponse
* @instance
* @returns {Object.<string,*>} JSON object
*/
FetchResponse.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
/**
* StatusCode enum.
* @name FetchResponse.StatusCode
* @enum {number}
* @property {number} OK=0 OK value
* @property {number} NOT_FOUND=1 NOT_FOUND value
* @property {number} ERROR=2 ERROR value
*/
FetchResponse.StatusCode = (function() {
const valuesById = {}, values = Object.create(valuesById);
values[valuesById[0] = "OK"] = 0;
values[valuesById[1] = "NOT_FOUND"] = 1;
values[valuesById[2] = "ERROR"] = 2;
return values;
})();
return FetchResponse;
})();
export { $root as default };

163
src/fetch/pb/proto.ts Normal file
View File

@@ -0,0 +1,163 @@
/* eslint-disable import/export */
/* eslint-disable @typescript-eslint/no-namespace */
import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime'
import type { Uint8ArrayList } from 'uint8arraylist'
import type { Codec } from 'protons-runtime'
export interface FetchRequest {
identifier: string
}
export namespace FetchRequest {
let _codec: Codec<FetchRequest>
export const codec = (): Codec<FetchRequest> => {
if (_codec == null) {
_codec = message<FetchRequest>((obj, writer, opts = {}) => {
if (opts.lengthDelimited !== false) {
writer.fork()
}
if (obj.identifier != null) {
writer.uint32(10)
writer.string(obj.identifier)
} else {
throw new Error('Protocol error: required field "identifier" was not found in object')
}
if (opts.lengthDelimited !== false) {
writer.ldelim()
}
}, (reader, length) => {
const obj: any = {}
const end = length == null ? reader.len : reader.pos + length
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1:
obj.identifier = reader.string()
break
default:
reader.skipType(tag & 7)
break
}
}
if (obj.identifier == null) {
throw new Error('Protocol error: value for required field "identifier" was not found in protobuf')
}
return obj
})
}
return _codec
}
export const encode = (obj: FetchRequest): Uint8Array => {
return encodeMessage(obj, FetchRequest.codec())
}
export const decode = (buf: Uint8Array | Uint8ArrayList): FetchRequest => {
return decodeMessage(buf, FetchRequest.codec())
}
}
export interface FetchResponse {
status: FetchResponse.StatusCode
data: Uint8Array
}
export namespace FetchResponse {
export enum StatusCode {
OK = 'OK',
NOT_FOUND = 'NOT_FOUND',
ERROR = 'ERROR'
}
enum __StatusCodeValues {
OK = 0,
NOT_FOUND = 1,
ERROR = 2
}
export namespace StatusCode {
export const codec = () => {
return enumeration<StatusCode>(__StatusCodeValues)
}
}
let _codec: Codec<FetchResponse>
export const codec = (): Codec<FetchResponse> => {
if (_codec == null) {
_codec = message<FetchResponse>((obj, writer, opts = {}) => {
if (opts.lengthDelimited !== false) {
writer.fork()
}
if (obj.status != null) {
writer.uint32(8)
FetchResponse.StatusCode.codec().encode(obj.status, writer)
} else {
throw new Error('Protocol error: required field "status" was not found in object')
}
if (obj.data != null) {
writer.uint32(18)
writer.bytes(obj.data)
} else {
throw new Error('Protocol error: required field "data" was not found in object')
}
if (opts.lengthDelimited !== false) {
writer.ldelim()
}
}, (reader, length) => {
const obj: any = {}
const end = length == null ? reader.len : reader.pos + length
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1:
obj.status = FetchResponse.StatusCode.codec().decode(reader)
break
case 2:
obj.data = reader.bytes()
break
default:
reader.skipType(tag & 7)
break
}
}
if (obj.status == null) {
throw new Error('Protocol error: value for required field "status" was not found in protobuf')
}
if (obj.data == null) {
throw new Error('Protocol error: value for required field "data" was not found in protobuf')
}
return obj
})
}
return _codec
}
export const encode = (obj: FetchResponse): Uint8Array => {
return encodeMessage(obj, FetchResponse.codec())
}
export const decode = (buf: Uint8Array | Uint8ArrayList): FetchResponse => {
return decodeMessage(buf, FetchResponse.codec())
}
}

View File

@@ -1,10 +1,11 @@
import { peerIdFromString } from '@libp2p/peer-id'
import { Multiaddr } from '@multiformats/multiaddr'
import type { Multiaddr } from '@multiformats/multiaddr'
import { multiaddr, isMultiaddr } from '@multiformats/multiaddr'
import errCode from 'err-code'
import { codes } from './errors.js'
import { isPeerId } from '@libp2p/interfaces/peer-id'
import type { PeerId } from '@libp2p/interfaces/peer-id'
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
import { isPeerId } from '@libp2p/interface-peer-id'
import type { PeerId } from '@libp2p/interface-peer-id'
import type { PeerInfo } from '@libp2p/interface-peer-info'
function peerIdFromMultiaddr (ma: Multiaddr) {
const idStr = ma.getPeerId()
@@ -39,12 +40,12 @@ export function getPeer (peer: PeerId | Multiaddr | string): PeerInfo {
}
if (typeof peer === 'string') {
peer = new Multiaddr(peer)
peer = multiaddr(peer)
}
let addr
if (Multiaddr.isMultiaddr(peer)) {
if (isMultiaddr(peer)) {
addr = peer
peer = peerIdFromMultiaddr(peer)
}

View File

@@ -2,13 +2,11 @@ import { logger } from '@libp2p/logger'
import errCode from 'err-code'
import * as lp from 'it-length-prefixed'
import { pipe } from 'it-pipe'
import all from 'it-all'
import take from 'it-take'
import drain from 'it-drain'
import first from 'it-first'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { Multiaddr, protocols } from '@multiformats/multiaddr'
import Message from './pb/message.js'
import { multiaddr, protocols } from '@multiformats/multiaddr'
import { Identify } from './pb/message.js'
import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record'
import {
MULTICODEC_IDENTIFY,
@@ -20,21 +18,51 @@ import {
MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION
} from './consts.js'
import { codes } from '../errors.js'
import type { IncomingStreamData } from '@libp2p/interfaces/registrar'
import type { Connection } from '@libp2p/interfaces/connection'
import type { Startable } from '@libp2p/interfaces'
import { peerIdFromKeys, peerIdFromString } from '@libp2p/peer-id'
import type { Components } from '@libp2p/interfaces/components'
import type { IncomingStreamData } from '@libp2p/interface-registrar'
import type { Connection, Stream } from '@libp2p/interface-connection'
import type { Startable } from '@libp2p/interfaces/startable'
import { peerIdFromKeys } from '@libp2p/peer-id'
import type { Components } from '@libp2p/components'
import { TimeoutController } from 'timeout-abort-controller'
import type { AbortOptions } from '@libp2p/interfaces'
import { abortableDuplex } from 'abortable-iterator'
import { setMaxListeners } from 'events'
const log = logger('libp2p:identify')
// https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L52
const MAX_IDENTIFY_MESSAGE_SIZE = 1024 * 8
export interface HostProperties {
agentVersion: string
}
export interface IdentifyServiceInit {
/**
* The prefix to use for the protocol (default: 'ipfs')
*/
protocolPrefix: string
/**
* What details we should send as part of an identify message
*/
host: HostProperties
/**
* How long we should wait for a remote peer to send their identify response
*/
timeout: number
/**
* Identify responses larger than this in bytes will be rejected (default: 8192)
*/
maxIdentifyMessageSize?: number
maxInboundStreams: number
maxOutboundStreams: number
maxPushIncomingStreams: number
maxPushOutgoingStreams: number
}
export class IdentifyService implements Startable {
@@ -46,13 +74,13 @@ export class IdentifyService implements Startable {
agentVersion: string
}
private readonly init: IdentifyServiceInit
private started: boolean
constructor (components: Components, init: IdentifyServiceInit) {
this.components = components
this.started = false
this.handleMessage = this.handleMessage.bind(this)
this.init = init
this.identifyProtocolStr = `/${init.protocolPrefix}/${MULTICODEC_IDENTIFY_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PROTOCOL_VERSION}`
this.identifyPushProtocolStr = `/${init.protocolPrefix}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION}`
@@ -100,13 +128,21 @@ export class IdentifyService implements Startable {
await this.components.getPeerStore().metadataBook.setValue(this.components.getPeerId(), 'AgentVersion', uint8ArrayFromString(this.host.agentVersion))
await this.components.getPeerStore().metadataBook.setValue(this.components.getPeerId(), 'ProtocolVersion', uint8ArrayFromString(this.host.protocolVersion))
await this.components.getRegistrar().handle([
this.identifyProtocolStr,
this.identifyPushProtocolStr
], (data) => {
void this.handleMessage(data)?.catch(err => {
await this.components.getRegistrar().handle(this.identifyProtocolStr, (data) => {
void this._handleIdentify(data).catch(err => {
log.error(err)
})
}, {
maxInboundStreams: this.init.maxInboundStreams,
maxOutboundStreams: this.init.maxOutboundStreams
})
await this.components.getRegistrar().handle(this.identifyPushProtocolStr, (data) => {
void this._handlePush(data).catch(err => {
log.error(err)
})
}, {
maxInboundStreams: this.init.maxPushIncomingStreams,
maxOutboundStreams: this.init.maxPushOutgoingStreams
})
this.started = true
@@ -128,22 +164,41 @@ export class IdentifyService implements Startable {
const protocols = await this.components.getPeerStore().protoBook.get(this.components.getPeerId())
const pushes = connections.map(async connection => {
let stream: Stream | undefined
const timeoutController = new TimeoutController(this.init.timeout)
try {
const { stream } = await connection.newStream([this.identifyPushProtocolStr])
// fails on node < 15.4
setMaxListeners?.(Infinity, timeoutController.signal)
} catch {}
try {
stream = await connection.newStream([this.identifyPushProtocolStr], {
signal: timeoutController.signal
})
// make stream abortable
const source = abortableDuplex(stream, timeoutController.signal)
await pipe(
[Message.Identify.encode({
[Identify.encode({
listenAddrs,
signedPeerRecord,
protocols
}).finish()],
})],
lp.encode(),
stream,
source,
drain
)
} catch (err: any) {
// Just log errors
log.error('could not push identify update to peer', err)
} finally {
if (stream != null) {
stream.close()
}
timeoutController.clear()
}
})
@@ -161,45 +216,80 @@ export class IdentifyService implements Startable {
const connections: Connection[] = []
for (const [peerIdStr, conns] of this.components.getConnectionManager().getConnectionMap().entries()) {
const peerId = peerIdFromString(peerIdStr)
for (const conn of this.components.getConnectionManager().getConnections()) {
const peerId = conn.remotePeer
const peer = await this.components.getPeerStore().get(peerId)
if (!peer.protocols.includes(this.identifyPushProtocolStr)) {
continue
}
connections.push(...conns)
connections.push(conn)
}
await this.push(connections)
}
async _identify (connection: Connection, options: AbortOptions = {}): Promise<Identify> {
let timeoutController
let signal = options.signal
let stream: Stream | undefined
// create a timeout if no abort signal passed
if (signal == null) {
timeoutController = new TimeoutController(this.init.timeout)
signal = timeoutController.signal
try {
// fails on node < 15.4
setMaxListeners?.(Infinity, timeoutController.signal)
} catch {}
}
try {
stream = await connection.newStream([this.identifyProtocolStr], {
signal
})
// make stream abortable
const source = abortableDuplex(stream, signal)
const data = await pipe(
[],
source,
lp.decode({
maxDataLength: this.init.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE
}),
async (source) => await first(source)
)
if (data == null) {
throw errCode(new Error('No data could be retrieved'), codes.ERR_CONNECTION_ENDED)
}
try {
return Identify.decode(data)
} catch (err: any) {
throw errCode(err, codes.ERR_INVALID_MESSAGE)
}
} finally {
if (timeoutController != null) {
timeoutController.clear()
}
if (stream != null) {
stream.close()
}
}
}
/**
* Requests the `Identify` message from peer associated with the given `connection`.
* If the identified peer does not match the `PeerId` associated with the connection,
* an error will be thrown.
*/
async identify (connection: Connection): Promise<void> {
const { stream } = await connection.newStream([this.identifyProtocolStr])
const [data] = await pipe(
[],
stream,
lp.decode(),
(source) => take(source, 1),
async (source) => await all(source)
)
if (data == null) {
throw errCode(new Error('No data could be retrieved'), codes.ERR_CONNECTION_ENDED)
}
let message
try {
message = Message.Identify.decode(data)
} catch (err: any) {
throw errCode(err, codes.ERR_INVALID_MESSAGE)
}
async identify (connection: Connection, options: AbortOptions = {}): Promise<void> {
const message = await this._identify(connection, options)
const {
publicKey,
@@ -264,7 +354,7 @@ export class IdentifyService implements Startable {
// LEGACY: Update peers data in PeerStore
try {
await this.components.getPeerStore().addressBook.set(id, listenAddrs.map((addr) => new Multiaddr(addr)))
await this.components.getPeerStore().addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
} catch (err: any) {
log.error('received invalid addrs', err)
}
@@ -286,28 +376,19 @@ export class IdentifyService implements Startable {
// this.components.getAddressManager().addObservedAddr(observedAddr)
}
/**
* A handler to register with Libp2p to process identify messages
*/
handleMessage (data: IncomingStreamData) {
const { protocol } = data
switch (protocol) {
case this.identifyProtocolStr:
return this._handleIdentify(data)
case this.identifyPushProtocolStr:
return this._handlePush(data)
default:
log.error('cannot handle unknown protocol %s', protocol)
}
}
/**
* Sends the `Identify` response with the Signed Peer Record
* to the requesting peer over the given `connection`
*/
async _handleIdentify (data: IncomingStreamData) {
const { connection, stream } = data
const timeoutController = new TimeoutController(this.init.timeout)
try {
// fails on node < 15.4
setMaxListeners?.(Infinity, timeoutController.signal)
} catch {}
try {
const publicKey = this.components.getPeerId().publicKey ?? new Uint8Array(0)
const peerData = await this.components.getPeerStore().get(this.components.getPeerId())
@@ -322,10 +403,10 @@ export class IdentifyService implements Startable {
const envelope = await RecordEnvelope.seal(peerRecord, this.components.getPeerId())
await this.components.getPeerStore().addressBook.consumePeerRecord(envelope)
signedPeerRecord = envelope.marshal()
signedPeerRecord = envelope.marshal().subarray()
}
const message = Message.Identify.encode({
const message = Identify.encode({
protocolVersion: this.host.protocolVersion,
agentVersion: this.host.agentVersion,
publicKey,
@@ -333,16 +414,22 @@ export class IdentifyService implements Startable {
signedPeerRecord,
observedAddr: connection.remoteAddr.bytes,
protocols: peerData.protocols
}).finish()
})
// make stream abortable
const source = abortableDuplex(stream, timeoutController.signal)
await pipe(
[message],
lp.encode(),
stream,
source,
drain
)
} catch (err: any) {
log.error('could not respond to identify request', err)
} finally {
stream.close()
timeoutController.clear()
}
}
@@ -351,21 +438,35 @@ export class IdentifyService implements Startable {
*/
async _handlePush (data: IncomingStreamData) {
const { connection, stream } = data
const timeoutController = new TimeoutController(this.init.timeout)
let message
try {
// fails on node < 15.4
setMaxListeners?.(Infinity, timeoutController.signal)
} catch {}
let message: Identify | undefined
try {
// make stream abortable
const source = abortableDuplex(stream, timeoutController.signal)
const data = await pipe(
[],
stream,
lp.decode(),
source,
lp.decode({
maxDataLength: this.init.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE
}),
async (source) => await first(source)
)
if (data != null) {
message = Message.Identify.decode(data)
message = Identify.decode(data)
}
} catch (err: any) {
return log.error('received invalid message', err)
} finally {
stream.close()
timeoutController.clear()
}
if (message == null) {
@@ -405,7 +506,7 @@ export class IdentifyService implements Startable {
// LEGACY: Update peers data in PeerStore
try {
await this.components.getPeerStore().addressBook.set(id,
message.listenAddrs.map((addr) => new Multiaddr(addr)))
message.listenAddrs.map((addr) => multiaddr(addr)))
} catch (err: any) {
log.error('received invalid addrs', err)
}
@@ -426,7 +527,7 @@ export class IdentifyService implements Startable {
static getCleanMultiaddr (addr: Uint8Array | string | null | undefined) {
if (addr != null && addr.length > 0) {
try {
return new Multiaddr(addr)
return multiaddr(addr)
} catch {
}
@@ -442,4 +543,4 @@ export const multicodecs = {
IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH
}
export { Message }
export const Message = { Identify }

View File

@@ -1,110 +0,0 @@
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|null);
/** Identify agentVersion. */
public agentVersion?: (string|null);
/** Identify publicKey. */
public publicKey?: (Uint8Array|null);
/** Identify listenAddrs. */
public listenAddrs: Uint8Array[];
/** Identify observedAddr. */
public observedAddr?: (Uint8Array|null);
/** Identify protocols. */
public protocols: string[];
/** Identify signedPeerRecord. */
public signedPeerRecord?: (Uint8Array|null);
/** Identify _protocolVersion. */
public _protocolVersion?: "protocolVersion";
/** Identify _agentVersion. */
public _agentVersion?: "agentVersion";
/** Identify _publicKey. */
public _publicKey?: "publicKey";
/** Identify _observedAddr. */
public _observedAddr?: "observedAddr";
/** Identify _signedPeerRecord. */
public _signedPeerRecord?: "signedPeerRecord";
/**
* 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,369 +0,0 @@
/*eslint-disable*/
import $protobuf from "protobufjs/minimal.js";
// Common aliases
const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
// Exported root namespace
const $root = $protobuf.roots["libp2p-identify"] || ($protobuf.roots["libp2p-identify"] = {});
export const Identify = $root.Identify = (() => {
/**
* 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
*/
/**
* 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]];
}
/**
* Identify protocolVersion.
* @member {string|null|undefined} protocolVersion
* @memberof Identify
* @instance
*/
Identify.prototype.protocolVersion = null;
/**
* Identify agentVersion.
* @member {string|null|undefined} agentVersion
* @memberof Identify
* @instance
*/
Identify.prototype.agentVersion = null;
/**
* Identify publicKey.
* @member {Uint8Array|null|undefined} publicKey
* @memberof Identify
* @instance
*/
Identify.prototype.publicKey = null;
/**
* Identify listenAddrs.
* @member {Array.<Uint8Array>} listenAddrs
* @memberof Identify
* @instance
*/
Identify.prototype.listenAddrs = $util.emptyArray;
/**
* Identify observedAddr.
* @member {Uint8Array|null|undefined} observedAddr
* @memberof Identify
* @instance
*/
Identify.prototype.observedAddr = null;
/**
* Identify protocols.
* @member {Array.<string>} protocols
* @memberof Identify
* @instance
*/
Identify.prototype.protocols = $util.emptyArray;
/**
* Identify signedPeerRecord.
* @member {Uint8Array|null|undefined} signedPeerRecord
* @memberof Identify
* @instance
*/
Identify.prototype.signedPeerRecord = null;
// OneOf field names bound to virtual getters and setters
let $oneOfFields;
/**
* Identify _protocolVersion.
* @member {"protocolVersion"|undefined} _protocolVersion
* @memberof Identify
* @instance
*/
Object.defineProperty(Identify.prototype, "_protocolVersion", {
get: $util.oneOfGetter($oneOfFields = ["protocolVersion"]),
set: $util.oneOfSetter($oneOfFields)
});
/**
* Identify _agentVersion.
* @member {"agentVersion"|undefined} _agentVersion
* @memberof Identify
* @instance
*/
Object.defineProperty(Identify.prototype, "_agentVersion", {
get: $util.oneOfGetter($oneOfFields = ["agentVersion"]),
set: $util.oneOfSetter($oneOfFields)
});
/**
* Identify _publicKey.
* @member {"publicKey"|undefined} _publicKey
* @memberof Identify
* @instance
*/
Object.defineProperty(Identify.prototype, "_publicKey", {
get: $util.oneOfGetter($oneOfFields = ["publicKey"]),
set: $util.oneOfSetter($oneOfFields)
});
/**
* Identify _observedAddr.
* @member {"observedAddr"|undefined} _observedAddr
* @memberof Identify
* @instance
*/
Object.defineProperty(Identify.prototype, "_observedAddr", {
get: $util.oneOfGetter($oneOfFields = ["observedAddr"]),
set: $util.oneOfSetter($oneOfFields)
});
/**
* Identify _signedPeerRecord.
* @member {"signedPeerRecord"|undefined} _signedPeerRecord
* @memberof Identify
* @instance
*/
Object.defineProperty(Identify.prototype, "_signedPeerRecord", {
get: $util.oneOfGetter($oneOfFields = ["signedPeerRecord"]),
set: $util.oneOfSetter($oneOfFields)
});
/**
* 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 (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 (o.oneofs)
d._publicKey = "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 (o.oneofs)
d._observedAddr = "observedAddr";
}
if (m.protocolVersion != null && m.hasOwnProperty("protocolVersion")) {
d.protocolVersion = m.protocolVersion;
if (o.oneofs)
d._protocolVersion = "protocolVersion";
}
if (m.agentVersion != null && m.hasOwnProperty("agentVersion")) {
d.agentVersion = m.agentVersion;
if (o.oneofs)
d._agentVersion = "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;
if (o.oneofs)
d._signedPeerRecord = "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;
})();
export { $root as default };

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