js-libp2p/src/peer-routing.ts

187 lines
5.2 KiB
TypeScript
Raw Normal View History

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'
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)
)
}
}