Skip to content

Commit

Permalink
fix: fix up types to allow custom services to depend on each other
Browse files Browse the repository at this point in the history
Derive the type of the components arg from a union of the internal
components plus the service map type.
  • Loading branch information
achingbrain committed Jun 12, 2024
1 parent 863b3de commit 92bfe2f
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 57 deletions.
24 changes: 12 additions & 12 deletions packages/interface/src/connection-gater/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface ConnectionGater {
*
* Return true to prevent dialing the passed peer.
*/
denyDialPeer?(peerId: PeerId): Promise<boolean>
denyDialPeer?(peerId: PeerId): Promise<boolean> | boolean

/**
* denyDialMultiaddr tests whether we're permitted to dial the specified
Expand All @@ -23,7 +23,7 @@ export interface ConnectionGater {
*
* Return true to prevent dialing the passed peer on the passed multiaddr.
*/
denyDialMultiaddr?(multiaddr: Multiaddr): Promise<boolean>
denyDialMultiaddr?(multiaddr: Multiaddr): Promise<boolean> | boolean

/**
* denyInboundConnection tests whether an incipient inbound connection is allowed.
Expand All @@ -33,7 +33,7 @@ export interface ConnectionGater {
*
* Return true to deny the incoming passed connection.
*/
denyInboundConnection?(maConn: MultiaddrConnection): Promise<boolean>
denyInboundConnection?(maConn: MultiaddrConnection): Promise<boolean> | boolean

/**
* denyOutboundConnection tests whether an incipient outbound connection is allowed.
Expand All @@ -43,7 +43,7 @@ export interface ConnectionGater {
*
* Return true to deny the incoming passed connection.
*/
denyOutboundConnection?(peerId: PeerId, maConn: MultiaddrConnection): Promise<boolean>
denyOutboundConnection?(peerId: PeerId, maConn: MultiaddrConnection): Promise<boolean> | boolean

/**
* denyInboundEncryptedConnection tests whether a given connection, now encrypted,
Expand All @@ -55,7 +55,7 @@ export interface ConnectionGater {
*
* Return true to deny the passed secured connection.
*/
denyInboundEncryptedConnection?(peerId: PeerId, maConn: MultiaddrConnection): Promise<boolean>
denyInboundEncryptedConnection?(peerId: PeerId, maConn: MultiaddrConnection): Promise<boolean> | boolean

/**
* denyOutboundEncryptedConnection tests whether a given connection, now encrypted,
Expand All @@ -67,7 +67,7 @@ export interface ConnectionGater {
*
* Return true to deny the passed secured connection.
*/
denyOutboundEncryptedConnection?(peerId: PeerId, maConn: MultiaddrConnection): Promise<boolean>
denyOutboundEncryptedConnection?(peerId: PeerId, maConn: MultiaddrConnection): Promise<boolean> | boolean

/**
* denyInboundUpgradedConnection tests whether a fully capable connection is allowed.
Expand All @@ -77,7 +77,7 @@ export interface ConnectionGater {
*
* Return true to deny the passed upgraded connection.
*/
denyInboundUpgradedConnection?(peerId: PeerId, maConn: MultiaddrConnection): Promise<boolean>
denyInboundUpgradedConnection?(peerId: PeerId, maConn: MultiaddrConnection): Promise<boolean> | boolean

/**
* denyOutboundUpgradedConnection tests whether a fully capable connection is allowed.
Expand All @@ -87,15 +87,15 @@ export interface ConnectionGater {
*
* Return true to deny the passed upgraded connection.
*/
denyOutboundUpgradedConnection?(peerId: PeerId, maConn: MultiaddrConnection): Promise<boolean>
denyOutboundUpgradedConnection?(peerId: PeerId, maConn: MultiaddrConnection): Promise<boolean> | boolean

/**
* denyInboundRelayReservation tests whether a remote peer is allowed make a
* relay reservation on this node.
*
* Return true to deny the relay reservation.
*/
denyInboundRelayReservation?(source: PeerId): Promise<boolean>
denyInboundRelayReservation?(source: PeerId): Promise<boolean> | boolean

/**
* denyOutboundRelayedConnection tests whether a remote peer is allowed to open a relayed
Expand All @@ -106,7 +106,7 @@ export interface ConnectionGater {
*
* Return true to deny the relayed connection.
*/
denyOutboundRelayedConnection?(source: PeerId, destination: PeerId): Promise<boolean>
denyOutboundRelayedConnection?(source: PeerId, destination: PeerId): Promise<boolean> | boolean

/**
* denyInboundRelayedConnection tests whether a remote peer is allowed to open a relayed
Expand All @@ -117,12 +117,12 @@ export interface ConnectionGater {
*
* Return true to deny the relayed connection.
*/
denyInboundRelayedConnection?(relay: PeerId, remotePeer: PeerId): Promise<boolean>
denyInboundRelayedConnection?(relay: PeerId, remotePeer: PeerId): Promise<boolean> | boolean

/**
* Used by the address book to filter passed addresses.
*
* Return true to allow storing the passed multiaddr for the passed peer.
*/
filterMultiaddrForPeer?(peer: PeerId, multiaddr: Multiaddr): Promise<boolean>
filterMultiaddrForPeer?(peer: PeerId, multiaddr: Multiaddr): Promise<boolean> | boolean
}
9 changes: 0 additions & 9 deletions packages/interface/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -684,15 +684,6 @@ export interface LoggerOptions {
log: Logger
}

