Skip to content

Commit dc01b32

Browse files
authored
fix!: remove merge-options (#3294)
`merge-options` is used to perform deep merges of option objects. Instead each component should receive an options object and validate it/populate default values - this way components only know about their own options and not the options of other components. BREAKING CHANGE: merge-options has been removed from `@libp2p/utils`
1 parent 597a884 commit dc01b32

File tree

13 files changed

+63
-566
lines changed

13 files changed

+63
-566
lines changed

packages/integration-tests/test/fetch.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-env mocha */
22

33
import { fetch } from '@libp2p/fetch'
4+
import { identify } from '@libp2p/identify'
45
import { expect } from 'aegir/chai'
56
import { createLibp2p } from 'libp2p'
67
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
@@ -12,7 +13,8 @@ import type { Libp2p } from '@libp2p/interface'
1213
async function createNode (): Promise<Libp2p<{ fetch: Fetch }>> {
1314
return createLibp2p(createBaseOptions({
1415
services: {
15-
fetch: fetch()
16+
fetch: fetch(),
17+
identify: identify()
1618
}
1719
}))
1820
}

packages/integration-tests/test/fixtures/base-options.browser.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { circuitRelayTransport } from '@libp2p/circuit-relay-v2'
22
import { identify } from '@libp2p/identify'
33
import { mplex } from '@libp2p/mplex'
44
import { plaintext } from '@libp2p/plaintext'
5-
import { mergeOptions } from '@libp2p/utils'
65
import { webRTC } from '@libp2p/webrtc'
76
import { webSockets } from '@libp2p/websockets'
87
import { yamux } from '@libp2p/yamux'
@@ -31,9 +30,11 @@ export function createBaseOptions <T extends ServiceMap = Record<string, unknown
3130
connectionGater: {
3231
denyDialMultiaddr: async () => false
3332
},
33+
// @ts-expect-error overrides could cause services to have wrong type
3434
services: {
3535
identify: identify()
36-
}
36+
},
37+
...overrides
3738
}
3839

3940
// WebWorkers cannot do WebRTC so only add support if we are not in a worker
@@ -43,5 +44,6 @@ export function createBaseOptions <T extends ServiceMap = Record<string, unknown
4344
options.transports?.push(webRTC())
4445
}
4546

46-
return mergeOptions(options, overrides)
47+
// @ts-expect-error overrides could cause services to have wrong type
48+
return options
4749
}

packages/integration-tests/test/fixtures/base-options.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@ import { identify } from '@libp2p/identify'
33
import { mplex } from '@libp2p/mplex'
44
import { plaintext } from '@libp2p/plaintext'
55
import { tcp } from '@libp2p/tcp'
6-
import { mergeOptions } from '@libp2p/utils'
76
import { webRTC } from '@libp2p/webrtc'
87
import { webSockets } from '@libp2p/websockets'
98
import { yamux } from '@libp2p/yamux'
109
import type { ServiceMap } from '@libp2p/interface'
1110
import type { Libp2pOptions } from 'libp2p'
1211

