mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-07-11 06:41:35 +00:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
6ac62da025 | |||
806804ac88 | |||
0ecc02b2a4 | |||
ec02351e65 | |||
3f57edaf3b | |||
62198414b3 | |||
a11260c753 | |||
a4ac534252 | |||
633d4a9740 | |||
a4566ede92 | |||
111e75d05e | |||
0218acfae2 | |||
b87632f97f | |||
dd14f82ed5 | |||
43b0418998 | |||
d63e08115b | |||
57ef75493d | |||
d281a60dac | |||
fc2224a1e8 | |||
0e7096d527 | |||
1f38ab7ac8 | |||
29c803a63e | |||
41990421bf | |||
509e56a603 | |||
886759b7fb | |||
2262f81924 | |||
6630cb19b9 | |||
8880eefa8f | |||
f439d9b589 | |||
564f4b8aa7 | |||
05e8e7ead9 | |||
3c0fb13bab | |||
54450d4342 | |||
627b8bf87c | |||
ba56c64662 | |||
0bb1b802c8 | |||
6eaab2e3ee | |||
750ed9c35f | |||
b1b91398e2 | |||
e6f646ed36 | |||
5af93883ce | |||
2836acc90f | |||
b1b2b216da | |||
ceb44f9e98 | |||
b270527c8f | |||
676cee2947 | |||
a5077cbc6b | |||
de30c2cec7 | |||
5371729646 | |||
d4dd664071 | |||
13d95b413c | |||
b0472686d2 | |||
f9073ecd21 | |||
eee256db8a | |||
3babbbd75a | |||
893f8c280f | |||
824720fb8f |
@ -1,6 +1,6 @@
|
|||||||
import { WebSockets } from '@libp2p/websockets'
|
import { WebSockets } from '@libp2p/websockets'
|
||||||
import { Mplex } from '@libp2p/mplex'
|
import { Mplex } from '@libp2p/mplex'
|
||||||
import { NOISE } from '@chainsafe/libp2p-noise'
|
import { Noise } from '@chainsafe/libp2p-noise'
|
||||||
import { pipe } from 'it-pipe'
|
import { pipe } from 'it-pipe'
|
||||||
import { createFromJSON } from '@libp2p/peer-id-factory'
|
import { createFromJSON } from '@libp2p/peer-id-factory'
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ export default {
|
|||||||
new Mplex()
|
new Mplex()
|
||||||
],
|
],
|
||||||
connectionEncryption: [
|
connectionEncryption: [
|
||||||
NOISE,
|
new Noise(),
|
||||||
new Plaintext()
|
new Plaintext()
|
||||||
],
|
],
|
||||||
relay: {
|
relay: {
|
||||||
|
26
.github/workflows/stale.yml
vendored
Normal file
26
.github/workflows/stale.yml
vendored
Normal 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
2
.gitignore
vendored
@ -10,7 +10,7 @@ test/repo-tests*
|
|||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
coverage
|
.coverage
|
||||||
.nyc_output
|
.nyc_output
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
|
89
CHANGELOG.md
89
CHANGELOG.md
@ -10,6 +10,95 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### [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)
|
### [0.37.1](https://www.github.com/libp2p/js-libp2p/compare/v0.37.0...v0.37.1) (2022-05-25)
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ We are in the process of writing better documentation, blog posts, tutorials and
|
|||||||
- [Specification (WIP)](https://github.com/libp2p/specs)
|
- [Specification (WIP)](https://github.com/libp2p/specs)
|
||||||
- [Discussion Forums](https://discuss.libp2p.io)
|
- [Discussion Forums](https://discuss.libp2p.io)
|
||||||
- Talks
|
- 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
|
- Articles
|
||||||
- [The overview of libp2p](https://github.com/libp2p/libp2p#description)
|
- [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
|
> git clone https://github.com/libp2p/js-libp2p.git
|
||||||
> cd js-libp2p
|
> cd js-libp2p
|
||||||
> npm install
|
> npm install
|
||||||
|
> npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
|
145
doc/API.md
145
doc/API.md
@ -46,6 +46,9 @@
|
|||||||
* [`peerStore.delete`](#peerstoredelete)
|
* [`peerStore.delete`](#peerstoredelete)
|
||||||
* [`peerStore.get`](#peerstoreget)
|
* [`peerStore.get`](#peerstoreget)
|
||||||
* [`peerStore.peers`](#peerstorepeers)
|
* [`peerStore.peers`](#peerstorepeers)
|
||||||
|
* [`peerStore.tagPeer`](#peerstoretagpeer)
|
||||||
|
* [`peerStore.unTagPeer`](#peerstoreuntagpeer)
|
||||||
|
* [`peerStore.getTags`](#peerstoregettags)
|
||||||
* [`pubsub.getSubscribers`](#pubsubgetsubscribers)
|
* [`pubsub.getSubscribers`](#pubsubgetsubscribers)
|
||||||
* [`pubsub.getTopics`](#pubsubgettopics)
|
* [`pubsub.getTopics`](#pubsubgettopics)
|
||||||
* [`pubsub.publish`](#pubsubpublish)
|
* [`pubsub.publish`](#pubsubpublish)
|
||||||
@ -56,7 +59,6 @@
|
|||||||
* [`pubsub.topicValidators.set`](#pubsubtopicvalidatorsset)
|
* [`pubsub.topicValidators.set`](#pubsubtopicvalidatorsset)
|
||||||
* [`pubsub.topicValidators.delete`](#pubsubtopicvalidatorsdelete)
|
* [`pubsub.topicValidators.delete`](#pubsubtopicvalidatorsdelete)
|
||||||
* [`connectionManager.get`](#connectionmanagerget)
|
* [`connectionManager.get`](#connectionmanagerget)
|
||||||
* [`connectionManager.setPeerValue`](#connectionmanagersetpeervalue)
|
|
||||||
* [`connectionManager.size`](#connectionmanagersize)
|
* [`connectionManager.size`](#connectionmanagersize)
|
||||||
* [`keychain.createKey`](#keychaincreatekey)
|
* [`keychain.createKey`](#keychaincreatekey)
|
||||||
* [`keychain.renameKey`](#keychainrenamekey)
|
* [`keychain.renameKey`](#keychainrenamekey)
|
||||||
@ -97,7 +99,7 @@ Creates an instance of Libp2p.
|
|||||||
| options.modules | [`Array<object>`](./CONFIGURATION.md#modules) | libp2p [modules](./CONFIGURATION.md#modules) to use |
|
| 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.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.config] | `object` | libp2p modules configuration and core configuration |
|
||||||
| [options.identify] | `{ protocolPrefix: string, host: { agentVersion: string }, timeout: number }` | libp2p identify protocol 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.ping] | `{ protocolPrefix: string }` | libp2p ping protocol options |
|
||||||
| [options.fetch] | `{ protocolPrefix: string }` | libp2p fetch 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.connectionManager] | [`object`](./CONFIGURATION.md#configuring-connection-manager) | libp2p Connection Manager [configuration](./CONFIGURATION.md#configuring-connection-manager) |
|
||||||
@ -183,36 +185,6 @@ Required keys in the `options` object:
|
|||||||
|
|
||||||
## Libp2p Instance Methods
|
## 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
|
### start
|
||||||
|
|
||||||
Starts the libp2p node.
|
Starts the libp2p node.
|
||||||
@ -389,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.
|
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.
|
In the event of a new handler for the same protocol being added, the first one is discarded.
|
||||||
|
|
||||||
@ -399,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 |
|
| protocols | `Array<string>|string` | protocols to register |
|
||||||
| handler | `function({ connection:*, stream:*, protocol:string })` | handler to call |
|
| handler | `function({ connection:*, stream:*, protocol:string })` | handler to call |
|
||||||
|
| options | `StreamHandlerOptions` | Options including protocol stream limits |
|
||||||
|
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
@ -409,7 +382,10 @@ const handler = ({ connection, stream, protocol }) => {
|
|||||||
// use stream or connection according to the needs
|
// 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
|
### unhandle
|
||||||
@ -1395,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
|
### pubsub.getSubscribers
|
||||||
|
|
||||||
Gets a list of the peer-ids that are subscribed to one topic.
|
Gets a list of the peer-ids that are subscribed to one topic.
|
||||||
@ -1668,32 +1719,6 @@ Get a connection with a given peer, if it exists.
|
|||||||
libp2p.connectionManager.get(peerId)
|
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
|
### connectionManager.size
|
||||||
|
|
||||||
Getter for obtaining the current number of open connections.
|
Getter for obtaining the current number of open connections.
|
||||||
|
@ -247,7 +247,7 @@ import { GossipSub } from 'libp2p-gossipsub'
|
|||||||
const node = await createLibp2p({
|
const node = await createLibp2p({
|
||||||
transports: [
|
transports: [
|
||||||
new TCP(),
|
new TCP(),
|
||||||
new WS()
|
new WebSockets()
|
||||||
],
|
],
|
||||||
streamMuxers: [new Mplex()],
|
streamMuxers: [new Mplex()],
|
||||||
connectionEncryption: [new Noise()],
|
connectionEncryption: [new Noise()],
|
||||||
@ -323,7 +323,7 @@ import { TCP } from '@libp2p/tcp'
|
|||||||
import { Mplex } from '@libp2p/mplex'
|
import { Mplex } from '@libp2p/mplex'
|
||||||
import { Noise } from '@chainsafe/libp2p-noise'
|
import { Noise } from '@chainsafe/libp2p-noise'
|
||||||
import { GossipSub } from 'libp2p-gossipsub'
|
import { GossipSub } from 'libp2p-gossipsub'
|
||||||
import { SignaturePolicy } from '@libp2p/interfaces/pubsub'
|
import { SignaturePolicy } from '@libp2p/interface-pubsub'
|
||||||
|
|
||||||
const node = await createLibp2p({
|
const node = await createLibp2p({
|
||||||
transports: [
|
transports: [
|
||||||
@ -494,8 +494,6 @@ const node = await createLibp2p({
|
|||||||
datastore: dsInstant,
|
datastore: dsInstant,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
await node.loadKeychain()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Configuring Dialing
|
#### 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. |
|
| 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 |
|
| 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. |
|
| 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:
|
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,
|
maxConnections: Infinity,
|
||||||
minConnections: 0,
|
minConnections: 0,
|
||||||
pollInterval: 2000,
|
pollInterval: 2000,
|
||||||
defaultPeerValue: 1,
|
|
||||||
// The below values will only be taken into account when Metrics are enabled
|
// The below values will only be taken into account when Metrics are enabled
|
||||||
maxData: Infinity,
|
maxData: Infinity,
|
||||||
maxSentData: Infinity,
|
maxSentData: Infinity,
|
||||||
|
@ -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`.
|
- `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).
|
- `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`
|
- `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.
|
|
||||||
|
@ -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.
|
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
|
```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:
|
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.
|
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
|
```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
|
```js
|
||||||
import { createLibp2p } from 'libp2p'
|
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.
|
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
|
```sh
|
||||||
npm install libp2p-mplex
|
npm install @libp2p/mplex
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -148,12 +148,9 @@ const node = await createLibp2p({
|
|||||||
await node.start()
|
await node.start()
|
||||||
console.log('libp2p has started')
|
console.log('libp2p has started')
|
||||||
|
|
||||||
const listenAddrs = node.transportManager.getAddrs()
|
const listenAddrs = node.getMultiaddrs()
|
||||||
console.log('libp2p is listening on the following addresses: ', listenAddrs)
|
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
|
// stop libp2p
|
||||||
await node.stop()
|
await node.stop()
|
||||||
console.log('libp2p has stopped')
|
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.
|
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:
|
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 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 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.
|
- 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.
|
- 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
|
```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.
|
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) => {
|
node.addEventListener('peer:discovery', (evt) => {
|
||||||
console.log('Discovered %s', peer.id.toB58String()) // Log discovered peer
|
console.log('Discovered %s', evt.detail.id.toString()) // Log discovered peer
|
||||||
})
|
})
|
||||||
|
|
||||||
node.connectionManager.on('peer:connect', (connection) => {
|
node.connectionManager.addEventListener('peer:connect', (evt) => {
|
||||||
console.log('Connected to %s', connection.remotePeer.toB58String()) // Log connected peer
|
console.log('Connected to %s', evt.detail.remotePeer.toString()) // Log connected peer
|
||||||
})
|
})
|
||||||
|
|
||||||
// start libp2p
|
// start libp2p
|
||||||
|
@ -240,7 +240,7 @@ libp2p.pubsub.off('my-topic', handler)
|
|||||||
**After**
|
**After**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import type { Message } from '@libp2p/interfaces/pubsub'
|
import type { Message } from '@libp2p/interface-pubsub'
|
||||||
|
|
||||||
const handler = (event: CustomEvent<Message>) => {
|
const handler = (event: CustomEvent<Message>) => {
|
||||||
const message = event.detail
|
const message = event.detail
|
||||||
|
@ -5,7 +5,7 @@ While direct connections to nodes are preferable, it's not always possible to do
|
|||||||
|
|
||||||
## 0. Setup the example
|
## 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.
|
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({
|
const node = await createLibp2p({
|
||||||
transports: [new WebSockets()],
|
transports: [new WebSockets()],
|
||||||
connectionEncryption: [new Noise()],
|
connectionEncryption: [new Noise()],
|
||||||
streamMuxers: [new Mplex()]
|
streamMuxers: [new Mplex()],
|
||||||
addresses: {
|
addresses: {
|
||||||
listen: ['/ip4/0.0.0.0/tcp/0/ws']
|
listen: ['/ip4/0.0.0.0/tcp/0/ws']
|
||||||
// TODO check "What is next?" section
|
// TODO check "What is next?" section
|
||||||
@ -43,9 +43,9 @@ const node = await createLibp2p({
|
|||||||
|
|
||||||
await node.start()
|
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:')
|
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.
|
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()
|
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)
|
const conn = await node.dial(relayAddr)
|
||||||
|
|
||||||
console.log(`Connected to the HOP relay ${conn.remotePeer.toString()}`)
|
console.log(`Connected to the HOP relay ${conn.remotePeer.toString()}`)
|
||||||
|
|
||||||
// Wait for connection and relay to be bind for the example purpose
|
// 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?
|
// Updated self multiaddrs?
|
||||||
if (peerId.equals(node.peerId)) {
|
if (evt.detail.peerId.equals(node.peerId)) {
|
||||||
console.log(`Advertising with a relay address of ${node.multiaddrs[0].toString()}/p2p/${node.peerId.toB58String()}`)
|
console.log(`Advertising with a relay address of ${node.getMultiaddrs()[0].toString()}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
@ -151,7 +151,7 @@ const node = await createLibp2p({
|
|||||||
})
|
})
|
||||||
|
|
||||||
await node.start()
|
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)
|
const conn = await node.dial(autoRelayNodeAddr)
|
||||||
console.log(`Connected to the auto relay node via ${conn.remoteAddr.toString()}`)
|
console.log(`Connected to the auto relay node via ${conn.remoteAddr.toString()}`)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import execa from 'execa'
|
import { execa } from 'execa'
|
||||||
import pDefer from 'p-defer'
|
import pDefer from 'p-defer'
|
||||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
This example creates a simple chat app in your terminal.
|
This example creates a simple chat app in your terminal.
|
||||||
|
|
||||||
## Setup
|
## 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.
|
2. Open 2 terminal windows in the `./examples/chat/src` directory.
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
import { Multiaddr } from '@multiformats/multiaddr'
|
import { multiaddr } from '@multiformats/multiaddr'
|
||||||
import { createLibp2p } from './libp2p.js'
|
import { createLibp2p } from './libp2p.js'
|
||||||
import { stdinToStream, streamToConsole } from './stream.js'
|
import { stdinToStream, streamToConsole } from './stream.js'
|
||||||
import { createFromJSON } from '@libp2p/peer-id-factory'
|
import { createFromJSON } from '@libp2p/peer-id-factory'
|
||||||
@ -31,8 +31,8 @@ async function run () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Dial to the remote peer (the "listener")
|
// Dial to the remote peer (the "listener")
|
||||||
const listenerMa = new Multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toString()}`)
|
const listenerMa = multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toString()}`)
|
||||||
const { stream } = await nodeDialer.dialProtocol(listenerMa, '/chat/1.0.0')
|
const stream = await nodeDialer.dialProtocol(listenerMa, '/chat/1.0.0')
|
||||||
|
|
||||||
console.log('Dialer dialed to listener on protocol: /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')
|
console.log('Type a message and see what happens')
|
||||||
|
@ -28,7 +28,7 @@ export function streamToConsole(stream) {
|
|||||||
// Decode length-prefixed data
|
// Decode length-prefixed data
|
||||||
lp.decode(),
|
lp.decode(),
|
||||||
// Turn buffers into strings
|
// Turn buffers into strings
|
||||||
(source) => map(source, (buf) => uint8ArrayToString(buf)),
|
(source) => map(source, (buf) => uint8ArrayToString(buf.subarray())),
|
||||||
// Sink function
|
// Sink function
|
||||||
async function (source) {
|
async function (source) {
|
||||||
// For each chunk of data
|
// For each chunk of data
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import execa from 'execa'
|
import { execa } from 'execa'
|
||||||
import pDefer from 'p-defer'
|
import pDefer from 'p-defer'
|
||||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
@ -34,13 +34,13 @@ const createNode = async () => {
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of 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(
|
await pipe(
|
||||||
[uint8ArrayFromString('This information is sent out encrypted to the other peer')],
|
[uint8ArrayFromString('This information is sent out encrypted to the other peer')],
|
||||||
|
@ -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
|
# 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
|
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.
|
||||||
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.
|
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
|
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.
|
||||||
various Peer Discovery modules and see the impact it has on your Peer count.
|
|
||||||
|
|
||||||
## Prerequisite
|
## 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
|
## Running this example
|
||||||
|
|
||||||
1. Install IPFS locally if you dont already have it. [Install Guide](https://docs.ipfs.io/introduction/install/)
|
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`
|
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`
|
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.
|
||||||
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.
|
- If there is no websocket address, you will need to add it in the ipfs config file (`~/.ipfs/config`)
|
||||||
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.
|
- Add to Swarm Addresses something like: `"/ip4/127.0.0.1/tcp/4010/ws"`
|
||||||
6. In `./src/App.js` replace `BootstrapNode` with your nodes Websocket address from step 4.
|
4. In `./src/App.js` replace `BootstrapNode` with your nodes Websocket address from the step above.
|
||||||
7. Start this example:
|
5. Start this example
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install
|
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.
|
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
|
### 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.
|
1. Add a file to your IPFS node. From this example root you can do `ipfs add ./README.md` to add the example readme.
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chainsafe/libp2p-noise": "^6.2.0",
|
"@chainsafe/libp2p-noise": "^8.0.1",
|
||||||
"ipfs-core": "^0.14.1",
|
"ipfs-core": "^0.15.4",
|
||||||
"libp2p": "../../",
|
"libp2p": "../../",
|
||||||
"@libp2p/delegated-content-routing": "^1.0.1",
|
"@libp2p/delegated-content-routing": "^2.0.1",
|
||||||
"@libp2p/delegated-peer-routing": "^1.0.1",
|
"@libp2p/delegated-peer-routing": "^2.0.1",
|
||||||
"@libp2p/kad-dht": "^1.0.9",
|
"@libp2p/kad-dht": "^3.0.0",
|
||||||
"@libp2p/mplex": "^1.0.4",
|
"@libp2p/mplex": "^5.2.3",
|
||||||
"@libp2p/webrtc-star": "^1.0.8",
|
"@libp2p/webrtc-star": "^3.0.3",
|
||||||
"@libp2p/websockets": "^1.0.7",
|
"@libp2p/websockets": "^3.0.4",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-scripts": "5.0.0"
|
"react-scripts": "5.0.0"
|
||||||
|
@ -67,7 +67,7 @@ const createNode = async (bootstrappers) => {
|
|||||||
const peer = evt.detail
|
const peer = evt.detail
|
||||||
console.log(`Peer ${node1.peerId.toString()} discovered: ${peer.id.toString()}`)
|
console.log(`Peer ${node1.peerId.toString()} discovered: ${peer.id.toString()}`)
|
||||||
})
|
})
|
||||||
node2.addEventListener('peer:discovery',(evt) => {
|
node2.addEventListener('peer:discovery', (evt) => {
|
||||||
const peer = evt.detail
|
const peer = evt.detail
|
||||||
console.log(`Peer ${node2.peerId.toString()} discovered: ${peer.id.toString()}`)
|
console.log(`Peer ${node2.peerId.toString()} discovered: ${peer.id.toString()}`)
|
||||||
})
|
})
|
||||||
@ -77,4 +77,4 @@ const createNode = async (bootstrappers) => {
|
|||||||
node1.start(),
|
node1.start(),
|
||||||
node2.start()
|
node2.start()
|
||||||
])
|
])
|
||||||
})();
|
})()
|
||||||
|
@ -15,6 +15,9 @@ First, we create our libp2p node.
|
|||||||
```JavaScript
|
```JavaScript
|
||||||
import { createLibp2p } from 'libp2p'
|
import { createLibp2p } from 'libp2p'
|
||||||
import { Bootstrap } from '@libp2p/bootstrap'
|
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({
|
const node = await createLibp2p({
|
||||||
transports: [
|
transports: [
|
||||||
@ -52,7 +55,6 @@ Now, once we create and start the node, we can listen for events such as `peer:d
|
|||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
const node = await createLibp2p({
|
const node = await createLibp2p({
|
||||||
peerId,
|
|
||||||
addresses: {
|
addresses: {
|
||||||
listen: ['/ip4/0.0.0.0/tcp/0']
|
listen: ['/ip4/0.0.0.0/tcp/0']
|
||||||
},
|
},
|
||||||
@ -73,13 +75,13 @@ const node = await createLibp2p({
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
node.connectionManager.on('peer:connect', (connection) => {
|
node.connectionManager.addEventListener('peer:connect', (evt) => {
|
||||||
console.log('Connection established to:', connection.remotePeer.toB58String()) // Emitted when a new connection has been created
|
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
|
// No need to dial, autoDial is on
|
||||||
console.log('Discovered:', peerId.toB58String())
|
console.log('Discovered:', evt.detail.id.toString())
|
||||||
})
|
})
|
||||||
|
|
||||||
await node.start()
|
await node.start()
|
||||||
@ -105,16 +107,19 @@ Connection established to: QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb
|
|||||||
|
|
||||||
## 2. MulticastDNS to find other peers in the network
|
## 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.
|
Update your libp2p configuration to include MulticastDNS.
|
||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
import { createLibp2p } from 'libp2p'
|
import { createLibp2p } from 'libp2p'
|
||||||
import { MulticastDNS } from '@libp2p/mdns'
|
import { MulticastDNS } from '@libp2p/mdns'
|
||||||
|
import { TCP } from '@libp2p/tcp'
|
||||||
|
import { Mplex } from '@libp2p/mplex'
|
||||||
|
import { Noise } from '@chainsafe/libp2p-noise'
|
||||||
|
|
||||||
const createNode = () => {
|
const createNode = () => {
|
||||||
return Libp2p.create({
|
return createLibp2p({
|
||||||
addresses: {
|
addresses: {
|
||||||
listen: ['/ip4/0.0.0.0/tcp/0']
|
listen: ['/ip4/0.0.0.0/tcp/0']
|
||||||
},
|
},
|
||||||
@ -144,8 +149,8 @@ const [node1, node2] = await Promise.all([
|
|||||||
createNode()
|
createNode()
|
||||||
])
|
])
|
||||||
|
|
||||||
node1.on('peer:discovery', (peer) => console.log('Discovered:', peerId.toB58String()))
|
node1.addEventListener('peer:discovery', (evt) => console.log('Discovered:', evt.detail.id.toString()))
|
||||||
node2.on('peer:discovery', (peer) => console.log('Discovered:', peerId.toB58String()))
|
node2.addEventListener('peer:discovery', (evt) => console.log('Discovered:', evt.detail.id.toString()))
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
node1.start(),
|
node1.start(),
|
||||||
@ -163,7 +168,7 @@ Discovered: QmRcXXhtG8vTqwVBRonKWtV4ovDoC1Fe56WYtcrw694eiJ
|
|||||||
|
|
||||||
## 3. Pubsub based Peer Discovery
|
## 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).
|
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 { TCP } from '@libp2p/tcp'
|
||||||
import { Mplex } from '@libp2p/mplex'
|
import { Mplex } from '@libp2p/mplex'
|
||||||
import { Noise } from '@chainsafe/libp2p-noise'
|
import { Noise } from '@chainsafe/libp2p-noise'
|
||||||
import { Gossipsub } from 'libp2p-gossipsub'
|
import { GossipSub } from '@chainsafe/libp2p-gossipsub'
|
||||||
import { Bootstrap } from '@libp2p/bootstrap'
|
import { Bootstrap } from '@libp2p/bootstrap'
|
||||||
const PubsubPeerDiscovery from 'libp2p-pubsub-peer-discovery')
|
import { PubSubPeerDiscovery } from '@libp2p/pubsub-peer-discovery'
|
||||||
|
|
||||||
const createNode = async (bootstrapers) => {
|
const createNode = async (bootstrapers) => {
|
||||||
const node = await createLibp2p({
|
const node = await createLibp2p({
|
||||||
@ -184,23 +189,25 @@ const createNode = async (bootstrapers) => {
|
|||||||
listen: ['/ip4/0.0.0.0/tcp/0']
|
listen: ['/ip4/0.0.0.0/tcp/0']
|
||||||
},
|
},
|
||||||
transports: [
|
transports: [
|
||||||
new TCP()
|
new TCP()
|
||||||
],
|
],
|
||||||
streamMuxers: [
|
streamMuxers: [
|
||||||
new Mplex()
|
new Mplex()
|
||||||
],
|
],
|
||||||
connectionEncryption: [
|
connectionEncryption: [
|
||||||
new Noise()
|
new Noise()
|
||||||
],
|
],
|
||||||
peerDiscovery: [
|
pubsub: new GossipSub({ allowPublishToZeroPeers: true }),
|
||||||
new Bootstrap({
|
peerDiscovery: [
|
||||||
interval: 60e3,
|
new Bootstrap({
|
||||||
list: bootstrapers
|
interval: 60e3,
|
||||||
}),
|
list: bootstrapers
|
||||||
new PubsubPeerDiscovery({
|
}),
|
||||||
interval: 1000
|
new PubSubPeerDiscovery({
|
||||||
})
|
interval: 1000
|
||||||
])
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
return node
|
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.
|
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
|
```js
|
||||||
const relay = await createRelayServer({
|
const relay = await createLibp2p({
|
||||||
addresses: {
|
addresses: {
|
||||||
listen: ['/ip4/0.0.0.0/tcp/0']
|
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()
|
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([
|
const [node1, node2] = await Promise.all([
|
||||||
createNode(relayMultiaddrs),
|
createNode(relayMultiaddrs),
|
||||||
createNode(relayMultiaddrs)
|
createNode(relayMultiaddrs)
|
||||||
])
|
])
|
||||||
|
|
||||||
node1.on('peer:discovery', (peerId) => {
|
node1.addEventListener('peer:discovery', (evt) => {
|
||||||
console.log(`Peer ${node1.peerId.toB58String()} discovered: ${peerId.toB58String()}`)
|
console.log(`Peer ${node1.peerId.toString()} discovered: ${evt.detail.id.toString()}`)
|
||||||
})
|
})
|
||||||
node2.on('peer:discovery', (peerId) => {
|
node2.addEventListener('peer:discovery', (evt) => {
|
||||||
console.log(`Peer ${node2.peerId.toB58String()} discovered: ${peerId.toB58String()}`)
|
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([
|
await Promise.all([
|
||||||
node1.start(),
|
node1.start(),
|
||||||
node2.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:
|
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.
|
- 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).
|
- 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!
|
- You can create your own Discovery service, a registry, a list, a radio beacon, you name it!
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import execa from 'execa'
|
import { execa } from 'execa'
|
||||||
import pWaitFor from 'p-wait-for'
|
import pWaitFor from 'p-wait-for'
|
||||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
@ -27,7 +27,7 @@ export async function test () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
await pWaitFor(() => discoveredNodes > 1)
|
await pWaitFor(() => discoveredNodes > 1, 600000)
|
||||||
|
|
||||||
proc.kill()
|
proc.kill()
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import execa from 'execa'
|
import { execa } from 'execa'
|
||||||
import pWaitFor from 'p-wait-for'
|
import pWaitFor from 'p-wait-for'
|
||||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
@ -7,7 +7,7 @@ import { fileURLToPath } from 'url'
|
|||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
export async function test () {
|
export async function test () {
|
||||||
let discoveredNodes = 0
|
const discoveredPeers = []
|
||||||
|
|
||||||
process.stdout.write('3.js\n')
|
process.stdout.write('3.js\n')
|
||||||
|
|
||||||
@ -19,15 +19,20 @@ export async function test () {
|
|||||||
proc.all.on('data', async (data) => {
|
proc.all.on('data', async (data) => {
|
||||||
process.stdout.write(data)
|
process.stdout.write(data)
|
||||||
const str = uint8ArrayToString(data)
|
const str = uint8ArrayToString(data)
|
||||||
|
const discoveredPeersRegex = /Peer\s+(?<Peer1>[^\s]+)\s+discovered:\s+(?<Peer2>[^\s]+)/
|
||||||
str.split('\n').forEach(line => {
|
str.split('\n').forEach(line => {
|
||||||
if (line.includes('discovered:')) {
|
const peers = line.match(discoveredPeersRegex)
|
||||||
discoveredNodes++
|
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()
|
proc.kill()
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
This example performs a simple echo from the listener to the dialer.
|
This example performs a simple echo from the listener to the dialer.
|
||||||
|
|
||||||
## Setup
|
## 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.
|
2. Open 2 terminal windows in the `./src` directory.
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
@ -37,7 +37,7 @@ async function run() {
|
|||||||
|
|
||||||
// Dial the listener node
|
// Dial the listener node
|
||||||
console.log('Dialing to peer:', listenerMultiaddr)
|
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')
|
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 each chunk of data
|
||||||
for await (const data of source) {
|
for await (const data of source) {
|
||||||
// Output the data
|
// Output the data
|
||||||
console.log('received echo:', uint8ArrayToString(data))
|
console.log('received echo:', uint8ArrayToString(data.subarray()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import execa from 'execa'
|
import { execa } from 'execa'
|
||||||
import pDefer from 'p-defer'
|
import pDefer from 'p-defer'
|
||||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# libp2p in the browser
|
# 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
|
## Setup
|
||||||
|
|
||||||
@ -11,13 +11,14 @@ In order to run the example:
|
|||||||
|
|
||||||
```
|
```
|
||||||
npm install
|
npm install
|
||||||
|
npm run build
|
||||||
cd ./examples/libp2p-in-the-browser
|
cd ./examples/libp2p-in-the-browser
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running the examples
|
## Running the examples
|
||||||
|
|
||||||
Start by running the Parcel server:
|
Start by running the vite server:
|
||||||
|
|
||||||
```
|
```
|
||||||
npm start
|
npm start
|
||||||
@ -29,7 +30,7 @@ The output should look something like this:
|
|||||||
$ npm start
|
$ npm start
|
||||||
|
|
||||||
> libp2p-in-browser@1.0.0 start
|
> libp2p-in-browser@1.0.0 start
|
||||||
> parcel index.html
|
> vite index.html
|
||||||
|
|
||||||
Server running at http://localhost:1234
|
Server running at http://localhost:1234
|
||||||
✨ Built in 1000ms.
|
✨ 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.
|
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?
|
## Going to production?
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>js-libp2p parcel.js browser example</title>
|
<title>js-libp2p vite browser example</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -9,11 +9,11 @@
|
|||||||
},
|
},
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chainsafe/libp2p-noise": "^6.2.0",
|
"@chainsafe/libp2p-noise": "^8.0.1",
|
||||||
"@libp2p/bootstrap": "^1.0.4",
|
"@libp2p/bootstrap": "^2.0.1",
|
||||||
"@libp2p/mplex": "^1.0.4",
|
"@libp2p/mplex": "^5.2.3",
|
||||||
"@libp2p/webrtc-star": "^1.0.8",
|
"@libp2p/webrtc-star": "^3.0.3",
|
||||||
"@libp2p/websockets": "^1.0.7",
|
"@libp2p/websockets": "^3.0.4",
|
||||||
"libp2p": "../../"
|
"libp2p": "../../"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import execa from 'execa'
|
import { execa } from 'execa'
|
||||||
import { chromium } from 'playwright'
|
import { chromium } from 'playwright'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
@ -9,13 +9,13 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@libp2p/pubsub-peer-discovery": "^5.0.2",
|
"@libp2p/pubsub-peer-discovery": "^6.0.2",
|
||||||
"@libp2p/floodsub": "^1.0.6",
|
"@libp2p/floodsub": "^3.0.3",
|
||||||
"@nodeutils/defaults-deep": "^1.1.0",
|
"@nodeutils/defaults-deep": "^1.1.0",
|
||||||
"execa": "^2.1.0",
|
"execa": "^6.1.0",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"libp2p": "../",
|
"libp2p": "../",
|
||||||
"p-defer": "^3.0.0",
|
"p-defer": "^4.0.0",
|
||||||
"uint8arrays": "^3.0.0",
|
"uint8arrays": "^3.0.0",
|
||||||
"which": "^2.0.1"
|
"which": "^2.0.1"
|
||||||
},
|
},
|
||||||
|
@ -40,7 +40,7 @@ const createNode = async () => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
// Wait for onConnect handlers in the DHT
|
// Wait for onConnect handlers in the DHT
|
||||||
await delay(100)
|
await delay(1000)
|
||||||
|
|
||||||
const cid = CID.parse('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL')
|
const cid = CID.parse('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL')
|
||||||
await node1.contentRouting.provide(cid)
|
await node1.contentRouting.provide(cid)
|
||||||
|
@ -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
|
# 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.
|
First, let's update our config to support Peer Routing and Content Routing.
|
||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
import { createLibp2p } from 'libp2p'
|
import { createLibp2p } from 'libp2p'
|
||||||
import { KadDHT } from '@libp2p/kad-dht'
|
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({
|
const createNode = async () => {
|
||||||
addresses: {
|
const node = await createLibp2p({
|
||||||
listen: ['/ip4/0.0.0.0/tcp/0']
|
addresses: {
|
||||||
},
|
listen: ['/ip4/0.0.0.0/tcp/0']
|
||||||
transports: [
|
},
|
||||||
new TCP()
|
transports: [new TCP()],
|
||||||
],
|
streamMuxers: [new Mplex()],
|
||||||
streamMuxers: [
|
connectionEncryption: [new Noise()],
|
||||||
new Mplex()
|
dht: new KadDHT()
|
||||||
],
|
})
|
||||||
connEncryption: [
|
|
||||||
new Noise()
|
await node.start()
|
||||||
],
|
return node
|
||||||
// we add the DHT module that will enable Peer and Content Routing
|
}
|
||||||
dht: KadDHT
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
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
|
```JavaScript
|
||||||
const node1 = nodes[0]
|
const [node1, node2, node3] = await Promise.all([
|
||||||
const node2 = nodes[1]
|
createNode(),
|
||||||
const node3 = nodes[2]
|
createNode(),
|
||||||
|
createNode()
|
||||||
|
])
|
||||||
|
|
||||||
await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
|
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
|
||||||
await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
|
await node2.peerStore.addressBook.set(node3.peerId, node3.getMultiaddrs())
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
node1.dial(node2.peerId),
|
node1.dial(node2.peerId),
|
||||||
@ -50,12 +53,12 @@ await Promise.all([
|
|||||||
])
|
])
|
||||||
|
|
||||||
// Set up of the cons might take time
|
// 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)
|
const peer = await node1.peerRouting.findPeer(node3.peerId)
|
||||||
|
|
||||||
console.log('Found it, multiaddrs are:')
|
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:
|
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`.
|
Instead of calling `peerRouting.findPeer`, we will use `contentRouting.provide` and `contentRouting.findProviders`.
|
||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
|
import { CID } from 'multiformats/cid'
|
||||||
|
import all from 'it-all'
|
||||||
|
|
||||||
|
const cid = CID.parse('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL')
|
||||||
await node1.contentRouting.provide(cid)
|
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:
|
The output of your program should look like:
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
This example shows how to set up a private network of libp2p nodes.
|
This example shows how to set up a private network of libp2p nodes.
|
||||||
|
|
||||||
## Setup
|
## 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
|
## Run
|
||||||
Running the example will cause two nodes with the same swarm key to be started and exchange basic information.
|
Running the example will cause two nodes with the same swarm key to be started and exchange basic information.
|
||||||
|
@ -37,13 +37,13 @@ generateKey(otherSwarmKey)
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of 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(
|
await pipe(
|
||||||
[uint8ArrayFromString('This message is sent on a private network')],
|
[uint8ArrayFromString('This message is sent on a private network')],
|
||||||
|
@ -36,7 +36,7 @@ const createNode = async () => {
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of 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(
|
await pipe(
|
||||||
[uint8ArrayFromString('my own protocol, wow!')],
|
[uint8ArrayFromString('my own protocol, wow!')],
|
||||||
stream
|
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(
|
await pipe(
|
||||||
['my own protocol, wow!'],
|
['my own protocol, wow!'],
|
||||||
|
@ -35,25 +35,28 @@ const createNode = async () => {
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of 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(
|
await pipe(
|
||||||
[uint8ArrayFromString('protocol (a)')],
|
[uint8ArrayFromString('protocol (a)')],
|
||||||
stream1
|
stream1
|
||||||
)
|
)
|
||||||
|
|
||||||
const { stream: stream2 } = await node1.dialProtocol(node2.peerId, ['/b'])
|
const stream2 = await node1.dialProtocol(node2.peerId, ['/b'])
|
||||||
await pipe(
|
await pipe(
|
||||||
[uint8ArrayFromString('protocol (b)')],
|
[uint8ArrayFromString('protocol (b)')],
|
||||||
stream2
|
stream2
|
||||||
)
|
)
|
||||||
|
|
||||||
const { stream: stream3 } = await node1.dialProtocol(node2.peerId, ['/b'])
|
const stream3 = await node1.dialProtocol(node2.peerId, ['/b'])
|
||||||
await pipe(
|
await pipe(
|
||||||
[uint8ArrayFromString('another stream on protocol (b)')],
|
[uint8ArrayFromString('another stream on protocol (b)')],
|
||||||
stream3
|
stream3
|
||||||
|
@ -37,7 +37,7 @@ const createNode = async () => {
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of source) {
|
for await (const msg of source) {
|
||||||
console.log(uint8ArrayToString(msg))
|
console.log(uint8ArrayToString(msg.subarray()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -48,19 +48,19 @@ const createNode = async () => {
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of 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(
|
await pipe(
|
||||||
[uint8ArrayFromString('from 1 to 2')],
|
[uint8ArrayFromString('from 1 to 2')],
|
||||||
stream1
|
stream1
|
||||||
)
|
)
|
||||||
|
|
||||||
const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1'])
|
const stream2 = await node2.dialProtocol(node1.peerId, ['/node-1'])
|
||||||
await pipe(
|
await pipe(
|
||||||
[uint8ArrayFromString('from 2 to 1')],
|
[uint8ArrayFromString('from 2 to 1')],
|
||||||
stream2
|
stream2
|
||||||
|
@ -6,21 +6,25 @@ The feature of agreeing on a protocol over an established connection is what we
|
|||||||
|
|
||||||
# 1. Handle multiple protocols
|
# 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.
|
After creating the nodes, we need to tell libp2p which protocols to handle.
|
||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
import { pipe } from 'it-pipe'
|
import { pipe } from 'it-pipe'
|
||||||
const { map } from 'streaming-iterables')
|
import { map } from 'streaming-iterables'
|
||||||
const { toBuffer } from 'it-buffer')
|
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 [node1, node2] = await Promise.all([
|
||||||
const node2 = nodes[1]
|
createNode(),
|
||||||
|
createNode()
|
||||||
|
])
|
||||||
|
|
||||||
// Add node's 2 data to the PeerStore
|
// 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`
|
// 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
|
// multicodec, the protocol identifier, please call this handler and give it the stream
|
||||||
@ -30,7 +34,7 @@ node2.handle('/your-protocol', ({ stream }) => {
|
|||||||
stream,
|
stream,
|
||||||
source => (async function () {
|
source => (async function () {
|
||||||
for await (const msg of source) {
|
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.
|
After the protocol is _handled_, now we can dial to it.
|
||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
const { stream } = await node1.dialProtocol(node2.peerId, ['/your-protocol'])
|
const stream = await node1.dialProtocol(node2.peerId, ['/your-protocol'])
|
||||||
|
|
||||||
await pipe(
|
await pipe(
|
||||||
['my own protocol, wow!'],
|
[uint8ArrayFromString('my own protocol, wow!')],
|
||||||
stream
|
stream
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
@ -56,16 +60,16 @@ node2.handle('/another-protocol/1.0.1', ({ stream }) => {
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of 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(
|
await pipe(
|
||||||
['my own protocol, wow!'],
|
[uint8ArrayFromString('my own protocol, wow!')],
|
||||||
stream
|
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
|
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
|
```JavaScript
|
||||||
node2.handle(['/another-protocol/1.0.0', '/another-protocol/2.0.0'], ({ protocol, stream }) => {
|
node2.handle(['/another-protocol/1.0.0', '/another-protocol/2.0.0'], ({ stream }) => {
|
||||||
if (protocol === '/another-protocol/2.0.0') {
|
if (stream.stat.protocol === '/another-protocol/2.0.0') {
|
||||||
// handle backwards compatibility
|
// handle backwards compatibility
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +88,7 @@ node2.handle(['/another-protocol/1.0.0', '/another-protocol/2.0.0'], ({ protocol
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of 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'
|
import { Mplex } from '@libp2p/mplex'
|
||||||
//...
|
//...
|
||||||
|
|
||||||
const createNode = () => {
|
createLibp2p({
|
||||||
return Libp2p.create({
|
//...
|
||||||
transports: [
|
transports: [
|
||||||
new TCP()
|
new TCP()
|
||||||
],
|
],
|
||||||
streamMuxers: [
|
streamMuxers: [
|
||||||
new Mplex()
|
new Mplex()
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
With this, we can dial as many times as we want to a peer and always reuse the same established underlying connection.
|
With this, we can dial as many times as we want to a peer and always reuse the same established underlying connection.
|
||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
node2.handle(['/a', '/b'], ({ protocol, stream }) => {
|
node2.handle(['/a', '/b'], ({ stream }) => {
|
||||||
pipe(
|
pipe(
|
||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of 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(
|
await pipe(
|
||||||
['protocol (a)'],
|
[uint8ArrayFromString('protocol (a)')],
|
||||||
stream
|
stream
|
||||||
)
|
)
|
||||||
|
|
||||||
const { stream: stream2 } = await node1.dialProtocol(node2.peerId, ['/b'])
|
const stream2 = await node1.dialProtocol(node2.peerId, ['/b'])
|
||||||
await pipe(
|
await pipe(
|
||||||
['protocol (b)'],
|
[uint8ArrayFromString('protocol (b)')],
|
||||||
stream2
|
stream2
|
||||||
)
|
)
|
||||||
|
|
||||||
const { stream: stream3 } = await node1.dialProtocol(node2.peerId, ['/b'])
|
const stream3 = await node1.dialProtocol(node2.peerId, ['/b'])
|
||||||
await pipe(
|
await pipe(
|
||||||
['another stream on protocol (b)'],
|
[uint8ArrayFromString('another stream on protocol (b)')],
|
||||||
stream3
|
stream3
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
@ -172,15 +176,13 @@ 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.
|
As we've seen earlier, we can create our node with this createNode function.
|
||||||
```js
|
```js
|
||||||
const createNode = async () => {
|
const createNode = async () => {
|
||||||
const node = await Libp2p.create({
|
const node = await createLibp2p({
|
||||||
addresses: {
|
addresses: {
|
||||||
listen: ['/ip4/0.0.0.0/tcp/0']
|
listen: ['/ip4/0.0.0.0/tcp/0']
|
||||||
},
|
},
|
||||||
modules: {
|
transports: [new TCP()],
|
||||||
transport: [TCP],
|
streamMuxers: [new Mplex()],
|
||||||
streamMuxer: [MPLEX],
|
connectionEncryption: [new Noise()],
|
||||||
connEncryption: [NOISE]
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await node.start()
|
await node.start()
|
||||||
@ -199,7 +201,7 @@ const [node1, node2] = await Promise.all([
|
|||||||
|
|
||||||
Since, we want to connect these nodes `node1` & `node2`, we add our `node2` multiaddr in key-value pair in `node1` peer store.
|
Since, we want to connect these nodes `node1` & `node2`, we add our `node2` multiaddr in key-value pair in `node1` peer store.
|
||||||
```js
|
```js
|
||||||
await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
|
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.
|
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.
|
||||||
@ -211,7 +213,7 @@ node1.handle('/node-1', ({ stream }) => {
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of source) {
|
for await (const msg of source) {
|
||||||
console.log(msg.toString())
|
console.log(uint8ArrayToString(msg.subarray()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -222,23 +224,23 @@ node2.handle('/node-2', ({ stream }) => {
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of source) {
|
for await (const msg of source) {
|
||||||
console.log(msg.toString())
|
console.log(uint8ArrayToString(msg.subarray()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Dialing node2 from node1
|
// Dialing node2 from node1
|
||||||
const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2'])
|
const stream1 = await node1.dialProtocol(node2.peerId, ['/node-2'])
|
||||||
await pipe(
|
await pipe(
|
||||||
['from 1 to 2'],
|
[uint8ArrayFromString('from 1 to 2')],
|
||||||
stream1
|
stream1
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dialing node1 from node2
|
// Dialing node1 from node2
|
||||||
const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1'])
|
const stream2 = await node2.dialProtocol(node1.peerId, ['/node-1'])
|
||||||
await pipe(
|
await pipe(
|
||||||
['from 2 to 1'],
|
[uint8ArrayFromString('from 2 to 1')],
|
||||||
stream2
|
stream2
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
@ -256,16 +258,16 @@ So, we have successfully set up a bidirectional connection with protocol muxing.
|
|||||||
The code below will result into an error as `the dial address is not valid`.
|
The code below will result into an error as `the dial address is not valid`.
|
||||||
```js
|
```js
|
||||||
// Dialing from node2 to node1
|
// Dialing from node2 to node1
|
||||||
const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1'])
|
const stream2 = await node2.dialProtocol(node1.peerId, ['/node-1'])
|
||||||
await pipe(
|
await pipe(
|
||||||
['from 2 to 1'],
|
[uint8ArrayFromString('from 2 to 1')],
|
||||||
stream2
|
stream2
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dialing from node1 to node2
|
// Dialing from node1 to node2
|
||||||
const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2'])
|
const stream1 = await node1.dialProtocol(node2.peerId, ['/node-2'])
|
||||||
await pipe(
|
await pipe(
|
||||||
['from 1 to 2'],
|
[uint8ArrayFromString('from 1 to 2')],
|
||||||
stream1
|
stream1
|
||||||
)
|
)
|
||||||
```
|
```
|
@ -1,6 +1,6 @@
|
|||||||
# Publish Subscribe
|
# 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:
|
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
|
## 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
|
## 1. Setting up a simple PubSub network on top of libp2p
|
||||||
|
|
||||||
@ -22,54 +22,60 @@ First, let's update our libp2p configuration with a pubsub implementation.
|
|||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
import { createLibp2p } from 'libp2p'
|
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({
|
const createNode = async () => {
|
||||||
addresses: {
|
const node = await createLibp2p({
|
||||||
listen: ['/ip4/0.0.0.0/tcp/0']
|
addresses: {
|
||||||
},
|
listen: ['/ip4/0.0.0.0/tcp/0']
|
||||||
transports: [
|
},
|
||||||
new TCP()
|
transports: [new TCP()],
|
||||||
],
|
streamMuxers: [new Mplex()],
|
||||||
streamMuxers: [
|
connectionEncryption: [new Noise()],
|
||||||
new Mplex()
|
// we add the Pubsub module we want
|
||||||
],
|
pubsub: new GossipSub({ allowPublishToZeroPeers: true })
|
||||||
connectionEncryption: [
|
})
|
||||||
new Noise()
|
|
||||||
],
|
await node.start()
|
||||||
// we add the Pubsub module we want
|
|
||||||
pubsub: new Gossipsub()
|
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.
|
Once that is done, we only need to create a few libp2p nodes, connect them and everything is ready to start using pubsub.
|
||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
const { fromString } from 'uint8arrays/from-string')
|
import { fromString as uint8ArrayFromString } from "uint8arrays/from-string";
|
||||||
const { toString } from 'uint8arrays/to-string')
|
import { toString as uint8ArrayToString } from "uint8arrays/to-string";
|
||||||
|
|
||||||
const topic = 'news'
|
const topic = 'news'
|
||||||
|
|
||||||
const node1 = nodes[0]
|
const [node1, node2] = await Promise.all([
|
||||||
const node2 = nodes[1]
|
createNode(),
|
||||||
|
createNode()
|
||||||
|
])
|
||||||
|
|
||||||
// Add node's 2 data to the PeerStore
|
// 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)
|
await node1.dial(node2.peerId)
|
||||||
|
|
||||||
node1.pubsub.on(topic, (msg) => {
|
node1.pubsub.addEventListener("message", (evt) => {
|
||||||
console.log(`node1 received: ${toString(msg.data)}`)
|
console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`)
|
||||||
})
|
})
|
||||||
await node1.pubsub.subscribe(topic)
|
await node1.pubsub.subscribe(topic)
|
||||||
|
|
||||||
// Will not receive own published messages by default
|
// Will not receive own published messages by default
|
||||||
node2.pubsub.on(topic, (msg) => {
|
node2.pubsub.addEventListener("message", (evt) => {
|
||||||
console.log(`node2 received: ${toString(msg.data)}`)
|
console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`)
|
||||||
})
|
})
|
||||||
await node2.pubsub.subscribe(topic)
|
await node2.pubsub.subscribe(topic)
|
||||||
|
|
||||||
// node2 publishes "news" every second
|
// node2 publishes "news" every second
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
node2.pubsub.publish(topic, fromString('Bird bird bird, bird is the word!')).catch(err => {
|
node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')).catch(err => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
@ -87,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.
|
You can change the pubsub `emitSelf` option if you want the publishing node to receive its own messages.
|
||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
const defaults = {
|
new GossipSub({ allowPublishToZeroPeers: true, emitSelf: true })
|
||||||
config: {
|
|
||||||
pubsub: {
|
|
||||||
enabled: true,
|
|
||||||
emitSelf: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The output of the program should look like:
|
The output of the program should look like:
|
||||||
|
@ -40,24 +40,42 @@ const createNode = async () => {
|
|||||||
await node2.dial(node3.peerId)
|
await node2.dial(node3.peerId)
|
||||||
|
|
||||||
//subscribe
|
//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
|
// Will not receive own published messages by default
|
||||||
console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)}`)
|
console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)}`)
|
||||||
})
|
})
|
||||||
node1.pubsub.subscribe(topic)
|
node1.pubsub.subscribe(topic)
|
||||||
|
|
||||||
node2.pubsub.addEventListener(topic, (evt) => {
|
node2.pubsub.addEventListener('message', (evt) => {
|
||||||
|
if (evt.detail.topic !== topic) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)}`)
|
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)}`)
|
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 validateFruit = (msgTopic, msg) => {
|
||||||
const fruit = uint8ArrayToString(msg.data)
|
const fruit = uint8ArrayToString(msg.data)
|
||||||
const validFruit = ['banana', 'apple', 'orange']
|
const validFruit = ['banana', 'apple', 'orange']
|
||||||
|
|
||||||
|
// car is not a fruit !
|
||||||
if (!validFruit.includes(fruit)) {
|
if (!validFruit.includes(fruit)) {
|
||||||
throw new Error('no valid fruit received')
|
throw new Error('no valid fruit received')
|
||||||
}
|
}
|
||||||
@ -68,18 +86,19 @@ const createNode = async () => {
|
|||||||
node2.pubsub.topicValidators.set(topic, validateFruit)
|
node2.pubsub.topicValidators.set(topic, validateFruit)
|
||||||
node3.pubsub.topicValidators.set(topic, validateFruit)
|
node3.pubsub.topicValidators.set(topic, validateFruit)
|
||||||
|
|
||||||
// node1 publishes "fruits" every five seconds
|
// node1 publishes "fruits"
|
||||||
var count = 0;
|
for (const fruit of ['banana', 'apple', 'car', 'orange']) {
|
||||||
const myFruits = ['banana', 'apple', 'car', 'orange'];
|
console.log('############## fruit ' + fruit + ' ##############')
|
||||||
// car is not a fruit !
|
await node1.pubsub.publish(topic, uint8ArrayFromString(fruit))
|
||||||
setInterval(() => {
|
}
|
||||||
console.log('############## fruit ' + myFruits[count] + ' ##############')
|
|
||||||
node1.pubsub.publish(topic, uint8ArrayFromString(myFruits[count])).catch(err => {
|
// wait a few seconds for messages to be received
|
||||||
console.info(err)
|
await delay(5000)
|
||||||
})
|
console.log('############## all messages sent ##############')
|
||||||
count++
|
|
||||||
if (count == myFruits.length) {
|
|
||||||
count = 0
|
|
||||||
}
|
|
||||||
}, 5000)
|
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
async function delay (ms) {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(() => resolve(), ms)
|
||||||
|
})
|
||||||
|
}
|
@ -8,23 +8,27 @@ First, let's update our libp2p configuration with a pubsub implementation.
|
|||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
import { createLibp2p } from 'libp2p'
|
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({
|
const createNode = async () => {
|
||||||
addresses: {
|
const node = await createLibp2p({
|
||||||
listen: ['/ip4/0.0.0.0/tcp/0']
|
addresses: {
|
||||||
},
|
listen: ['/ip4/0.0.0.0/tcp/0']
|
||||||
transports: [
|
},
|
||||||
new TCP()
|
transports: [new TCP()],
|
||||||
],
|
streamMuxers: [new Mplex()],
|
||||||
streamMuxers: [
|
connectionEncryption: [new Noise()],
|
||||||
new Mplex()
|
// we add the Pubsub module we want
|
||||||
],
|
pubsub: new GossipSub({ allowPublishToZeroPeers: true })
|
||||||
connectionEncryption: [
|
})
|
||||||
new Noise()
|
|
||||||
],
|
await node.start()
|
||||||
pubsub: new Gossipsub()
|
|
||||||
})
|
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.
|
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(),
|
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 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)
|
await node2.dial(node3.peerId)
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we' can subscribe to the fruit topic and log incoming messages.
|
Now we' can subscribe to the fruit topic and log incoming messages.
|
||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
|
import { fromString as uint8ArrayFromString } from "uint8arrays/from-string";
|
||||||
|
import { toString as uint8ArrayToString } from "uint8arrays/to-string";
|
||||||
|
|
||||||
const topic = 'fruit'
|
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)}`)
|
console.log(`node1 received: ${uint8ArrayToString(msg.data)}`)
|
||||||
})
|
})
|
||||||
await node1.pubsub.subscribe(topic)
|
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)}`)
|
console.log(`node2 received: ${uint8ArrayToString(msg.data)}`)
|
||||||
})
|
})
|
||||||
await node2.pubsub.subscribe(topic)
|
await node2.pubsub.subscribe(topic)
|
||||||
|
|
||||||
node3.pubsub.on(topic, (msg) => {
|
node3.pubsub.addEventListener('message', (msg) => {
|
||||||
console.log(`node3 received: ${uint8ArrayToString(msg.data)}`)
|
if (msg.detail.topic !== topic) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`node3 received: ${uint8ArrayToString(msg.data)}`)
|
||||||
})
|
})
|
||||||
await node3.pubsub.subscribe(topic)
|
await node3.pubsub.subscribe(topic)
|
||||||
```
|
```
|
||||||
@ -83,19 +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.
|
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
|
```JavaScript
|
||||||
var count = 0;
|
for (const fruit of ['banana', 'apple', 'car', 'orange']) {
|
||||||
const myFruits = ['banana', 'apple', 'car', 'orange'];
|
console.log('############## fruit ' + fruit + ' ##############')
|
||||||
|
await node1.pubsub.publish(topic, uint8ArrayFromString(fruit))
|
||||||
setInterval(() => {
|
}
|
||||||
console.log('############## fruit ' + myFruits[count] + ' ##############')
|
|
||||||
node1.pubsub.publish(topic, new TextEncoder().encode(myFruits[count])).catch(err => {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
count++
|
|
||||||
if (count == myFruits.length) {
|
|
||||||
count = 0
|
|
||||||
}
|
|
||||||
}, 5000)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Result
|
Result
|
||||||
|
@ -1,34 +1,16 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import execa from 'execa'
|
import { execa } from 'execa'
|
||||||
import pDefer from 'p-defer'
|
import pDefer from 'p-defer'
|
||||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
const stdout = [
|
// holds messages received by peers
|
||||||
{
|
const messages = {}
|
||||||
topic: 'banana',
|
|
||||||
messageCount: 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
topic: 'apple',
|
|
||||||
messageCount: 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
topic: 'car',
|
|
||||||
messageCount: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
topic: 'orange',
|
|
||||||
messageCount: 2
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export async function test () {
|
export async function test () {
|
||||||
const defer = pDefer()
|
const defer = pDefer()
|
||||||
let topicCount = 0
|
|
||||||
let topicMessageCount = 0
|
|
||||||
|
|
||||||
process.stdout.write('message-filtering/1.js\n')
|
process.stdout.write('message-filtering/1.js\n')
|
||||||
|
|
||||||
@ -38,26 +20,27 @@ export async function test () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
proc.all.on('data', async (data) => {
|
proc.all.on('data', async (data) => {
|
||||||
// End
|
|
||||||
if (topicCount === stdout.length) {
|
|
||||||
defer.resolve()
|
|
||||||
proc.all.removeAllListeners('data')
|
|
||||||
}
|
|
||||||
|
|
||||||
process.stdout.write(data)
|
process.stdout.write(data)
|
||||||
const line = uint8ArrayToString(data)
|
const line = uint8ArrayToString(data)
|
||||||
|
|
||||||
if (stdout[topicCount] && line.includes(stdout[topicCount].topic)) {
|
// End
|
||||||
// Validate previous number of messages
|
if (line.includes('all messages sent')) {
|
||||||
if (topicCount > 0 && topicMessageCount > stdout[topicCount - 1].messageCount) {
|
if (messages.car > 0) {
|
||||||
defer.reject()
|
defer.reject(new Error('Message validation failed - peers failed to filter car messages'))
|
||||||
throw new Error(`topic ${stdout[topicCount - 1].topic} had ${topicMessageCount} messages instead of ${stdout[topicCount - 1].messageCount}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
topicCount++
|
for (const fruit of ['banana', 'apple', 'orange']) {
|
||||||
topicMessageCount = 0
|
if (messages[fruit] !== 2) {
|
||||||
} else {
|
defer.reject(new Error(`Not enough ${fruit} messages - received ${messages[fruit] ?? 0}, expected 2`))
|
||||||
topicMessageCount++
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defer.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.includes('received:')) {
|
||||||
|
const fruit = line.split('received:')[1].trim()
|
||||||
|
messages[fruit] = (messages[fruit] ?? 0) + 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import execa from 'execa'
|
import { execa } from 'execa'
|
||||||
import pDefer from 'p-defer'
|
import pDefer from 'p-defer'
|
||||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
@ -3,7 +3,7 @@ process.env.CI = true // needed for some "clever" build tools
|
|||||||
|
|
||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import execa from 'execa'
|
import { execa } from 'execa'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
@ -36,7 +36,8 @@ async function installDeps (dir) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const proc = execa.command('npm install', {
|
const proc = execa('npm', ['install'], {
|
||||||
|
all: true,
|
||||||
cwd: dir
|
cwd: dir
|
||||||
})
|
})
|
||||||
proc.all.on('data', (data) => {
|
proc.all.on('data', (data) => {
|
||||||
@ -71,6 +72,7 @@ async function build (dir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const proc = execa('npm', ['run', build], {
|
const proc = execa('npm', ['run', build], {
|
||||||
|
all: true,
|
||||||
cwd: dir
|
cwd: dir
|
||||||
})
|
})
|
||||||
proc.all.on('data', (data) => {
|
proc.all.on('data', (data) => {
|
||||||
|
@ -42,13 +42,18 @@ function printAddrs (node, number) {
|
|||||||
node2.handle('/print', async ({ stream }) => {
|
node2.handle('/print', async ({ stream }) => {
|
||||||
const result = await pipe(
|
const result = await pipe(
|
||||||
stream,
|
stream,
|
||||||
|
async function * (source) {
|
||||||
|
for await (const list of source) {
|
||||||
|
yield list.subarray()
|
||||||
|
}
|
||||||
|
},
|
||||||
toBuffer
|
toBuffer
|
||||||
)
|
)
|
||||||
console.log(uint8ArrayToString(result))
|
console.log(uint8ArrayToString(result))
|
||||||
})
|
})
|
||||||
|
|
||||||
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
|
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(
|
await pipe(
|
||||||
['Hello', ' ', 'p2p', ' ', 'world', '!'].map(str => uint8ArrayFromString(str)),
|
['Hello', ' ', 'p2p', ' ', 'world', '!'].map(str => uint8ArrayFromString(str)),
|
||||||
|
@ -37,13 +37,13 @@ function print ({ stream }) {
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of 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([
|
const [node1, node2, node3] = await Promise.all([
|
||||||
createNode([new TCP()], '/ip4/0.0.0.0/tcp/0'),
|
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 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())
|
await node3.peerStore.addressBook.set(node1.peerId, node1.getMultiaddrs())
|
||||||
|
|
||||||
// node 1 (TCP) dials to node 2 (TCP+WebSockets)
|
// 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(
|
await pipe(
|
||||||
[uint8ArrayFromString('node 1 dialed to node 2 successfully')],
|
[uint8ArrayFromString('node 1 dialed to node 2 successfully')],
|
||||||
stream
|
stream
|
||||||
)
|
)
|
||||||
|
|
||||||
// node 2 (TCP+WebSockets) dials to node 2 (WebSockets)
|
// node 2 (TCP+WebSockets) dials to node 3 (WebSockets)
|
||||||
const { stream: stream2 } = await node2.dialProtocol(node3.peerId, '/print')
|
const stream2 = await node2.dialProtocol(node3.peerId, '/print')
|
||||||
await pipe(
|
await pipe(
|
||||||
[uint8ArrayFromString('node 2 dialed to node 3 successfully')],
|
[uint8ArrayFromString('node 2 dialed to node 3 successfully')],
|
||||||
stream2
|
stream2
|
||||||
|
@ -57,7 +57,7 @@ function print ({ stream }) {
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of 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];
|
const targetAddr = node1.getMultiaddrs()[0];
|
||||||
|
|
||||||
// node 2 (Secure WebSockets) dials to node 1 (Secure Websockets)
|
// 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(
|
await pipe(
|
||||||
[uint8ArrayFromString('node 2 dialed to node 1 successfully')],
|
[uint8ArrayFromString('node 2 dialed to node 1 successfully')],
|
||||||
stream
|
stream
|
||||||
|
@ -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:
|
You will need 4 dependencies total, so go ahead and install all of them with:
|
||||||
|
|
||||||
```bash
|
```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`.
|
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
|
// 0, which means "listen in any network interface and pick
|
||||||
// a port for me
|
// a port for me
|
||||||
console.log('listening on:')
|
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:
|
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.
|
For this step, we will need some more dependencies.
|
||||||
|
|
||||||
```bash
|
```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:
|
And we also need to import the modules on our .js file:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { pipe } from 'it-pipe'
|
import { pipe } from 'it-pipe'
|
||||||
import toBuffer from 'it-to-buffer'
|
|
||||||
import { Mplex } from '@libp2p/mplex'
|
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`.
|
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
|
```JavaScript
|
||||||
function printAddrs (node, number) {
|
function printAddrs (node, number) {
|
||||||
console.log('node %s is listening on:', 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,
|
Then add,
|
||||||
|
|
||||||
```js
|
```js
|
||||||
;(async () => {
|
import { fromString as uint8ArrayFromString } from "uint8arrays/from-string";
|
||||||
const [node1, node2] = await Promise.all([
|
import { toString as uint8ArrayToString } from "uint8arrays/to-string";
|
||||||
createNode(),
|
|
||||||
createNode()
|
|
||||||
])
|
|
||||||
|
|
||||||
printAddrs(node1, '1')
|
const [node1, node2] = await Promise.all([
|
||||||
printAddrs(node2, '2')
|
createNode(),
|
||||||
|
createNode()
|
||||||
|
])
|
||||||
|
|
||||||
node2.handle('/print', async ({ stream }) => {
|
printAddrs(node1, '1')
|
||||||
const result = await pipe(
|
printAddrs(node2, '2')
|
||||||
stream,
|
|
||||||
toBuffer
|
|
||||||
)
|
|
||||||
console.log(result.toString())
|
|
||||||
})
|
|
||||||
|
|
||||||
await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
|
node2.handle('/print', async ({ stream }) => {
|
||||||
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
|
const result = await pipe(
|
||||||
|
stream,
|
||||||
await pipe(
|
all
|
||||||
['Hello', ' ', 'p2p', ' ', 'world', '!'],
|
|
||||||
stream
|
|
||||||
)
|
)
|
||||||
})();
|
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).
|
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).
|
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
|
```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:
|
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: {
|
addresses: {
|
||||||
listen: addresses
|
listen: addresses
|
||||||
},
|
},
|
||||||
transport: transports,
|
transports: transports,
|
||||||
connectionEncryption: [new Noise()],
|
connectionEncryption: [new Noise()],
|
||||||
streamMuxers: [new Mplex()]
|
streamMuxers: [new Mplex()]
|
||||||
})
|
})
|
||||||
@ -207,9 +208,9 @@ import { WebSockets } from '@libp2p/websockets'
|
|||||||
import { TCP } from '@libp2p/tcp'
|
import { TCP } from '@libp2p/tcp'
|
||||||
|
|
||||||
const [node1, node2, node3] = await Promise.all([
|
const [node1, node2, node3] = await Promise.all([
|
||||||
createNode([TCP], '/ip4/0.0.0.0/tcp/0'),
|
createNode([new 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([new TCP(), new 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 WebSockets()], '/ip4/127.0.0.1/tcp/20000/ws')
|
||||||
])
|
])
|
||||||
|
|
||||||
printAddrs(node1, '1')
|
printAddrs(node1, '1')
|
||||||
@ -220,21 +221,21 @@ node1.handle('/print', print)
|
|||||||
node2.handle('/print', print)
|
node2.handle('/print', print)
|
||||||
node3.handle('/print', print)
|
node3.handle('/print', print)
|
||||||
|
|
||||||
await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
|
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
|
||||||
await node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
|
await node2.peerStore.addressBook.set(node3.peerId, node3.getMultiaddrs())
|
||||||
await node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs)
|
await node3.peerStore.addressBook.set(node1.peerId, node1.getMultiaddrs())
|
||||||
|
|
||||||
// node 1 (TCP) dials to node 2 (TCP+WebSockets)
|
// 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(
|
await pipe(
|
||||||
['node 1 dialed to node 2 successfully'],
|
['node 1 dialed to node 2 successfully'].map(str => uint8ArrayFromString(str)),
|
||||||
stream
|
stream
|
||||||
)
|
)
|
||||||
|
|
||||||
// node 2 (TCP+WebSockets) dials to node 2 (WebSockets)
|
// node 2 (TCP+WebSockets) dials to node 3 (WebSockets)
|
||||||
const { stream: stream2 } = await node2.dialProtocol(node3.peerId, '/print')
|
const stream2 = await node2.dialProtocol(node3.peerId, '/print')
|
||||||
await pipe(
|
await pipe(
|
||||||
['node 2 dialed to node 3 successfully'],
|
['node 2 dialed to node 3 successfully'].map(str => uint8ArrayFromString(str)),
|
||||||
stream2
|
stream2
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -254,7 +255,7 @@ function print ({ stream }) {
|
|||||||
stream,
|
stream,
|
||||||
async function (source) {
|
async function (source) {
|
||||||
for await (const msg of source) {
|
for await (const msg of source) {
|
||||||
console.log(msg.toString())
|
console.log(uint8ArrayToString(msg.subarray()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import execa from 'execa'
|
import { execa } from 'execa'
|
||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
import which from 'which'
|
import which from 'which'
|
||||||
|
|
||||||
@ -26,7 +26,10 @@ export async function waitForOutput (expectedOutput, command, args = [], opts =
|
|||||||
command = 'node'
|
command = 'node'
|
||||||
}
|
}
|
||||||
|
|
||||||
const proc = execa(command, args, opts)
|
const proc = execa(command, args, {
|
||||||
|
...opts,
|
||||||
|
all: true
|
||||||
|
})
|
||||||
let output = ''
|
let output = ''
|
||||||
let time = 600000
|
let time = 600000
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
### Webrtc-direct example
|
### 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:
|
nodejs libp2p and browser libp2p clients. To run the example:
|
||||||
|
|
||||||
## 0. Run a nodejs libp2p listener
|
## 0. Run a nodejs libp2p listener
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createLibp2p } from 'libp2p'
|
import { createLibp2p } from 'libp2p'
|
||||||
import { WebRTCDirect } from '@achingbrain/webrtc-direct'
|
import { WebRTCDirect } from '@libp2p/webrtc-direct'
|
||||||
import { Mplex } from '@libp2p/mplex'
|
import { Mplex } from '@libp2p/mplex'
|
||||||
import { Noise } from '@chainsafe/libp2p-noise'
|
import { Noise } from '@chainsafe/libp2p-noise'
|
||||||
import { Bootstrap } from '@libp2p/bootstrap'
|
import { Bootstrap } from '@libp2p/bootstrap'
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>js-libp2p parcel.js browser example</title>
|
<title>js-libp2p vite browser example</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createLibp2p } from 'libp2p'
|
import { createLibp2p } from 'libp2p'
|
||||||
import { WebRTCDirect } from '@achingbrain/webrtc-direct'
|
import { WebRTCDirect } from '@libp2p/webrtc-direct'
|
||||||
import { Mplex } from '@libp2p/mplex'
|
import { Mplex } from '@libp2p/mplex'
|
||||||
import { Noise } from '@chainsafe/libp2p-noise'
|
import { Noise } from '@chainsafe/libp2p-noise'
|
||||||
import { createFromJSON } from '@libp2p/peer-id-factory'
|
import { createFromJSON } from '@libp2p/peer-id-factory'
|
||||||
|
@ -9,10 +9,10 @@
|
|||||||
},
|
},
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@libp2p/webrtc-direct": "^1.0.1",
|
"@libp2p/webrtc-direct": "^2.0.0",
|
||||||
"@chainsafe/libp2p-noise": "^6.2.0",
|
"@chainsafe/libp2p-noise": "^8.0.1",
|
||||||
"@libp2p/bootstrap": "^1.0.4",
|
"@libp2p/bootstrap": "^2.0.1",
|
||||||
"@libp2p/mplex": "^1.0.4",
|
"@libp2p/mplex": "^5.2.3",
|
||||||
"libp2p": "../../",
|
"libp2p": "../../",
|
||||||
"wrtc": "^0.4.7"
|
"wrtc": "^0.4.7"
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import execa from 'execa'
|
import { execa } from 'execa'
|
||||||
import pDefer from 'p-defer'
|
import pDefer from 'p-defer'
|
||||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||||
import { chromium } from 'playwright'
|
import { chromium } from 'playwright'
|
||||||
@ -60,8 +60,8 @@ export async function test () {
|
|||||||
selector => {
|
selector => {
|
||||||
const text = document.querySelector(selector).innerText
|
const text = document.querySelector(selector).innerText
|
||||||
return text.includes('libp2p id is') &&
|
return text.includes('libp2p id is') &&
|
||||||
text.includes('Found peer') &&
|
text.includes('Found peer 12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m') &&
|
||||||
text.includes('Connected to')
|
text.includes('Connected to 12D3KooWCuo3MdXfMgaqpLC5Houi1TRoFqgK9aoxok4NK5udMu8m')
|
||||||
},
|
},
|
||||||
'#output',
|
'#output',
|
||||||
{ timeout: 10000 }
|
{ timeout: 10000 }
|
||||||
|
111
package.json
111
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "libp2p",
|
"name": "libp2p",
|
||||||
"version": "0.37.1",
|
"version": "0.39.3",
|
||||||
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
|
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
|
||||||
"license": "Apache-2.0 OR MIT",
|
"license": "Apache-2.0 OR MIT",
|
||||||
"homepage": "https://github.com/libp2p/js-libp2p#readme",
|
"homepage": "https://github.com/libp2p/js-libp2p#readme",
|
||||||
@ -49,15 +49,19 @@
|
|||||||
],
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
|
"types": "./src/index.d.ts",
|
||||||
"import": "./dist/src/index.js"
|
"import": "./dist/src/index.js"
|
||||||
},
|
},
|
||||||
"./insecure": {
|
"./insecure": {
|
||||||
|
"types": "./dist/src/insecure/index.d.ts",
|
||||||
"import": "./dist/src/insecure/index.js"
|
"import": "./dist/src/insecure/index.js"
|
||||||
},
|
},
|
||||||
"./pnet": {
|
"./pnet": {
|
||||||
|
"types": "./dist/src/pnet/index.d.ts",
|
||||||
"import": "./dist/src/pnet/index.js"
|
"import": "./dist/src/pnet/index.js"
|
||||||
},
|
},
|
||||||
"./transport-manager": {
|
"./transport-manager": {
|
||||||
|
"types": "./dist/src/transport-manager.d.ts",
|
||||||
"import": "./dist/src/transport-manager.js"
|
"import": "./dist/src/transport-manager.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -76,6 +80,7 @@
|
|||||||
"clean": "aegir clean",
|
"clean": "aegir clean",
|
||||||
"lint": "aegir lint",
|
"lint": "aegir lint",
|
||||||
"dep-check": "aegir dep-check",
|
"dep-check": "aegir dep-check",
|
||||||
|
"prepublishOnly": "node scripts/update-version.js",
|
||||||
"build": "aegir build",
|
"build": "aegir build",
|
||||||
"generate": "run-s generate:proto:*",
|
"generate": "run-s generate:proto:*",
|
||||||
"generate:proto:circuit": "protons ./src/circuit/pb/index.proto",
|
"generate:proto:circuit": "protons ./src/circuit/pb/index.proto",
|
||||||
@ -93,34 +98,51 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@achingbrain/nat-port-mapper": "^1.0.3",
|
"@achingbrain/nat-port-mapper": "^1.0.3",
|
||||||
"@libp2p/connection": "^2.0.2",
|
"@libp2p/components": "^2.1.0",
|
||||||
"@libp2p/crypto": "^0.22.11",
|
"@libp2p/connection": "^4.0.2",
|
||||||
"@libp2p/interfaces": "^2.0.2",
|
"@libp2p/crypto": "^1.0.4",
|
||||||
"@libp2p/logger": "^1.1.4",
|
"@libp2p/interface-address-manager": "^1.0.3",
|
||||||
"@libp2p/multistream-select": "^1.0.4",
|
"@libp2p/interface-connection": "^3.0.2",
|
||||||
"@libp2p/peer-collections": "^1.0.2",
|
"@libp2p/interface-connection-encrypter": "^2.0.1",
|
||||||
"@libp2p/peer-id": "^1.1.10",
|
"@libp2p/interface-connection-manager": "^1.1.1",
|
||||||
"@libp2p/peer-id-factory": "^1.0.9",
|
"@libp2p/interface-content-routing": "^1.0.2",
|
||||||
"@libp2p/peer-record": "^1.0.8",
|
"@libp2p/interface-dht": "^1.0.1",
|
||||||
"@libp2p/peer-store": "^1.0.10",
|
"@libp2p/interface-metrics": "^3.0.0",
|
||||||
"@libp2p/tracked-map": "^1.0.5",
|
"@libp2p/interface-peer-discovery": "^1.0.1",
|
||||||
"@libp2p/utils": "^1.0.10",
|
"@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/mafmt": "^11.0.2",
|
||||||
"@multiformats/multiaddr": "^10.1.8",
|
"@multiformats/multiaddr": "^11.0.0",
|
||||||
"abortable-iterator": "^4.0.2",
|
"abortable-iterator": "^4.0.2",
|
||||||
"any-signal": "^3.0.0",
|
"any-signal": "^3.0.0",
|
||||||
"datastore-core": "^7.0.0",
|
"datastore-core": "^8.0.1",
|
||||||
"err-code": "^3.0.1",
|
"err-code": "^3.0.1",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"hashlru": "^2.3.0",
|
"hashlru": "^2.3.0",
|
||||||
"interface-datastore": "^6.1.0",
|
"interface-datastore": "^7.0.0",
|
||||||
"it-all": "^1.0.6",
|
"it-all": "^1.0.6",
|
||||||
"it-drain": "^1.0.5",
|
"it-drain": "^1.0.5",
|
||||||
"it-filter": "^1.0.3",
|
"it-filter": "^1.0.3",
|
||||||
"it-first": "^1.0.6",
|
"it-first": "^1.0.6",
|
||||||
"it-foreach": "^0.1.1",
|
"it-foreach": "^0.1.1",
|
||||||
"it-handshake": "^3.0.1",
|
"it-handshake": "^4.1.2",
|
||||||
"it-length-prefixed": "^7.0.1",
|
"it-length-prefixed": "^8.0.2",
|
||||||
"it-map": "^1.0.6",
|
"it-map": "^1.0.6",
|
||||||
"it-merge": "^1.0.3",
|
"it-merge": "^1.0.3",
|
||||||
"it-pair": "^2.0.2",
|
"it-pair": "^2.0.2",
|
||||||
@ -130,58 +152,57 @@
|
|||||||
"merge-options": "^3.0.4",
|
"merge-options": "^3.0.4",
|
||||||
"multiformats": "^9.6.3",
|
"multiformats": "^9.6.3",
|
||||||
"mutable-proxy": "^1.0.0",
|
"mutable-proxy": "^1.0.0",
|
||||||
"node-forge": "^1.2.1",
|
"node-forge": "^1.3.1",
|
||||||
"p-fifo": "^1.0.0",
|
"p-fifo": "^1.0.0",
|
||||||
"p-retry": "^5.0.0",
|
"p-retry": "^5.0.0",
|
||||||
"p-settle": "^5.0.0",
|
"p-settle": "^5.0.0",
|
||||||
"private-ip": "^2.3.3",
|
"private-ip": "^2.3.3",
|
||||||
"protons-runtime": "^1.0.4",
|
"protons-runtime": "^3.0.1",
|
||||||
"retimer": "^3.0.0",
|
"retimer": "^3.0.0",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"set-delayed-interval": "^1.0.0",
|
"set-delayed-interval": "^1.0.0",
|
||||||
"timeout-abort-controller": "^3.0.0",
|
"timeout-abort-controller": "^3.0.0",
|
||||||
|
"uint8arraylist": "^2.3.2",
|
||||||
"uint8arrays": "^3.0.0",
|
"uint8arrays": "^3.0.0",
|
||||||
"wherearewe": "^1.0.0",
|
"wherearewe": "^2.0.0",
|
||||||
"xsalsa20": "^1.1.0"
|
"xsalsa20": "^1.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@chainsafe/libp2p-noise": "^6.2.0",
|
"@chainsafe/libp2p-noise": "^8.0.1",
|
||||||
"@libp2p/bootstrap": "^1.0.4",
|
"@chainsafe/libp2p-yamux": "^1.0.0",
|
||||||
"@libp2p/daemon-client": "^1.0.2",
|
"@libp2p/bootstrap": "^3.0.0",
|
||||||
"@libp2p/daemon-server": "^1.0.2",
|
"@libp2p/daemon-client": "^3.0.1",
|
||||||
"@libp2p/delegated-content-routing": "^1.0.2",
|
"@libp2p/daemon-server": "^3.0.1",
|
||||||
"@libp2p/delegated-peer-routing": "^1.0.2",
|
"@libp2p/floodsub": "^3.0.0",
|
||||||
"@libp2p/floodsub": "^1.0.6",
|
"@libp2p/interface-compliance-tests": "^3.0.2",
|
||||||
"@libp2p/interface-compliance-tests": "^2.0.3",
|
"@libp2p/interface-connection-encrypter-compliance-tests": "^2.0.2",
|
||||||
"@libp2p/interop": "^1.0.3",
|
"@libp2p/interface-mocks": "^4.0.3",
|
||||||
"@libp2p/kad-dht": "^1.0.9",
|
"@libp2p/interop": "^3.0.1",
|
||||||
"@libp2p/mdns": "^1.0.5",
|
"@libp2p/kad-dht": "^3.0.5",
|
||||||
"@libp2p/mplex": "^1.1.0",
|
"@libp2p/mdns": "^3.0.1",
|
||||||
"@libp2p/pubsub": "^1.2.18",
|
"@libp2p/mplex": "^5.2.3",
|
||||||
"@libp2p/tcp": "^1.0.9",
|
"@libp2p/pubsub": "^3.1.3",
|
||||||
"@libp2p/topology": "^1.1.7",
|
"@libp2p/tcp": "^3.1.1",
|
||||||
"@libp2p/webrtc-star": "^1.0.8",
|
"@libp2p/topology": "^3.0.1",
|
||||||
"@libp2p/websockets": "^1.0.7",
|
"@libp2p/webrtc-star": "^3.0.3",
|
||||||
|
"@libp2p/websockets": "^3.0.4",
|
||||||
"@types/node-forge": "^1.0.0",
|
"@types/node-forge": "^1.0.0",
|
||||||
"@types/p-fifo": "^1.0.0",
|
"@types/p-fifo": "^1.0.0",
|
||||||
"@types/varint": "^6.0.0",
|
"@types/varint": "^6.0.0",
|
||||||
"@types/xsalsa20": "^1.1.0",
|
"@types/xsalsa20": "^1.1.0",
|
||||||
"aegir": "^37.0.9",
|
"aegir": "^37.3.0",
|
||||||
"cborg": "^1.8.1",
|
"cborg": "^1.8.1",
|
||||||
"delay": "^5.0.0",
|
"delay": "^5.0.0",
|
||||||
"execa": "^6.1.0",
|
"execa": "^6.1.0",
|
||||||
"go-libp2p": "^0.0.6",
|
"go-libp2p": "^0.0.6",
|
||||||
"into-stream": "^7.0.0",
|
"it-pushable": "^3.0.0",
|
||||||
"ipfs-http-client": "^56.0.1",
|
|
||||||
"it-pushable": "^2.0.1",
|
|
||||||
"it-to-buffer": "^2.0.2",
|
"it-to-buffer": "^2.0.2",
|
||||||
"nock": "^13.0.3",
|
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"p-defer": "^4.0.0",
|
"p-defer": "^4.0.0",
|
||||||
"p-event": "^5.0.1",
|
"p-event": "^5.0.1",
|
||||||
"p-times": "^4.0.0",
|
"p-times": "^4.0.0",
|
||||||
"p-wait-for": "^4.1.0",
|
"p-wait-for": "^5.0.0",
|
||||||
"protons": "^3.0.4",
|
"protons": "^5.0.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"sinon": "^14.0.0",
|
"sinon": "^14.0.0",
|
||||||
"ts-sinon": "^2.0.2"
|
"ts-sinon": "^2.0.2"
|
||||||
|
14
scripts/update-version.js
Normal file
14
scripts/update-version.js
Normal 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}'
|
||||||
|
`
|
||||||
|
)
|
@ -1,8 +1,9 @@
|
|||||||
import type { AddressManagerEvents } from '@libp2p/interfaces/address-manager'
|
import type { AddressManagerEvents } from '@libp2p/interface-address-manager'
|
||||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
||||||
import { Multiaddr } from '@multiformats/multiaddr'
|
import type { Multiaddr } from '@multiformats/multiaddr'
|
||||||
|
import { multiaddr } from '@multiformats/multiaddr'
|
||||||
import { peerIdFromString } from '@libp2p/peer-id'
|
import { peerIdFromString } from '@libp2p/peer-id'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
|
|
||||||
export interface AddressManagerInit {
|
export interface AddressManagerInit {
|
||||||
announceFilter?: AddressFilter
|
announceFilter?: AddressFilter
|
||||||
@ -58,28 +59,28 @@ export class DefaultAddressManager extends EventEmitter<AddressManagerEvents> {
|
|||||||
* Get peer listen multiaddrs
|
* Get peer listen multiaddrs
|
||||||
*/
|
*/
|
||||||
getListenAddrs (): Multiaddr[] {
|
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
|
* Get peer announcing multiaddrs
|
||||||
*/
|
*/
|
||||||
getAnnounceAddrs (): Multiaddr[] {
|
getAnnounceAddrs (): Multiaddr[] {
|
||||||
return Array.from(this.announce).map((a) => new Multiaddr(a))
|
return Array.from(this.announce).map((a) => multiaddr(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get observed multiaddrs
|
* Get observed multiaddrs
|
||||||
*/
|
*/
|
||||||
getObservedAddrs (): Multiaddr[] {
|
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
|
* Add peer observed addresses
|
||||||
*/
|
*/
|
||||||
addObservedAddr (addr: string | Multiaddr): void {
|
addObservedAddr (addr: string | Multiaddr): void {
|
||||||
let ma = new Multiaddr(addr)
|
let ma = multiaddr(addr)
|
||||||
const remotePeer = ma.getPeerId()
|
const remotePeer = ma.getPeerId()
|
||||||
|
|
||||||
// strip our peer id if it has been passed
|
// strip our peer id if it has been passed
|
||||||
@ -88,7 +89,7 @@ export class DefaultAddressManager extends EventEmitter<AddressManagerEvents> {
|
|||||||
|
|
||||||
// use same encoding for comparison
|
// use same encoding for comparison
|
||||||
if (remotePeerId.equals(this.components.getPeerId())) {
|
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()}`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +119,7 @@ export class DefaultAddressManager extends EventEmitter<AddressManagerEvents> {
|
|||||||
|
|
||||||
// Create advertising list
|
// Create advertising list
|
||||||
return this.announceFilter(Array.from(addrSet)
|
return this.announceFilter(Array.from(addrSet)
|
||||||
.map(str => new Multiaddr(str)))
|
.map(str => multiaddr(str)))
|
||||||
.map(ma => {
|
.map(ma => {
|
||||||
if (ma.getPeerId() === this.components.getPeerId().toString()) {
|
if (ma.getPeerId() === this.components.getPeerId().toString()) {
|
||||||
return ma
|
return ma
|
||||||
|
@ -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:
|
Once you have a circuit relay node running, you can configure other nodes to use it as a relay as follows:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { Multiaddr } from '@multiformats/multiaddr'
|
import { multiaddr } from '@multiformats/multiaddr'
|
||||||
import Libp2p from 'libp2p'
|
import Libp2p from 'libp2p'
|
||||||
import { TCP } from '@libp2p/tcp'
|
import { TCP } from '@libp2p/tcp'
|
||||||
import { Mplex } from '@libp2p/mplex'
|
import { Mplex } from '@libp2p/mplex'
|
||||||
import { NOISE } from '@chainsafe/libp2p-noise'
|
import { Noise } from '@chainsafe/libp2p-noise'
|
||||||
|
|
||||||
const relayAddr = ...
|
const relayAddr = ...
|
||||||
|
|
||||||
const node = await createLibp2p({
|
const node = await createLibp2p({
|
||||||
addresses: {
|
addresses: {
|
||||||
listen: [new Multiaddr(`${relayAddr}/p2p-circuit`)]
|
listen: [multiaddr(`${relayAddr}/p2p-circuit`)]
|
||||||
},
|
},
|
||||||
transports: [
|
transports: [
|
||||||
new TCP()
|
new TCP()
|
||||||
@ -56,7 +56,7 @@ const node = await createLibp2p({
|
|||||||
new Mplex()
|
new Mplex()
|
||||||
],
|
],
|
||||||
connectionEncryption: [
|
connectionEncryption: [
|
||||||
NOISE
|
new Noise()
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
|
@ -10,10 +10,10 @@ import {
|
|||||||
HOP_METADATA_VALUE,
|
HOP_METADATA_VALUE,
|
||||||
RELAY_RENDEZVOUS_NS
|
RELAY_RENDEZVOUS_NS
|
||||||
} from './constants.js'
|
} from './constants.js'
|
||||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
||||||
import type { AddressSorter, PeerProtocolsChangeData } from '@libp2p/interfaces/peer-store'
|
import type { AddressSorter, PeerProtocolsChangeData } from '@libp2p/interface-peer-store'
|
||||||
import type { Connection } from '@libp2p/interfaces/connection'
|
import type { Connection } from '@libp2p/interface-connection'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
import sort from 'it-sort'
|
import sort from 'it-sort'
|
||||||
import all from 'it-all'
|
import all from 'it-all'
|
||||||
import { pipe } from 'it-pipe'
|
import { pipe } from 'it-pipe'
|
||||||
@ -260,6 +260,12 @@ export class AutoRelay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const peerId = provider.id
|
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.components.getPeerStore().addressBook.add(peerId, provider.multiaddrs)
|
||||||
|
|
||||||
await this._tryToListenOnRelay(peerId)
|
await this._tryToListenOnRelay(peerId)
|
||||||
|
@ -7,11 +7,13 @@ import { pipe } from 'it-pipe'
|
|||||||
import { codes as Errors } from '../../errors.js'
|
import { codes as Errors } from '../../errors.js'
|
||||||
import { stop } from './stop.js'
|
import { stop } from './stop.js'
|
||||||
import { RELAY_CODEC } from '../multicodec.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 { peerIdFromBytes } from '@libp2p/peer-id'
|
||||||
import type { Duplex } from 'it-stream-types'
|
import type { Duplex } from 'it-stream-types'
|
||||||
import type { Circuit } from '../transport.js'
|
import type { Circuit } from '../transport.js'
|
||||||
import type { ConnectionManager } from '@libp2p/interfaces/connection-manager'
|
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')
|
const log = logger('libp2p:circuit:hop')
|
||||||
|
|
||||||
@ -23,7 +25,7 @@ export interface HopRequest {
|
|||||||
connectionManager: ConnectionManager
|
connectionManager: ConnectionManager
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleHop (hopRequest: HopRequest) {
|
export async function handleHop (hopRequest: HopRequest): Promise<void> {
|
||||||
const {
|
const {
|
||||||
connection,
|
connection,
|
||||||
request,
|
request,
|
||||||
@ -83,7 +85,7 @@ export async function handleHop (hopRequest: HopRequest) {
|
|||||||
srcPeer: request.srcPeer
|
srcPeer: request.srcPeer
|
||||||
}
|
}
|
||||||
|
|
||||||
let destinationStream: Duplex<Uint8Array>
|
let destinationStream: Duplex<Uint8ArrayList>
|
||||||
try {
|
try {
|
||||||
log('performing STOP request')
|
log('performing STOP request')
|
||||||
const result = await stop({
|
const result = await stop({
|
||||||
@ -118,7 +120,7 @@ export async function handleHop (hopRequest: HopRequest) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HopConfig {
|
export interface HopConfig extends AbortOptions {
|
||||||
connection: Connection
|
connection: Connection
|
||||||
request: CircuitPB
|
request: CircuitPB
|
||||||
}
|
}
|
||||||
@ -127,14 +129,17 @@ export interface HopConfig {
|
|||||||
* Performs a HOP request to a relay peer, to request a connection to another
|
* 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.
|
* 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 {
|
const {
|
||||||
connection,
|
connection,
|
||||||
request
|
request,
|
||||||
|
signal
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
// Create a new stream to the relay
|
// 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
|
// Send the HOP request
|
||||||
const streamHandler = new StreamHandler({ stream })
|
const streamHandler = new StreamHandler({ stream })
|
||||||
streamHandler.write(request)
|
streamHandler.write(request)
|
||||||
@ -147,6 +152,7 @@ export async function hop (options: HopConfig): Promise<Duplex<Uint8Array>> {
|
|||||||
|
|
||||||
if (response.code === CircuitPB.Status.SUCCESS) {
|
if (response.code === CircuitPB.Status.SUCCESS) {
|
||||||
log('hop request was successful')
|
log('hop request was successful')
|
||||||
|
|
||||||
return streamHandler.rest()
|
return streamHandler.rest()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +162,7 @@ export async function hop (options: HopConfig): Promise<Duplex<Uint8Array>> {
|
|||||||
throw errCode(new Error(`HOP request failed with code "${response.code ?? 'unknown'}"`), 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
|
connection: Connection
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,11 +171,14 @@ export interface CanHopOptions {
|
|||||||
*/
|
*/
|
||||||
export async function canHop (options: CanHopOptions) {
|
export async function canHop (options: CanHopOptions) {
|
||||||
const {
|
const {
|
||||||
connection
|
connection,
|
||||||
|
signal
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
// Create a new stream to the relay
|
// 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
|
// Send the HOP request
|
||||||
const streamHandler = new StreamHandler({ stream })
|
const streamHandler = new StreamHandler({ stream })
|
||||||
|
@ -3,8 +3,10 @@ import { CircuitRelay as CircuitPB } from '../pb/index.js'
|
|||||||
import { RELAY_CODEC } from '../multicodec.js'
|
import { RELAY_CODEC } from '../multicodec.js'
|
||||||
import { StreamHandler } from './stream-handler.js'
|
import { StreamHandler } from './stream-handler.js'
|
||||||
import { validateAddrs } from './utils.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 { Duplex } from 'it-stream-types'
|
||||||
|
import type { AbortOptions } from '@libp2p/interfaces'
|
||||||
|
import type { Uint8ArrayList } from 'uint8arraylist'
|
||||||
|
|
||||||
const log = logger('libp2p:circuit:stop')
|
const log = logger('libp2p:circuit:stop')
|
||||||
|
|
||||||
@ -17,7 +19,7 @@ export interface HandleStopOptions {
|
|||||||
/**
|
/**
|
||||||
* Handles incoming STOP requests
|
* Handles incoming STOP requests
|
||||||
*/
|
*/
|
||||||
export function handleStop (options: HandleStopOptions): Duplex<Uint8Array> | undefined {
|
export function handleStop (options: HandleStopOptions): Duplex<Uint8ArrayList, Uint8ArrayList | Uint8Array> | undefined {
|
||||||
const {
|
const {
|
||||||
connection,
|
connection,
|
||||||
request,
|
request,
|
||||||
@ -42,7 +44,7 @@ export function handleStop (options: HandleStopOptions): Duplex<Uint8Array> | un
|
|||||||
return streamHandler.rest()
|
return streamHandler.rest()
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StopOptions {
|
export interface StopOptions extends AbortOptions {
|
||||||
connection: Connection
|
connection: Connection
|
||||||
request: CircuitPB
|
request: CircuitPB
|
||||||
}
|
}
|
||||||
@ -53,10 +55,13 @@ export interface StopOptions {
|
|||||||
export async function stop (options: StopOptions) {
|
export async function stop (options: StopOptions) {
|
||||||
const {
|
const {
|
||||||
connection,
|
connection,
|
||||||
request
|
request,
|
||||||
|
signal
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
const { stream } = await connection.newStream([RELAY_CODEC])
|
const stream = await connection.newStream(RELAY_CODEC, {
|
||||||
|
signal
|
||||||
|
})
|
||||||
log('starting stop request to %p', connection.remotePeer)
|
log('starting stop request to %p', connection.remotePeer)
|
||||||
const streamHandler = new StreamHandler({ stream })
|
const streamHandler = new StreamHandler({ stream })
|
||||||
|
|
||||||
|
@ -2,8 +2,9 @@ import { logger } from '@libp2p/logger'
|
|||||||
import * as lp from 'it-length-prefixed'
|
import * as lp from 'it-length-prefixed'
|
||||||
import { Handshake, handshake } from 'it-handshake'
|
import { Handshake, handshake } from 'it-handshake'
|
||||||
import { CircuitRelay } from '../pb/index.js'
|
import { CircuitRelay } from '../pb/index.js'
|
||||||
import type { Stream } from '@libp2p/interfaces/connection'
|
import type { Stream } from '@libp2p/interface-connection'
|
||||||
import type { Source } from 'it-stream-types'
|
import type { Source } from 'it-stream-types'
|
||||||
|
import type { Uint8ArrayList } from 'uint8arraylist'
|
||||||
|
|
||||||
const log = logger('libp2p:circuit:stream-handler')
|
const log = logger('libp2p:circuit:stream-handler')
|
||||||
|
|
||||||
@ -21,8 +22,8 @@ export interface StreamHandlerOptions {
|
|||||||
|
|
||||||
export class StreamHandler {
|
export class StreamHandler {
|
||||||
private readonly stream: Stream
|
private readonly stream: Stream
|
||||||
private readonly shake: Handshake
|
private readonly shake: Handshake<Uint8ArrayList | Uint8Array>
|
||||||
private readonly decoder: Source<Uint8Array>
|
private readonly decoder: Source<Uint8ArrayList>
|
||||||
|
|
||||||
constructor (options: StreamHandlerOptions) {
|
constructor (options: StreamHandlerOptions) {
|
||||||
const { stream, maxLength = 4096 } = options
|
const { stream, maxLength = 4096 } = options
|
||||||
@ -40,7 +41,7 @@ export class StreamHandler {
|
|||||||
const msg = await this.decoder.next()
|
const msg = await this.decoder.next()
|
||||||
|
|
||||||
if (msg.value != null) {
|
if (msg.value != null) {
|
||||||
const value = CircuitRelay.decode(msg.value.slice())
|
const value = CircuitRelay.decode(msg.value)
|
||||||
log('read message type', value.type)
|
log('read message type', value.type)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@ -55,7 +56,7 @@ export class StreamHandler {
|
|||||||
*/
|
*/
|
||||||
write (msg: CircuitRelay) {
|
write (msg: CircuitRelay) {
|
||||||
log('write message type %s', msg.type)
|
log('write message type %s', msg.type)
|
||||||
this.shake.write(lp.encode.single(CircuitRelay.encode(msg)).slice())
|
this.shake.write(lp.encode.single(CircuitRelay.encode(msg)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Multiaddr } from '@multiformats/multiaddr'
|
import { multiaddr } from '@multiformats/multiaddr'
|
||||||
import { CircuitRelay } from '../pb/index.js'
|
import { CircuitRelay } from '../pb/index.js'
|
||||||
import type { StreamHandler } from './stream-handler.js'
|
import type { StreamHandler } from './stream-handler.js'
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ export function validateAddrs (msg: CircuitRelay, streamHandler: StreamHandler)
|
|||||||
try {
|
try {
|
||||||
if (msg.dstPeer?.addrs != null) {
|
if (msg.dstPeer?.addrs != null) {
|
||||||
msg.dstPeer.addrs.forEach((addr) => {
|
msg.dstPeer.addrs.forEach((addr) => {
|
||||||
return new Multiaddr(addr)
|
return multiaddr(addr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@ -32,7 +32,7 @@ export function validateAddrs (msg: CircuitRelay, streamHandler: StreamHandler)
|
|||||||
try {
|
try {
|
||||||
if (msg.srcPeer?.addrs != null) {
|
if (msg.srcPeer?.addrs != null) {
|
||||||
msg.srcPeer.addrs.forEach((addr) => {
|
msg.srcPeer.addrs.forEach((addr) => {
|
||||||
return new Multiaddr(addr)
|
return multiaddr(addr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
@ -10,9 +10,10 @@ import { namespaceToCid } from './utils.js'
|
|||||||
import {
|
import {
|
||||||
RELAY_RENDEZVOUS_NS
|
RELAY_RENDEZVOUS_NS
|
||||||
} from './constants.js'
|
} from './constants.js'
|
||||||
import type { AddressSorter } from '@libp2p/interfaces/peer-store'
|
import type { AddressSorter } from '@libp2p/interface-peer-store'
|
||||||
import type { Startable } from '@libp2p/interfaces/startable'
|
import type { Startable } from '@libp2p/interfaces/startable'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
|
import type { RelayConfig } from '../index.js'
|
||||||
|
|
||||||
const log = logger('libp2p:relay')
|
const log = logger('libp2p:relay')
|
||||||
|
|
||||||
@ -22,11 +23,6 @@ export interface RelayAdvertiseConfig {
|
|||||||
ttl?: number
|
ttl?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HopConfig {
|
|
||||||
enabled?: boolean
|
|
||||||
active?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AutoRelayConfig {
|
export interface AutoRelayConfig {
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
|
|
||||||
@ -36,13 +32,8 @@ export interface AutoRelayConfig {
|
|||||||
maxListeners: number
|
maxListeners: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RelayInit {
|
export interface RelayInit extends RelayConfig {
|
||||||
addressSorter?: AddressSorter
|
addressSorter?: AddressSorter
|
||||||
maxListeners?: number
|
|
||||||
onError?: (error: Error, msg?: string) => void
|
|
||||||
hop: HopConfig
|
|
||||||
advertise: RelayAdvertiseConfig
|
|
||||||
autoRelay: AutoRelayConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Relay implements Startable {
|
export class Relay implements Startable {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
||||||
import type { ConnectionManager } from '@libp2p/interfaces/connection-manager'
|
import type { ConnectionManager } from '@libp2p/interface-connection-manager'
|
||||||
import type { PeerStore } from '@libp2p/interfaces/peer-store'
|
import type { PeerStore } from '@libp2p/interface-peer-store'
|
||||||
import type { Listener } from '@libp2p/interfaces/transport'
|
import type { Listener } from '@libp2p/interface-transport'
|
||||||
import { peerIdFromString } from '@libp2p/peer-id'
|
import { peerIdFromString } from '@libp2p/peer-id'
|
||||||
import { Multiaddr } from '@multiformats/multiaddr'
|
import type { Multiaddr } from '@multiformats/multiaddr'
|
||||||
|
import { multiaddr } from '@multiformats/multiaddr'
|
||||||
|
|
||||||
export interface ListenerOptions {
|
export interface ListenerOptions {
|
||||||
peerStore: PeerStore
|
peerStore: PeerStore
|
||||||
@ -18,7 +19,7 @@ export function createListener (options: ListenerOptions): Listener {
|
|||||||
*/
|
*/
|
||||||
async function listen (addr: Multiaddr): Promise<void> {
|
async function listen (addr: Multiaddr): Promise<void> {
|
||||||
const addrString = addr.toString().split('/p2p-circuit').find(a => a !== '')
|
const addrString = addr.toString().split('/p2p-circuit').find(a => a !== '')
|
||||||
const ma = new Multiaddr(addrString)
|
const ma = multiaddr(addrString)
|
||||||
|
|
||||||
const relayPeerStr = ma.getPeerId()
|
const relayPeerStr = ma.getPeerId()
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
/* eslint-disable import/export */
|
/* eslint-disable import/export */
|
||||||
/* eslint-disable @typescript-eslint/no-namespace */
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
|
|
||||||
import { enumeration, encodeMessage, decodeMessage, message, bytes } from 'protons-runtime'
|
import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime'
|
||||||
|
import type { Uint8ArrayList } from 'uint8arraylist'
|
||||||
import type { Codec } from 'protons-runtime'
|
import type { Codec } from 'protons-runtime'
|
||||||
|
|
||||||
export interface CircuitRelay {
|
export interface CircuitRelay {
|
||||||
@ -52,7 +53,7 @@ export namespace CircuitRelay {
|
|||||||
|
|
||||||
export namespace Status {
|
export namespace Status {
|
||||||
export const codec = () => {
|
export const codec = () => {
|
||||||
return enumeration<typeof Status>(__StatusValues)
|
return enumeration<Status>(__StatusValues)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ export namespace CircuitRelay {
|
|||||||
|
|
||||||
export namespace Type {
|
export namespace Type {
|
||||||
export const codec = () => {
|
export const codec = () => {
|
||||||
return enumeration<typeof Type>(__TypeValues)
|
return enumeration<Type>(__TypeValues)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,36 +83,153 @@ export namespace CircuitRelay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace Peer {
|
export namespace Peer {
|
||||||
|
let _codec: Codec<Peer>
|
||||||
|
|
||||||
export const codec = (): Codec<Peer> => {
|
export const codec = (): Codec<Peer> => {
|
||||||
return message<Peer>({
|
if (_codec == null) {
|
||||||
1: { name: 'id', codec: bytes },
|
_codec = message<Peer>((obj, writer, opts = {}) => {
|
||||||
2: { name: 'addrs', codec: bytes, repeats: true }
|
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 => {
|
export const encode = (obj: Peer): Uint8Array => {
|
||||||
return encodeMessage(obj, Peer.codec())
|
return encodeMessage(obj, Peer.codec())
|
||||||
}
|
}
|
||||||
|
|
||||||
export const decode = (buf: Uint8Array): Peer => {
|
export const decode = (buf: Uint8Array | Uint8ArrayList): Peer => {
|
||||||
return decodeMessage(buf, Peer.codec())
|
return decodeMessage(buf, Peer.codec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _codec: Codec<CircuitRelay>
|
||||||
|
|
||||||
export const codec = (): Codec<CircuitRelay> => {
|
export const codec = (): Codec<CircuitRelay> => {
|
||||||
return message<CircuitRelay>({
|
if (_codec == null) {
|
||||||
1: { name: 'type', codec: CircuitRelay.Type.codec(), optional: true },
|
_codec = message<CircuitRelay>((obj, writer, opts = {}) => {
|
||||||
2: { name: 'srcPeer', codec: CircuitRelay.Peer.codec(), optional: true },
|
if (opts.lengthDelimited !== false) {
|
||||||
3: { name: 'dstPeer', codec: CircuitRelay.Peer.codec(), optional: true },
|
writer.fork()
|
||||||
4: { name: 'code', codec: CircuitRelay.Status.codec(), optional: true }
|
}
|
||||||
})
|
|
||||||
|
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 => {
|
export const encode = (obj: CircuitRelay): Uint8Array => {
|
||||||
return encodeMessage(obj, CircuitRelay.codec())
|
return encodeMessage(obj, CircuitRelay.codec())
|
||||||
}
|
}
|
||||||
|
|
||||||
export const decode = (buf: Uint8Array): CircuitRelay => {
|
export const decode = (buf: Uint8Array | Uint8ArrayList): CircuitRelay => {
|
||||||
return decodeMessage(buf, CircuitRelay.codec())
|
return decodeMessage(buf, CircuitRelay.codec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { logger } from '@libp2p/logger'
|
import { logger } from '@libp2p/logger'
|
||||||
import errCode from 'err-code'
|
import errCode from 'err-code'
|
||||||
import * as mafmt from '@multiformats/mafmt'
|
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 { CircuitRelay as CircuitPB } from './pb/index.js'
|
||||||
import { codes } from '../errors.js'
|
import { codes } from '../errors.js'
|
||||||
import { streamToMaConnection } from '@libp2p/utils/stream-to-ma-conn'
|
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 { handleCanHop, handleHop, hop } from './circuit/hop.js'
|
||||||
import { handleStop } from './circuit/stop.js'
|
import { handleStop } from './circuit/stop.js'
|
||||||
import { StreamHandler } from './circuit/stream-handler.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 { 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 { AbortOptions } from '@libp2p/interfaces'
|
||||||
import type { IncomingStreamData } from '@libp2p/interfaces/registrar'
|
import type { IncomingStreamData } from '@libp2p/interface-registrar'
|
||||||
import type { Listener, Transport, CreateListenerOptions, ConnectionHandler } from '@libp2p/interfaces/transport'
|
import type { Listener, Transport, CreateListenerOptions, ConnectionHandler } from '@libp2p/interface-transport'
|
||||||
import type { Connection } from '@libp2p/interfaces/connection'
|
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')
|
const log = logger('libp2p:circuit')
|
||||||
|
|
||||||
export class Circuit implements Transport, Initializable {
|
export class Circuit implements Transport, Initializable {
|
||||||
private handler?: ConnectionHandler
|
private handler?: ConnectionHandler
|
||||||
private components: Components = new Components()
|
private components: Components = new Components()
|
||||||
|
private readonly _init: RelayConfig
|
||||||
|
|
||||||
|
constructor (init: RelayConfig) {
|
||||||
|
this._init = init
|
||||||
|
}
|
||||||
|
|
||||||
init (components: Components): void {
|
init (components: Components): void {
|
||||||
this.components = components
|
this.components = components
|
||||||
@ -54,49 +66,25 @@ export class Circuit implements Transport, Initializable {
|
|||||||
|
|
||||||
async _onProtocol (data: IncomingStreamData) {
|
async _onProtocol (data: IncomingStreamData) {
|
||||||
const { connection, stream } = data
|
const { connection, stream } = data
|
||||||
const streamHandler = new StreamHandler({ stream })
|
const controller = new TimeoutController(this._init.hop.timeout)
|
||||||
const request = await streamHandler.read()
|
|
||||||
|
|
||||||
if (request == null) {
|
try {
|
||||||
log('request was invalid, could not read from stream')
|
// fails on node < 15.4
|
||||||
streamHandler.write({
|
setMaxListeners?.(Infinity, controller.signal)
|
||||||
type: CircuitPB.Type.STATUS,
|
} catch {}
|
||||||
code: CircuitPB.Status.MALFORMED_MESSAGE
|
|
||||||
|
try {
|
||||||
|
const source = abortableDuplex(stream, controller.signal)
|
||||||
|
const streamHandler = new StreamHandler({
|
||||||
|
stream: {
|
||||||
|
...stream,
|
||||||
|
...source
|
||||||
|
}
|
||||||
})
|
})
|
||||||
streamHandler.close()
|
const request = await streamHandler.read()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let virtualConnection
|
if (request == null) {
|
||||||
|
log('request was invalid, could not read from stream')
|
||||||
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)
|
|
||||||
streamHandler.write({
|
streamHandler.write({
|
||||||
type: CircuitPB.Type.STATUS,
|
type: CircuitPB.Type.STATUS,
|
||||||
code: CircuitPB.Status.MALFORMED_MESSAGE
|
code: CircuitPB.Status.MALFORMED_MESSAGE
|
||||||
@ -104,27 +92,68 @@ export class Circuit implements Transport, Initializable {
|
|||||||
streamHandler.close()
|
streamHandler.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (virtualConnection != null) {
|
let virtualConnection: Duplex<Uint8ArrayList, Uint8ArrayList | Uint8Array> | undefined
|
||||||
// @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)
|
|
||||||
|
|
||||||
const conn = await this.components.getUpgrader().upgradeInbound(maConn)
|
switch (request.type) {
|
||||||
log('%s connection %s upgraded', type, maConn.remoteAddr)
|
case CircuitPB.Type.CAN_HOP: {
|
||||||
|
log('received CAN_HOP request from %p', connection.remotePeer)
|
||||||
if (this.handler != null) {
|
await handleCanHop({ circuit: this, connection, streamHandler })
|
||||||
this.handler(conn)
|
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> {
|
async dial (ma: Multiaddr, options: AbortOptions = {}): Promise<Connection> {
|
||||||
// Check the multiaddr to see if it contains a relay and a destination peer
|
// Check the multiaddr to see if it contains a relay and a destination peer
|
||||||
const addrs = ma.toString().split('/p2p-circuit')
|
const addrs = ma.toString().split('/p2p-circuit')
|
||||||
const relayAddr = new Multiaddr(addrs[0])
|
const relayAddr = multiaddr(addrs[0])
|
||||||
const destinationAddr = new Multiaddr(addrs[addrs.length - 1])
|
const destinationAddr = multiaddr(addrs[addrs.length - 1])
|
||||||
const relayId = relayAddr.getPeerId()
|
const relayId = relayAddr.getPeerId()
|
||||||
const destinationId = destinationAddr.getPeerId()
|
const destinationId = destinationAddr.getPeerId()
|
||||||
|
|
||||||
@ -160,6 +189,7 @@ export class Circuit implements Transport, Initializable {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const virtualConnection = await hop({
|
const virtualConnection = await hop({
|
||||||
|
...options,
|
||||||
connection: relayConnection,
|
connection: relayConnection,
|
||||||
request: {
|
request: {
|
||||||
type: CircuitPB.Type.HOP,
|
type: CircuitPB.Type.HOP,
|
||||||
@ -169,7 +199,7 @@ export class Circuit implements Transport, Initializable {
|
|||||||
},
|
},
|
||||||
dstPeer: {
|
dstPeer: {
|
||||||
id: destinationPeer.toBytes(),
|
id: destinationPeer.toBytes(),
|
||||||
addrs: [new Multiaddr(destinationAddr).bytes]
|
addrs: [multiaddr(destinationAddr).bytes]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -10,6 +10,7 @@ import type { Libp2pInit } from './index.js'
|
|||||||
import { codes, messages } from './errors.js'
|
import { codes, messages } from './errors.js'
|
||||||
import errCode from 'err-code'
|
import errCode from 'err-code'
|
||||||
import type { RecursivePartial } from '@libp2p/interfaces'
|
import type { RecursivePartial } from '@libp2p/interfaces'
|
||||||
|
import { isNode, isBrowser, isWebWorker, isElectronMain, isElectronRenderer, isReactNative } from 'wherearewe'
|
||||||
|
|
||||||
const DefaultConfig: Partial<Libp2pInit> = {
|
const DefaultConfig: Partial<Libp2pInit> = {
|
||||||
addresses: {
|
addresses: {
|
||||||
@ -26,6 +27,7 @@ const DefaultConfig: Partial<Libp2pInit> = {
|
|||||||
maxParallelDials: Constants.MAX_PARALLEL_DIALS,
|
maxParallelDials: Constants.MAX_PARALLEL_DIALS,
|
||||||
maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS,
|
maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS,
|
||||||
dialTimeout: Constants.DIAL_TIMEOUT,
|
dialTimeout: Constants.DIAL_TIMEOUT,
|
||||||
|
inboundUpgradeTimeout: Constants.INBOUND_UPGRADE_TIMEOUT,
|
||||||
resolvers: {
|
resolvers: {
|
||||||
dnsaddr: dnsaddrResolver
|
dnsaddr: dnsaddrResolver
|
||||||
},
|
},
|
||||||
@ -67,7 +69,8 @@ const DefaultConfig: Partial<Libp2pInit> = {
|
|||||||
},
|
},
|
||||||
hop: {
|
hop: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
active: false
|
active: false,
|
||||||
|
timeout: 30000
|
||||||
},
|
},
|
||||||
autoRelay: {
|
autoRelay: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@ -79,13 +82,24 @@ const DefaultConfig: Partial<Libp2pInit> = {
|
|||||||
host: {
|
host: {
|
||||||
agentVersion: AGENT_VERSION
|
agentVersion: AGENT_VERSION
|
||||||
},
|
},
|
||||||
timeout: 30000
|
// 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: {
|
ping: {
|
||||||
protocolPrefix: 'ipfs'
|
protocolPrefix: 'ipfs',
|
||||||
|
maxInboundStreams: 1,
|
||||||
|
maxOutboundStreams: 1,
|
||||||
|
timeout: 10000
|
||||||
},
|
},
|
||||||
fetch: {
|
fetch: {
|
||||||
protocolPrefix: 'libp2p'
|
protocolPrefix: 'libp2p',
|
||||||
|
maxInboundStreams: 1,
|
||||||
|
maxOutboundStreams: 1,
|
||||||
|
timeout: 10000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,5 +118,14 @@ export function validateConfig (opts: RecursivePartial<Libp2pInit>): Libp2pInit
|
|||||||
throw errCode(new Error(messages.ERR_PROTECTOR_REQUIRED), codes.ERR_PROTECTOR_REQUIRED)
|
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
|
return resultingOptions
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import { pipe } from 'it-pipe'
|
|||||||
import filter from 'it-filter'
|
import filter from 'it-filter'
|
||||||
import sort from 'it-sort'
|
import sort from 'it-sort'
|
||||||
import type { Startable } from '@libp2p/interfaces/startable'
|
import type { Startable } from '@libp2p/interfaces/startable'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
|
|
||||||
const log = logger('libp2p:connection-manager:auto-dialler')
|
const log = logger('libp2p:connection-manager:auto-dialler')
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
|
import type { PeerInfo } from '@libp2p/interface-peer-info'
|
||||||
import { logger } from '@libp2p/logger'
|
import { logger } from '@libp2p/logger'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
import { TimeoutController } from 'timeout-abort-controller'
|
import { TimeoutController } from 'timeout-abort-controller'
|
||||||
|
import { setMaxListeners } from 'events'
|
||||||
|
|
||||||
const log = logger('libp2p:dialer:auto-dialer')
|
const log = logger('libp2p:dialer:auto-dialer')
|
||||||
|
|
||||||
@ -44,6 +45,11 @@ export class AutoDialer {
|
|||||||
|
|
||||||
const controller = new TimeoutController(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, {
|
void this.components.getConnectionManager().openConnection(peer.id, {
|
||||||
signal: controller.signal
|
signal: controller.signal
|
||||||
})
|
})
|
||||||
|
@ -5,9 +5,9 @@ import { setMaxListeners } from 'events'
|
|||||||
import { codes } from '../../errors.js'
|
import { codes } from '../../errors.js'
|
||||||
import { logger } from '@libp2p/logger'
|
import { logger } from '@libp2p/logger'
|
||||||
import type { Multiaddr } from '@multiformats/multiaddr'
|
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 { AbortOptions } from '@libp2p/interfaces'
|
||||||
import type { Dialer } from './index.js'
|
import type { Dialer } from '@libp2p/interface-connection-manager'
|
||||||
|
|
||||||
const log = logger('libp2p:dialer:dial-request')
|
const log = logger('libp2p:dialer:dial-request')
|
||||||
|
|
||||||
|
@ -3,7 +3,8 @@ import all from 'it-all'
|
|||||||
import filter from 'it-filter'
|
import filter from 'it-filter'
|
||||||
import { pipe } from 'it-pipe'
|
import { pipe } from 'it-pipe'
|
||||||
import errCode from 'err-code'
|
import errCode from 'err-code'
|
||||||
import { Multiaddr, Resolver } from '@multiformats/multiaddr'
|
import type { Multiaddr, Resolver } from '@multiformats/multiaddr'
|
||||||
|
import { multiaddr, resolvers } from '@multiformats/multiaddr'
|
||||||
import { TimeoutController } from 'timeout-abort-controller'
|
import { TimeoutController } from 'timeout-abort-controller'
|
||||||
import { AbortError } from '@libp2p/interfaces/errors'
|
import { AbortError } from '@libp2p/interfaces/errors'
|
||||||
import { anySignal } from 'any-signal'
|
import { anySignal } from 'any-signal'
|
||||||
@ -18,16 +19,17 @@ import {
|
|||||||
MAX_PER_PEER_DIALS,
|
MAX_PER_PEER_DIALS,
|
||||||
MAX_ADDRS_TO_DIAL
|
MAX_ADDRS_TO_DIAL
|
||||||
} from '../../constants.js'
|
} from '../../constants.js'
|
||||||
import type { Connection } from '@libp2p/interfaces/connection'
|
import type { Connection } from '@libp2p/interface-connection'
|
||||||
import type { AbortOptions } from '@libp2p/interfaces'
|
import type { AbortOptions } from '@libp2p/interfaces'
|
||||||
import type { Startable } from '@libp2p/interfaces/startable'
|
import type { Startable } from '@libp2p/interfaces/startable'
|
||||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
||||||
import { getPeer } from '../../get-peer.js'
|
import { getPeer } from '../../get-peer.js'
|
||||||
import sort from 'it-sort'
|
import sort from 'it-sort'
|
||||||
import { Components, Initializable } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
import map from 'it-map'
|
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/interfaces/metrics'
|
import type { ComponentMetricsTracker } from '@libp2p/interface-metrics'
|
||||||
|
import type { Dialer } from '@libp2p/interface-connection-manager'
|
||||||
|
|
||||||
const log = logger('libp2p:dialer')
|
const log = logger('libp2p:dialer')
|
||||||
|
|
||||||
@ -85,8 +87,8 @@ export interface DialerInit {
|
|||||||
metrics?: ComponentMetricsTracker
|
metrics?: ComponentMetricsTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Dialer implements Startable, Initializable {
|
export class DefaultDialer implements Startable, Dialer {
|
||||||
private components: Components = new Components()
|
private readonly components: Components
|
||||||
private readonly addressSorter: AddressSorter
|
private readonly addressSorter: AddressSorter
|
||||||
private readonly maxAddrsToDial: number
|
private readonly maxAddrsToDial: number
|
||||||
private readonly timeout: number
|
private readonly timeout: number
|
||||||
@ -96,13 +98,14 @@ export class Dialer implements Startable, Initializable {
|
|||||||
public pendingDialTargets: Map<string, PendingDialTarget>
|
public pendingDialTargets: Map<string, PendingDialTarget>
|
||||||
private started: boolean
|
private started: boolean
|
||||||
|
|
||||||
constructor (init: DialerInit = {}) {
|
constructor (components: Components, init: DialerInit = {}) {
|
||||||
this.started = false
|
this.started = false
|
||||||
this.addressSorter = init.addressSorter ?? publicAddressesFirst
|
this.addressSorter = init.addressSorter ?? publicAddressesFirst
|
||||||
this.maxAddrsToDial = init.maxAddrsToDial ?? MAX_ADDRS_TO_DIAL
|
this.maxAddrsToDial = init.maxAddrsToDial ?? MAX_ADDRS_TO_DIAL
|
||||||
this.timeout = init.dialTimeout ?? DIAL_TIMEOUT
|
this.timeout = init.dialTimeout ?? DIAL_TIMEOUT
|
||||||
this.maxDialsPerPeer = init.maxDialsPerPeer ?? MAX_PER_PEER_DIALS
|
this.maxDialsPerPeer = init.maxDialsPerPeer ?? MAX_PER_PEER_DIALS
|
||||||
this.tokens = [...new Array(init.maxParallelDials ?? MAX_PARALLEL_DIALS)].map((_, index) => index)
|
this.tokens = [...new Array(init.maxParallelDials ?? MAX_PARALLEL_DIALS)].map((_, index) => index)
|
||||||
|
this.components = components
|
||||||
this.pendingDials = trackedMap({
|
this.pendingDials = trackedMap({
|
||||||
component: METRICS_COMPONENT,
|
component: METRICS_COMPONENT,
|
||||||
metric: METRICS_PENDING_DIALS,
|
metric: METRICS_PENDING_DIALS,
|
||||||
@ -111,18 +114,14 @@ export class Dialer implements Startable, Initializable {
|
|||||||
this.pendingDialTargets = trackedMap({
|
this.pendingDialTargets = trackedMap({
|
||||||
component: METRICS_COMPONENT,
|
component: METRICS_COMPONENT,
|
||||||
metric: METRICS_PENDING_DIAL_TARGETS,
|
metric: METRICS_PENDING_DIAL_TARGETS,
|
||||||
metrics: init.metrics
|
metrics: components.getMetrics()
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(init.resolvers ?? {})) {
|
for (const [key, value] of Object.entries(init.resolvers ?? {})) {
|
||||||
Multiaddr.resolvers.set(key, value)
|
resolvers.set(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init (components: Components): void {
|
|
||||||
this.components = components
|
|
||||||
}
|
|
||||||
|
|
||||||
isStarted () {
|
isStarted () {
|
||||||
return this.started
|
return this.started
|
||||||
}
|
}
|
||||||
@ -177,7 +176,7 @@ export class Dialer implements Startable, Initializable {
|
|||||||
|
|
||||||
log('creating dial target for %p', id)
|
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) {
|
if (dialTarget.addrs.length === 0) {
|
||||||
throw errCode(new Error('The dial request has no valid addresses'), codes.ERR_NO_VALID_ADDRESSES)
|
throw errCode(new Error('The dial request has no valid addresses'), codes.ERR_NO_VALID_ADDRESSES)
|
||||||
@ -207,7 +206,7 @@ export class Dialer implements Startable, Initializable {
|
|||||||
* The dial to the first address that is successfully able to upgrade a connection
|
* The dial to the first address that is successfully able to upgrade a connection
|
||||||
* will be used.
|
* will be used.
|
||||||
*/
|
*/
|
||||||
async _createCancellableDialTarget (peer: PeerId): Promise<DialTarget> {
|
async _createCancellableDialTarget (peer: PeerId, options: AbortOptions): Promise<DialTarget> {
|
||||||
// Make dial target promise cancellable
|
// Make dial target promise cancellable
|
||||||
const id = `${(parseInt(String(Math.random() * 1e9), 10)).toString()}${Date.now()}`
|
const id = `${(parseInt(String(Math.random() * 1e9), 10)).toString()}${Date.now()}`
|
||||||
const cancellablePromise = new Promise<DialTarget>((resolve, reject) => {
|
const cancellablePromise = new Promise<DialTarget>((resolve, reject) => {
|
||||||
@ -216,7 +215,7 @@ export class Dialer implements Startable, Initializable {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const dialTarget = await Promise.race([
|
const dialTarget = await Promise.race([
|
||||||
this._createDialTarget(peer),
|
this._createDialTarget(peer, options),
|
||||||
cancellablePromise
|
cancellablePromise
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -232,16 +231,24 @@ export class Dialer implements Startable, Initializable {
|
|||||||
* If a multiaddr is received it should be the first address attempted.
|
* If a multiaddr is received it should be the first address attempted.
|
||||||
* Multiaddrs not supported by the available transports will be filtered out.
|
* Multiaddrs not supported by the available transports will be filtered out.
|
||||||
*/
|
*/
|
||||||
async _createDialTarget (peer: PeerId): Promise<DialTarget> {
|
async _createDialTarget (peer: PeerId, options: AbortOptions): Promise<DialTarget> {
|
||||||
const knownAddrs = await pipe(
|
const _resolve = this._resolve.bind(this)
|
||||||
|
|
||||||
|
const addrs = await pipe(
|
||||||
await this.components.getPeerStore().addressBook.get(peer),
|
await this.components.getPeerStore().addressBook.get(peer),
|
||||||
(source) => filter(source, async (address) => {
|
(source) => filter(source, async (address) => {
|
||||||
return !(await this.components.getConnectionGater().denyDialMultiaddr(peer, address.multiaddr))
|
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) => sort(source, this.addressSorter),
|
||||||
(source) => map(source, (address) => {
|
async function * resolve (source) {
|
||||||
const ma = address.multiaddr
|
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()) {
|
if (peer.toString() === ma.getPeerId()) {
|
||||||
return ma
|
return ma
|
||||||
}
|
}
|
||||||
@ -251,23 +258,14 @@ export class Dialer implements Startable, Initializable {
|
|||||||
async (source) => await all(source)
|
async (source) => await all(source)
|
||||||
)
|
)
|
||||||
|
|
||||||
const addrs: Multiaddr[] = []
|
if (addrs.length > this.maxAddrsToDial) {
|
||||||
for (const a of knownAddrs) {
|
|
||||||
const resolvedAddrs = await this._resolve(a)
|
|
||||||
resolvedAddrs.forEach(ra => addrs.push(ra))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiaddrs not supported by the available transports will be filtered out.
|
|
||||||
const supportedAddrs = addrs.filter(a => this.components.getTransportManager().transportForMultiaddr(a))
|
|
||||||
|
|
||||||
if (supportedAddrs.length > this.maxAddrsToDial) {
|
|
||||||
await this.components.getPeerStore().delete(peer)
|
await this.components.getPeerStore().delete(peer)
|
||||||
throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES)
|
throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: peer.toString(),
|
id: peer.toString(),
|
||||||
addrs: supportedAddrs
|
addrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,7 +282,10 @@ export class Dialer implements Startable, Initializable {
|
|||||||
throw errCode(new Error('already aborted'), codes.ERR_ALREADY_ABORTED)
|
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({
|
const dialRequest = new DialRequest({
|
||||||
@ -341,7 +342,7 @@ export class Dialer implements Startable, Initializable {
|
|||||||
/**
|
/**
|
||||||
* Resolve multiaddr recursively
|
* 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
|
// TODO: recursive logic should live in multiaddr once dns4/dns6 support is in place
|
||||||
// Now only supporting resolve for dnsaddr
|
// Now only supporting resolve for dnsaddr
|
||||||
const resolvableProto = ma.protoNames().includes('dnsaddr')
|
const resolvableProto = ma.protoNames().includes('dnsaddr')
|
||||||
@ -351,9 +352,9 @@ export class Dialer implements Startable, Initializable {
|
|||||||
return [ma]
|
return [ma]
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolvedMultiaddrs = await this._resolveRecord(ma)
|
const resolvedMultiaddrs = await this._resolveRecord(ma, options)
|
||||||
const recursiveMultiaddrs = await Promise.all(resolvedMultiaddrs.map(async (nm) => {
|
const recursiveMultiaddrs = await Promise.all(resolvedMultiaddrs.map(async (nm) => {
|
||||||
return await this._resolve(nm)
|
return await this._resolve(nm, options)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const addrs = recursiveMultiaddrs.flat()
|
const addrs = recursiveMultiaddrs.flat()
|
||||||
@ -368,10 +369,10 @@ export class Dialer implements Startable, Initializable {
|
|||||||
/**
|
/**
|
||||||
* Resolve a given multiaddr. If this fails, an empty array will be returned
|
* 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 {
|
try {
|
||||||
ma = new Multiaddr(ma.toString()) // Use current multiaddr module
|
ma = multiaddr(ma.toString()) // Use current multiaddr module
|
||||||
const multiaddrs = await ma.resolve()
|
const multiaddrs = await ma.resolve(options)
|
||||||
return multiaddrs
|
return multiaddrs
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error(`multiaddr ${ma.toString()} could not be resolved`, err)
|
log.error(`multiaddr ${ma.toString()} could not be resolved`, err)
|
||||||
|
@ -7,17 +7,18 @@ import retimer from 'retimer'
|
|||||||
import type { AbortOptions } from '@libp2p/interfaces'
|
import type { AbortOptions } from '@libp2p/interfaces'
|
||||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
||||||
import type { Startable } from '@libp2p/interfaces/startable'
|
import type { Startable } from '@libp2p/interfaces/startable'
|
||||||
import { trackedMap } from '@libp2p/tracked-map'
|
|
||||||
import { codes } from '../errors.js'
|
import { codes } from '../errors.js'
|
||||||
import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id'
|
import { isPeerId, PeerId } from '@libp2p/interface-peer-id'
|
||||||
import { setMaxListeners } from 'events'
|
import { setMaxListeners } from 'events'
|
||||||
import type { Connection } from '@libp2p/interfaces/connection'
|
import type { Connection } from '@libp2p/interface-connection'
|
||||||
import type { ConnectionManager } from '@libp2p/interfaces/connection-manager'
|
import type { ConnectionManager } from '@libp2p/interface-connection-manager'
|
||||||
import { Components, Initializable } from '@libp2p/interfaces/components'
|
import { Components, Initializable } from '@libp2p/components'
|
||||||
import * as STATUS from '@libp2p/interfaces/connection/status'
|
import * as STATUS from '@libp2p/interface-connection/status'
|
||||||
import { Dialer } from './dialer/index.js'
|
import type { AddressSorter } from '@libp2p/interface-peer-store'
|
||||||
import type { AddressSorter } from '@libp2p/interfaces/peer-store'
|
|
||||||
import type { Resolver } from '@multiformats/multiaddr'
|
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')
|
const log = logger('libp2p:connection-manager')
|
||||||
|
|
||||||
@ -30,13 +31,12 @@ const defaultOptions: Partial<ConnectionManagerInit> = {
|
|||||||
maxEventLoopDelay: Infinity,
|
maxEventLoopDelay: Infinity,
|
||||||
pollInterval: 2000,
|
pollInterval: 2000,
|
||||||
autoDialInterval: 10000,
|
autoDialInterval: 10000,
|
||||||
movingAverageInterval: 60000,
|
movingAverageInterval: 60000
|
||||||
defaultPeerValue: 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const METRICS_SYSTEM = 'libp2p'
|
||||||
const METRICS_COMPONENT = 'connection-manager'
|
const METRICS_COMPONENT = 'connection-manager'
|
||||||
const METRICS_PEER_CONNECTIONS = 'peer-connections'
|
const STARTUP_RECONNECT_TIMEOUT = 60000
|
||||||
const METRICS_PEER_VALUES = 'peer-values'
|
|
||||||
|
|
||||||
export interface ConnectionManagerInit {
|
export interface ConnectionManagerInit {
|
||||||
/**
|
/**
|
||||||
@ -79,11 +79,6 @@ export interface ConnectionManagerInit {
|
|||||||
*/
|
*/
|
||||||
movingAverageInterval?: number
|
movingAverageInterval?: number
|
||||||
|
|
||||||
/**
|
|
||||||
* The value of the peer
|
|
||||||
*/
|
|
||||||
defaultPeerValue?: number
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, try to connect to all discovered peers up to the connection manager limit
|
* If true, try to connect to all discovered peers up to the connection manager limit
|
||||||
*/
|
*/
|
||||||
@ -111,10 +106,17 @@ export interface ConnectionManagerInit {
|
|||||||
maxAddrsToDial?: number
|
maxAddrsToDial?: number
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How long a dial attempt is allowed to take
|
* 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
|
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
|
* Number of max concurrent dials per peer
|
||||||
*/
|
*/
|
||||||
@ -124,6 +126,12 @@ export interface ConnectionManagerInit {
|
|||||||
* Multiaddr resolvers to use when dialing
|
* Multiaddr resolvers to use when dialing
|
||||||
*/
|
*/
|
||||||
resolvers?: Record<string, Resolver>
|
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 {
|
export interface ConnectionManagerEvents {
|
||||||
@ -135,14 +143,15 @@ export interface ConnectionManagerEvents {
|
|||||||
* Responsible for managing known connections.
|
* Responsible for managing known connections.
|
||||||
*/
|
*/
|
||||||
export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEvents> implements ConnectionManager, Startable, Initializable {
|
export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEvents> implements ConnectionManager, Startable, Initializable {
|
||||||
public readonly dialer: Dialer
|
|
||||||
private components = new Components()
|
private components = new Components()
|
||||||
private readonly opts: Required<ConnectionManagerInit>
|
private readonly opts: Required<ConnectionManagerInit>
|
||||||
private readonly peerValues: Map<string, number>
|
|
||||||
private readonly connections: Map<string, Connection[]>
|
private readonly connections: Map<string, Connection[]>
|
||||||
private started: boolean
|
private started: boolean
|
||||||
private timer?: ReturnType<retimer>
|
private timer?: ReturnType<retimer>
|
||||||
private readonly latencyMonitor: LatencyMonitor
|
private readonly latencyMonitor: LatencyMonitor
|
||||||
|
private readonly startupReconnectTimeout: number
|
||||||
|
private connectOnStartupController?: TimeoutController
|
||||||
|
private readonly dialTimeout: number
|
||||||
|
|
||||||
constructor (init: ConnectionManagerInit) {
|
constructor (init: ConnectionManagerInit) {
|
||||||
super()
|
super()
|
||||||
@ -155,25 +164,10 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
|||||||
|
|
||||||
log('options: %o', this.opts)
|
log('options: %o', this.opts)
|
||||||
|
|
||||||
/**
|
|
||||||
* 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()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of connections per peer
|
* Map of connections per peer
|
||||||
*/
|
*/
|
||||||
this.connections = trackedMap({
|
this.connections = new Map()
|
||||||
component: METRICS_COMPONENT,
|
|
||||||
metric: METRICS_PEER_CONNECTIONS,
|
|
||||||
metrics: this.components.getMetrics()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.started = false
|
this.started = false
|
||||||
this._checkMetrics = this._checkMetrics.bind(this)
|
this._checkMetrics = this._checkMetrics.bind(this)
|
||||||
@ -188,16 +182,103 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
|||||||
setMaxListeners?.(Infinity, this)
|
setMaxListeners?.(Infinity, this)
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
this.dialer = new Dialer(this.opts)
|
|
||||||
|
|
||||||
this.onConnect = this.onConnect.bind(this)
|
this.onConnect = this.onConnect.bind(this)
|
||||||
this.onDisconnect = this.onDisconnect.bind(this)
|
this.onDisconnect = this.onDisconnect.bind(this)
|
||||||
|
|
||||||
|
this.startupReconnectTimeout = init.startupReconnectTimeout ?? STARTUP_RECONNECT_TIMEOUT
|
||||||
|
this.dialTimeout = init.dialTimeout ?? 30000
|
||||||
}
|
}
|
||||||
|
|
||||||
init (components: Components): void {
|
init (components: Components): void {
|
||||||
this.components = components
|
this.components = components
|
||||||
|
|
||||||
this.dialer.init(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
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
isStarted () {
|
isStarted () {
|
||||||
@ -217,7 +298,6 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
|||||||
this.latencyMonitor.start()
|
this.latencyMonitor.start()
|
||||||
this._onLatencyMeasure = this._onLatencyMeasure.bind(this)
|
this._onLatencyMeasure = this._onLatencyMeasure.bind(this)
|
||||||
this.latencyMonitor.addEventListener('data', this._onLatencyMeasure)
|
this.latencyMonitor.addEventListener('data', this._onLatencyMeasure)
|
||||||
await this.dialer.start()
|
|
||||||
|
|
||||||
this.started = true
|
this.started = true
|
||||||
log('started')
|
log('started')
|
||||||
@ -226,9 +306,51 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
|||||||
async afterStart () {
|
async afterStart () {
|
||||||
this.components.getUpgrader().addEventListener('connection', this.onConnect)
|
this.components.getUpgrader().addEventListener('connection', this.onConnect)
|
||||||
this.components.getUpgrader().addEventListener('connectionEnd', this.onDisconnect)
|
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 () {
|
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('connection', this.onConnect)
|
||||||
this.components.getUpgrader().removeEventListener('connectionEnd', this.onDisconnect)
|
this.components.getUpgrader().removeEventListener('connectionEnd', this.onDisconnect)
|
||||||
}
|
}
|
||||||
@ -241,7 +363,6 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
|||||||
|
|
||||||
this.latencyMonitor.removeEventListener('data', this._onLatencyMeasure)
|
this.latencyMonitor.removeEventListener('data', this._onLatencyMeasure)
|
||||||
this.latencyMonitor.stop()
|
this.latencyMonitor.stop()
|
||||||
await this.dialer.stop()
|
|
||||||
|
|
||||||
this.started = false
|
this.started = false
|
||||||
await this._close()
|
await this._close()
|
||||||
@ -271,18 +392,6 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
|||||||
this.connections.clear()
|
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
|
* Checks the libp2p metrics to determine if any values have exceeded
|
||||||
* the configured maximums.
|
* the configured maximums.
|
||||||
@ -340,11 +449,10 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
|||||||
await this.components.getPeerStore().keyBook.set(peerId, peerId.publicKey)
|
await this.components.getPeerStore().keyBook.set(peerId, peerId.publicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.peerValues.has(peerIdStr)) {
|
const numConnections = this.getConnections().length
|
||||||
this.peerValues.set(peerIdStr, this.opts.defaultPeerValue)
|
const toPrune = numConnections - this.opts.maxConnections
|
||||||
}
|
|
||||||
|
|
||||||
await this._checkMaxLimit('maxConnections', this.getConnections().length)
|
await this._checkMaxLimit('maxConnections', numConnections, toPrune)
|
||||||
this.dispatchEvent(new CustomEvent<Connection>('peer:connect', { detail: connection }))
|
this.dispatchEvent(new CustomEvent<Connection>('peer:connect', { detail: connection }))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,7 +475,6 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
|||||||
this.connections.set(peerId, storedConn)
|
this.connections.set(peerId, storedConn)
|
||||||
} else if (storedConn != null) {
|
} else if (storedConn != null) {
|
||||||
this.connections.delete(peerId)
|
this.connections.delete(peerId)
|
||||||
this.peerValues.delete(connection.remotePeer.toString())
|
|
||||||
this.dispatchEvent(new CustomEvent<Connection>('peer:disconnect', { detail: connection }))
|
this.dispatchEvent(new CustomEvent<Connection>('peer:disconnect', { detail: connection }))
|
||||||
|
|
||||||
this.components.getMetrics()?.onPeerDisconnected(connection.remotePeer)
|
this.components.getMetrics()?.onPeerDisconnected(connection.remotePeer)
|
||||||
@ -388,7 +495,7 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
|||||||
return conns
|
return conns
|
||||||
}
|
}
|
||||||
|
|
||||||
async openConnection (peerId: PeerId, options?: AbortOptions): Promise<Connection> {
|
async openConnection (peerId: PeerId, options: AbortOptions = {}): Promise<Connection> {
|
||||||
log('dial to %p', peerId)
|
log('dial to %p', peerId)
|
||||||
const existingConnections = this.getConnections(peerId)
|
const existingConnections = this.getConnections(peerId)
|
||||||
|
|
||||||
@ -398,30 +505,48 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
|||||||
return existingConnections[0]
|
return existingConnections[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
const connection = await this.dialer.dial(peerId, options)
|
let timeoutController: TimeoutController | undefined
|
||||||
let peerConnections = this.connections.get(peerId.toString())
|
|
||||||
|
|
||||||
if (peerConnections == null) {
|
if (options?.signal == null) {
|
||||||
peerConnections = []
|
timeoutController = new TimeoutController(this.dialTimeout)
|
||||||
this.connections.set(peerId.toString(), peerConnections)
|
options.signal = timeoutController.signal
|
||||||
|
|
||||||
|
try {
|
||||||
|
// fails on node < 15.4
|
||||||
|
setMaxListeners?.(Infinity, timeoutController.signal)
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we get notified of connections via the Upgrader emitting "connection"
|
try {
|
||||||
// events, double check we aren't already tracking this connection before
|
const connection = await this.components.getDialer().dial(peerId, options)
|
||||||
// storing it
|
let peerConnections = this.connections.get(peerId.toString())
|
||||||
let trackedConnection = false
|
|
||||||
|
|
||||||
for (const conn of peerConnections) {
|
if (peerConnections == null) {
|
||||||
if (conn.id === connection.id) {
|
peerConnections = []
|
||||||
trackedConnection = true
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!trackedConnection) {
|
|
||||||
peerConnections.push(connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
return connection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async closeConnections (peerId: PeerId): Promise<void> {
|
async closeConnections (peerId: PeerId): Promise<void> {
|
||||||
@ -459,7 +584,7 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
|||||||
_onLatencyMeasure (evt: CustomEvent<SummaryObject>) {
|
_onLatencyMeasure (evt: CustomEvent<SummaryObject>) {
|
||||||
const { detail: summary } = evt
|
const { detail: summary } = evt
|
||||||
|
|
||||||
this._checkMaxLimit('maxEventLoopDelay', summary.avgMs)
|
this._checkMaxLimit('maxEventLoopDelay', summary.avgMs, 1)
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
log.error(err)
|
log.error(err)
|
||||||
})
|
})
|
||||||
@ -468,46 +593,86 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
|||||||
/**
|
/**
|
||||||
* If the `value` of `name` has exceeded its limit, maybe close a connection
|
* If the `value` of `name` has exceeded its limit, maybe close a connection
|
||||||
*/
|
*/
|
||||||
async _checkMaxLimit (name: keyof ConnectionManagerInit, value: number) {
|
async _checkMaxLimit (name: keyof ConnectionManagerInit, value: number, toPrune: number = 1) {
|
||||||
const limit = this.opts[name]
|
const limit = this.opts[name]
|
||||||
log.trace('checking limit of %s. current value: %d of %d', name, value, limit)
|
log.trace('checking limit of %s. current value: %d of %d', name, value, limit)
|
||||||
if (value > limit) {
|
if (value > limit) {
|
||||||
log('%s: limit exceeded: %p, %d', this.components.getPeerId(), name, value)
|
log('%s: limit exceeded: %p, %d/%d, pruning %d connection(s)', this.components.getPeerId(), name, value, limit, toPrune)
|
||||||
await this._maybeDisconnectOne()
|
await this._maybePruneConnections(toPrune)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If we have more connections than our maximum, close a connection
|
* If we have more connections than our maximum, select some excess connections
|
||||||
* to the lowest valued peer.
|
* to prune based on peer value
|
||||||
*/
|
*/
|
||||||
async _maybeDisconnectOne () {
|
async _maybePruneConnections (toPrune: number) {
|
||||||
if (this.opts.minConnections < this.connections.size) {
|
const connections = this.getConnections()
|
||||||
const peerValues = Array.from(new Map([...this.peerValues.entries()].sort((a, b) => a[1] - b[1])))
|
|
||||||
|
|
||||||
log('%p: sorted peer values: %j', this.components.getPeerId(), peerValues)
|
if (connections.length <= this.opts.minConnections || toPrune < 1) {
|
||||||
const disconnectPeer = peerValues[0]
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (disconnectPeer != null) {
|
const peerValues = new PeerMap<number>()
|
||||||
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)
|
|
||||||
|
|
||||||
for (const connections of this.connections.values()) {
|
// work out peer values
|
||||||
if (connections[0].remotePeer.toString() === peerId) {
|
for (const connection of connections) {
|
||||||
void connections[0].close()
|
const remotePeer = connection.remotePeer
|
||||||
.catch(err => {
|
|
||||||
log.error(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: should not need to invoke this manually
|
if (peerValues.has(remotePeer)) {
|
||||||
this.onDisconnect(new CustomEvent<Connection>('connectionEnd', {
|
continue
|
||||||
detail: connections[0]
|
}
|
||||||
}))
|
|
||||||
break
|
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
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
export const DIAL_TIMEOUT = 30e3
|
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
|
* Maximum allowed concurrent dials
|
||||||
*/
|
*/
|
||||||
|
@ -8,11 +8,11 @@ import {
|
|||||||
import drain from 'it-drain'
|
import drain from 'it-drain'
|
||||||
import merge from 'it-merge'
|
import merge from 'it-merge'
|
||||||
import { pipe } from 'it-pipe'
|
import { pipe } from 'it-pipe'
|
||||||
import type { ContentRouting } from '@libp2p/interfaces/content-routing'
|
import type { ContentRouting } from '@libp2p/interface-content-routing'
|
||||||
import type { AbortOptions } from '@libp2p/interfaces'
|
import type { AbortOptions } from '@libp2p/interfaces'
|
||||||
import type { Startable } from '@libp2p/interfaces/startable'
|
import type { Startable } from '@libp2p/interfaces/startable'
|
||||||
import type { CID } from 'multiformats/cid'
|
import type { CID } from 'multiformats/cid'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
|
|
||||||
export interface CompoundContentRoutingInit {
|
export interface CompoundContentRoutingInit {
|
||||||
routers: ContentRouting[]
|
routers: ContentRouting[]
|
||||||
|
@ -2,8 +2,8 @@ import errCode from 'err-code'
|
|||||||
import filter from 'it-filter'
|
import filter from 'it-filter'
|
||||||
import map from 'it-map'
|
import map from 'it-map'
|
||||||
import type { Source } from 'it-stream-types'
|
import type { Source } from 'it-stream-types'
|
||||||
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
|
import type { PeerInfo } from '@libp2p/interface-peer-info'
|
||||||
import type { PeerStore } from '@libp2p/interfaces/peer-store'
|
import type { PeerStore } from '@libp2p/interface-peer-store'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store the multiaddrs from every peer in the passed peer store
|
* Store the multiaddrs from every peer in the passed peer store
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import drain from 'it-drain'
|
import drain from 'it-drain'
|
||||||
import errCode from 'err-code'
|
import errCode from 'err-code'
|
||||||
import type { DHT } from '@libp2p/interfaces/dht'
|
import type { DHT } from '@libp2p/interface-dht'
|
||||||
import type { ContentRouting } from '@libp2p/interfaces/content-routing'
|
import type { ContentRouting } from '@libp2p/interface-content-routing'
|
||||||
import type { CID } from 'multiformats/cid'
|
import type { CID } from 'multiformats/cid'
|
||||||
import type { AbortOptions } from '@libp2p/interfaces'
|
import type { AbortOptions } from '@libp2p/interfaces'
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import errCode from 'err-code'
|
import errCode from 'err-code'
|
||||||
import { messages, codes } from '../errors.js'
|
import { messages, codes } from '../errors.js'
|
||||||
import type { PeerRouting } from '@libp2p/interfaces/peer-routing'
|
import type { PeerRouting } from '@libp2p/interface-peer-routing'
|
||||||
import type { DHT } from '@libp2p/interfaces/dht'
|
import type { DHT } from '@libp2p/interface-dht'
|
||||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
||||||
import type { AbortOptions } from '@libp2p/interfaces'
|
import type { AbortOptions } from '@libp2p/interfaces'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,8 +27,8 @@ export class DHTPeerRouting implements PeerRouting {
|
|||||||
|
|
||||||
async * getClosestPeers (key: Uint8Array, options: AbortOptions = {}) {
|
async * getClosestPeers (key: Uint8Array, options: AbortOptions = {}) {
|
||||||
for await (const event of this.dht.getClosestPeers(key, options)) {
|
for await (const event of this.dht.getClosestPeers(key, options)) {
|
||||||
if (event.name === 'PEER_RESPONSE') {
|
if (event.name === 'FINAL_PEER') {
|
||||||
yield * event.closer
|
yield event.peer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import type { DualDHT, QueryEvent, SingleDHT } from '@libp2p/interfaces/dht'
|
import type { DualDHT, QueryEvent, SingleDHT } from '@libp2p/interface-dht'
|
||||||
import type { PeerDiscoveryEvents } from '@libp2p/interfaces/peer-discovery'
|
import type { PeerDiscoveryEvents } from '@libp2p/interface-peer-discovery'
|
||||||
import errCode from 'err-code'
|
import errCode from 'err-code'
|
||||||
import { messages, codes } from '../errors.js'
|
import { messages, codes } from '../errors.js'
|
||||||
import { EventEmitter } from '@libp2p/interfaces/events'
|
import { EventEmitter } from '@libp2p/interfaces/events'
|
||||||
import { symbol } from '@libp2p/interfaces/peer-discovery'
|
import { symbol } from '@libp2p/interface-peer-discovery'
|
||||||
|
|
||||||
export class DummyDHT extends EventEmitter<PeerDiscoveryEvents> implements DualDHT {
|
export class DummyDHT extends EventEmitter<PeerDiscoveryEvents> implements DualDHT {
|
||||||
get [symbol] (): true {
|
get [symbol] (): true {
|
||||||
|
@ -70,5 +70,8 @@ export enum codes {
|
|||||||
ERR_NOT_IMPLEMENTED = 'ERR_NOT_IMPLEMENTED',
|
ERR_NOT_IMPLEMENTED = 'ERR_NOT_IMPLEMENTED',
|
||||||
ERR_WRONG_PING_ACK = 'ERR_WRONG_PING_ACK',
|
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_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'
|
||||||
}
|
}
|
||||||
|
@ -3,21 +3,30 @@ import errCode from 'err-code'
|
|||||||
import { codes } from '../errors.js'
|
import { codes } from '../errors.js'
|
||||||
import * as lp from 'it-length-prefixed'
|
import * as lp from 'it-length-prefixed'
|
||||||
import { FetchRequest, FetchResponse } from './pb/proto.js'
|
import { FetchRequest, FetchResponse } from './pb/proto.js'
|
||||||
import { handshake } from 'it-handshake'
|
|
||||||
import { PROTOCOL_NAME, PROTOCOL_VERSION } from './constants.js'
|
import { PROTOCOL_NAME, PROTOCOL_VERSION } from './constants.js'
|
||||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
||||||
import type { Startable } from '@libp2p/interfaces/startable'
|
import type { Startable } from '@libp2p/interfaces/startable'
|
||||||
import type { Stream } from '@libp2p/interfaces/connection'
|
import type { Stream } from '@libp2p/interface-connection'
|
||||||
import type { IncomingStreamData } from '@libp2p/interfaces/registrar'
|
import type { IncomingStreamData } from '@libp2p/interface-registrar'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
import type { AbortOptions } from '@libp2p/interfaces'
|
import type { AbortOptions } from '@libp2p/interfaces'
|
||||||
import type { Duplex } from 'it-stream-types'
|
|
||||||
import { abortableDuplex } from 'abortable-iterator'
|
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')
|
const log = logger('libp2p:fetch')
|
||||||
|
|
||||||
export interface FetchServiceInit {
|
export interface FetchServiceInit {
|
||||||
protocolPrefix: string
|
protocolPrefix: string
|
||||||
|
maxInboundStreams: number
|
||||||
|
maxOutboundStreams: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long we should wait for a remote peer to send any data
|
||||||
|
*/
|
||||||
|
timeout: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HandleMessageOptions {
|
export interface HandleMessageOptions {
|
||||||
@ -40,6 +49,7 @@ export class FetchService implements Startable {
|
|||||||
private readonly components: Components
|
private readonly components: Components
|
||||||
private readonly lookupFunctions: Map<string, LookupFunction>
|
private readonly lookupFunctions: Map<string, LookupFunction>
|
||||||
private started: boolean
|
private started: boolean
|
||||||
|
private readonly init: FetchServiceInit
|
||||||
|
|
||||||
constructor (components: Components, init: FetchServiceInit) {
|
constructor (components: Components, init: FetchServiceInit) {
|
||||||
this.started = false
|
this.started = false
|
||||||
@ -47,13 +57,21 @@ export class FetchService implements Startable {
|
|||||||
this.protocol = `/${init.protocolPrefix ?? 'libp2p'}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`
|
this.protocol = `/${init.protocolPrefix ?? 'libp2p'}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`
|
||||||
this.lookupFunctions = new Map() // Maps key prefix to value lookup function
|
this.lookupFunctions = new Map() // Maps key prefix to value lookup function
|
||||||
this.handleMessage = this.handleMessage.bind(this)
|
this.handleMessage = this.handleMessage.bind(this)
|
||||||
|
this.init = init
|
||||||
}
|
}
|
||||||
|
|
||||||
async start () {
|
async start () {
|
||||||
await this.components.getRegistrar().handle(this.protocol, (data) => {
|
await this.components.getRegistrar().handle(this.protocol, (data) => {
|
||||||
void this.handleMessage(data).catch(err => {
|
void this.handleMessage(data)
|
||||||
log.error(err)
|
.catch(err => {
|
||||||
})
|
log.error(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
data.stream.close()
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
maxInboundStreams: this.init.maxInboundStreams,
|
||||||
|
maxOutboundStreams: this.init.maxOutboundStreams
|
||||||
})
|
})
|
||||||
this.started = true
|
this.started = true
|
||||||
}
|
}
|
||||||
@ -74,35 +92,69 @@ export class FetchService implements Startable {
|
|||||||
log('dialing %s to %p', this.protocol, peer)
|
log('dialing %s to %p', this.protocol, peer)
|
||||||
|
|
||||||
const connection = await this.components.getConnectionManager().openConnection(peer, options)
|
const connection = await this.components.getConnectionManager().openConnection(peer, options)
|
||||||
const { stream } = await connection.newStream([this.protocol], options)
|
let timeoutController
|
||||||
let source: Duplex<Uint8Array> = stream
|
let signal = options.signal
|
||||||
|
let stream: Stream | undefined
|
||||||
|
|
||||||
// make stream abortable if AbortSignal passed
|
// create a timeout if no abort signal passed
|
||||||
if (options.signal != null) {
|
if (signal == null) {
|
||||||
source = abortableDuplex(stream, options.signal)
|
timeoutController = new TimeoutController(this.init.timeout)
|
||||||
|
signal = timeoutController.signal
|
||||||
|
|
||||||
|
try {
|
||||||
|
// fails on node < 15.4
|
||||||
|
setMaxListeners?.(Infinity, timeoutController.signal)
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const shake = handshake(source)
|
try {
|
||||||
|
stream = await connection.newStream([this.protocol], {
|
||||||
|
signal
|
||||||
|
})
|
||||||
|
|
||||||
// send message
|
// make stream abortable
|
||||||
shake.write(lp.encode.single(FetchRequest.encode({ identifier: key })).slice())
|
const source = abortableDuplex(stream, signal)
|
||||||
|
|
||||||
// read response
|
const result = await pipe(
|
||||||
// @ts-expect-error fromReader returns a Source which has no .next method
|
[FetchRequest.encode({ identifier: key })],
|
||||||
const response = FetchResponse.decode((await lp.decode.fromReader(shake.reader).next()).value.slice())
|
lp.encode(),
|
||||||
switch (response.status) {
|
source,
|
||||||
case (FetchResponse.StatusCode.OK): {
|
lp.decode(),
|
||||||
return response.data
|
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
|
if (stream != null) {
|
||||||
}
|
stream.close()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,25 +166,40 @@ export class FetchService implements Startable {
|
|||||||
*/
|
*/
|
||||||
async handleMessage (data: IncomingStreamData) {
|
async handleMessage (data: IncomingStreamData) {
|
||||||
const { stream } = data
|
const { stream } = data
|
||||||
const shake = handshake(stream)
|
const self = this
|
||||||
// @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())
|
|
||||||
|
|
||||||
let response: FetchResponse
|
await pipe(
|
||||||
const lookup = this._getLookupFunction(request.identifier)
|
stream,
|
||||||
if (lookup != null) {
|
lp.decode(),
|
||||||
const data = await lookup(request.identifier)
|
async function * (source) {
|
||||||
if (data != null) {
|
const buf = await first(source)
|
||||||
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 }
|
|
||||||
}
|
|
||||||
|
|
||||||
shake.write(lp.encode.single(FetchResponse.encode(response)).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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
/* eslint-disable import/export */
|
/* eslint-disable import/export */
|
||||||
/* eslint-disable @typescript-eslint/no-namespace */
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
|
|
||||||
import { encodeMessage, decodeMessage, message, string, enumeration, bytes } from 'protons-runtime'
|
import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime'
|
||||||
|
import type { Uint8ArrayList } from 'uint8arraylist'
|
||||||
import type { Codec } from 'protons-runtime'
|
import type { Codec } from 'protons-runtime'
|
||||||
|
|
||||||
export interface FetchRequest {
|
export interface FetchRequest {
|
||||||
@ -9,17 +10,59 @@ export interface FetchRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace FetchRequest {
|
export namespace FetchRequest {
|
||||||
|
let _codec: Codec<FetchRequest>
|
||||||
|
|
||||||
export const codec = (): Codec<FetchRequest> => {
|
export const codec = (): Codec<FetchRequest> => {
|
||||||
return message<FetchRequest>({
|
if (_codec == null) {
|
||||||
1: { name: 'identifier', codec: string }
|
_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 => {
|
export const encode = (obj: FetchRequest): Uint8Array => {
|
||||||
return encodeMessage(obj, FetchRequest.codec())
|
return encodeMessage(obj, FetchRequest.codec())
|
||||||
}
|
}
|
||||||
|
|
||||||
export const decode = (buf: Uint8Array): FetchRequest => {
|
export const decode = (buf: Uint8Array | Uint8ArrayList): FetchRequest => {
|
||||||
return decodeMessage(buf, FetchRequest.codec())
|
return decodeMessage(buf, FetchRequest.codec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,22 +87,77 @@ export namespace FetchResponse {
|
|||||||
|
|
||||||
export namespace StatusCode {
|
export namespace StatusCode {
|
||||||
export const codec = () => {
|
export const codec = () => {
|
||||||
return enumeration<typeof StatusCode>(__StatusCodeValues)
|
return enumeration<StatusCode>(__StatusCodeValues)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _codec: Codec<FetchResponse>
|
||||||
|
|
||||||
export const codec = (): Codec<FetchResponse> => {
|
export const codec = (): Codec<FetchResponse> => {
|
||||||
return message<FetchResponse>({
|
if (_codec == null) {
|
||||||
1: { name: 'status', codec: FetchResponse.StatusCode.codec() },
|
_codec = message<FetchResponse>((obj, writer, opts = {}) => {
|
||||||
2: { name: 'data', codec: bytes }
|
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 => {
|
export const encode = (obj: FetchResponse): Uint8Array => {
|
||||||
return encodeMessage(obj, FetchResponse.codec())
|
return encodeMessage(obj, FetchResponse.codec())
|
||||||
}
|
}
|
||||||
|
|
||||||
export const decode = (buf: Uint8Array): FetchResponse => {
|
export const decode = (buf: Uint8Array | Uint8ArrayList): FetchResponse => {
|
||||||
return decodeMessage(buf, FetchResponse.codec())
|
return decodeMessage(buf, FetchResponse.codec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { peerIdFromString } from '@libp2p/peer-id'
|
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 errCode from 'err-code'
|
||||||
import { codes } from './errors.js'
|
import { codes } from './errors.js'
|
||||||
import { isPeerId } from '@libp2p/interfaces/peer-id'
|
import { isPeerId } from '@libp2p/interface-peer-id'
|
||||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
||||||
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
|
import type { PeerInfo } from '@libp2p/interface-peer-info'
|
||||||
|
|
||||||
function peerIdFromMultiaddr (ma: Multiaddr) {
|
function peerIdFromMultiaddr (ma: Multiaddr) {
|
||||||
const idStr = ma.getPeerId()
|
const idStr = ma.getPeerId()
|
||||||
@ -39,12 +40,12 @@ export function getPeer (peer: PeerId | Multiaddr | string): PeerInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof peer === 'string') {
|
if (typeof peer === 'string') {
|
||||||
peer = new Multiaddr(peer)
|
peer = multiaddr(peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
let addr
|
let addr
|
||||||
|
|
||||||
if (Multiaddr.isMultiaddr(peer)) {
|
if (isMultiaddr(peer)) {
|
||||||
addr = peer
|
addr = peer
|
||||||
peer = peerIdFromMultiaddr(peer)
|
peer = peerIdFromMultiaddr(peer)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { pipe } from 'it-pipe'
|
|||||||
import drain from 'it-drain'
|
import drain from 'it-drain'
|
||||||
import first from 'it-first'
|
import first from 'it-first'
|
||||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||||
import { Multiaddr, protocols } from '@multiformats/multiaddr'
|
import { multiaddr, protocols } from '@multiformats/multiaddr'
|
||||||
import { Identify } from './pb/message.js'
|
import { Identify } from './pb/message.js'
|
||||||
import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record'
|
import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record'
|
||||||
import {
|
import {
|
||||||
@ -18,28 +18,51 @@ import {
|
|||||||
MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION
|
MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION
|
||||||
} from './consts.js'
|
} from './consts.js'
|
||||||
import { codes } from '../errors.js'
|
import { codes } from '../errors.js'
|
||||||
import type { IncomingStreamData } from '@libp2p/interfaces/registrar'
|
import type { IncomingStreamData } from '@libp2p/interface-registrar'
|
||||||
import type { Connection, Stream } from '@libp2p/interfaces/connection'
|
import type { Connection, Stream } from '@libp2p/interface-connection'
|
||||||
import type { Startable } from '@libp2p/interfaces/startable'
|
import type { Startable } from '@libp2p/interfaces/startable'
|
||||||
import { peerIdFromKeys } from '@libp2p/peer-id'
|
import { peerIdFromKeys } from '@libp2p/peer-id'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
import { TimeoutController } from 'timeout-abort-controller'
|
import { TimeoutController } from 'timeout-abort-controller'
|
||||||
import type { AbortOptions } from '@libp2p/interfaces'
|
import type { AbortOptions } from '@libp2p/interfaces'
|
||||||
import { abortableDuplex } from 'abortable-iterator'
|
import { abortableDuplex } from 'abortable-iterator'
|
||||||
import type { Duplex } from 'it-stream-types'
|
import { setMaxListeners } from 'events'
|
||||||
|
|
||||||
const log = logger('libp2p:identify')
|
const log = logger('libp2p:identify')
|
||||||
|
|
||||||
const IDENTIFY_TIMEOUT = 30000
|
// https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L52
|
||||||
|
const MAX_IDENTIFY_MESSAGE_SIZE = 1024 * 8
|
||||||
|
|
||||||
export interface HostProperties {
|
export interface HostProperties {
|
||||||
agentVersion: string
|
agentVersion: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IdentifyServiceInit {
|
export interface IdentifyServiceInit {
|
||||||
|
/**
|
||||||
|
* The prefix to use for the protocol (default: 'ipfs')
|
||||||
|
*/
|
||||||
protocolPrefix: string
|
protocolPrefix: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What details we should send as part of an identify message
|
||||||
|
*/
|
||||||
host: HostProperties
|
host: HostProperties
|
||||||
timeout?: number
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
export class IdentifyService implements Startable {
|
||||||
@ -59,8 +82,6 @@ export class IdentifyService implements Startable {
|
|||||||
this.started = false
|
this.started = false
|
||||||
this.init = init
|
this.init = init
|
||||||
|
|
||||||
this.handleMessage = this.handleMessage.bind(this)
|
|
||||||
|
|
||||||
this.identifyProtocolStr = `/${init.protocolPrefix}/${MULTICODEC_IDENTIFY_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PROTOCOL_VERSION}`
|
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}`
|
this.identifyPushProtocolStr = `/${init.protocolPrefix}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION}`
|
||||||
|
|
||||||
@ -107,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(), 'AgentVersion', uint8ArrayFromString(this.host.agentVersion))
|
||||||
await this.components.getPeerStore().metadataBook.setValue(this.components.getPeerId(), 'ProtocolVersion', uint8ArrayFromString(this.host.protocolVersion))
|
await this.components.getPeerStore().metadataBook.setValue(this.components.getPeerId(), 'ProtocolVersion', uint8ArrayFromString(this.host.protocolVersion))
|
||||||
|
|
||||||
await this.components.getRegistrar().handle([
|
await this.components.getRegistrar().handle(this.identifyProtocolStr, (data) => {
|
||||||
this.identifyProtocolStr,
|
void this._handleIdentify(data).catch(err => {
|
||||||
this.identifyPushProtocolStr
|
|
||||||
], (data) => {
|
|
||||||
void this.handleMessage(data)?.catch(err => {
|
|
||||||
log.error(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
|
this.started = true
|
||||||
@ -135,17 +164,21 @@ export class IdentifyService implements Startable {
|
|||||||
const protocols = await this.components.getPeerStore().protoBook.get(this.components.getPeerId())
|
const protocols = await this.components.getPeerStore().protoBook.get(this.components.getPeerId())
|
||||||
|
|
||||||
const pushes = connections.map(async connection => {
|
const pushes = connections.map(async connection => {
|
||||||
const timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT)
|
|
||||||
let stream: Stream | undefined
|
let stream: Stream | undefined
|
||||||
|
const timeoutController = new TimeoutController(this.init.timeout)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = 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
|
signal: timeoutController.signal
|
||||||
})
|
})
|
||||||
stream = data.stream
|
|
||||||
|
|
||||||
// make stream abortable
|
// make stream abortable
|
||||||
const source: Duplex<Uint8Array> = abortableDuplex(stream, timeoutController.signal)
|
const source = abortableDuplex(stream, timeoutController.signal)
|
||||||
|
|
||||||
await pipe(
|
await pipe(
|
||||||
[Identify.encode({
|
[Identify.encode({
|
||||||
@ -198,19 +231,35 @@ export class IdentifyService implements Startable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _identify (connection: Connection, options: AbortOptions = {}): Promise<Identify> {
|
async _identify (connection: Connection, options: AbortOptions = {}): Promise<Identify> {
|
||||||
const { stream } = await connection.newStream([this.identifyProtocolStr], options)
|
let timeoutController
|
||||||
let source: Duplex<Uint8Array> = stream
|
let signal = options.signal
|
||||||
|
let stream: Stream | undefined
|
||||||
|
|
||||||
// make stream abortable if AbortSignal passed
|
// create a timeout if no abort signal passed
|
||||||
if (options.signal != null) {
|
if (signal == null) {
|
||||||
source = abortableDuplex(stream, options.signal)
|
timeoutController = new TimeoutController(this.init.timeout)
|
||||||
|
signal = timeoutController.signal
|
||||||
|
|
||||||
|
try {
|
||||||
|
// fails on node < 15.4
|
||||||
|
setMaxListeners?.(Infinity, timeoutController.signal)
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
stream = await connection.newStream([this.identifyProtocolStr], {
|
||||||
|
signal
|
||||||
|
})
|
||||||
|
|
||||||
|
// make stream abortable
|
||||||
|
const source = abortableDuplex(stream, signal)
|
||||||
|
|
||||||
const data = await pipe(
|
const data = await pipe(
|
||||||
[],
|
[],
|
||||||
source,
|
source,
|
||||||
lp.decode(),
|
lp.decode({
|
||||||
|
maxDataLength: this.init.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE
|
||||||
|
}),
|
||||||
async (source) => await first(source)
|
async (source) => await first(source)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -224,7 +273,13 @@ export class IdentifyService implements Startable {
|
|||||||
throw errCode(err, codes.ERR_INVALID_MESSAGE)
|
throw errCode(err, codes.ERR_INVALID_MESSAGE)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
stream.close()
|
if (timeoutController != null) {
|
||||||
|
timeoutController.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream != null) {
|
||||||
|
stream.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +354,7 @@ export class IdentifyService implements Startable {
|
|||||||
|
|
||||||
// LEGACY: Update peers data in PeerStore
|
// LEGACY: Update peers data in PeerStore
|
||||||
try {
|
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) {
|
} catch (err: any) {
|
||||||
log.error('received invalid addrs', err)
|
log.error('received invalid addrs', err)
|
||||||
}
|
}
|
||||||
@ -321,29 +376,18 @@ export class IdentifyService implements Startable {
|
|||||||
// this.components.getAddressManager().addObservedAddr(observedAddr)
|
// 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
|
* Sends the `Identify` response with the Signed Peer Record
|
||||||
* to the requesting peer over the given `connection`
|
* to the requesting peer over the given `connection`
|
||||||
*/
|
*/
|
||||||
async _handleIdentify (data: IncomingStreamData) {
|
async _handleIdentify (data: IncomingStreamData) {
|
||||||
const { connection, stream } = data
|
const { connection, stream } = data
|
||||||
const timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT)
|
const timeoutController = new TimeoutController(this.init.timeout)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// fails on node < 15.4
|
||||||
|
setMaxListeners?.(Infinity, timeoutController.signal)
|
||||||
|
} catch {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const publicKey = this.components.getPeerId().publicKey ?? new Uint8Array(0)
|
const publicKey = this.components.getPeerId().publicKey ?? new Uint8Array(0)
|
||||||
@ -359,7 +403,7 @@ export class IdentifyService implements Startable {
|
|||||||
|
|
||||||
const envelope = await RecordEnvelope.seal(peerRecord, this.components.getPeerId())
|
const envelope = await RecordEnvelope.seal(peerRecord, this.components.getPeerId())
|
||||||
await this.components.getPeerStore().addressBook.consumePeerRecord(envelope)
|
await this.components.getPeerStore().addressBook.consumePeerRecord(envelope)
|
||||||
signedPeerRecord = envelope.marshal()
|
signedPeerRecord = envelope.marshal().subarray()
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = Identify.encode({
|
const message = Identify.encode({
|
||||||
@ -373,7 +417,7 @@ export class IdentifyService implements Startable {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// make stream abortable
|
// make stream abortable
|
||||||
const source: Duplex<Uint8Array> = abortableDuplex(stream, timeoutController.signal)
|
const source = abortableDuplex(stream, timeoutController.signal)
|
||||||
|
|
||||||
await pipe(
|
await pipe(
|
||||||
[message],
|
[message],
|
||||||
@ -394,17 +438,24 @@ export class IdentifyService implements Startable {
|
|||||||
*/
|
*/
|
||||||
async _handlePush (data: IncomingStreamData) {
|
async _handlePush (data: IncomingStreamData) {
|
||||||
const { connection, stream } = data
|
const { connection, stream } = data
|
||||||
const timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT)
|
const timeoutController = new TimeoutController(this.init.timeout)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// fails on node < 15.4
|
||||||
|
setMaxListeners?.(Infinity, timeoutController.signal)
|
||||||
|
} catch {}
|
||||||
|
|
||||||
let message: Identify | undefined
|
let message: Identify | undefined
|
||||||
try {
|
try {
|
||||||
// make stream abortable
|
// make stream abortable
|
||||||
const source: Duplex<Uint8Array> = abortableDuplex(stream, timeoutController.signal)
|
const source = abortableDuplex(stream, timeoutController.signal)
|
||||||
|
|
||||||
const data = await pipe(
|
const data = await pipe(
|
||||||
[],
|
[],
|
||||||
source,
|
source,
|
||||||
lp.decode(),
|
lp.decode({
|
||||||
|
maxDataLength: this.init.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE
|
||||||
|
}),
|
||||||
async (source) => await first(source)
|
async (source) => await first(source)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -455,7 +506,7 @@ export class IdentifyService implements Startable {
|
|||||||
// LEGACY: Update peers data in PeerStore
|
// LEGACY: Update peers data in PeerStore
|
||||||
try {
|
try {
|
||||||
await this.components.getPeerStore().addressBook.set(id,
|
await this.components.getPeerStore().addressBook.set(id,
|
||||||
message.listenAddrs.map((addr) => new Multiaddr(addr)))
|
message.listenAddrs.map((addr) => multiaddr(addr)))
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
log.error('received invalid addrs', err)
|
log.error('received invalid addrs', err)
|
||||||
}
|
}
|
||||||
@ -476,7 +527,7 @@ export class IdentifyService implements Startable {
|
|||||||
static getCleanMultiaddr (addr: Uint8Array | string | null | undefined) {
|
static getCleanMultiaddr (addr: Uint8Array | string | null | undefined) {
|
||||||
if (addr != null && addr.length > 0) {
|
if (addr != null && addr.length > 0) {
|
||||||
try {
|
try {
|
||||||
return new Multiaddr(addr)
|
return multiaddr(addr)
|
||||||
} catch {
|
} catch {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
/* eslint-disable import/export */
|
/* eslint-disable import/export */
|
||||||
/* eslint-disable @typescript-eslint/no-namespace */
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
|
|
||||||
import { encodeMessage, decodeMessage, message, string, bytes } from 'protons-runtime'
|
import { encodeMessage, decodeMessage, message } from 'protons-runtime'
|
||||||
|
import type { Uint8ArrayList } from 'uint8arraylist'
|
||||||
import type { Codec } from 'protons-runtime'
|
import type { Codec } from 'protons-runtime'
|
||||||
|
|
||||||
export interface Identify {
|
export interface Identify {
|
||||||
@ -15,23 +16,122 @@ export interface Identify {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace Identify {
|
export namespace Identify {
|
||||||
|
let _codec: Codec<Identify>
|
||||||
|
|
||||||
export const codec = (): Codec<Identify> => {
|
export const codec = (): Codec<Identify> => {
|
||||||
return message<Identify>({
|
if (_codec == null) {
|
||||||
5: { name: 'protocolVersion', codec: string, optional: true },
|
_codec = message<Identify>((obj, writer, opts = {}) => {
|
||||||
6: { name: 'agentVersion', codec: string, optional: true },
|
if (opts.lengthDelimited !== false) {
|
||||||
1: { name: 'publicKey', codec: bytes, optional: true },
|
writer.fork()
|
||||||
2: { name: 'listenAddrs', codec: bytes, repeats: true },
|
}
|
||||||
4: { name: 'observedAddr', codec: bytes, optional: true },
|
|
||||||
3: { name: 'protocols', codec: string, repeats: true },
|
if (obj.protocolVersion != null) {
|
||||||
8: { name: 'signedPeerRecord', codec: bytes, optional: true }
|
writer.uint32(42)
|
||||||
})
|
writer.string(obj.protocolVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.agentVersion != null) {
|
||||||
|
writer.uint32(50)
|
||||||
|
writer.string(obj.agentVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.publicKey != null) {
|
||||||
|
writer.uint32(10)
|
||||||
|
writer.bytes(obj.publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.listenAddrs != null) {
|
||||||
|
for (const value of obj.listenAddrs) {
|
||||||
|
writer.uint32(18)
|
||||||
|
writer.bytes(value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Protocol error: required field "listenAddrs" was not found in object')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.observedAddr != null) {
|
||||||
|
writer.uint32(34)
|
||||||
|
writer.bytes(obj.observedAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.protocols != null) {
|
||||||
|
for (const value of obj.protocols) {
|
||||||
|
writer.uint32(26)
|
||||||
|
writer.string(value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Protocol error: required field "protocols" was not found in object')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.signedPeerRecord != null) {
|
||||||
|
writer.uint32(66)
|
||||||
|
writer.bytes(obj.signedPeerRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 5:
|
||||||
|
obj.protocolVersion = reader.string()
|
||||||
|
break
|
||||||
|
case 6:
|
||||||
|
obj.agentVersion = reader.string()
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
obj.publicKey = reader.bytes()
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
obj.listenAddrs = obj.listenAddrs ?? []
|
||||||
|
obj.listenAddrs.push(reader.bytes())
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
obj.observedAddr = reader.bytes()
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
obj.protocols = obj.protocols ?? []
|
||||||
|
obj.protocols.push(reader.string())
|
||||||
|
break
|
||||||
|
case 8:
|
||||||
|
obj.signedPeerRecord = reader.bytes()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
reader.skipType(tag & 7)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.listenAddrs = obj.listenAddrs ?? []
|
||||||
|
obj.protocols = obj.protocols ?? []
|
||||||
|
|
||||||
|
if (obj.listenAddrs == null) {
|
||||||
|
throw new Error('Protocol error: value for required field "listenAddrs" was not found in protobuf')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.protocols == null) {
|
||||||
|
throw new Error('Protocol error: value for required field "protocols" was not found in protobuf')
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return _codec
|
||||||
}
|
}
|
||||||
|
|
||||||
export const encode = (obj: Identify): Uint8Array => {
|
export const encode = (obj: Identify): Uint8Array => {
|
||||||
return encodeMessage(obj, Identify.codec())
|
return encodeMessage(obj, Identify.codec())
|
||||||
}
|
}
|
||||||
|
|
||||||
export const decode = (buf: Uint8Array): Identify => {
|
export const decode = (buf: Uint8Array | Uint8ArrayList): Identify => {
|
||||||
return decodeMessage(buf, Identify.codec())
|
return decodeMessage(buf, Identify.codec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
src/index.ts
41
src/index.ts
@ -5,23 +5,23 @@ import type { Startable } from '@libp2p/interfaces/startable'
|
|||||||
import type { Multiaddr } from '@multiformats/multiaddr'
|
import type { Multiaddr } from '@multiformats/multiaddr'
|
||||||
import type { FaultTolerance } from './transport-manager.js'
|
import type { FaultTolerance } from './transport-manager.js'
|
||||||
import type { IdentifyServiceInit } from './identify/index.js'
|
import type { IdentifyServiceInit } from './identify/index.js'
|
||||||
import type { DualDHT } from '@libp2p/interfaces/dht'
|
import type { DualDHT } from '@libp2p/interface-dht'
|
||||||
import type { Datastore } from 'interface-datastore'
|
import type { Datastore } from 'interface-datastore'
|
||||||
import type { PeerStore, PeerStoreInit } from '@libp2p/interfaces/peer-store'
|
import type { PeerStore, PeerStoreInit } from '@libp2p/interface-peer-store'
|
||||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
||||||
import type { AutoRelayConfig, RelayAdvertiseConfig } from './circuit/index.js'
|
import type { AutoRelayConfig, RelayAdvertiseConfig } from './circuit/index.js'
|
||||||
import type { PeerDiscovery } from '@libp2p/interfaces/peer-discovery'
|
import type { PeerDiscovery } from '@libp2p/interface-peer-discovery'
|
||||||
import type { Connection, ConnectionGater, ConnectionProtector, ProtocolStream } from '@libp2p/interfaces/connection'
|
import type { Connection, ConnectionGater, ConnectionProtector, Stream } from '@libp2p/interface-connection'
|
||||||
import type { Transport } from '@libp2p/interfaces/transport'
|
import type { Transport } from '@libp2p/interface-transport'
|
||||||
import type { StreamMuxerFactory } from '@libp2p/interfaces/stream-muxer'
|
import type { StreamMuxerFactory } from '@libp2p/interface-stream-muxer'
|
||||||
import type { ConnectionEncrypter } from '@libp2p/interfaces/connection-encrypter'
|
import type { ConnectionEncrypter } from '@libp2p/interface-connection-encrypter'
|
||||||
import type { PeerRouting } from '@libp2p/interfaces/peer-routing'
|
import type { PeerRouting } from '@libp2p/interface-peer-routing'
|
||||||
import type { ContentRouting } from '@libp2p/interfaces/content-routing'
|
import type { ContentRouting } from '@libp2p/interface-content-routing'
|
||||||
import type { PubSub } from '@libp2p/interfaces/pubsub'
|
import type { PubSub } from '@libp2p/interface-pubsub'
|
||||||
import type { Registrar, StreamHandler } from '@libp2p/interfaces/registrar'
|
import type { Registrar, StreamHandler, StreamHandlerOptions } from '@libp2p/interface-registrar'
|
||||||
import type { ConnectionManager } from '@libp2p/interfaces/connection-manager'
|
import type { ConnectionManager } from '@libp2p/interface-connection-manager'
|
||||||
import type { Metrics, MetricsInit } from '@libp2p/interfaces/metrics'
|
import type { Metrics, MetricsInit } from '@libp2p/interface-metrics'
|
||||||
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
|
import type { PeerInfo } from '@libp2p/interface-peer-info'
|
||||||
import type { KeyChain } from './keychain/index.js'
|
import type { KeyChain } from './keychain/index.js'
|
||||||
import type { ConnectionManagerInit } from './connection-manager/index.js'
|
import type { ConnectionManagerInit } from './connection-manager/index.js'
|
||||||
import type { PingServiceInit } from './ping/index.js'
|
import type { PingServiceInit } from './ping/index.js'
|
||||||
@ -50,6 +50,7 @@ export interface MetricsConfig {
|
|||||||
export interface HopConfig {
|
export interface HopConfig {
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
active?: boolean
|
active?: boolean
|
||||||
|
timeout: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RelayConfig {
|
export interface RelayConfig {
|
||||||
@ -139,12 +140,6 @@ export interface Libp2p extends Startable, EventEmitter<Libp2pEvents> {
|
|||||||
pubsub: PubSub
|
pubsub: PubSub
|
||||||
dht: DualDHT
|
dht: DualDHT
|
||||||
|
|
||||||
/**
|
|
||||||
* Load keychain keys from the datastore.
|
|
||||||
* Imports the private key as 'self', if needed.
|
|
||||||
*/
|
|
||||||
loadKeychain: () => Promise<void>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a deduplicated list of peer advertising multiaddrs by concatenating
|
* Get a deduplicated list of peer advertising multiaddrs by concatenating
|
||||||
* the listen addresses used by transports with any configured
|
* the listen addresses used by transports with any configured
|
||||||
@ -177,7 +172,7 @@ export interface Libp2p extends Startable, EventEmitter<Libp2pEvents> {
|
|||||||
* If successful, the known metadata of the peer will be added to the nodes `peerStore`,
|
* If successful, the known metadata of the peer will be added to the nodes `peerStore`,
|
||||||
* and the `MuxedStream` will be returned together with the successful negotiated protocol.
|
* and the `MuxedStream` will be returned together with the successful negotiated protocol.
|
||||||
*/
|
*/
|
||||||
dialProtocol: (peer: PeerId | Multiaddr, protocols: string | string[], options?: AbortOptions) => Promise<ProtocolStream>
|
dialProtocol: (peer: PeerId | Multiaddr, protocols: string | string[], options?: AbortOptions) => Promise<Stream>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnects all connections to the given `peer`
|
* Disconnects all connections to the given `peer`
|
||||||
@ -187,7 +182,7 @@ export interface Libp2p extends Startable, EventEmitter<Libp2pEvents> {
|
|||||||
/**
|
/**
|
||||||
* Registers the `handler` for each protocol
|
* Registers the `handler` for each protocol
|
||||||
*/
|
*/
|
||||||
handle: (protocol: string | string[], handler: StreamHandler) => Promise<void>
|
handle: (protocol: string | string[], handler: StreamHandler, options?: StreamHandlerOptions) => Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the handler for each protocol. The protocol
|
* Removes the handler for each protocol. The protocol
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { logger } from '@libp2p/logger'
|
import { logger } from '@libp2p/logger'
|
||||||
import { handshake } from 'it-handshake'
|
import { handshake } from 'it-handshake'
|
||||||
import * as lp from 'it-length-prefixed'
|
import * as lp from 'it-length-prefixed'
|
||||||
import { UnexpectedPeerError, InvalidCryptoExchangeError } from '@libp2p/interfaces/connection-encrypter/errors'
|
import { UnexpectedPeerError, InvalidCryptoExchangeError } from '@libp2p/interface-connection-encrypter/errors'
|
||||||
import { Exchange, KeyType } from './pb/proto.js'
|
import { Exchange, KeyType } from './pb/proto.js'
|
||||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
||||||
import { peerIdFromBytes, peerIdFromKeys } from '@libp2p/peer-id'
|
import { peerIdFromBytes, peerIdFromKeys } from '@libp2p/peer-id'
|
||||||
import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interfaces/connection-encrypter'
|
import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interface-connection-encrypter'
|
||||||
import type { Duplex } from 'it-stream-types'
|
import type { Duplex } from 'it-stream-types'
|
||||||
|
import map from 'it-map'
|
||||||
|
|
||||||
const log = logger('libp2p:plaintext')
|
const log = logger('libp2p:plaintext')
|
||||||
const PROTOCOL = '/plaintext/2.0.0'
|
const PROTOCOL = '/plaintext/2.0.0'
|
||||||
@ -39,7 +40,7 @@ async function encrypt (localId: PeerId, conn: Duplex<Uint8Array>, remoteId?: Pe
|
|||||||
Type: type,
|
Type: type,
|
||||||
Data: localId.publicKey ?? new Uint8Array(0)
|
Data: localId.publicKey ?? new Uint8Array(0)
|
||||||
}
|
}
|
||||||
}).slice()
|
}).subarray()
|
||||||
)
|
)
|
||||||
|
|
||||||
log('write pubkey exchange to peer %p', remoteId)
|
log('write pubkey exchange to peer %p', remoteId)
|
||||||
@ -47,7 +48,7 @@ async function encrypt (localId: PeerId, conn: Duplex<Uint8Array>, remoteId?: Pe
|
|||||||
// Get the Exchange message
|
// Get the Exchange message
|
||||||
// @ts-expect-error needs to be generator
|
// @ts-expect-error needs to be generator
|
||||||
const response = (await lp.decode.fromReader(shake.reader).next()).value
|
const response = (await lp.decode.fromReader(shake.reader).next()).value
|
||||||
const id = Exchange.decode(response.slice())
|
const id = Exchange.decode(response)
|
||||||
log('read pubkey exchange from peer %p', remoteId)
|
log('read pubkey exchange from peer %p', remoteId)
|
||||||
|
|
||||||
let peerId
|
let peerId
|
||||||
@ -81,8 +82,12 @@ async function encrypt (localId: PeerId, conn: Duplex<Uint8Array>, remoteId?: Pe
|
|||||||
log('plaintext key exchange completed successfully with peer %p', peerId)
|
log('plaintext key exchange completed successfully with peer %p', peerId)
|
||||||
|
|
||||||
shake.rest()
|
shake.rest()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
conn: shake.stream,
|
conn: {
|
||||||
|
sink: shake.stream.sink,
|
||||||
|
source: map(shake.stream.source, (buf) => buf.subarray())
|
||||||
|
},
|
||||||
remotePeer: peerId,
|
remotePeer: peerId,
|
||||||
remoteEarlyData: new Uint8Array()
|
remoteEarlyData: new Uint8Array()
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
/* eslint-disable import/export */
|
/* eslint-disable import/export */
|
||||||
/* eslint-disable @typescript-eslint/no-namespace */
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
|
|
||||||
import { encodeMessage, decodeMessage, message, bytes, enumeration } from 'protons-runtime'
|
import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime'
|
||||||
|
import type { Uint8ArrayList } from 'uint8arraylist'
|
||||||
import type { Codec } from 'protons-runtime'
|
import type { Codec } from 'protons-runtime'
|
||||||
|
|
||||||
export interface Exchange {
|
export interface Exchange {
|
||||||
@ -10,18 +11,61 @@ export interface Exchange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace Exchange {
|
export namespace Exchange {
|
||||||
|
let _codec: Codec<Exchange>
|
||||||
|
|
||||||
export const codec = (): Codec<Exchange> => {
|
export const codec = (): Codec<Exchange> => {
|
||||||
return message<Exchange>({
|
if (_codec == null) {
|
||||||
1: { name: 'id', codec: bytes, optional: true },
|
_codec = message<Exchange>((obj, writer, opts = {}) => {
|
||||||
2: { name: 'pubkey', codec: PublicKey.codec(), optional: true }
|
if (opts.lengthDelimited !== false) {
|
||||||
})
|
writer.fork()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.id != null) {
|
||||||
|
writer.uint32(10)
|
||||||
|
writer.bytes(obj.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.pubkey != null) {
|
||||||
|
writer.uint32(18)
|
||||||
|
PublicKey.codec().encode(obj.pubkey, 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.id = reader.bytes()
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
obj.pubkey = PublicKey.codec().decode(reader, reader.uint32())
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
reader.skipType(tag & 7)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return _codec
|
||||||
}
|
}
|
||||||
|
|
||||||
export const encode = (obj: Exchange): Uint8Array => {
|
export const encode = (obj: Exchange): Uint8Array => {
|
||||||
return encodeMessage(obj, Exchange.codec())
|
return encodeMessage(obj, Exchange.codec())
|
||||||
}
|
}
|
||||||
|
|
||||||
export const decode = (buf: Uint8Array): Exchange => {
|
export const decode = (buf: Uint8Array | Uint8ArrayList): Exchange => {
|
||||||
return decodeMessage(buf, Exchange.codec())
|
return decodeMessage(buf, Exchange.codec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,7 +86,7 @@ enum __KeyTypeValues {
|
|||||||
|
|
||||||
export namespace KeyType {
|
export namespace KeyType {
|
||||||
export const codec = () => {
|
export const codec = () => {
|
||||||
return enumeration<typeof KeyType>(__KeyTypeValues)
|
return enumeration<KeyType>(__KeyTypeValues)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface PublicKey {
|
export interface PublicKey {
|
||||||
@ -51,18 +95,73 @@ export interface PublicKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace PublicKey {
|
export namespace PublicKey {
|
||||||
|
let _codec: Codec<PublicKey>
|
||||||
|
|
||||||
export const codec = (): Codec<PublicKey> => {
|
export const codec = (): Codec<PublicKey> => {
|
||||||
return message<PublicKey>({
|
if (_codec == null) {
|
||||||
1: { name: 'Type', codec: KeyType.codec() },
|
_codec = message<PublicKey>((obj, writer, opts = {}) => {
|
||||||
2: { name: 'Data', codec: bytes }
|
if (opts.lengthDelimited !== false) {
|
||||||
})
|
writer.fork()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.Type != null) {
|
||||||
|
writer.uint32(8)
|
||||||
|
KeyType.codec().encode(obj.Type, writer)
|
||||||
|
} else {
|
||||||
|
throw new Error('Protocol error: required field "Type" 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.Type = KeyType.codec().decode(reader)
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
obj.Data = reader.bytes()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
reader.skipType(tag & 7)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.Type == null) {
|
||||||
|
throw new Error('Protocol error: value for required field "Type" 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: PublicKey): Uint8Array => {
|
export const encode = (obj: PublicKey): Uint8Array => {
|
||||||
return encodeMessage(obj, PublicKey.codec())
|
return encodeMessage(obj, PublicKey.codec())
|
||||||
}
|
}
|
||||||
|
|
||||||
export const decode = (buf: Uint8Array): PublicKey => {
|
export const decode = (buf: Uint8Array | Uint8ArrayList): PublicKey => {
|
||||||
return decodeMessage(buf, PublicKey.codec())
|
return decodeMessage(buf, PublicKey.codec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,10 @@ import { codes } from '../errors.js'
|
|||||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||||
import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/keys'
|
import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/keys'
|
||||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
import { pbkdf2, randomBytes } from '@libp2p/crypto'
|
import { pbkdf2, randomBytes } from '@libp2p/crypto'
|
||||||
|
import type { Startable } from '@libp2p/interfaces/dist/src/startable'
|
||||||
|
|
||||||
const log = logger('libp2p:keychain')
|
const log = logger('libp2p:keychain')
|
||||||
|
|
||||||
@ -110,9 +111,10 @@ function DsInfoName (name: string) {
|
|||||||
* - '/pkcs8/*key-name*', contains the PKCS #8 for the key
|
* - '/pkcs8/*key-name*', contains the PKCS #8 for the key
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export class KeyChain {
|
export class KeyChain implements Startable {
|
||||||
private readonly components: Components
|
private readonly components: Components
|
||||||
private init: KeyChainInit
|
private init: KeyChainInit
|
||||||
|
private started: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of a key chain
|
* Creates a new instance of a key chain
|
||||||
@ -145,6 +147,25 @@ export class KeyChain {
|
|||||||
: ''
|
: ''
|
||||||
|
|
||||||
privates.set(this, { dek })
|
privates.set(this, { dek })
|
||||||
|
this.started = false
|
||||||
|
}
|
||||||
|
|
||||||
|
isStarted () {
|
||||||
|
return this.started
|
||||||
|
}
|
||||||
|
|
||||||
|
async start () {
|
||||||
|
const dsname = DsInfoName('self')
|
||||||
|
|
||||||
|
if (!(await this.components.getDatastore().has(dsname))) {
|
||||||
|
await this.importPeer('self', this.components.getPeerId())
|
||||||
|
}
|
||||||
|
|
||||||
|
this.started = true
|
||||||
|
}
|
||||||
|
|
||||||
|
stop () {
|
||||||
|
this.started = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -474,8 +495,11 @@ export class KeyChain {
|
|||||||
if (!validateKeyName(name)) {
|
if (!validateKeyName(name)) {
|
||||||
throw errCode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)
|
throw errCode(new Error(`Invalid key name '${name}'`), codes.ERR_INVALID_KEY_NAME)
|
||||||
}
|
}
|
||||||
if (peer == null || peer.privateKey == null) {
|
if (peer == null) {
|
||||||
throw errCode(new Error('Peer.privKey is required'), codes.ERR_MISSING_PRIVATE_KEY)
|
throw errCode(new Error('PeerId is required'), codes.ERR_MISSING_PRIVATE_KEY)
|
||||||
|
}
|
||||||
|
if (peer.privateKey == null) {
|
||||||
|
throw errCode(new Error('PeerId.privKey is required'), codes.ERR_MISSING_PRIVATE_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
const privateKey = await unmarshalPrivateKey(peer.privateKey)
|
const privateKey = await unmarshalPrivateKey(peer.privateKey)
|
||||||
|
@ -27,28 +27,29 @@ import { DHTPeerRouting } from './dht/dht-peer-routing.js'
|
|||||||
import { PersistentPeerStore } from '@libp2p/peer-store'
|
import { PersistentPeerStore } from '@libp2p/peer-store'
|
||||||
import { DHTContentRouting } from './dht/dht-content-routing.js'
|
import { DHTContentRouting } from './dht/dht-content-routing.js'
|
||||||
import { AutoDialer } from './connection-manager/dialer/auto-dialer.js'
|
import { AutoDialer } from './connection-manager/dialer/auto-dialer.js'
|
||||||
import { Initializable, Components, isInitializable } from '@libp2p/interfaces/components'
|
import { Initializable, Components, isInitializable } from '@libp2p/components'
|
||||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
||||||
import type { Connection } from '@libp2p/interfaces/connection'
|
import type { Connection } from '@libp2p/interface-connection'
|
||||||
import type { PeerRouting } from '@libp2p/interfaces/peer-routing'
|
import type { PeerRouting } from '@libp2p/interface-peer-routing'
|
||||||
import type { ContentRouting } from '@libp2p/interfaces/content-routing'
|
import type { ContentRouting } from '@libp2p/interface-content-routing'
|
||||||
import type { PubSub } from '@libp2p/interfaces/pubsub'
|
import type { PubSub } from '@libp2p/interface-pubsub'
|
||||||
import type { Registrar, StreamHandler } from '@libp2p/interfaces/registrar'
|
import type { Registrar, StreamHandler, StreamHandlerOptions } from '@libp2p/interface-registrar'
|
||||||
import type { ConnectionManager } from '@libp2p/interfaces/connection-manager'
|
import type { ConnectionManager } from '@libp2p/interface-connection-manager'
|
||||||
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
|
import type { PeerInfo } from '@libp2p/interface-peer-info'
|
||||||
import type { Libp2p, Libp2pEvents, Libp2pInit, Libp2pOptions } from './index.js'
|
import type { Libp2p, Libp2pEvents, Libp2pInit, Libp2pOptions } from './index.js'
|
||||||
import { validateConfig } from './config.js'
|
import { validateConfig } from './config.js'
|
||||||
import { createEd25519PeerId } from '@libp2p/peer-id-factory'
|
import { createEd25519PeerId } from '@libp2p/peer-id-factory'
|
||||||
import type { PeerStore } from '@libp2p/interfaces/peer-store'
|
import type { PeerStore } from '@libp2p/interface-peer-store'
|
||||||
import type { DualDHT } from '@libp2p/interfaces/dht'
|
import type { DualDHT } from '@libp2p/interface-dht'
|
||||||
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
|
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
|
||||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||||
import errCode from 'err-code'
|
import errCode from 'err-code'
|
||||||
import { unmarshalPublicKey } from '@libp2p/crypto/keys'
|
import { unmarshalPublicKey } from '@libp2p/crypto/keys'
|
||||||
import type { Metrics } from '@libp2p/interfaces/metrics'
|
import type { Metrics } from '@libp2p/interface-metrics'
|
||||||
import { DummyDHT } from './dht/dummy-dht.js'
|
import { DummyDHT } from './dht/dummy-dht.js'
|
||||||
import { DummyPubSub } from './pubsub/dummy-pubsub.js'
|
import { DummyPubSub } from './pubsub/dummy-pubsub.js'
|
||||||
import { PeerSet } from '@libp2p/peer-collections'
|
import { PeerSet } from '@libp2p/peer-collections'
|
||||||
|
import { DefaultDialer } from './connection-manager/dialer/index.js'
|
||||||
|
|
||||||
const log = logger('libp2p')
|
const log = logger('libp2p')
|
||||||
|
|
||||||
@ -124,9 +125,13 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
|||||||
// Set up the Upgrader
|
// Set up the Upgrader
|
||||||
this.components.setUpgrader(new DefaultUpgrader(this.components, {
|
this.components.setUpgrader(new DefaultUpgrader(this.components, {
|
||||||
connectionEncryption: (init.connectionEncryption ?? []).map(component => this.configureComponent(component)),
|
connectionEncryption: (init.connectionEncryption ?? []).map(component => this.configureComponent(component)),
|
||||||
muxers: (init.streamMuxers ?? []).map(component => this.configureComponent(component))
|
muxers: (init.streamMuxers ?? []).map(component => this.configureComponent(component)),
|
||||||
|
inboundUpgradeTimeout: init.connectionManager.inboundUpgradeTimeout
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Create the dialer
|
||||||
|
this.components.setDialer(new DefaultDialer(this.components, init.connectionManager))
|
||||||
|
|
||||||
// Create the Connection Manager
|
// Create the Connection Manager
|
||||||
this.connectionManager = this.components.setConnectionManager(new DefaultConnectionManager(init.connectionManager))
|
this.connectionManager = this.components.setConnectionManager(new DefaultConnectionManager(init.connectionManager))
|
||||||
|
|
||||||
@ -217,7 +222,7 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
|||||||
})))
|
})))
|
||||||
|
|
||||||
if (init.relay.enabled) {
|
if (init.relay.enabled) {
|
||||||
this.components.getTransportManager().add(this.configureComponent(new Circuit()))
|
this.components.getTransportManager().add(this.configureComponent(new Circuit(init.relay)))
|
||||||
|
|
||||||
this.configureComponent(new Relay(this.components, {
|
this.configureComponent(new Relay(this.components, {
|
||||||
addressSorter: init.connectionManager.addressSorter,
|
addressSorter: init.connectionManager.addressSorter,
|
||||||
@ -309,18 +314,6 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
|||||||
)
|
)
|
||||||
|
|
||||||
log('libp2p has started')
|
log('libp2p has started')
|
||||||
|
|
||||||
// Once we start, emit any peers we may have already discovered
|
|
||||||
// TODO: this should be removed, as we already discovered these peers in the past
|
|
||||||
await this.components.getPeerStore().forEach(peer => {
|
|
||||||
this.dispatchEvent(new CustomEvent<PeerInfo>('peer:discovery', {
|
|
||||||
detail: {
|
|
||||||
id: peer.id,
|
|
||||||
multiaddrs: peer.addresses.map(addr => addr.multiaddr),
|
|
||||||
protocols: peer.protocols
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
log.error('An error occurred starting libp2p', err)
|
log.error('An error occurred starting libp2p', err)
|
||||||
await this.stop()
|
await this.stop()
|
||||||
@ -349,7 +342,7 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.services.map(servce => servce.stop())
|
this.services.map(service => service.stop())
|
||||||
)
|
)
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@ -363,22 +356,6 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
|||||||
log('libp2p has stopped')
|
log('libp2p has stopped')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load keychain keys from the datastore.
|
|
||||||
* Imports the private key as 'self', if needed.
|
|
||||||
*/
|
|
||||||
async loadKeychain () {
|
|
||||||
if (this.keychain == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.keychain.findKeyByName('self')
|
|
||||||
} catch (err: any) {
|
|
||||||
await this.keychain.importPeer('self', this.peerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isStarted () {
|
isStarted () {
|
||||||
return this.started
|
return this.started
|
||||||
}
|
}
|
||||||
@ -490,12 +467,28 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
|||||||
return await this.pingService.ping(id, options)
|
return await this.pingService.ping(id, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async handle (protocols: string | string[], handler: StreamHandler): Promise<void> {
|
async handle (protocols: string | string[], handler: StreamHandler, options?: StreamHandlerOptions): Promise<void> {
|
||||||
return await this.components.getRegistrar().handle(protocols, handler)
|
if (!Array.isArray(protocols)) {
|
||||||
|
protocols = [protocols]
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
protocols.map(async protocol => {
|
||||||
|
await this.components.getRegistrar().handle(protocol, handler, options)
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async unhandle (protocols: string[] | string): Promise<void> {
|
async unhandle (protocols: string[] | string): Promise<void> {
|
||||||
return await this.components.getRegistrar().unhandle(protocols)
|
if (!Array.isArray(protocols)) {
|
||||||
|
protocols = [protocols]
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
protocols.map(async protocol => {
|
||||||
|
await this.components.getRegistrar().unhandle(protocol)
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,10 +3,9 @@ import each from 'it-foreach'
|
|||||||
import LRU from 'hashlru'
|
import LRU from 'hashlru'
|
||||||
import { METRICS as defaultOptions } from '../constants.js'
|
import { METRICS as defaultOptions } from '../constants.js'
|
||||||
import { DefaultStats, StatsInit } from './stats.js'
|
import { DefaultStats, StatsInit } from './stats.js'
|
||||||
import type { ComponentMetricsUpdate, Metrics, Stats, TrackStreamOptions } from '@libp2p/interfaces/metrics'
|
import type { ComponentMetricsUpdate, Metrics, Stats, TrackedMetric, TrackStreamOptions } from '@libp2p/interface-metrics'
|
||||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
||||||
import type { Startable } from '@libp2p/interfaces/startable'
|
import type { Startable } from '@libp2p/interfaces/startable'
|
||||||
import type { Duplex } from 'it-stream-types'
|
|
||||||
|
|
||||||
const initialCounters: ['dataReceived', 'dataSent'] = [
|
const initialCounters: ['dataReceived', 'dataSent'] = [
|
||||||
'dataReceived',
|
'dataReceived',
|
||||||
@ -41,7 +40,7 @@ export class DefaultMetrics implements Metrics, Startable {
|
|||||||
private readonly protocolStats: Map<string, DefaultStats>
|
private readonly protocolStats: Map<string, DefaultStats>
|
||||||
private readonly oldPeers: ReturnType<typeof LRU>
|
private readonly oldPeers: ReturnType<typeof LRU>
|
||||||
private running: boolean
|
private running: boolean
|
||||||
private readonly systems: Map<string, Map<string, Map<string, number>>>
|
private readonly systems: Map<string, Map<string, Map<string, TrackedMetric>>>
|
||||||
private readonly statsInit: StatsInit
|
private readonly statsInit: StatsInit
|
||||||
|
|
||||||
constructor (init: MetricsInit) {
|
constructor (init: MetricsInit) {
|
||||||
@ -115,7 +114,7 @@ export class DefaultMetrics implements Metrics, Startable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateComponentMetric (update: ComponentMetricsUpdate) {
|
updateComponentMetric (update: ComponentMetricsUpdate) {
|
||||||
const { system = 'libp2p', component, metric, value } = update
|
const { system = 'libp2p', component, metric, value, label, help } = update
|
||||||
|
|
||||||
if (!this.systems.has(system)) {
|
if (!this.systems.has(system)) {
|
||||||
this.systems.set(system, new Map())
|
this.systems.set(system, new Map())
|
||||||
@ -137,7 +136,11 @@ export class DefaultMetrics implements Metrics, Startable {
|
|||||||
throw new Error('Unknown metric component')
|
throw new Error('Unknown metric component')
|
||||||
}
|
}
|
||||||
|
|
||||||
componentMetrics.set(metric, value)
|
componentMetrics.set(metric, {
|
||||||
|
label,
|
||||||
|
help,
|
||||||
|
calculate: typeof value !== 'function' ? () => value : value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -259,11 +262,11 @@ export class DefaultMetrics implements Metrics, Startable {
|
|||||||
* When the `PeerId` is known, `Metrics.updatePlaceholder` should be called
|
* When the `PeerId` is known, `Metrics.updatePlaceholder` should be called
|
||||||
* with the placeholder string returned from here, and the known `PeerId`.
|
* with the placeholder string returned from here, and the known `PeerId`.
|
||||||
*/
|
*/
|
||||||
trackStream <T extends Duplex<Uint8Array>> (opts: TrackStreamOptions<T>): T {
|
trackStream (opts: TrackStreamOptions): void {
|
||||||
const { stream, remotePeer, protocol } = opts
|
const { stream, remotePeer, protocol } = opts
|
||||||
|
|
||||||
if (!this.running) {
|
if (!this.running) {
|
||||||
return stream
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const source = stream.source
|
const source = stream.source
|
||||||
@ -271,7 +274,7 @@ export class DefaultMetrics implements Metrics, Startable {
|
|||||||
remotePeer,
|
remotePeer,
|
||||||
protocol,
|
protocol,
|
||||||
direction: 'in',
|
direction: 'in',
|
||||||
dataLength: chunk.length
|
dataLength: chunk.byteLength
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const sink = stream.sink
|
const sink = stream.sink
|
||||||
@ -283,14 +286,12 @@ export class DefaultMetrics implements Metrics, Startable {
|
|||||||
remotePeer,
|
remotePeer,
|
||||||
protocol,
|
protocol,
|
||||||
direction: 'out',
|
direction: 'out',
|
||||||
dataLength: chunk.length
|
dataLength: chunk.byteLength
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
sink
|
sink
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return stream
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { MovingAverage } from '@libp2p/interfaces/metrics'
|
import type { MovingAverage } from '@libp2p/interface-metrics'
|
||||||
|
|
||||||
export class DefaultMovingAverage {
|
export class DefaultMovingAverage {
|
||||||
public movingAverage: number
|
public movingAverage: number
|
||||||
|
@ -2,7 +2,7 @@ import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
|||||||
import { createMovingAverage } from './moving-average.js'
|
import { createMovingAverage } from './moving-average.js'
|
||||||
// @ts-expect-error no types
|
// @ts-expect-error no types
|
||||||
import retimer from 'retimer'
|
import retimer from 'retimer'
|
||||||
import type { MovingAverages, Stats, TransferStats } from '@libp2p/interfaces/metrics'
|
import type { MovingAverages, Stats, TransferStats } from '@libp2p/interface-metrics'
|
||||||
|
|
||||||
export interface StatsEvents {
|
export interface StatsEvents {
|
||||||
'update': CustomEvent<TransferStats>
|
'update': CustomEvent<TransferStats>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { upnpNat, NatAPI } from '@achingbrain/nat-port-mapper'
|
import { upnpNat, NatAPI } from '@achingbrain/nat-port-mapper'
|
||||||
import { logger } from '@libp2p/logger'
|
import { logger } from '@libp2p/logger'
|
||||||
import { Multiaddr } from '@multiformats/multiaddr'
|
import { fromNodeAddress } from '@multiformats/multiaddr'
|
||||||
import { isBrowser } from 'wherearewe'
|
import { isBrowser } from 'wherearewe'
|
||||||
import isPrivateIp from 'private-ip'
|
import isPrivateIp from 'private-ip'
|
||||||
import * as pkg from './version.js'
|
import * as pkg from './version.js'
|
||||||
@ -8,7 +8,7 @@ import errCode from 'err-code'
|
|||||||
import { codes } from './errors.js'
|
import { codes } from './errors.js'
|
||||||
import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback'
|
import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback'
|
||||||
import type { Startable } from '@libp2p/interfaces/startable'
|
import type { Startable } from '@libp2p/interfaces/startable'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
|
|
||||||
const log = logger('libp2p:nat')
|
const log = logger('libp2p:nat')
|
||||||
const DEFAULT_TTL = 7200
|
const DEFAULT_TTL = 7200
|
||||||
@ -157,7 +157,7 @@ export class NatManager implements Startable {
|
|||||||
protocol: transport.toUpperCase() === 'TCP' ? 'TCP' : 'UDP'
|
protocol: transport.toUpperCase() === 'TCP' ? 'TCP' : 'UDP'
|
||||||
})
|
})
|
||||||
|
|
||||||
this.components.getAddressManager().addObservedAddr(Multiaddr.fromNodeAddress({
|
this.components.getAddressManager().addObservedAddr(fromNodeAddress({
|
||||||
family: 4,
|
family: 4,
|
||||||
address: publicIp,
|
address: publicIp,
|
||||||
port: publicPort
|
port: publicPort
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record'
|
import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
import type { Startable } from '@libp2p/interfaces/startable'
|
import type { Startable } from '@libp2p/interfaces/startable'
|
||||||
import { logger } from '@libp2p/logger'
|
import { logger } from '@libp2p/logger'
|
||||||
import { protocols } from '@multiformats/multiaddr'
|
import { protocols } from '@multiformats/multiaddr'
|
||||||
|
@ -18,12 +18,12 @@ import {
|
|||||||
// @ts-expect-error module with no types
|
// @ts-expect-error module with no types
|
||||||
} from 'set-delayed-interval'
|
} from 'set-delayed-interval'
|
||||||
import { setMaxListeners } from 'events'
|
import { setMaxListeners } from 'events'
|
||||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
||||||
import type { PeerRouting } from '@libp2p/interfaces/peer-routing'
|
import type { PeerRouting } from '@libp2p/interface-peer-routing'
|
||||||
import type { AbortOptions } from '@libp2p/interfaces'
|
import type { AbortOptions } from '@libp2p/interfaces'
|
||||||
import type { Startable } from '@libp2p/interfaces/startable'
|
import type { Startable } from '@libp2p/interfaces/startable'
|
||||||
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
|
import type { PeerInfo } from '@libp2p/interface-peer-info'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
|
|
||||||
const log = logger('libp2p:peer-routing')
|
const log = logger('libp2p:peer-routing')
|
||||||
|
|
||||||
|
@ -6,33 +6,47 @@ import { pipe } from 'it-pipe'
|
|||||||
import first from 'it-first'
|
import first from 'it-first'
|
||||||
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
|
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
|
||||||
import { PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION } from './constants.js'
|
import { PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION } from './constants.js'
|
||||||
import type { IncomingStreamData } from '@libp2p/interfaces/registrar'
|
import type { IncomingStreamData } from '@libp2p/interface-registrar'
|
||||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
||||||
import type { Startable } from '@libp2p/interfaces/startable'
|
import type { Startable } from '@libp2p/interfaces/startable'
|
||||||
import type { Components } from '@libp2p/interfaces/components'
|
import type { Components } from '@libp2p/components'
|
||||||
import type { AbortOptions } from '@libp2p/interfaces'
|
import type { AbortOptions } from '@libp2p/interfaces'
|
||||||
import type { Duplex } from 'it-stream-types'
|
|
||||||
import { abortableDuplex } from 'abortable-iterator'
|
import { abortableDuplex } from 'abortable-iterator'
|
||||||
|
import { TimeoutController } from 'timeout-abort-controller'
|
||||||
|
import type { Stream } from '@libp2p/interface-connection'
|
||||||
|
import { setMaxListeners } from 'events'
|
||||||
|
|
||||||
const log = logger('libp2p:ping')
|
const log = logger('libp2p:ping')
|
||||||
|
|
||||||
export interface PingServiceInit {
|
export interface PingServiceInit {
|
||||||
protocolPrefix: string
|
protocolPrefix: string
|
||||||
|
maxInboundStreams: number
|
||||||
|
maxOutboundStreams: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long we should wait for a ping response
|
||||||
|
*/
|
||||||
|
timeout: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PingService implements Startable {
|
export class PingService implements Startable {
|
||||||
public readonly protocol: string
|
public readonly protocol: string
|
||||||
private readonly components: Components
|
private readonly components: Components
|
||||||
private started: boolean
|
private started: boolean
|
||||||
|
private readonly init: PingServiceInit
|
||||||
|
|
||||||
constructor (components: Components, init: PingServiceInit) {
|
constructor (components: Components, init: PingServiceInit) {
|
||||||
this.components = components
|
this.components = components
|
||||||
this.started = false
|
this.started = false
|
||||||
this.protocol = `/${init.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`
|
this.protocol = `/${init.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`
|
||||||
|
this.init = init
|
||||||
}
|
}
|
||||||
|
|
||||||
async start () {
|
async start () {
|
||||||
await this.components.getRegistrar().handle(this.protocol, this.handleMessage)
|
await this.components.getRegistrar().handle(this.protocol, this.handleMessage, {
|
||||||
|
maxInboundStreams: this.init.maxInboundStreams,
|
||||||
|
maxOutboundStreams: this.init.maxOutboundStreams
|
||||||
|
})
|
||||||
this.started = true
|
this.started = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,19 +80,32 @@ export class PingService implements Startable {
|
|||||||
async ping (peer: PeerId, options: AbortOptions = {}): Promise<number> {
|
async ping (peer: PeerId, options: AbortOptions = {}): Promise<number> {
|
||||||
log('dialing %s to %p', this.protocol, peer)
|
log('dialing %s to %p', this.protocol, peer)
|
||||||
|
|
||||||
const connection = await this.components.getConnectionManager().openConnection(peer, options)
|
|
||||||
const { stream } = await connection.newStream([this.protocol], options)
|
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
const data = randomBytes(PING_LENGTH)
|
const data = randomBytes(PING_LENGTH)
|
||||||
|
const connection = await this.components.getConnectionManager().openConnection(peer, options)
|
||||||
|
let timeoutController
|
||||||
|
let signal = options.signal
|
||||||
|
let stream: Stream | undefined
|
||||||
|
|
||||||
let source: Duplex<Uint8Array> = stream
|
// create a timeout if no abort signal passed
|
||||||
|
if (signal == null) {
|
||||||
|
timeoutController = new TimeoutController(this.init.timeout)
|
||||||
|
signal = timeoutController.signal
|
||||||
|
|
||||||
// make stream abortable if AbortSignal passed
|
try {
|
||||||
if (options.signal != null) {
|
// fails on node < 15.4
|
||||||
source = abortableDuplex(stream, options.signal)
|
setMaxListeners?.(Infinity, timeoutController.signal)
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
stream = await connection.newStream([this.protocol], {
|
||||||
|
signal
|
||||||
|
})
|
||||||
|
|
||||||
|
// make stream abortable
|
||||||
|
const source = abortableDuplex(stream, signal)
|
||||||
|
|
||||||
const result = await pipe(
|
const result = await pipe(
|
||||||
[data],
|
[data],
|
||||||
source,
|
source,
|
||||||
@ -86,13 +113,19 @@ export class PingService implements Startable {
|
|||||||
)
|
)
|
||||||
const end = Date.now()
|
const end = Date.now()
|
||||||
|
|
||||||
if (result == null || !uint8ArrayEquals(data, result)) {
|
if (result == null || !uint8ArrayEquals(data, result.subarray())) {
|
||||||
throw errCode(new Error('Received wrong ping ack'), codes.ERR_WRONG_PING_ACK)
|
throw errCode(new Error('Received wrong ping ack'), codes.ERR_WRONG_PING_ACK)
|
||||||
}
|
}
|
||||||
|
|
||||||
return end - start
|
return end - start
|
||||||
} finally {
|
} finally {
|
||||||
stream.close()
|
if (timeoutController != null) {
|
||||||
|
timeoutController.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream != null) {
|
||||||
|
stream.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,8 @@ import {
|
|||||||
} from './crypto.js'
|
} from './crypto.js'
|
||||||
import { handshake } from 'it-handshake'
|
import { handshake } from 'it-handshake'
|
||||||
import { NONCE_LENGTH } from './key-generator.js'
|
import { NONCE_LENGTH } from './key-generator.js'
|
||||||
import type { MultiaddrConnection } from '@libp2p/interfaces/transport'
|
import type { ConnectionProtector, MultiaddrConnection } from '@libp2p/interface-connection'
|
||||||
import type { ConnectionProtector } from '@libp2p/interfaces/connection'
|
import map from 'it-map'
|
||||||
|
|
||||||
const log = logger('libp2p:pnet')
|
const log = logger('libp2p:pnet')
|
||||||
|
|
||||||
@ -84,6 +84,7 @@ export class PreSharedKeyConnectionProtector implements ConnectionProtector {
|
|||||||
// Encrypt all outbound traffic
|
// Encrypt all outbound traffic
|
||||||
createBoxStream(localNonce, this.psk),
|
createBoxStream(localNonce, this.psk),
|
||||||
shake.stream,
|
shake.stream,
|
||||||
|
(source) => map(source, (buf) => buf.subarray()),
|
||||||
// Decrypt all inbound traffic
|
// Decrypt all inbound traffic
|
||||||
createUnboxStream(remoteNonce, this.psk),
|
createUnboxStream(remoteNonce, this.psk),
|
||||||
external
|
external
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user