/**
* Returns a new type with all fields marked optional.
*
* Borrowed from the tsdef module.
*/
export type RecursivePartial<T> = {
[P in keyof T]?: T[P] extends Array<infer I> ? Array<RecursivePartial<I>> : T[P] extends (...args: any[]) => any ? T[P] : RecursivePartial<T[P]>
}

/**
* When a routing operation involves reading values, these options allow
* controlling where the values are read from. By default libp2p will check
Expand Down
10 changes: 5 additions & 5 deletions packages/libp2p/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers'
import mergeOptions from 'merge-options'
import { codes, messages } from './errors.js'
import type { Libp2pInit } from './index.js'
import type { ServiceMap, RecursivePartial } from '@libp2p/interface'
import type { ServiceMap } from '@libp2p/interface'
import type { Multiaddr } from '@multiformats/multiaddr'

const DefaultConfig: Partial<Libp2pInit> = {
const DefaultConfig: Libp2pInit = {
addresses: {
listen: [],
announce: [],
Expand All @@ -26,14 +26,14 @@ const DefaultConfig: Partial<Libp2pInit> = {
}
}

export async function validateConfig <T extends ServiceMap = Record<string, unknown>> (opts: RecursivePartial<Libp2pInit<T>>): Promise<Libp2pInit<T>> {
const resultingOptions: Libp2pInit<T> = mergeOptions(DefaultConfig, opts)
export async function validateConfig <T extends ServiceMap = Record<string, unknown>> (opts: Libp2pInit<T>): Promise<Libp2pInit<T> & Required<Pick<Libp2pInit<T>, 'peerId'>>> {
const resultingOptions: Libp2pInit<T> & Required<Pick<Libp2pInit<T>, 'peerId'>> = mergeOptions(DefaultConfig, opts)

if (resultingOptions.connectionProtector === null && globalThis.process?.env?.LIBP2P_FORCE_PNET != null) { // eslint-disable-line no-undef
throw new CodeError(messages.ERR_PROTECTOR_REQUIRED, codes.ERR_PROTECTOR_REQUIRED)
}

if (!(await peerIdFromKeys(resultingOptions.privateKey.public.bytes, resultingOptions.privateKey.bytes)).equals(resultingOptions.peerId)) {
if (resultingOptions.privateKey != null && !(await peerIdFromKeys(resultingOptions.privateKey.public.bytes, resultingOptions.privateKey.bytes)).equals(resultingOptions.peerId)) {
throw new CodeError('Private key doesn\'t match peer id', codes.ERR_INVALID_KEY)
}

Expand Down
26 changes: 13 additions & 13 deletions packages/libp2p/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import type { AddressManagerInit } from './address-manager/index.js'
import type { Components } from './components.js'
import type { ConnectionManagerInit } from './connection-manager/index.js'
import type { TransportManagerInit } from './transport-manager.js'
import type { Libp2p, ServiceMap, RecursivePartial, ComponentLogger, NodeInfo, ConnectionProtector, ConnectionEncrypter, ConnectionGater, ContentRouting, Metrics, PeerDiscovery, PeerId, PeerRouting, StreamMuxerFactory, Transport, PrivateKey } from '@libp2p/interface'
import type { Libp2p, ServiceMap, ComponentLogger, NodeInfo, ConnectionProtector, ConnectionEncrypter, ConnectionGater, ContentRouting, Metrics, PeerDiscovery, PeerId, PeerRouting, StreamMuxerFactory, Transport, PrivateKey } from '@libp2p/interface'
import type { PersistentPeerStoreInit } from '@libp2p/peer-store'
import type { DNS } from '@multiformats/dns'
import type { Datastore } from 'interface-datastore'

export type ServiceFactoryMap<T extends Record<string, unknown> = Record<string, unknown>> = {
[Property in keyof T]: (components: Components) => T[Property]
[Property in keyof T]: (components: Components & T) => T[Property]
}

/**
Expand All @@ -35,49 +35,49 @@ export interface Libp2pInit<T extends ServiceMap = { x: Record<string, unknown>
/**
* peerId instance (it will be created if not provided)
*/
peerId: PeerId
peerId?: PeerId