13-
export function createBaseOptions <T extends ServiceMap = Record<string, unknown>> (...overrides: Array<Libp2pOptions<T>>): Libp2pOptions<T> {
12+
export function createBaseOptions <T extends ServiceMap = Record<string, unknown>> (overrides: Libp2pOptions<T> = {}): Libp2pOptions<T> {
1413
const options: Libp2pOptions = {
1514
addresses: {
1615
listen: [
@@ -35,10 +34,13 @@ export function createBaseOptions <T extends ServiceMap = Record<string, unknown
3534
connectionEncrypters: [
3635
plaintext()
3736
],
37+
// @ts-expect-error overrides could cause services to have wrong type
3838
services: {
3939
identify: identify()
40-
}
40+
},
41+
...overrides
4142
}
4243

43-
return mergeOptions(options, ...overrides)
44+
// @ts-expect-error overrides could cause services to have wrong type
45+
return options
4446
}

packages/integration-tests/test/ping.spec.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-env mocha */
22

3+
import { identify } from '@libp2p/identify'
34
import { ping, PING_PROTOCOL } from '@libp2p/ping'
45
import { multiaddr } from '@multiformats/multiaddr'
56
import { expect } from 'aegir/chai'
@@ -15,17 +16,20 @@ describe('ping', () => {
1516
nodes = await Promise.all([
1617
createLibp2p(createBaseOptions({
1718
services: {
18-
ping: ping()
19+
ping: ping(),
20+
identify: identify()
1921
}
2022
})),
2123
createLibp2p(createBaseOptions({
2224
services: {
23-
ping: ping()
25+
ping: ping(),
26+
identify: identify()
2427
}
2528
})),
2629
createLibp2p(createBaseOptions({
2730
services: {
28-
ping: ping()
31+
ping: ping(),
32+
identify: identify()
2933
}
3034
}))
3135
])
@@ -80,7 +84,8 @@ describe('ping', () => {
8084
// Allow two outbound ping streams.
8185
// It is not allowed by the spec, but this test needs to open two concurrent streams.
8286
maxOutboundStreams: 2
83-
})
87+
}),
88+
identify: identify()
8489
}
8590
}))
8691
await client.peerStore.patch(remote.peerId, {

packages/keychain/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
"dependencies": {
5555
"@libp2p/crypto": "^5.1.8",
5656
"@libp2p/interface": "^2.11.0",
57-
"@libp2p/utils": "^6.7.2",
5857
"@noble/hashes": "^1.8.0",
5958
"asn1js": "^3.0.6",
6059
"interface-datastore": "^8.3.2",

packages/keychain/src/constants.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Default options for key derivation
3+
*
4+
* @see https://cryptosense.com/parametesr-choice-for-pbkdf2/
5+
*/
6+
export const DEK_INIT = {
7+
keyLength: 512 / 8,
8+
iterationCount: 10000,
9+
salt: 'you should override this value with a crypto secure random number',
10+
hash: 'sha2-512'
11+
}

packages/keychain/src/keychain.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
import { pbkdf2, randomBytes } from '@libp2p/crypto'
44
import { privateKeyToProtobuf } from '@libp2p/crypto/keys'
55
import { InvalidParametersError, NotFoundError, serviceCapabilities } from '@libp2p/interface'
6-
import { mergeOptions } from '@libp2p/utils'
76
import { Key } from 'interface-datastore/key'
87
import { base58btc } from 'multiformats/bases/base58'
98
import { sha256 } from 'multiformats/hashes/sha2'
109
import sanitize from 'sanitize-filename'
1110
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
1211
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
12+
import { DEK_INIT } from './constants.ts'
1313
import { exportPrivateKey } from './utils/export.js'
1414
import { importPrivateKey } from './utils/import.js'
1515
import type { KeychainComponents, KeychainInit, Keychain as KeychainInterface, KeyInfo } from './index.js'
@@ -26,16 +26,6 @@ const NIST = {
2626
minIterationCount: 1000
2727
}
2828

29-
const defaultOptions = {
30-
// See https://cryptosense.com/parametesr-choice-for-pbkdf2/
31-
dek: {
32-
keyLength: 512 / 8,
33-
iterationCount: 10000,
34-
salt: 'you should override this value with a crypto secure random number',
35-
hash: 'sha2-512'
36-
}
37-
}
38-
3929
function validateKeyName (name: string): boolean {
4030
if (name == null) {
4131
return false
@@ -101,7 +91,13 @@ export class Keychain implements KeychainInterface {
10191
constructor (components: KeychainComponents, init: KeychainInit) {
10292
this.components = components
10393
this.log = components.logger.forComponent('libp2p:keychain')
104-
this.init = mergeOptions(defaultOptions, init)
94+
this.init = {
95+
...init,
96+
dek: {
97+
...DEK_INIT,
98+
...init.dek
99+
}
100+
}
105101
this.self = init.selfKey ?? 'self'
106102

107103
// Enforce NIST SP 800-132
@@ -142,9 +138,13 @@ export class Keychain implements KeychainInterface {
142138
* @returns {object}
143139
*/
144140
static generateOptions (): KeychainInit {
145-
const options = Object.assign({}, defaultOptions)
141+
const options = Object.assign({}, this.options)
146142
const saltLength = Math.ceil(NIST.minSaltLength / 3) * 3 // no base64 padding
147-
options.dek.salt = uint8ArrayToString(randomBytes(saltLength), 'base64')
143+
144+
if (options.dek != null) {
145+
options.dek.salt = uint8ArrayToString(randomBytes(saltLength), 'base64')
146+
}
147+
148148
return options
149149
}
150150

@@ -154,8 +154,12 @@ export class Keychain implements KeychainInterface {
154154
*
155155
* @returns {object}
156156
*/
157-
static get options (): typeof defaultOptions {
158-
return defaultOptions
157+
static get options (): KeychainInit {
158+
return {
159+
dek: {
160+
...DEK_INIT
161+
}
162+
}
159163
}
160164

161165
async findKeyByName (name: string): Promise<KeyInfo> {

packages/libp2p/src/config.ts

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,11 @@
1-
import { FaultTolerance, InvalidParametersError } from '@libp2p/interface'
2-
import { mergeOptions } from '@libp2p/utils'
3-
import { dnsaddrResolver } from './connection-manager/resolvers/dnsaddr.ts'
1+
import { InvalidParametersError } from '@libp2p/interface'
42
import type { Libp2pInit } from './index.js'
53
import type { ServiceMap } from '@libp2p/interface'
6-
import type { Multiaddr } from '@multiformats/multiaddr'
7-
8-
const DefaultConfig: Libp2pInit = {
9-
addresses: {
10-
listen: [],
11-
announce: [],
12-
noAnnounce: [],
13-
announceFilter: (multiaddrs: Multiaddr[]) => multiaddrs
14-
},
15-
connectionManager: {
16-
resolvers: {
17-
dnsaddr: dnsaddrResolver
18-
}
19-
},
20-
transportManager: {
21-
faultTolerance: FaultTolerance.FATAL_ALL
22-
}
23-
}
244

255
export async function validateConfig <T extends ServiceMap = Record<string, unknown>> (opts: Libp2pInit<T>): Promise<Libp2pInit<T>> {
26-
const resultingOptions: Libp2pInit<T> = mergeOptions(DefaultConfig, opts)
27-
28-
if (resultingOptions.connectionProtector === null && globalThis.process?.env?.LIBP2P_FORCE_PNET != null) {
6+
if (opts.connectionProtector === null && globalThis.process?.env?.LIBP2P_FORCE_PNET != null) {
297
throw new InvalidParametersError('Private network is enforced, but no protector was provided')
308
}
319

32-
return resultingOptions
10+
return opts
3311
}

packages/libp2p/src/registrar.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { InvalidParametersError } from '@libp2p/interface'
2-
import { mergeOptions, trackedMap } from '@libp2p/utils'
2+
import { trackedMap } from '@libp2p/utils'
33
import { DuplicateProtocolHandlerError, UnhandledProtocolError } from './errors.js'
44
import type { IdentifyResult, Libp2pEvents, Logger, PeerUpdate, PeerId, PeerStore, Topology, StreamHandler, StreamHandlerRecord, StreamHandlerOptions, AbortOptions, Metrics, StreamMiddleware } from '@libp2p/interface'
55
import type { Registrar as RegistrarInterface } from '@libp2p/interface-internal'
@@ -95,14 +95,13 @@ export class Registrar implements RegistrarInterface {
9595
throw new DuplicateProtocolHandlerError(`Handler already registered for protocol ${protocol}`)
9696
}
9797

98-
const options = mergeOptions.bind({ ignoreUndefined: true })({
99-
maxInboundStreams: DEFAULT_MAX_INBOUND_STREAMS,
100-
maxOutboundStreams: DEFAULT_MAX_OUTBOUND_STREAMS
101-
}, opts)
102-
10398
this.handlers.set(protocol, {
10499
handler,
105-
options
100+
options: {
101+
maxInboundStreams: DEFAULT_MAX_INBOUND_STREAMS,
102+
maxOutboundStreams: DEFAULT_MAX_OUTBOUND_STREAMS,
103+
...opts
104+
}
106105
})
107106

108107
// Add new protocol to self protocols in the peer store

packages/utils/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
"cborg": "^4.2.14",
5656
"delay": "^6.0.0",
5757
"is-loopback-addr": "^2.0.2",
58-
"is-plain-obj": "^4.1.0",
5958
"it-length-prefixed": "^10.0.1",
6059
"it-pipe": "^3.0.1",
6160
"it-pushable": "^3.2.3",

0 commit comments

Comments
 (0)