Skip to content

Commit e9099d4

Browse files
maschadSgtPooki
andauthored
feat: configuration validation (#1778)
Co-authored-by: Russell Dempsey <[email protected]>
1 parent 980857c commit e9099d4

File tree

28 files changed

+338
-162
lines changed

28 files changed

+338
-162
lines changed

packages/libp2p/.aegir.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ export default {
2424
const peerId = await createEd25519PeerId()
2525
const libp2p = await createLibp2p({
2626
connectionManager: {
27-
inboundConnectionThreshold: Infinity,
27+
inboundConnectionThreshold: 1000,
28+
maxIncomingPendingConnections: 1000,
29+
maxConnections: 1000,
2830
minConnections: 0
2931
},
3032
addresses: {
@@ -51,7 +53,7 @@ export default {
5153
fetch: fetchService(),
5254
relay: circuitRelayServer({
5355
reservations: {
54-
maxReservations: Infinity
56+
maxReservations: 100000
5557
}
5658
})
5759
}

packages/libp2p/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@
164164
"uint8arraylist": "^2.4.3",
165165
"uint8arrays": "^4.0.6",
166166
"wherearewe": "^2.0.1",
167-
"xsalsa20": "^1.1.0"
167+
"xsalsa20": "^1.1.0",
168+
"yup": "^1.2.0"
168169
},
169170
"devDependencies": {
170171
"@chainsafe/libp2p-gossipsub": "^10.0.0",

packages/libp2p/src/address-manager/utils.ts

+14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import { type ObjectSchema, object, array, string, mixed } from 'yup'
2+
import { validateMultiaddr } from '../config/helpers.js'
3+
import type { AddressManagerInit } from '.'
4+
import type { Multiaddr } from '@multiformats/multiaddr'
5+
16
export function debounce (func: () => void, wait: number): () => void {
27
let timeout: ReturnType<typeof setTimeout> | undefined
38

@@ -11,3 +16,12 @@ export function debounce (func: () => void, wait: number): () => void {
1116
timeout = setTimeout(later, wait)
1217
}
1318
}
19+
20+
export function validateAddressManagerConfig (opts: AddressManagerInit): ObjectSchema<Record<string, unknown>> {
21+
return object({
22+
listen: array().of(string()).test('is multiaddr', validateMultiaddr).default([]),
23+
announce: array().of(string()).test('is multiaddr', validateMultiaddr).default([]),
24+
noAnnounce: array().of(string()).test('is multiaddr', validateMultiaddr).default([]),
25+
announceFilter: mixed().default(() => (addrs: Multiaddr[]): Multiaddr[] => addrs)
26+
})
27+
}

packages/libp2p/src/autonat/index.ts

+16-6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import map from 'it-map'
3131
import parallel from 'it-parallel'
3232
import { pipe } from 'it-pipe'
3333
import isPrivateIp from 'private-ip'
34+
import { number, object, string } from 'yup'
3435
import { codes } from '../errors.js'
3536
import {
3637
MAX_INBOUND_STREAMS,
@@ -108,14 +109,23 @@ class DefaultAutoNATService implements Startable {
108109
private started: boolean
109110

110111
constructor (components: AutoNATComponents, init: AutoNATServiceInit) {
112+
const validatedConfig = object({
113+
protocolPrefix: string().default(PROTOCOL_PREFIX),
114+
timeout: number().integer().default(TIMEOUT),
115+
startupDelay: number().integer().default(STARTUP_DELAY),
116+
refreshInterval: number().integer().default(REFRESH_INTERVAL),
117+
maxInboundStreams: number().integer().default(MAX_INBOUND_STREAMS),
118+
maxOutboundStreams: number().integer().default(MAX_OUTBOUND_STREAMS)
119+
}).validateSync(init)
120+
111121
this.components = components
112122
this.started = false
113-
this.protocol = `/${init.protocolPrefix ?? PROTOCOL_PREFIX}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`
114-
this.timeout = init.timeout ?? TIMEOUT
115-
this.maxInboundStreams = init.maxInboundStreams ?? MAX_INBOUND_STREAMS
116-
this.maxOutboundStreams = init.maxOutboundStreams ?? MAX_OUTBOUND_STREAMS
117-
this.startupDelay = init.startupDelay ?? STARTUP_DELAY
118-
this.refreshInterval = init.refreshInterval ?? REFRESH_INTERVAL
123+
this.protocol = `/${validatedConfig.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`
124+
this.timeout = validatedConfig.timeout
125+
this.maxInboundStreams = validatedConfig.maxInboundStreams
126+
this.maxOutboundStreams = validatedConfig.maxOutboundStreams
127+
this.startupDelay = validatedConfig.startupDelay
128+
this.refreshInterval = validatedConfig.refreshInterval
119129
this._verifyExternalAddresses = this._verifyExternalAddresses.bind(this)
120130
}
121131

packages/libp2p/src/circuit-relay/constants.ts

+5
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,8 @@ export const DEFAULT_HOP_TIMEOUT = 30 * second
7070
* How long to wait before starting to advertise the relay service
7171
*/
7272
export const DEFAULT_ADVERT_BOOT_DELAY = 30 * second
73+
74+
/**
75+
* The default timeout for Incoming STOP requests from the relay
76+
*/
77+
export const DEFAULT_STOP_TIMEOUT = 30 * second

packages/libp2p/src/circuit-relay/server/index.ts

+24-8
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ import { RecordEnvelope } from '@libp2p/peer-record'
66
import { type Multiaddr, multiaddr } from '@multiformats/multiaddr'
77
import { pbStream, type ProtobufStream } from 'it-protobuf-stream'
88
import pDefer from 'p-defer'
9+
import { object, number, boolean } from 'yup'
910
import { MAX_CONNECTIONS } from '../../connection-manager/constants.js'
11+
import { DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_OUTBOUND_STREAMS } from '../../registrar.js'
1012
import {
1113
CIRCUIT_PROTO_CODE,
14+
DEFAULT_DURATION_LIMIT,
1215
DEFAULT_HOP_TIMEOUT,
16+
DEFAULT_MAX_RESERVATION_CLEAR_INTERVAL,
17+
DEFAULT_MAX_RESERVATION_STORE_SIZE,
18+
DEFAULT_MAX_RESERVATION_TTL,
1319
RELAY_SOURCE_TAG,
1420
RELAY_V2_HOP_CODEC,
1521
RELAY_V2_STOP_CODEC
@@ -95,10 +101,6 @@ export interface RelayServerEvents {
95101
'relay:advert:error': CustomEvent<Error>
96102
}
97103

98-
const defaults = {
99-
maxOutboundStopStreams: MAX_CONNECTIONS
100-
}
101-
102104
class CircuitRelayServer extends EventEmitter<RelayServerEvents> implements Startable, CircuitRelayService {
103105
private readonly registrar: Registrar
104106
private readonly peerStore: PeerStore
@@ -121,18 +123,32 @@ class CircuitRelayServer extends EventEmitter<RelayServerEvents> implements Star
121123
constructor (components: CircuitRelayServerComponents, init: CircuitRelayServerInit = {}) {
122124
super()
123125

126+
const validatedConfig = object({
127+
hopTimeout: number().min(0).integer().default(DEFAULT_HOP_TIMEOUT),
128+
reservations: object({
129+
maxReservations: number().integer().min(0).default(DEFAULT_MAX_RESERVATION_STORE_SIZE),
130+
reservationClearInterval: number().integer().min(0).default(DEFAULT_MAX_RESERVATION_CLEAR_INTERVAL),
131+
applyDefaultLimit: boolean().default(true),
132+
reservationTtl: number().integer().min(0).default(DEFAULT_MAX_RESERVATION_TTL),
133+
defaultDurationLimit: number().integer().min(0).default(DEFAULT_DURATION_LIMIT).max(init?.reservations?.reservationTtl ?? DEFAULT_MAX_RESERVATION_TTL, `default duration limit must be less than reservation TTL: ${init?.reservations?.reservationTtl}`)
134+
}),
135+
maxInboundHopStreams: number().integer().min(0).default(DEFAULT_MAX_INBOUND_STREAMS),
136+
maxOutboundHopStreams: number().integer().min(0).default(DEFAULT_MAX_OUTBOUND_STREAMS),
137+
maxOutboundStopStreams: number().integer().min(0).default(MAX_CONNECTIONS)
138+
}).validateSync(init)
139+
124140
this.registrar = components.registrar
125141
this.peerStore = components.peerStore
126142
this.addressManager = components.addressManager
127143
this.peerId = components.peerId
128144
this.connectionManager = components.connectionManager
129145
this.connectionGater = components.connectionGater
130146
this.started = false
131-
this.hopTimeout = init?.hopTimeout ?? DEFAULT_HOP_TIMEOUT
147+
this.hopTimeout = validatedConfig.hopTimeout
132148
this.shutdownController = new AbortController()
133-
this.maxInboundHopStreams = init.maxInboundHopStreams
134-
this.maxOutboundHopStreams = init.maxOutboundHopStreams
135-
this.maxOutboundStopStreams = init.maxOutboundStopStreams ?? defaults.maxOutboundStopStreams
149+
this.maxInboundHopStreams = validatedConfig.maxInboundHopStreams
150+
this.maxOutboundHopStreams = validatedConfig.maxOutboundHopStreams
151+
this.maxOutboundStopStreams = validatedConfig.maxOutboundStopStreams
136152

137153
try {
138154
// fails on node < 15.4

packages/libp2p/src/circuit-relay/server/reservation-store.ts

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { PeerMap } from '@libp2p/peer-collections'
2+
import { object, mixed, number, boolean } from 'yup'
23
import { DEFAULT_DATA_LIMIT, DEFAULT_DURATION_LIMIT, DEFAULT_MAX_RESERVATION_CLEAR_INTERVAL, DEFAULT_MAX_RESERVATION_STORE_SIZE, DEFAULT_MAX_RESERVATION_TTL } from '../constants.js'
34
import { type Limit, Status } from '../pb/index.js'
45
import type { RelayReservation } from '../index.js'
@@ -50,12 +51,21 @@ export class ReservationStore implements Startable {
5051
private readonly defaultDataLimit: bigint
5152

5253
constructor (options: ReservationStoreOptions = {}) {
53-
this.maxReservations = options.maxReservations ?? DEFAULT_MAX_RESERVATION_STORE_SIZE
54-
this.reservationClearInterval = options.reservationClearInterval ?? DEFAULT_MAX_RESERVATION_CLEAR_INTERVAL
55-
this.applyDefaultLimit = options.applyDefaultLimit !== false
56-
this.reservationTtl = options.reservationTtl ?? DEFAULT_MAX_RESERVATION_TTL
57-
this.defaultDurationLimit = options.defaultDurationLimit ?? DEFAULT_DURATION_LIMIT
58-
this.defaultDataLimit = options.defaultDataLimit ?? DEFAULT_DATA_LIMIT
54+
const validatedConfig = object({
55+
maxReservations: number().min(0).integer().default(DEFAULT_MAX_RESERVATION_STORE_SIZE),
56+
reservationClearInterval: number().integer().min(0).default(DEFAULT_MAX_RESERVATION_CLEAR_INTERVAL),
57+
applyDefaultLimit: boolean().default(true),
58+
reservationTtl: number().integer().min(0).default(DEFAULT_MAX_RESERVATION_TTL),
59+
defaultDurationLimit: number().integer().min(0).default(DEFAULT_DURATION_LIMIT),
60+
defaultDataLimit: mixed().test('is-bigint', 'Invalid bigint', value => typeof value === 'bigint').default(DEFAULT_DATA_LIMIT)
61+
}).validateSync(options)
62+
63+
this.maxReservations = validatedConfig.maxReservations
64+
this.reservationClearInterval = validatedConfig.reservationClearInterval
65+
this.applyDefaultLimit = validatedConfig.applyDefaultLimit
66+
this.reservationTtl = validatedConfig.reservationTtl
67+
this.defaultDurationLimit = validatedConfig.defaultDurationLimit
68+
this.defaultDataLimit = validatedConfig.defaultDataLimit as bigint
5969
}
6070

6171
isStarted (): boolean {

packages/libp2p/src/circuit-relay/transport/index.ts

+16-14
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import { streamToMaConnection } from '@libp2p/utils/stream-to-ma-conn'
66
import * as mafmt from '@multiformats/mafmt'
77
import { multiaddr } from '@multiformats/multiaddr'
88
import { pbStream } from 'it-protobuf-stream'
9+
import { number, object } from 'yup'
910
import { MAX_CONNECTIONS } from '../../connection-manager/constants.js'
1011
import { codes } from '../../errors.js'
11-
import { CIRCUIT_PROTO_CODE, RELAY_V2_HOP_CODEC, RELAY_V2_STOP_CODEC } from '../constants.js'
12+
import { CIRCUIT_PROTO_CODE, DEFAULT_STOP_TIMEOUT, RELAY_V2_HOP_CODEC, RELAY_V2_STOP_CODEC } from '../constants.js'
1213
import { StopMessage, HopMessage, Status } from '../pb/index.js'
1314
import { RelayDiscovery, type RelayDiscoveryComponents } from './discovery.js'
1415
import { createListener } from './listener.js'
@@ -100,12 +101,6 @@ export interface CircuitRelayTransportInit extends RelayStoreInit {
100101
reservationCompletionTimeout?: number
101102
}
102103

103-
const defaults = {
104-
maxInboundStopStreams: MAX_CONNECTIONS,
105-
maxOutboundStopStreams: MAX_CONNECTIONS,
106-
stopTimeout: 30000
107-
}
108-
109104
class CircuitRelayTransport implements Transport {
110105
private readonly discovery?: RelayDiscovery
111106
private readonly registrar: Registrar
@@ -116,24 +111,31 @@ class CircuitRelayTransport implements Transport {
116111
private readonly addressManager: AddressManager
117112
private readonly connectionGater: ConnectionGater
118113
private readonly reservationStore: ReservationStore
119-
private readonly maxInboundStopStreams: number
114+
private readonly maxInboundStopStreams?: number
120115
private readonly maxOutboundStopStreams?: number
121-
private readonly stopTimeout: number
116+
private readonly stopTimeout?: number
122117
private started: boolean
123118

124119
constructor (components: CircuitRelayTransportComponents, init: CircuitRelayTransportInit) {
120+
const validatedConfig = object({
121+
discoverRelays: number().min(0).integer().default(0),
122+
maxInboundStopStreams: number().min(0).integer().default(MAX_CONNECTIONS),
123+
maxOutboundStopStreams: number().min(0).integer().default(MAX_CONNECTIONS),
124+
stopTimeout: number().min(0).integer().default(DEFAULT_STOP_TIMEOUT)
125+
}).validateSync(init)
126+
125127
this.registrar = components.registrar
126128
this.peerStore = components.peerStore
127129
this.connectionManager = components.connectionManager
128130
this.peerId = components.peerId
129131
this.upgrader = components.upgrader
130132
this.addressManager = components.addressManager
131133
this.connectionGater = components.connectionGater
132-
this.maxInboundStopStreams = init.maxInboundStopStreams ?? defaults.maxInboundStopStreams
133-
this.maxOutboundStopStreams = init.maxOutboundStopStreams ?? defaults.maxOutboundStopStreams
134-
this.stopTimeout = init.stopTimeout ?? defaults.stopTimeout
134+
this.maxInboundStopStreams = validatedConfig.maxInboundStopStreams
135+
this.maxOutboundStopStreams = validatedConfig.maxOutboundStopStreams
136+
this.stopTimeout = validatedConfig.stopTimeout
135137

136-
if (init.discoverRelays != null && init.discoverRelays > 0) {
138+
if (validatedConfig.discoverRelays > 0) {
137139
this.discovery = new RelayDiscovery(components)
138140
this.discovery.addEventListener('relay:discover', (evt) => {
139141
this.reservationStore.addRelay(evt.detail, 'discovered')
@@ -321,7 +323,7 @@ class CircuitRelayTransport implements Transport {
321323
* An incoming STOP request means a remote peer wants to dial us via a relay
322324
*/
323325
async onStop ({ connection, stream }: IncomingStreamData): Promise<void> {
324-
const signal = AbortSignal.timeout(this.stopTimeout)
326+
const signal = AbortSignal.timeout(this.stopTimeout ?? DEFAULT_STOP_TIMEOUT)
325327
const pbstr = pbStream(stream).pb(StopMessage)
326328
const request = await pbstr.read({
327329
signal

packages/libp2p/src/config.ts

-41
This file was deleted.

packages/libp2p/src/config/config.ts

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { FaultTolerance } from '@libp2p/interface/transport'
2+
import { publicAddressesFirst } from '@libp2p/utils/address-sort'
3+
import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers'
4+
import mergeOptions from 'merge-options'
5+
import { object } from 'yup'
6+
import { validateAddressManagerConfig } from '../address-manager/utils.js'
7+
import { validateConnectionManagerConfig } from '../connection-manager/utils.js'
8+
import type { AddressManagerInit } from '../address-manager'
9+
import type { ConnectionManagerInit } from '../connection-manager/index.js'
10+
import type { Libp2pInit } from '../index.js'
11+
import type { ServiceMap, RecursivePartial } from '@libp2p/interface'
12+
13+
const DefaultConfig: Partial<Libp2pInit> = {
14+
connectionManager: {
15+
resolvers: {
16+
dnsaddr: dnsaddrResolver
17+
},
18+
addressSorter: publicAddressesFirst
19+
},
20+
transportManager: {
21+
faultTolerance: FaultTolerance.FATAL_ALL
22+
}
23+
}
24+
25+
export function validateConfig <T extends ServiceMap = Record<string, unknown>> (opts: RecursivePartial<Libp2pInit<T>>): Libp2pInit<T> {
26+
const libp2pConfig = object({
27+
addresses: validateAddressManagerConfig(opts?.addresses as AddressManagerInit),
28+
connectionManager: validateConnectionManagerConfig(opts?.connectionManager as ConnectionManagerInit)
29+
})
30+
31+
if ((opts?.services) != null) {
32+
// @ts-expect-error until we resolve https://github.com/libp2p/js-libp2p/pull/1762 and have a better way of discovering type dependencies
33+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
34+
if ((opts.services?.kadDHT || opts.services?.relay || opts.services?.ping) && !opts.services.identify) {
35+
throw new Error('identify service is required when using kadDHT, relay, or ping')
36+
}
37+
}
38+
39+
const parsedOpts = libp2pConfig.validateSync(opts)
40+
41+
const resultingOptions: Libp2pInit<T> = mergeOptions(DefaultConfig, parsedOpts)
42+
43+
return resultingOptions
44+
}

packages/libp2p/src/config/helpers.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { multiaddr } from '@multiformats/multiaddr'
2+
3+
export const validateMultiaddr = (value: Array<string | undefined> | undefined): boolean => {
4+
value?.forEach((addr) => {
5+
try {
6+
multiaddr(addr)
7+
} catch (err) {
8+
throw new Error(`invalid multiaddr: ${addr}`)
9+
}
10+
})
11+
return true
12+
}

0 commit comments

Comments
 (0)