/**
* Private key associated with the peerId
*/
privateKey: PrivateKey
privateKey?: PrivateKey

/**
* Metadata about the node - implementation name, version number, etc
*/
nodeInfo: NodeInfo
nodeInfo?: NodeInfo

/**
* Addresses for transport listening and to advertise to the network
*/
addresses: AddressManagerInit
addresses?: AddressManagerInit

/**
* libp2p Connection Manager configuration
*/
connectionManager: ConnectionManagerInit
connectionManager?: ConnectionManagerInit

/**
* A connection gater can deny new connections based on user criteria
*/
connectionGater: ConnectionGater
connectionGater?: ConnectionGater

/**
* libp2p transport manager configuration
*/
transportManager: TransportManagerInit
transportManager?: TransportManagerInit

/**
* An optional datastore to persist peer information, DHT records, etc.
*
* An in-memory datastore will be used if one is not provided.
*/
datastore: Datastore
datastore?: Datastore

/**
* libp2p PeerStore configuration
*/
peerStore: PersistentPeerStoreInit
peerStore?: PersistentPeerStoreInit

/**
* An array that must include at least 1 compliant transport
Expand All @@ -102,7 +102,7 @@ export interface Libp2pInit<T extends ServiceMap = { x: Record<string, unknown>
/**
* Arbitrary libp2p modules
*/
services: ServiceFactoryMap<T>
services?: ServiceFactoryMap<T>

