feat: convert to typescript (#1172)
Converts this module to typescript.
- Ecosystem modules renamed from (e.g.) `libp2p-tcp` to `@libp2p/tcp`
- Ecosystem module now have named exports
- Configuration has been updated, now pass instances of modules instead of classes:
- Some configuration keys have been renamed to make them more descriptive. `transport` -> `transports`, `connEncryption` -> `connectionEncryption`. In general where we pass multiple things, the key is now plural, e.g. `streamMuxer` -> `streamMuxers`, `contentRouting` -> `contentRouters`, etc. Where we are configuring a singleton the config key is singular, e.g. `connProtector` -> `connectionProtector` etc.
- Properties of the `modules` config key have been moved to the root
- Properties of the `config` config key have been moved to the root
```js
// before
import Libp2p from 'libp2p'
import TCP from 'libp2p-tcp'
await Libp2p.create({
modules: {
transport: [
TCP
],
}
config: {
transport: {
[TCP.tag]: {
foo: 'bar'
}
},
relay: {
enabled: true,
hop: {
enabled: true,
active: true
}
}
}
})
```
```js
// after
import { createLibp2p } from 'libp2p'
import { TCP } from '@libp2p/tcp'
await createLibp2p({
transports: [
new TCP({ foo: 'bar' })
],
relay: {
enabled: true,
hop: {
enabled: true,
active: true
}
}
})
```
- Use of `enabled` flag has been reduced - previously you could pass a module but disable it with config. Now if you don't want a feature, just don't pass an implementation. Eg:
```js
// before
await Libp2p.create({
modules: {
transport: [
TCP
],
pubsub: Gossipsub
},
config: {
pubsub: {
enabled: false
}
}
})
```
```js
// after
await createLibp2p({
transports: [
new TCP()
]
})
```
- `.multiaddrs` renamed to `.getMultiaddrs()` because it's not a property accessor, work is done by that method to calculate announce addresses, observed addresses, etc
- `/p2p/${peerId}` is now appended to all addresses returned by `.getMultiaddrs()` so they can be used opaquely (every consumer has to append the peer ID to the address to actually use it otherwise). If you need low-level unadulterated addresses, call methods on the address manager.
BREAKING CHANGE: types are no longer hand crafted, this module is now ESM only
2022-03-28 14:30:27 +01:00
|
|
|
import { logger } from '@libp2p/logger'
|
|
|
|
import errCode from 'err-code'
|
|
|
|
import { codes, messages } from './errors.js'
|
|
|
|
import {
|
|
|
|
storeAddresses,
|
|
|
|
uniquePeers,
|
|
|
|
requirePeers
|
|
|
|
} from './content-routing/utils.js'
|
|
|
|
import { TimeoutController } from 'timeout-abort-controller'
|
|
|
|
import merge from 'it-merge'
|
|
|
|
import { pipe } from 'it-pipe'
|
|
|
|
import first from 'it-first'
|
|
|
|
import drain from 'it-drain'
|
|
|
|
import filter from 'it-filter'
|
|
|
|
import {
|
|
|
|
setDelayedInterval,
|
|
|
|
clearDelayedInterval
|
|
|
|
// @ts-expect-error module with no types
|
|
|
|
} from 'set-delayed-interval'
|
|
|
|
// @ts-expect-error setMaxListeners is missing from the node 16 types
|
|
|
|
import { setMaxListeners } from 'events'
|
|
|
|
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
|
|
|
import type { PeerRouting } from '@libp2p/interfaces/peer-routing'
|
2022-05-04 16:03:43 +01:00
|
|
|
import type { AbortOptions } from '@libp2p/interfaces'
|
|
|
|
import type { Startable } from '@libp2p/interfaces/startable'
|
feat: convert to typescript (#1172)
Converts this module to typescript.
- Ecosystem modules renamed from (e.g.) `libp2p-tcp` to `@libp2p/tcp`
- Ecosystem module now have named exports
- Configuration has been updated, now pass instances of modules instead of classes:
- Some configuration keys have been renamed to make them more descriptive. `transport` -> `transports`, `connEncryption` -> `connectionEncryption`. In general where we pass multiple things, the key is now plural, e.g. `streamMuxer` -> `streamMuxers`, `contentRouting` -> `contentRouters`, etc. Where we are configuring a singleton the config key is singular, e.g. `connProtector` -> `connectionProtector` etc.
- Properties of the `modules` config key have been moved to the root
- Properties of the `config` config key have been moved to the root
```js
// before
import Libp2p from 'libp2p'
import TCP from 'libp2p-tcp'
await Libp2p.create({
modules: {
transport: [
TCP
],
}
config: {
transport: {
[TCP.tag]: {
foo: 'bar'
}
},
relay: {
enabled: true,
hop: {
enabled: true,
active: true
}
}
}
})
```
```js
// after
import { createLibp2p } from 'libp2p'
import { TCP } from '@libp2p/tcp'
await createLibp2p({
transports: [
new TCP({ foo: 'bar' })
],
relay: {
enabled: true,
hop: {
enabled: true,
active: true
}
}
})
```
- Use of `enabled` flag has been reduced - previously you could pass a module but disable it with config. Now if you don't want a feature, just don't pass an implementation. Eg:
```js
// before
await Libp2p.create({
modules: {
transport: [
TCP
],
pubsub: Gossipsub
},
config: {
pubsub: {
enabled: false
}
}
})
```
```js
// after
await createLibp2p({
transports: [
new TCP()
]
})
```
- `.multiaddrs` renamed to `.getMultiaddrs()` because it's not a property accessor, work is done by that method to calculate announce addresses, observed addresses, etc
- `/p2p/${peerId}` is now appended to all addresses returned by `.getMultiaddrs()` so they can be used opaquely (every consumer has to append the peer ID to the address to actually use it otherwise). If you need low-level unadulterated addresses, call methods on the address manager.
BREAKING CHANGE: types are no longer hand crafted, this module is now ESM only
2022-03-28 14:30:27 +01:00
|
|
|
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
|
|
|
|
import type { Components } from '@libp2p/interfaces/components'
|
|
|
|
|
|
|
|
const log = logger('libp2p:peer-routing')
|
|
|
|
|
|
|
|
export interface RefreshManagerInit {
|
|
|
|
/**
|
|
|
|
* Whether to enable the Refresh manager
|
|
|
|
*/
|
|
|
|
enabled?: boolean
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Boot delay to start the Refresh Manager (in ms)
|
|
|
|
*/
|
|
|
|
bootDelay?: number
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Interval between each Refresh Manager run (in ms)
|
|
|
|
*/
|
|
|
|
interval?: number
|
|
|
|
|
|
|
|
/**
|
|
|
|
* How long to let each refresh run (in ms)
|
|
|
|
*/
|
|
|
|
timeout?: number
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface PeerRoutingInit {
|
|
|
|
routers: PeerRouting[]
|
|
|
|
refreshManager?: RefreshManagerInit
|
|
|
|
}
|
|
|
|
|
|
|
|
export class DefaultPeerRouting implements PeerRouting, Startable {
|
|
|
|
private readonly components: Components
|
|
|
|
private readonly routers: PeerRouting[]
|
|
|
|
private readonly refreshManagerInit: RefreshManagerInit
|
|
|
|
private timeoutId?: ReturnType<typeof setTimeout>
|
|
|
|
private started: boolean
|
|
|
|
private abortController?: TimeoutController
|
|
|
|
|
|
|
|
constructor (components: Components, init: PeerRoutingInit) {
|
|
|
|
this.components = components
|
|
|
|
this.routers = init.routers
|
|
|
|
this.refreshManagerInit = init.refreshManager ?? {}
|
|
|
|
this.started = false
|
|
|
|
|
|
|
|
this._findClosestPeersTask = this._findClosestPeersTask.bind(this)
|
|
|
|
}
|
|
|
|
|
|
|
|
isStarted () {
|
|
|
|
return this.started
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start peer routing service.
|
|
|
|
*/
|
|
|
|
async start () {
|
|
|
|
if (this.started || this.routers.length === 0 || this.timeoutId != null || this.refreshManagerInit.enabled === false) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.timeoutId = setDelayedInterval(
|
|
|
|
this._findClosestPeersTask, this.refreshManagerInit.interval, this.refreshManagerInit.bootDelay
|
|
|
|
)
|
|
|
|
|
|
|
|
this.started = true
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Recurrent task to find closest peers and add their addresses to the Address Book.
|
|
|
|
*/
|
|
|
|
async _findClosestPeersTask () {
|
|
|
|
if (this.abortController != null) {
|
|
|
|
// we are already running the query
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
this.abortController = new TimeoutController(this.refreshManagerInit.timeout ?? 10e3)
|
|
|
|
|
|
|
|
// this controller may be used while dialing lots of peers so prevent MaxListenersExceededWarning
|
|
|
|
// appearing in the console
|
|
|
|
try {
|
|
|
|
// fails on node < 15.4
|
|
|
|
setMaxListeners?.(Infinity, this.abortController.signal)
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
// nb getClosestPeers adds the addresses to the address book
|
|
|
|
await drain(this.getClosestPeers(this.components.getPeerId().toBytes(), { signal: this.abortController.signal }))
|
|
|
|
} catch (err: any) {
|
|
|
|
log.error(err)
|
|
|
|
} finally {
|
|
|
|
this.abortController?.clear()
|
|
|
|
this.abortController = undefined
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop peer routing service.
|
|
|
|
*/
|
|
|
|
async stop () {
|
|
|
|
clearDelayedInterval(this.timeoutId)
|
|
|
|
|
|
|
|
// abort query if it is in-flight
|
|
|
|
this.abortController?.abort()
|
|
|
|
|
|
|
|
this.started = false
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterates over all peer routers in parallel to find the given peer
|
|
|
|
*/
|
|
|
|
async findPeer (id: PeerId, options?: AbortOptions): Promise<PeerInfo> {
|
|
|
|
if (this.routers.length === 0) {
|
|
|
|
throw errCode(new Error('No peer routers available'), codes.ERR_NO_ROUTERS_AVAILABLE)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (id.toString() === this.components.getPeerId().toString()) {
|
|
|
|
throw errCode(new Error('Should not try to find self'), codes.ERR_FIND_SELF)
|
|
|
|
}
|
|
|
|
|
|
|
|
const output = await pipe(
|
|
|
|
merge(
|
|
|
|
...this.routers.map(router => (async function * () {
|
|
|
|
try {
|
|
|
|
yield await router.findPeer(id, options)
|
|
|
|
} catch (err) {
|
|
|
|
log.error(err)
|
|
|
|
}
|
|
|
|
})())
|
|
|
|
),
|
|
|
|
(source) => filter(source, Boolean),
|
|
|
|
(source) => storeAddresses(source, this.components.getPeerStore()),
|
|
|
|
async (source) => await first(source)
|
|
|
|
)
|
|
|
|
|
|
|
|
if (output != null) {
|
|
|
|
return output
|
|
|
|
}
|
|
|
|
|
|
|
|
throw errCode(new Error(messages.NOT_FOUND), codes.ERR_NOT_FOUND)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attempt to find the closest peers on the network to the given key
|
|
|
|
*/
|
|
|
|
async * getClosestPeers (key: Uint8Array, options?: AbortOptions): AsyncIterable<PeerInfo> {
|
|
|
|
if (this.routers.length === 0) {
|
|
|
|
throw errCode(new Error('No peer routers available'), codes.ERR_NO_ROUTERS_AVAILABLE)
|
|
|
|
}
|
|
|
|
|
|
|
|
yield * pipe(
|
|
|
|
merge(
|
|
|
|
...this.routers.map(router => router.getClosestPeers(key, options))
|
|
|
|
),
|
|
|
|
(source) => storeAddresses(source, this.components.getPeerStore()),
|
|
|
|
(source) => uniquePeers(source),
|
|
|
|
(source) => requirePeers(source)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|