/**
* An optional logging implementation that can be used to write runtime logs.
Expand Down Expand Up @@ -135,7 +135,7 @@ export interface Libp2pInit<T extends ServiceMap = { x: Record<string, unknown>

export type { Libp2p }

export type Libp2pOptions<T extends ServiceMap = Record<string, unknown>> = RecursivePartial<Libp2pInit<T>> & { start?: boolean }
export type Libp2pOptions<T extends ServiceMap = Record<string, unknown>> = Libp2pInit<T> & { start?: boolean }

/**
* Returns a new instance of the Libp2p interface, generating a new PeerId
Expand Down
9 changes: 6 additions & 3 deletions packages/libp2p/src/libp2p.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ export class Libp2pNode<T extends ServiceMap = Record<string, unknown>> extends
public logger: ComponentLogger
public status: Libp2pStatus

public components: Components
public components: Components & T[keyof T]
private readonly log: Logger

constructor (init: Libp2pInit<T>) {
constructor (init: Libp2pInit<T> & Required<Pick<Libp2pInit<T>, 'peerId'>>) {
super()

this.status = 'stopped'
Expand All @@ -66,6 +66,7 @@ export class Libp2pNode<T extends ServiceMap = Record<string, unknown>> extends
this.log = this.logger.forComponent('libp2p')
// @ts-expect-error {} may not be of type T
this.services = {}
// @ts-expect-error defaultComponents is missing component types added later
const components = this.components = defaultComponents({
peerId: init.peerId,
privateKey: init.privateKey,
Expand Down Expand Up @@ -111,7 +112,7 @@ export class Libp2pNode<T extends ServiceMap = Record<string, unknown>> extends
this.components.upgrader = new DefaultUpgrader(this.components, {
connectionEncryption: (init.connectionEncryption ?? []).map((fn, index) => this.configureComponent(`connection-encryption-${index}`, fn(this.components))),
muxers: (init.streamMuxers ?? []).map((fn, index) => this.configureComponent(`stream-muxers-${index}`, fn(this.components))),
inboundUpgradeTimeout: init.connectionManager.inboundUpgradeTimeout
inboundUpgradeTimeout: init.connectionManager?.inboundUpgradeTimeout
})

// Setup the transport manager
Expand Down Expand Up @@ -159,6 +160,7 @@ export class Libp2pNode<T extends ServiceMap = Record<string, unknown>> extends
if (init.services != null) {
for (const name of Object.keys(init.services)) {
const createService = init.services[name]
// @ts-expect-error components type is not fully formed yet
const service: any = createService(this.components)

if (service == null) {
Expand Down Expand Up @@ -194,6 +196,7 @@ export class Libp2pNode<T extends ServiceMap = Record<string, unknown>> extends
this.log.error('component %s was null or undefined', name)
}

// @ts-expect-error cannot assign props
this.components[name] = component

return component
Expand Down
12 changes: 7 additions & 5 deletions packages/libp2p/test/connection-manager/direct.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { yamux } from '@chainsafe/libp2p-yamux'
import { type Connection, type ConnectionProtector, isConnection, type PeerId, type Stream } from '@libp2p/interface'
import { type Connection, type ConnectionProtector, isConnection, type PeerId, type Stream, type Libp2p } from '@libp2p/interface'
import { AbortError, ERR_TIMEOUT, TypedEventEmitter, start, stop } from '@libp2p/interface'
import { mockConnection, mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader } from '@libp2p/interface-compliance-tests/mocks'
import { defaultLogger } from '@libp2p/logger'
Expand All @@ -30,7 +30,8 @@ import { defaultComponents, type Components } from '../../src/components.js'
import { DialQueue } from '../../src/connection-manager/dial-queue.js'
import { DefaultConnectionManager } from '../../src/connection-manager/index.js'
import { codes as ErrorCodes } from '../../src/errors.js'
import { createLibp2pNode, type Libp2pNode } from '../../src/libp2p.js'
import { createLibp2p } from '../../src/index.js'
import { createLibp2pNode } from '../../src/libp2p.js'
import { DefaultPeerRouting } from '../../src/peer-routing.js'
import { DefaultTransportManager } from '../../src/transport-manager.js'
import { ECHO_PROTOCOL, echo } from '../fixtures/echo-service.js'
Expand Down Expand Up @@ -281,8 +282,8 @@ describe('dialing (direct, TCP)', () => {
describe('libp2p.dialer (direct, TCP)', () => {
let peerId: PeerId
let remotePeerId: PeerId
let libp2p: Libp2pNode
let remoteLibp2p: Libp2pNode
let libp2p: Libp2p
let remoteLibp2p: Libp2p
let remoteAddr: Multiaddr

beforeEach(async () => {
Expand All @@ -291,7 +292,7 @@ describe('libp2p.dialer (direct, TCP)', () => {
createEd25519PeerId()
])

remoteLibp2p = await createLibp2pNode({
remoteLibp2p = await createLibp2p({
peerId: remotePeerId,
addresses: {
listen: [listenAddr.toString()]
Expand Down Expand Up @@ -565,6 +566,7 @@ describe('libp2p.dialer (direct, TCP)', () => {

const dials = 10
const error = new Error('Boom')
// @ts-expect-error private field access
Sinon.stub(libp2p.components.transportManager, 'dial').callsFake(async () => Promise.reject(error))

await libp2p.peerStore.patch(remotePeerId, {
Expand Down
6 changes: 3 additions & 3 deletions packages/libp2p/test/connection-manager/index.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,8 +419,8 @@ describe('libp2p.connections', () => {
})

describe('connection gater', () => {
let libp2p: Libp2pNode
let remoteLibp2p: Libp2pNode
let libp2p: Libp2p
let remoteLibp2p: Libp2p

beforeEach(async () => {
remoteLibp2p = await createNode({
Expand Down Expand Up @@ -485,7 +485,7 @@ describe('libp2p.connections', () => {
await libp2p.peerStore.patch(remoteLibp2p.peerId, {
multiaddrs: remoteLibp2p.getMultiaddrs()
})
await libp2p.components.connectionManager.openConnection(remoteLibp2p.peerId)
await libp2p.dial(remoteLibp2p.peerId)

for (const multiaddr of remoteLibp2p.getMultiaddrs()) {
expect(denyDialMultiaddr.calledWith(multiaddr)).to.be.true()
Expand Down
4 changes: 2 additions & 2 deletions packages/libp2p/test/registrar/protocols.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { expect } from 'aegir/chai'
import pDefer from 'p-defer'
import { createLibp2pNode } from '../../src/libp2p.js'
import type { Components } from '../../src/components.js'
import type { Libp2pNode } from '../../src/libp2p.js'
import type { Libp2p } from '@libp2p/interface'

describe('registrar protocols', () => {
let libp2p: Libp2pNode
let libp2p: Libp2p

it('should be able to register and unregister a handler', async () => {
const deferred = pDefer<Components>()
Expand Down
2 changes: 1 addition & 1 deletion packages/peer-store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface PersistentPeerStoreComponents {
* Return true to allow storing the passed multiaddr for the passed peer
*/
export interface AddressFilter {
(peerId: PeerId, multiaddr: Multiaddr): Promise<boolean>
(peerId: PeerId, multiaddr: Multiaddr): Promise<boolean> | boolean
}

export interface PersistentPeerStoreInit {
Expand Down
Loading

0 comments on commit 92bfe2f

Please sign in to comment.