From 836a5af58ae754ae6d78f82e13c84e4b67aea58f Mon Sep 17 00:00:00 2001 From: chad Date: Mon, 5 Dec 2022 15:13:16 -0500 Subject: [PATCH 01/16] doc: Updated docs to seperate max/min incoming and outgoing connections (#1508) --- doc/CONFIGURATION.md | 387 ++++++++++++++++++++----------------------- doc/LIMITS.md | 85 +++++----- 2 files changed, 226 insertions(+), 246 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index e16a21a3b0..5fbf1c639e 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -206,13 +206,14 @@ const modules = { peerRouting: [], peerDiscovery: [], dht: dhtImplementation, - pubsub: pubsubImplementation -} + pubsub: pubsubImplementation, +}; ``` Moreover, the majority of the modules can be customized via option parameters. This way, it is also possible to provide this options through a `config` object. This config object should have the property name of each building block to configure, the same way as the modules specification. Besides the `modules` and `config`, libp2p allows other internal options and configurations: + - `datastore`: an instance of [ipfs/interface-datastore](https://github.com/ipfs/js-ipfs-interfaces/tree/master/packages/interface-datastore) modules. - This is used in modules such as the DHT. If it is not provided, `js-libp2p` will use an in memory datastore. - `peerId`: the identity of the node, an instance of [libp2p/js-peer-id](https://github.com/libp2p/js-peer-id). @@ -235,26 +236,23 @@ Besides the `modules` and `config`, libp2p allows other internal options and con // dht: kad-dht // pubsub: gossipsub -import { createLibp2p } from 'libp2p' -import { tcp } from '@libp2p/tcp' -import { webSockets } from '@libp2p/websockets' -import { mplex } from '@libp2p/mplex' -import { noise } from '@chainsafe/libp2p-noise' -import { mdns } from '@libp2p/mdns' -import { kadDHT } from '@libp2p/kad-dht' -import { gossipsub } from 'libp2p-gossipsub' +import { createLibp2p } from "libp2p"; +import { tcp } from "@libp2p/tcp"; +import { webSockets } from "@libp2p/websockets"; +import { mplex } from "@libp2p/mplex"; +import { noise } from "@chainsafe/libp2p-noise"; +import { mdns } from "@libp2p/mdns"; +import { kadDHT } from "@libp2p/kad-dht"; +import { gossipsub } from "libp2p-gossipsub"; const node = await createLibp2p({ - transports: [ - tcp(), - webSockets() - ], + transports: [tcp(), webSockets()], streamMuxers: [mplex()], connectionEncryption: [noise()], peerDiscovery: [MulticastDNS], dht: kadDHT(), - pubsub: gossipsub() -}) + pubsub: gossipsub(), +}); ``` #### Customizing Peer Discovery @@ -295,29 +293,20 @@ const node = await createLibp2p({ #### Setup webrtc transport and discovery ```js -import { createLibp2p } from 'libp2p' -import { webSockets } from '@libp2p/websockets' -import { webRTCStar } from '@libp2p/webrtc-star' -import { mplex } from '@libp2p/mplex' -import { noise } from '@chainsafe/libp2p-noise' +import { createLibp2p } from "libp2p"; +import { webSockets } from "@libp2p/websockets"; +import { webRTCStar } from "@libp2p/webrtc-star"; +import { mplex } from "@libp2p/mplex"; +import { noise } from "@chainsafe/libp2p-noise"; -const webRtc = webRTCStar() +const webRtc = webRTCStar(); const node = await createLibp2p({ - transports: [ - webSockets(), - webRtc.transport - ], - peerDiscovery: [ - webRtc.discovery - ], - streamMuxers: [ - mplex() - ], - connectionEncryption: [ - noise() - ] -}) + transports: [webSockets(), webRtc.transport], + peerDiscovery: [webRtc.discovery], + streamMuxers: [mplex()], + connectionEncryption: [noise()], +}); ``` #### Customizing Pubsub @@ -351,101 +340,99 @@ const node = await createLibp2p({ #### Customizing DHT ```js -import { createLibp2p } from 'libp2p' -import { tcp } from '@libp2p/tcp' -import { mplex } from '@libp2p/mplex' -import { noise } from '@chainsafe/libp2p-noise' -import { kadDHT } from '@libp2p/kad-dht' +import { createLibp2p } from "libp2p"; +import { tcp } from "@libp2p/tcp"; +import { mplex } from "@libp2p/mplex"; +import { noise } from "@chainsafe/libp2p-noise"; +import { kadDHT } from "@libp2p/kad-dht"; const node = await createLibp2p({ - transports: [ - tcp() - ], - streamMuxers: [ - mplex() - ], - connectionEncryption: [ - noise() - ], + transports: [tcp()], + streamMuxers: [mplex()], + connectionEncryption: [noise()], dht: kadDHT({ kBucketSize: 20, - clientMode: false // Whether to run the WAN DHT in client or server mode (default: client mode) - }) -}) + clientMode: false, // Whether to run the WAN DHT in client or server mode (default: client mode) + }), +}); ``` #### Setup with Content and Peer Routing ```js -import { createLibp2p } from 'libp2p' -import { tcp } from '@libp2p/tcp' -import { mplex } from '@libp2p/mplex' -import { noise } from '@chainsafe/libp2p-noise' -import { create as ipfsHttpClient } from 'ipfs-http-client' -import { DelegatedPeerRouting } from '@libp2p/delegated-peer-routing' -import { DelegatedContentRouting} from '@libp2p/delegated-content-routing' +import { createLibp2p } from "libp2p"; +import { tcp } from "@libp2p/tcp"; +import { mplex } from "@libp2p/mplex"; +import { noise } from "@chainsafe/libp2p-noise"; +import { create as ipfsHttpClient } from "ipfs-http-client"; +import { DelegatedPeerRouting } from "@libp2p/delegated-peer-routing"; +import { DelegatedContentRouting } from "@libp2p/delegated-content-routing"; // create a peerId -const peerId = await PeerId.create() +const peerId = await PeerId.create(); -const delegatedPeerRouting = new DelegatedPeerRouting(ipfsHttpClient.create({ - host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates - protocol: 'https', - port: 443 -})) +const delegatedPeerRouting = new DelegatedPeerRouting( + ipfsHttpClient.create({ + host: "node0.delegate.ipfs.io", // In production you should setup your own delegates + protocol: "https", + port: 443, + }) +); -const delegatedContentRouting = new DelegatedContentRouting(peerId, ipfsHttpClient.create({ - host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates - protocol: 'https', - port: 443 -})) +const delegatedContentRouting = new DelegatedContentRouting( + peerId, + ipfsHttpClient.create({ + host: "node0.delegate.ipfs.io", // In production you should setup your own delegates + protocol: "https", + port: 443, + }) +); const node = await createLibp2p({ transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], - contentRouting: [ - delegatedContentRouting - ], - peerRouting: [ - delegatedPeerRouting - ], + contentRouting: [delegatedContentRouting], + peerRouting: [delegatedPeerRouting], peerId, - peerRouting: { // Peer routing configuration - refreshManager: { // Refresh known and connected closest peers + peerRouting: { + // Peer routing configuration + refreshManager: { + // Refresh known and connected closest peers enabled: true, // Should find the closest peers. interval: 6e5, // Interval for getting the new for closest peers of 10min - bootDelay: 10e3 // Delay for the initial query for closest peers - } - } -}) + bootDelay: 10e3, // Delay for the initial query for closest peers + }, + }, +}); ``` #### Setup with Relay ```js -import { createLibp2p } from 'libp2p' -import { tcp } from '@libp2p/tcp' -import { mplex } from '@libp2p/mplex' -import { noise } from '@chainsafe/libp2p-noise' +import { createLibp2p } from "libp2p"; +import { tcp } from "@libp2p/tcp"; +import { mplex } from "@libp2p/mplex"; +import { noise } from "@chainsafe/libp2p-noise"; const node = await createLibp2p({ transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], - relay: { // Circuit Relay options (this config is part of libp2p core configurations) - enabled: true, // Allows you to dial and accept relayed connections. Does not make you a relay. + relay: { + // Circuit Relay options (this config is part of libp2p core configurations) + enabled: true, // Allows you to dial and accept relayed connections. Does not make you a relay. hop: { - enabled: true, // Allows you to be a relay for other peers - active: true // You will attempt to dial destination peers if you are not connected to them + enabled: true, // Allows you to be a relay for other peers + active: true, // You will attempt to dial destination peers if you are not connected to them }, advertise: { bootDelay: 15 * 60 * 1000, // Delay before HOP relay service is advertised on the network - enabled: true, // Allows you to disable the advertise of the Hop service - ttl: 30 * 60 * 1000 // Delay Between HOP relay service advertisements on the network - } - } -}) + enabled: true, // Allows you to disable the advertise of the Hop service + ttl: 30 * 60 * 1000, // Delay Between HOP relay service advertisements on the network + }, + }, +}); ``` #### Setup with Auto Relay @@ -474,45 +461,45 @@ const node = await createLibp2p({ Libp2p allows you to setup a secure keychain to manage your keys. The keychain configuration object should have the following properties: -| Name | Type | Description | -|------|------|-------------| -| pass | `string` | Passphrase to use in the keychain (minimum of 20 characters). | +| Name | Type | Description | +| --------- | -------- | -------------------------------------------------------------------------------------- | +| pass | `string` | Passphrase to use in the keychain (minimum of 20 characters). | | datastore | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) | ```js -import { createLibp2p } from 'libp2p' -import { tcp } from '@libp2p/tcp' -import { mplex } from '@libp2p/mplex' -import { noise } from '@chainsafe/libp2p-noise' -import { LevelDatastore } from 'datastore-level' +import { createLibp2p } from "libp2p"; +import { tcp } from "@libp2p/tcp"; +import { mplex } from "@libp2p/mplex"; +import { noise } from "@chainsafe/libp2p-noise"; +import { LevelDatastore } from "datastore-level"; -const datastore = new LevelDatastore('path/to/store') -await datastore.open() +const datastore = new LevelDatastore("path/to/store"); +await datastore.open(); const node = await createLibp2p({ transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], keychain: { - pass: 'notsafepassword123456789', + pass: "notsafepassword123456789", datastore: dsInstant, - } -}) + }, +}); ``` #### Configuring Dialing Dialing in libp2p can be configured to limit the rate of dialing, and how long dials are allowed to take. The dialer configuration object should have the following properties: -| Name | Type | Description | -|------|------|-------------| -| maxParallelDials | `number` | How many multiaddrs we can dial in parallel. | -| maxAddrsToDial | `number` | How many multiaddrs is the dial allowed to dial for a single peer. | -| maxDialsPerPeer | `number` | How many multiaddrs we can dial per peer, in parallel. | -| dialTimeout | `number` | Second dial timeout per peer in ms. | -| resolvers | `object` | Dial [Resolvers](https://github.com/multiformats/js-multiaddr/blob/master/src/resolvers/index.js) for resolving multiaddrs | -| addressSorter | `(Array
) => Array
` | Sort the known addresses of a peer before trying to dial. | -| startupReconnectTimeout | `number` | When a node is restarted, we try to connect to any peers marked with the `keep-alive` tag up until to this timeout in ms is reached (default: 60000) | +| Name | Type | Description | +| ----------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| maxParallelDials | `number` | How many multiaddrs we can dial in parallel. | +| maxAddrsToDial | `number` | How many multiaddrs is the dial allowed to dial for a single peer. | +| maxDialsPerPeer | `number` | How many multiaddrs we can dial per peer, in parallel. | +| dialTimeout | `number` | Second dial timeout per peer in ms. | +| resolvers | `object` | Dial [Resolvers](https://github.com/multiformats/js-multiaddr/blob/master/src/resolvers/index.js) for resolving multiaddrs | +| addressSorter | `(Array
) => Array
` | Sort the known addresses of a peer before trying to dial. | +| startupReconnectTimeout | `number` | When a node is restarted, we try to connect to any peers marked with the `keep-alive` tag up until to this timeout in ms is reached (default: 60000) | The below configuration example shows how the dialer should be configured, with the current defaults: @@ -546,27 +533,28 @@ const node = await createLibp2p({ The Connection Manager prunes Connections in libp2p whenever certain limits are exceeded. If Metrics are enabled, you can also configure the Connection Manager to monitor the bandwidth of libp2p and prune connections as needed. You can read more about what Connection Manager does at [./CONNECTION_MANAGER.md](./CONNECTION_MANAGER.md). The configuration values below show the defaults for Connection Manager. See [./CONNECTION_MANAGER.md](./CONNECTION_MANAGER.md#options) for a full description of the parameters. ```js -import { createLibp2p } from 'libp2p' -import { tcp } from '@libp2p/tcp' -import { mplex } from '@libp2p/mplex' -import { noise } from '@chainsafe/libp2p-noise' +import { createLibp2p } from "libp2p"; +import { tcp } from "@libp2p/tcp"; +import { mplex } from "@libp2p/mplex"; +import { noise } from "@chainsafe/libp2p-noise"; const node = await createLibp2p({ transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], connectionManager: { - maxConnections: Infinity, + maxIncomingConnections: Infinity, minConnections: 0, + maxOutgoingConnections: Infinity, pollInterval: 2000, // The below values will only be taken into account when Metrics are enabled maxData: Infinity, maxSentData: Infinity, maxReceivedData: Infinity, maxEventLoopDelay: Infinity, - movingAverageInterval: 60000 - } -}) + movingAverageInterval: 60000, + }, +}); ``` #### Configuring Connection Gater @@ -695,33 +683,33 @@ const node = await createLibp2p({ The Transport Manager is responsible for managing the libp2p transports life cycle. This includes starting listeners for the provided listen addresses, closing these listeners and dialing using the provided transports. By default, if a libp2p node has a list of multiaddrs for listening on and there are no valid transports for those multiaddrs, libp2p will throw an error on startup and shutdown. However, for some applications it is perfectly acceptable for libp2p nodes to start in dial only mode if all the listen multiaddrs failed. This error tolerance can be enabled as follows: ```js -import { createLibp2p } from 'libp2p' -import { tcp } from '@libp2p/tcp' -import { mplex } from '@libp2p/mplex' -import { noise } from '@chainsafe/libp2p-noise' -import { FaultTolerance } from '@libp2p/interface-transport' +import { createLibp2p } from "libp2p"; +import { tcp } from "@libp2p/tcp"; +import { mplex } from "@libp2p/mplex"; +import { noise } from "@chainsafe/libp2p-noise"; +import { FaultTolerance } from "@libp2p/interface-transport"; const node = await createLibp2p({ transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], transportManager: { - faultTolerance: FaultTolerance.NO_FATAL - } -}) + faultTolerance: FaultTolerance.NO_FATAL, + }, +}); ``` #### Configuring Metrics Metrics are disabled in libp2p by default. You can enable and configure them as follows: -| Name | Type | Description | -|------|------|-------------| -| enabled | `boolean` | Enabled metrics collection. | -| computeThrottleMaxQueueSize | `number` | How many messages a stat will queue before processing. | -| computeThrottleTimeout | `number` | Time in milliseconds a stat will wait, after the last item was added, before processing. | -| movingAverageIntervals | `Array` | The moving averages that will be computed. | -| maxOldPeersRetention | `number` | How many disconnected peers we will retain stats for. | +| Name | Type | Description | +| --------------------------- | --------------- | ---------------------------------------------------------------------------------------- | +| enabled | `boolean` | Enabled metrics collection. | +| computeThrottleMaxQueueSize | `number` | How many messages a stat will queue before processing. | +| computeThrottleTimeout | `number` | Time in milliseconds a stat will wait, after the last item was added, before processing. | +| movingAverageIntervals | `Array` | The moving averages that will be computed. | +| maxOldPeersRetention | `number` | How many disconnected peers we will retain stats for. | The below configuration example shows how the metrics should be configured. Aside from enabled being `false` by default, the following default configuration options are listed below: @@ -755,22 +743,22 @@ PeerStore persistence is disabled in libp2p by default. You can enable and confi The threshold number represents the maximum number of "dirty peers" allowed in the PeerStore, i.e. peers that are not updated in the datastore. In this context, browser nodes should use a threshold of 1, since they might not "stop" properly in several scenarios and the PeerStore might end up with unflushed records when the window is closed. -| Name | Type | Description | -|------|------|-------------| -| persistence | `boolean` | Is persistence enabled. | -| threshold | `number` | Number of dirty peers allowed. | +| Name | Type | Description | +| ----------- | --------- | ------------------------------ | +| persistence | `boolean` | Is persistence enabled. | +| threshold | `number` | Number of dirty peers allowed. | The below configuration example shows how the PeerStore should be configured. Aside from persistence being `false` by default, the following default configuration options are listed below: ```js -import { createLibp2p } from 'libp2p' -import { tcp } from '@libp2p/tcp' -import { mplex } from '@libp2p/mplex' -import { noise } from '@chainsafe/libp2p-noise' -import { LevelDatastore } from 'datastore-level' +import { createLibp2p } from "libp2p"; +import { tcp } from "@libp2p/tcp"; +import { mplex } from "@libp2p/mplex"; +import { noise } from "@chainsafe/libp2p-noise"; +import { LevelDatastore } from "datastore-level"; -const datastore = new LevelDatastore('path/to/store') -await datastore.open() // level database must be ready before node boot +const datastore = new LevelDatastore("path/to/store"); +await datastore.open(); // level database must be ready before node boot const node = await createLibp2p({ datastore, // pass the opened datastore @@ -779,9 +767,9 @@ const node = await createLibp2p({ connectionEncryption: [noise()], peerStore: { persistence: true, - threshold: 5 - } -}) + threshold: 5, + }, +}); ``` #### Customizing Transports @@ -789,62 +777,47 @@ const node = await createLibp2p({ Some Transports can be passed additional options when they are created. For example, `libp2p-webrtc-star` accepts an optional, custom `wrtc` implementation. In addition to libp2p passing itself and an `Upgrader` to handle connection upgrading, libp2p will also pass the options, if they are provided, from `config.transport`. ```js -import { createLibp2p } from 'libp2p' -import { webRTCStar } from '@libp2p/webrtc-star' -import { mplex } from '@libp2p/mplex' -import { noise } from '@chainsafe/libp2p-noise' -import wrtc from 'wrtc' +import { createLibp2p } from "libp2p"; +import { webRTCStar } from "@libp2p/webrtc-star"; +import { mplex } from "@libp2p/mplex"; +import { noise } from "@chainsafe/libp2p-noise"; +import wrtc from "wrtc"; const webRTC = webRTCStar({ - wrtc -}) + wrtc, +}); const node = await createLibp2p({ - transports: [ - webRTC.transport - ], - peerDiscovery: [ - webRTC.discovery - ], - streamMuxers: [ - mplex() - ], - connectionEncryption: [ - noise() - ] -}) + transports: [webRTC.transport], + peerDiscovery: [webRTC.discovery], + streamMuxers: [mplex()], + connectionEncryption: [noise()], +}); ``` -During Libp2p startup, transport listeners will be created for the configured listen multiaddrs. Some transports support custom listener options and you can set them using the `listenerOptions` in the transport configuration. For example, [libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) transport listener supports the configuration of its underlying [simple-peer](https://github.com/feross/simple-peer) ice server(STUN/TURN) config as follows: +During Libp2p startup, transport listeners will be created for the configured listen multiaddrs. Some transports support custom listener options and you can set them using the `listenerOptions` in the transport configuration. For example, [libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) transport listener supports the configuration of its underlying [simple-peer](https://github.com/feross/simple-peer) ice server(STUN/TURN) config as follows: ```js const webRTC = webRTCStar({ listenerOptions: { config: { iceServers: [ - {"urls": ["turn:YOUR.TURN.SERVER:3478"], "username": "YOUR.USER", "credential": "YOUR.PASSWORD"}, - {"urls": ["stun:YOUR.STUN.SERVER:3478"], "username": "", "credential": ""}] - } - } -}) + { urls: ["turn:YOUR.TURN.SERVER:3478"], username: "YOUR.USER", credential: "YOUR.PASSWORD" }, + { urls: ["stun:YOUR.STUN.SERVER:3478"], username: "", credential: "" }, + ], + }, + }, +}); const node = await createLibp2p({ - transports: [ - webRTC.transport - ], - peerDiscovery: [ - webRTC.discovery - ], - streamMuxers: [ - mplex() - ], - connectionEncryption: [ - noise() - ], + transports: [webRTC.transport], + peerDiscovery: [webRTC.discovery], + streamMuxers: [mplex()], + connectionEncryption: [noise()], addresses: { - listen: ['/dns4/your-wrtc-star.pub/tcp/443/wss/p2p-webrtc-star'] // your webrtc dns multiaddr - } -}) + listen: ["/dns4/your-wrtc-star.pub/tcp/443/wss/p2p-webrtc-star"], // your webrtc dns multiaddr + }, +}); ``` #### Configuring the NAT Manager @@ -858,15 +831,15 @@ const node = await createLibp2p({ config: { nat: { enabled: true, // defaults to true - description: 'my-node', // set as the port mapping description on the router, defaults the current libp2p version and your peer id - gateway: '192.168.1.1', // leave unset to auto-discover - externalIp: '80.1.1.1', // leave unset to auto-discover - localAddress: '129.168.1.123', // leave unset to auto-discover + description: "my-node", // set as the port mapping description on the router, defaults the current libp2p version and your peer id + gateway: "192.168.1.1", // leave unset to auto-discover + externalIp: "80.1.1.1", // leave unset to auto-discover + localAddress: "129.168.1.123", // leave unset to auto-discover ttl: 7200, // TTL for port mappings (min 20 minutes) keepAlive: true, // Refresh port mapping after TTL expires - } - } -}) + }, + }, +}); ``` ##### Browser support @@ -886,12 +859,12 @@ Changing the protocol name prefix can isolate default public network (IPFS) for ```js const node = await createLibp2p({ identify: { - protocolPrefix: 'ipfs' // default + protocolPrefix: "ipfs", // default }, ping: { - protocolPrefix: 'ipfs' // default - } -}) + protocolPrefix: "ipfs", // default + }, +}); /* protocols: [ "/ipfs/id/1.0.0", // identify service protocol (if we have multiplexers) diff --git a/doc/LIMITS.md b/doc/LIMITS.md index 6173b413a7..3b8d8fa9a8 100644 --- a/doc/LIMITS.md +++ b/doc/LIMITS.md @@ -21,9 +21,7 @@ This is important for [DoS](https://en.wikipedia.org/wiki/Denial-of-service_atta ## Connection limits -It's possible to limit the total amount of connections a node is able to make (combining incoming and outgoing). When this limit is reached and an attempt to open a new connection is made, existing connections may be closed to make room for the new connection (see [Closing connections][#closing-connections]). - -* Note: there currently isn't a way to specify different limits for incoming vs. outgoing. Connection limits are applied across both incoming and outgoing connections combined. There is a backlog item for this [here](https://github.com/libp2p/js-libp2p/issues/1508). +It's possible to limit the amount of incoming and outgoing connections a node is able to make. When this limit is reached and an attempt to open a new connection is made, existing connections may be closed to make room for the new connection. We can also limit the number of connections in a "pending" state. These connections have been opened by a remote peer but peer IDs have yet to be exchanged and/or connection encryption and multiplexing negotiated. Once this limit is hit further connections will be closed unless the remote peer has an address in the [allow list](#allowdeny-lists). @@ -33,22 +31,27 @@ All fields are optional. The default values are defined in [src/connection-manag const node = await createLibp2pNode({ connectionManager: { /** - * The total number of connections allowed to be open at one time + * The total number of incoming connections allowed to be open at one time + */ + maxIncomingConnections: number, + + /** + * The total number of outgoing connections allowed to be open at one time */ - maxConnections: number + maxOutgoingConnections: number, /** * If the number of open connections goes below this number, the node * will try to connect to randomly selected peers from the peer store */ - minConnections: number + minConnections: number, /** * How many connections can be open but not yet upgraded */ - maxIncomingPendingConnections: number - } -}) + maxIncomingPendingConnections: number, + }, +}); ``` ## Closing connections @@ -57,10 +60,10 @@ When choosing connections to close the connection manager sorts the list of conn ```js // tag a peer -await libp2p.peerStore.tagPeer(peerId, 'my-tag', { +await libp2p.peerStore.tagPeer(peerId, "my-tag", { value: 50, // 0-100 is the typical value range - ttl: 1000 // optional field, this tag will be deleted after this many ms -}) + ttl: 1000, // optional field, this tag will be deleted after this many ms +}); ``` ## Inbound connection threshold @@ -69,17 +72,17 @@ To prevent individual peers from opening multiple connections to a node, an `inb All fields are optional. The default values are defined in [src/connection-manager/index.ts](https://github.com/libp2p/js-libp2p/blob/master/src/connection-manager/index.ts) - please see that file for the current values. -```ts +````ts const node = await createLibp2pNode({ connectionManager: { /** * A remote peer may attempt to open up to this many connections per second, * any more than that will be automatically rejected */ - inboundConnectionThreshold: number - } -}) -``` + inboundConnectionThreshold: number, + }, +}); +o``` ## Data transfer and Event Loop limits @@ -131,7 +134,7 @@ const node = await createLibp2pNode({ maxEventLoopDelay: number } }) -``` +```` ## Stream limits @@ -253,21 +256,21 @@ const node = await createLibp2pNode({ outboundSocketInactivityTimeout: number /** - * Once this many connections are open on this listener any further connections + * Once this many incoming connections are open on this listener any further connections * will be rejected - this will have no effect if it is larger than the value - * configured for the ConnectionManager maxConnections parameter + * configured for the ConnectionManager maxIncomingConnections parameter */ - maxConnections: number - }) - ] -}) + maxIncomingConnections: number, + }), + ], +}); ``` ## Allow/deny lists It is possible to configure some hosts to always accept connections from and some to always reject connections from. -```js +```ts const node = await createLibp2pNode({ connectionManager: { /** @@ -276,9 +279,9 @@ const node = await createLibp2pNode({ * all connection limits */ allow: [ - '/ip4/43.123.5.23/tcp/3984', - '/ip4/234.243.64.2', - '/ip4/52.55', + "/ip4/43.123.5.23/tcp/3984", + "/ip4/234.243.64.2", + "/ip4/52.55", // etc ], @@ -286,14 +289,14 @@ const node = await createLibp2pNode({ * Any connection with a `remoteAddress` property that has any of these * addresses as a prefix will be immediately rejected */ - deny: [ - '/ip4/132.14.52.64/tcp/3984', - '/ip4/234.243.64.2', - '/ip4/34.42', + deny: [ + "/ip4/132.14.52.64/tcp/3984", + "/ip4/234.243.64.2", + "/ip4/34.42", // etc - ] - } -}) + ], + }, +}); ``` ## How much memory will be used for buffering? @@ -302,10 +305,10 @@ There is no a single config value to control the amount of memory js-libp2p uses Important details for ascertaining this are: -* Each connection has a multiplexer -* Each multiplexer has a buffer for raw incoming data (`muxer.maxUnprocessedMessageQueueSize`) -* The incoming data is parsed into messages for each stream and queued (`muxer.maxStreamBufferSize`) -* Each multiplexer has a stream limit for number of streams (`muxer.maxInboundStreams`, `muxer.maxOutboundStreams`). +- Each connection has a multiplexer +- Each multiplexer has a buffer for raw incoming data (`muxer.maxUnprocessedMessageQueueSize`) +- The incoming data is parsed into messages for each stream and queued (`muxer.maxStreamBufferSize`) +- Each multiplexer has a stream limit for number of streams (`muxer.maxInboundStreams`, `muxer.maxOutboundStreams`). As a result, the max amount of memory buffered by libp2p is approximately: @@ -316,3 +319,7 @@ connectionManager.maxConnections * + (muxer.maxOutboundStreams * muxer.maxStreamBufferSize) ) ``` + +``` + +``` From b8bb3673718805d4d9deee5c0d8e4b1d8c94db16 Mon Sep 17 00:00:00 2001 From: chad Date: Mon, 5 Dec 2022 15:13:58 -0500 Subject: [PATCH 02/16] test: updated tests to check for max/min incoming and outgoing connections from connection manager (#1508) --- test/connection-manager/index.node.ts | 10 ++- test/connection-manager/index.spec.ts | 105 +++++++++++++++++++++++--- test/dialing/direct.node.ts | 3 +- test/dialing/direct.spec.ts | 3 +- test/fetch/index.spec.ts | 3 +- test/identify/index.spec.ts | 3 +- test/identify/push.spec.ts | 3 +- test/ping/index.spec.ts | 3 +- test/registrar/registrar.spec.ts | 3 +- 9 files changed, 114 insertions(+), 22 deletions(-) diff --git a/test/connection-manager/index.node.ts b/test/connection-manager/index.node.ts index 8de955eeb1..8c236431f9 100644 --- a/test/connection-manager/index.node.ts +++ b/test/connection-manager/index.node.ts @@ -57,8 +57,9 @@ describe('Connection Manager', () => { upgrader, peerStore }, { - maxConnections: 1000, + maxIncomingConnections: 1000, minConnections: 50, + maxOutgoingConnections: 1000, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 }) @@ -97,8 +98,9 @@ describe('Connection Manager', () => { upgrader, peerStore }, { - maxConnections: 1000, + maxIncomingConnections: 1000, minConnections: 50, + maxOutgoingConnections: 1000, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 }) @@ -233,7 +235,7 @@ describe('libp2p.connections', () => { }, connectionManager: { minConnections, - maxConnections: 1 + maxIncomingConnections: 1 } } }) @@ -265,7 +267,7 @@ describe('libp2p.connections', () => { }, connectionManager: { minConnections, - maxConnections: 1 + maxIncomingConnections: 1 } } }) diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index b5668b2148..b494b518e0 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -17,10 +17,12 @@ import type { Dialer } from '@libp2p/interface-connection-manager' import type { Connection } from '@libp2p/interface-connection' import type { Upgrader } from '@libp2p/interface-transport' import type { PeerStore } from '@libp2p/interface-peer-store' +import { codes as ErrorCodes } from '../../src/errors.js' const defaultOptions = { - maxConnections: 10, + maxIncomingConnections: 10, minConnections: 1, + maxOutgoingConnections: 10, autoDialInterval: Infinity, inboundUpgradeTimeout: 10000 } @@ -69,7 +71,7 @@ describe('Connection Manager', () => { libp2p = await createNode({ config: createBaseOptions({ connectionManager: { - maxConnections: max, + maxIncomingConnections: max, minConnections: 2 } }), @@ -119,7 +121,7 @@ describe('Connection Manager', () => { libp2p = await createNode({ config: createBaseOptions({ connectionManager: { - maxConnections: max, + maxIncomingConnections: max, minConnections: 2 } }), @@ -169,12 +171,77 @@ describe('Connection Manager', () => { expect(shortestLivedWithLowestTagSpy).to.have.property('callCount', 1) }) - it('should close connection when the maximum has been reached even without tags', async () => { + it('should not close connection that is on the allowlist when pruning', async () => { + const max = 5 + const remoteAddrPeerId = await createEd25519PeerId() + + libp2p = await createNode({ + config: createBaseOptions({ + connectionManager: { + maxIncomingConnections: max, + minConnections: 0, + allow: [ + '/ip4/83.13.55.32' + ] + } + }), + started: false + }) + + await libp2p.start() + + const connectionManager = libp2p.connectionManager as DefaultConnectionManager + const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_pruneConnections') + const spies = new Map>>() + + // Max out connections + for (let i = 1; i < max; i++) { + const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) + const spy = sinon.spy(connection, 'close') + const value = i * 10 + spies.set(value, spy) + await libp2p.peerStore.tagPeer(connection.remotePeer, 'test-tag', { + value + }) + await connectionManager._onConnect(new CustomEvent('connection', { detail: connection })) + } + + // Connect to the peer on the allowed list + const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), remoteAddrPeerId)) + + // Tag that allowed peer with lowest value + const value = 0 * 10 + await libp2p.peerStore.tagPeer(connection.remotePeer, 'test-tag', { + value + }) + + await connectionManager._onConnect(new CustomEvent('connection', { detail: connection })) + + // get the lowest value + const lowest = Array.from(spies.keys()).sort((a, b) => { + if (a > b) { + return 1 + } + + if (a < b) { + return -1 + } + + return 0 + })[0] + const lowestSpy = spies.get(lowest) + + expect(connectionManagerMaybeDisconnectOneSpy.callCount).to.equal(0) + // expect lowest value spy NOT to be called since the peer is in the allow list + expect(lowestSpy).to.have.property('callCount', 0) + }) + + it('should close connection when the maximum incoming has been reached even without tags', async () => { const max = 5 libp2p = await createNode({ config: createBaseOptions({ connectionManager: { - maxConnections: max, + maxIncomingConnections: max, minConnections: 0 } }), @@ -198,16 +265,28 @@ describe('Connection Manager', () => { expect(spy).to.have.property('callCount', 1) }) - it('should fail if the connection manager has mismatched connection limit options', async () => { + it('should fail if the connection manager has mismatched incoming connection limit options', async () => { await expect(createNode({ config: createBaseOptions({ connectionManager: { - maxConnections: 5, + maxIncomingConnections: 5, minConnections: 6 } }), started: false - })).to.eventually.rejected('maxConnections must be greater') + })).to.eventually.rejected('maxIncomingConnections must be greater') + }) + + it('should fail if the connection manager has mismatched outgoing connection limit options', async () => { + await expect(createNode({ + config: createBaseOptions({ + connectionManager: { + maxOutgoingConnections: 5, + minConnections: 6 + } + }), + started: false + })).to.eventually.rejected('maxOutgoingConnections must be greater') }) it('should reconnect to important peers on startup', async () => { @@ -263,7 +342,7 @@ describe('Connection Manager', () => { .to.eventually.be.false() }) - it('should deny connections when maxConnections is exceeded', async () => { + it('should deny connections when maxIncomingConnections is exceeded', async () => { const dialer = stubInterface() dialer.dial.resolves(stubInterface()) @@ -274,7 +353,7 @@ describe('Connection Manager', () => { dialer }, { ...defaultOptions, - maxConnections: 1 + maxIncomingConnections: 1 }) // max out the connection limit @@ -292,6 +371,10 @@ describe('Connection Manager', () => { .to.eventually.be.false() }) + it.skip('should deny connections when maxOutgoingConnections is exceeded', async () => { + + }) + it('should deny connections from peers that connect too frequently', async () => { const dialer = stubInterface() dialer.dial.resolves(stubInterface()) @@ -334,7 +417,7 @@ describe('Connection Manager', () => { dialer }, { ...defaultOptions, - maxConnections: 1, + maxIncomingConnections: 1, allow: [ '/ip4/83.13.55.32' ] diff --git a/test/dialing/direct.node.ts b/test/dialing/direct.node.ts index 86ff236db8..c51f292f25 100644 --- a/test/dialing/direct.node.ts +++ b/test/dialing/direct.node.ts @@ -79,7 +79,8 @@ describe('Dialing (direct, TCP)', () => { }) localComponents.peerStore = new PersistentPeerStore(localComponents) localComponents.connectionManager = new DefaultConnectionManager(localComponents, { - maxConnections: 100, + maxIncomingConnections: 100, + maxOutgoingConnections: 100, minConnections: 50, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 diff --git a/test/dialing/direct.spec.ts b/test/dialing/direct.spec.ts index 1084db1270..9e6bc78406 100644 --- a/test/dialing/direct.spec.ts +++ b/test/dialing/direct.spec.ts @@ -51,7 +51,8 @@ describe('Dialing (direct, WebSockets)', () => { addressFilter: localComponents.connectionGater.filterMultiaddrForPeer }) localComponents.connectionManager = new DefaultConnectionManager(localComponents, { - maxConnections: 100, + maxIncomingConnections: 100, + maxOutgoingConnections: 100, minConnections: 50, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 diff --git a/test/fetch/index.spec.ts b/test/fetch/index.spec.ts index 27a7e80db3..2d532a3088 100644 --- a/test/fetch/index.spec.ts +++ b/test/fetch/index.spec.ts @@ -35,7 +35,8 @@ async function createComponents (index: number) { components.peerStore = new PersistentPeerStore(components) components.connectionManager = new DefaultConnectionManager(components, { minConnections: 50, - maxConnections: 1000, + maxOutgoingConnections: 1000, + maxIncomingConnections: 1000, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 }) diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index 5a5f0b247e..1639d9dcd6 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -59,7 +59,8 @@ async function createComponents (index: number) { components.peerStore = new PersistentPeerStore(components) components.connectionManager = new DefaultConnectionManager(components, { minConnections: 50, - maxConnections: 1000, + maxIncomingConnections: 1000, + maxOutgoingConnections: 1000, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 }) diff --git a/test/identify/push.spec.ts b/test/identify/push.spec.ts index c6482aecce..b2dbddd29c 100644 --- a/test/identify/push.spec.ts +++ b/test/identify/push.spec.ts @@ -57,7 +57,8 @@ async function createComponents (index: number): Promise { components.peerStore = new PersistentPeerStore(components) components.connectionManager = new DefaultConnectionManager(components, { minConnections: 50, - maxConnections: 1000, + maxIncomingConnections: 1000, + maxOutgoingConnections: 1000, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 }) diff --git a/test/ping/index.spec.ts b/test/ping/index.spec.ts index 4fedd39806..9bc5a703a0 100644 --- a/test/ping/index.spec.ts +++ b/test/ping/index.spec.ts @@ -35,7 +35,8 @@ async function createComponents (index: number): Promise { components.peerStore = new PersistentPeerStore(components) components.connectionManager = new DefaultConnectionManager(components, { minConnections: 50, - maxConnections: 1000, + maxOutgoingConnections: 1000, + maxIncomingConnections: 1000, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 }) diff --git a/test/registrar/registrar.spec.ts b/test/registrar/registrar.spec.ts index d9fac96a4f..bb1f0e8c69 100644 --- a/test/registrar/registrar.spec.ts +++ b/test/registrar/registrar.spec.ts @@ -43,7 +43,8 @@ describe('registrar', () => { components.peerStore = new PersistentPeerStore(components) components.connectionManager = new DefaultConnectionManager(components, { minConnections: 50, - maxConnections: 1000, + maxOutgoingConnections: 1000, + maxIncomingConnections: 1000, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 }) From d2cad14efc1f6ba8adeed4e286a1f769ba6ffedc Mon Sep 17 00:00:00 2001 From: chad Date: Mon, 5 Dec 2022 15:14:49 -0500 Subject: [PATCH 03/16] feat: Added the configuration capabilities on connection manager to seperate incoming and outgoing connection limits (#1508) --- src/config.ts | 3 +- src/connection-manager/auto-dialler.ts | 4 +- src/connection-manager/index.ts | 113 +++++++++++++++---------- 3 files changed, 74 insertions(+), 46 deletions(-) diff --git a/src/config.ts b/src/config.ts index e856061c99..9cb805622a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -20,7 +20,8 @@ const DefaultConfig: Partial = { announceFilter: (multiaddrs: Multiaddr[]) => multiaddrs }, connectionManager: { - maxConnections: 300, + maxIncomingConnections: 300, + maxOutgoingConnections: 300, minConnections: 50, autoDial: true, autoDialInterval: 10000, diff --git a/src/connection-manager/auto-dialler.ts b/src/connection-manager/auto-dialler.ts index 2b26b00ad2..136ba73a6f 100644 --- a/src/connection-manager/auto-dialler.ts +++ b/src/connection-manager/auto-dialler.ts @@ -20,7 +20,7 @@ export interface AutoDiallerInit { enabled?: boolean /** - * The minimum number of connections to avoid pruning + * The minimum number of incoming connections to avoid pruning */ minConnections?: number @@ -107,7 +107,7 @@ export class AutoDialler implements Startable { this.autoDialTimeout.clear() } - const minConnections = this.options.minConnections + const { minConnections } = this.options // Already has enough connections if (this.components.connectionManager.getConnections().length >= minConnections) { diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 0c40953863..a9d32a98d1 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -27,7 +27,12 @@ export interface ConnectionManagerConfig { /** * The maximum number of connections libp2p is willing to have before it starts disconnecting. Defaults to `Infinity` */ - maxConnections: number + maxIncomingConnections: number + + /** + * The maximum number of outgoing connections to keep open + */ + maxOutgoingConnections: number /** * The minimum number of connections below which libp2p not activate preemptive disconnections. Defaults to `0`. @@ -100,7 +105,7 @@ export interface ConnectionManagerConfig { /** * A list of multiaddrs that will always be allowed (except if they are in the - * deny list) to open connections to this node even if we've reached maxConnections + * deny list) to open connections to this node even if we've reached maxIncomingConnections */ allow?: string[] @@ -121,10 +126,17 @@ export interface ConnectionManagerConfig { * complete the connection upgrade - e.g. choosing connection encryption, muxer, etc */ maxIncomingPendingConnections?: number + + /** + * The maximum number of parallel outgoing connections allowed that have yet to + * complete the connection upgrade - e.g. choosing connection encryption, muxer, etc + */ + maxOutgoingPendingConnections?: number } const defaultOptions: Partial = { - maxConnections: Infinity, + maxIncomingConnections: Infinity, + maxOutgoingConnections: Infinity, minConnections: 0, maxEventLoopDelay: Infinity, pollInterval: 2000, @@ -167,8 +179,18 @@ export class DefaultConnectionManager extends EventEmitter multiaddr(ma)) - this.deny = (init.deny ?? []).map(ma => multiaddr(ma)) + this.allow = (init.allow ?? []).map((ma) => multiaddr(ma)) + this.deny = (init.deny ?? []).map((ma) => multiaddr(ma)) this.inboundConnectionRateLimiter = new RateLimiterMemory({ points: this.opts.inboundConnectionThreshold, @@ -318,7 +340,7 @@ export class DefaultConnectionManager extends EventEmitter tag.name === KEEP_ALIVE).length > 0 + const hasKeepAlive = tags.filter((tag) => tag.name === KEEP_ALIVE).length > 0 if (hasKeepAlive) { keepAlivePeers.push(peer.id) @@ -331,20 +353,19 @@ export class DefaultConnectionManager extends EventEmitter { + keepAlivePeers.map(async (peer) => { await this.openConnection(peer, { signal: this.connectOnStartupController?.signal + }).catch((err) => { + log.error(err) }) - .catch(err => { - log.error(err) - }) }) ) }) - .catch(err => { + .catch((err) => { log.error(err) }) .finally(() => { @@ -379,13 +400,15 @@ export class DefaultConnectionManager extends EventEmitter> = [] for (const connectionList of this.connections.values()) { for (const connection of connectionList) { - tasks.push((async () => { - try { - await connection.close() - } catch (err) { - log.error(err) - } - })()) + tasks.push( + (async () => { + try { + await connection.close() + } catch (err) { + log.error(err) + } + })() + ) } } @@ -395,7 +418,7 @@ export class DefaultConnectionManager extends EventEmitter) { - void this._onConnect(evt).catch(err => { + void this._onConnect(evt).catch((err) => { log.error(err) }) } @@ -427,9 +450,9 @@ export class DefaultConnectionManager extends EventEmitter('peer:connect', { detail: connection })) } @@ -498,7 +521,7 @@ export class DefaultConnectionManager extends EventEmitter { + connections.map(async (connection) => { return await connection.close() }) ) @@ -556,7 +579,7 @@ export class DefaultConnectionManager extends EventEmitter connection.stat.status === STATUS.OPEN) + return connections.filter((connection) => connection.stat.status === STATUS.OPEN) } return [] @@ -568,10 +591,9 @@ export class DefaultConnectionManager extends EventEmitter) { const { detail: summary } = evt - this._checkMaxLimit('maxEventLoopDelay', summary.avgMs, 1) - .catch(err => { - log.error(err) - }) + this._checkMaxLimit('maxEventLoopDelay', summary.avgMs, 1).catch((err) => { + log.error(err) + }) } /** @@ -611,9 +633,12 @@ export class DefaultConnectionManager extends EventEmitter { - return acc + curr.value - }, 0)) + peerValues.set( + remotePeer, + tags.reduce((acc, curr) => { + return acc + curr.value + }, 0) + ) } // sort by value, lowest to highest @@ -658,7 +683,7 @@ export class DefaultConnectionManager extends EventEmitter { + toClose.map(async (connection) => { try { await connection.close() } catch (err) { @@ -666,16 +691,18 @@ export class DefaultConnectionManager extends EventEmitter('connectionEnd', { - detail: connection - })) + this.onDisconnect( + new CustomEvent('connectionEnd', { + detail: connection + }) + ) }) ) } async acceptIncomingConnection (maConn: MultiaddrConnection): Promise { // check deny list - const denyConnection = this.deny.some(ma => { + const denyConnection = this.deny.some((ma) => { return maConn.remoteAddr.toString().startsWith(ma.toString()) }) @@ -685,7 +712,7 @@ export class DefaultConnectionManager extends EventEmitter { + const allowConnection = this.allow.some((ma) => { return maConn.remoteAddr.toString().startsWith(ma.toString()) }) @@ -712,13 +739,13 @@ export class DefaultConnectionManager extends EventEmitter Date: Tue, 6 Dec 2022 16:29:56 -0500 Subject: [PATCH 04/16] test: throws an error for outbound connection beyond limit and added test --- src/connection-manager/index.ts | 7 +++++++ test/connection-manager/index.spec.ts | 20 +++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index a9d32a98d1..53615a2bb3 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -512,6 +512,13 @@ export class DefaultConnectionManager extends EventEmitter this.opts.maxOutgoingConnections) { + throw errCode( + new Error('Connection Manager maxOutgoing connections exceeded'), + codes.ERR_CONNECTION_DENIED + ) + } + let timeoutController: TimeoutController | undefined if (options?.signal == null) { diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index b494b518e0..72f548cd1f 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -371,8 +371,26 @@ describe('Connection Manager', () => { .to.eventually.be.false() }) - it.skip('should deny connections when maxOutgoingConnections is exceeded', async () => { + it('should throw an error when attempting to connect and maxOutgoingConnections is exceeded', async () => { + const dialer = stubInterface() + dialer.dial.resolves(stubInterface()) + + const connectionManager = new DefaultConnectionManager({ + peerId: libp2p.peerId, + upgrader: stubInterface(), + peerStore: stubInterface(), + dialer + }, { + ...defaultOptions, + maxOutgoingConnections: 1 + }) + + // max out the connection limit + await connectionManager.openConnection(await createEd25519PeerId()) + expect(connectionManager.getConnections()).to.have.lengthOf(1) + await expect(connectionManager.openConnection(await createEd25519PeerId())).to.eventually.be.rejected() + .and.to.have.property('code', ErrorCodes.ERR_CONNECTION_DENIED) }) it('should deny connections from peers that connect too frequently', async () => { From 9344798ef1cea7f6d07c913988b4708f53913d9f Mon Sep 17 00:00:00 2001 From: chad Date: Thu, 8 Dec 2022 16:50:52 -0500 Subject: [PATCH 05/16] fix: should not prune a peer that is in the allow list once the connection limit is reached (#1515) --- src/connection-manager/index.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 53615a2bb3..6e3f3cfee8 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -681,7 +681,15 @@ export class DefaultConnectionManager extends EventEmitter { + return ma.getPeerId() === connection.remotePeer.toString() + }) + + // Connections in the allow list should be excluded from pruning + if (!connectionInAllowList) { + toClose.push(connection) + } if (toClose.length === toPrune) { break From 189906410070c1ca1bff131517cb4ee718233cf8 Mon Sep 17 00:00:00 2001 From: Chad Nehemiah Date: Tue, 17 Jan 2023 00:10:05 -0500 Subject: [PATCH 06/16] docs: update connection manager docs (#1555) --- doc/CONNECTION_MANAGER.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/CONNECTION_MANAGER.md b/doc/CONNECTION_MANAGER.md index 4b1af662e5..a22a14fcb1 100644 --- a/doc/CONNECTION_MANAGER.md +++ b/doc/CONNECTION_MANAGER.md @@ -1,3 +1,3 @@ # Connection Manager -The documentation here has moved to https://libp2p.github.io/js-libp2p/interfaces/index.ConnectionManagerConfig.html - please update your bookmarks! +The documentation here has moved to https://libp2p.github.io/js-libp2p-interfaces/modules/_libp2p_interface_connection_manager.html - please update your bookmarks! From 78c9bbb898335ec6a54f1b2589386705c6842b56 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 17 Jan 2023 10:10:25 +0000 Subject: [PATCH 07/16] chore: update dependanbot config (#1558) Update config so it works with our semantic release config --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 290ad02837..0bc3b42de8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,3 +6,6 @@ updates: interval: daily time: "10:00" open-pull-requests-limit: 10 + commit-message: + prefix: "deps" + prefix-development: "deps(dev)" From 6188a5198356d59471797066ded82d08a61c6dc6 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 17 Jan 2023 10:10:36 +0000 Subject: [PATCH 08/16] chore: remove unused rimraf dep (#1559) This isn't used any more so remove it --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index e1bc116d88..75159c5676 100644 --- a/package.json +++ b/package.json @@ -200,7 +200,6 @@ "p-times": "^4.0.0", "p-wait-for": "^5.0.0", "protons": "^6.0.0", - "rimraf": "^3.0.2", "sinon": "^15.0.1", "sinon-ts": "^1.0.0" }, From cb245ce33aa4448f188f2a8d69f2a13101588dc5 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 17 Jan 2023 10:11:02 +0000 Subject: [PATCH 09/16] fix: allow reading PeerId from keychain (#1552) libp2p has a secure keychain where all items are stored in the datastore in an encrypted format, including the PeerId of the current node. If no PeerId is passed into the factory function, a new PeerId is created for the current node. Instead, if the factory function is passed a DataStore, it should try to read the PeerId from the DataStore and only create a new PeerId if reading the `self` key fails. --- src/libp2p.ts | 25 +++++++ test/core/peer-id.spec.ts | 145 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 test/core/peer-id.spec.ts diff --git a/src/libp2p.ts b/src/libp2p.ts index a35316a293..00cdb23647 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -49,6 +49,7 @@ import { DummyPubSub } from './pubsub/dummy-pubsub.js' import { PeerSet } from '@libp2p/peer-collections' import { DefaultDialer } from './connection-manager/dialer/index.js' import { peerIdFromString } from '@libp2p/peer-id' +import type { Datastore } from 'interface-datastore' const log = logger('libp2p') @@ -510,6 +511,30 @@ export class Libp2pNode extends EventEmitter implements Libp2p { */ export async function createLibp2pNode (options: Libp2pOptions): Promise { if (options.peerId == null) { + const datastore = options.datastore as Datastore | undefined + + if (datastore != null) { + try { + // try load the peer id from the keychain + // @ts-expect-error missing the peer id property + const keyChain = new KeyChain({ + datastore + }, { + ...KeyChain.generateOptions(), + ...(options.keychain ?? {}) + }) + + options.peerId = await keyChain.exportPeerId('self') + } catch (err: any) { + if (err.code !== 'ERR_NOT_FOUND') { + throw err + } + } + } + } + + if (options.peerId == null) { + // no peer id in the keychain, create a new peer id options.peerId = await createEd25519PeerId() } diff --git a/test/core/peer-id.spec.ts b/test/core/peer-id.spec.ts new file mode 100644 index 0000000000..0219d9ffc8 --- /dev/null +++ b/test/core/peer-id.spec.ts @@ -0,0 +1,145 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/chai' +import { webSockets } from '@libp2p/websockets' +import { plaintext } from '../../src/insecure/index.js' +import { createLibp2p, Libp2p } from '../../src/index.js' +import { MemoryDatastore } from 'datastore-core' + +describe('peer-id', () => { + let libp2p: Libp2p + + afterEach(async () => { + if (libp2p != null) { + await libp2p.stop() + } + }) + + it('should create a PeerId if none is passed', async () => { + libp2p = await createLibp2p({ + transports: [ + webSockets() + ], + connectionEncryption: [ + plaintext() + ] + }) + + expect(libp2p.peerId).to.be.ok() + }) + + it('should retrieve the PeerId from the datastore', async () => { + const datastore = new MemoryDatastore() + + libp2p = await createLibp2p({ + datastore, + transports: [ + webSockets() + ], + connectionEncryption: [ + plaintext() + ] + }) + + // this PeerId was created by default + const peerId = libp2p.peerId + + await libp2p.stop() + + // create a new node from the same datastore + libp2p = await createLibp2p({ + datastore, + transports: [ + webSockets() + ], + connectionEncryption: [ + plaintext() + ] + }) + + // the new node should have read the PeerId from the datastore + // instead of creating a new one + expect(libp2p.peerId.toString()).to.equal(peerId.toString()) + }) + + it('should retrieve the PeerId from the datastore with a keychain password', async () => { + const datastore = new MemoryDatastore() + const keychain = { + pass: 'very-long-password-must-be-over-twenty-characters-long', + dek: { + salt: 'CpjNIxMqAZ+aJg+ezLfuzG4a' + } + } + + libp2p = await createLibp2p({ + datastore, + keychain, + transports: [ + webSockets() + ], + connectionEncryption: [ + plaintext() + ] + }) + + // this PeerId was created by default + const peerId = libp2p.peerId + + await libp2p.stop() + + // create a new node from the same datastore + libp2p = await createLibp2p({ + datastore, + keychain, + transports: [ + webSockets() + ], + connectionEncryption: [ + plaintext() + ] + }) + + // the new node should have read the PeerId from the datastore + // instead of creating a new one + expect(libp2p.peerId.toString()).to.equal(peerId.toString()) + }) + + it('should fail to start if retrieving the PeerId from the datastore fails', async () => { + const datastore = new MemoryDatastore() + const keychain = { + pass: 'very-long-password-must-be-over-twenty-characters-long', + dek: { + salt: 'CpjNIxMqAZ+aJg+ezLfuzG4a' + } + } + + libp2p = await createLibp2p({ + datastore, + keychain, + transports: [ + webSockets() + ], + connectionEncryption: [ + plaintext() + ] + }) + await libp2p.stop() + + // creating a new node from the same datastore but with the wrong keychain config should fail + await expect(createLibp2p({ + datastore, + keychain: { + pass: 'different-very-long-password-must-be-over-twenty-characters-long', + dek: { + salt: 'different-CpjNIxMqAZ+aJg+ezLfuzG4a' + } + }, + transports: [ + webSockets() + ], + connectionEncryption: [ + plaintext() + ] + })).to.eventually.rejectedWith('Invalid PEM formatted message') + }) +}) From 07352f61d9b93011192bc0e2293bc1722c5fa218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Tue, 17 Jan 2023 11:13:55 +0100 Subject: [PATCH 10/16] fix: allow configuring circuit stream limits (#1542) Co-authored-by: Alex Potsides --- src/circuit/index.ts | 3 ++- src/circuit/transport.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/circuit/index.ts b/src/circuit/index.ts index 387dc02a7e..a5870fbc71 100644 --- a/src/circuit/index.ts +++ b/src/circuit/index.ts @@ -16,8 +16,9 @@ import type { ContentRouting } from '@libp2p/interface-content-routing' import type { ConnectionManager } from '@libp2p/interface-connection-manager' import type { TransportManager } from '@libp2p/interface-transport' import type { PeerId } from '@libp2p/interface-peer-id' +import type { StreamHandlerOptions } from '@libp2p/interface-registrar' -export interface RelayConfig { +export interface RelayConfig extends StreamHandlerOptions { enabled: boolean advertise: RelayAdvertiseConfig hop: HopConfig diff --git a/src/circuit/transport.ts b/src/circuit/transport.ts index 95b56ddc99..ebb793ca18 100644 --- a/src/circuit/transport.ts +++ b/src/circuit/transport.ts @@ -67,7 +67,7 @@ export class Circuit implements Transport, Startable { void this._onProtocol(data).catch(err => { log.error(err) }) - }) + }, { ...this._init }) .catch(err => { log.error(err) }) From 5a5330e7e04ff291a95658a471bcc113614aedf4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 18 Jan 2023 09:52:26 +0000 Subject: [PATCH 11/16] chore: release 0.42.2 (#1551) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1baf146e1f..833a952ac9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,18 @@ +### [0.42.2](https://www.github.com/libp2p/js-libp2p/compare/v0.42.1...v0.42.2) (2023-01-17) + + +### Bug Fixes + +* allow configuring circuit stream limits ([#1542](https://www.github.com/libp2p/js-libp2p/issues/1542)) ([f82e6b8](https://www.github.com/libp2p/js-libp2p/commit/f82e6b86e375b86e71cd339660a348ecba4bf68d)) +* allow dialing multiaddrs without peer ids ([#1548](https://www.github.com/libp2p/js-libp2p/issues/1548)) ([398e231](https://www.github.com/libp2p/js-libp2p/commit/398e231337c3db1ccd5b4254fb18ab1903aa68b2)) +* allow exporting PeerIds from the keychain ([#1546](https://www.github.com/libp2p/js-libp2p/issues/1546)) ([141e072](https://www.github.com/libp2p/js-libp2p/commit/141e0722ee2cd92b2b928767710de7443b5a4c56)) +* allow reading PeerId from keychain ([#1552](https://www.github.com/libp2p/js-libp2p/issues/1552)) ([0831cd9](https://www.github.com/libp2p/js-libp2p/commit/0831cd960d423545ee60b457d66a6a996888804b)) +* do not append peer id to path addresses ([#1547](https://www.github.com/libp2p/js-libp2p/issues/1547)) ([bd2bdf7](https://www.github.com/libp2p/js-libp2p/commit/bd2bdf7ca0d87ab63b2e9acf7edf7a5752e0559c)) +* improve pubsub example ([#1549](https://www.github.com/libp2p/js-libp2p/issues/1549)) ([ba8527c](https://www.github.com/libp2p/js-libp2p/commit/ba8527c317b9f1f31f5066b6204fda35d393058f)) + ### [0.42.1](https://www.github.com/libp2p/js-libp2p/compare/v0.42.0...v0.42.1) (2023-01-11) diff --git a/package.json b/package.json index 75159c5676..8f340b2262 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libp2p", - "version": "0.42.1", + "version": "0.42.2", "description": "JavaScript implementation of libp2p, a modular peer to peer network stack", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/libp2p/js-libp2p#readme", From b534c8b607f70deada589ba6dafb0bbf50cc1650 Mon Sep 17 00:00:00 2001 From: chad Date: Wed, 18 Jan 2023 15:59:48 -0500 Subject: [PATCH 12/16] fixup! doc: Updated docs to seperate max/min incoming and outgoing connections (#1508) --- doc/CONFIGURATION.md | 387 +++++++++++++++++++++++-------------------- doc/LIMITS.md | 85 +++++----- 2 files changed, 246 insertions(+), 226 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 5fbf1c639e..e16a21a3b0 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -206,14 +206,13 @@ const modules = { peerRouting: [], peerDiscovery: [], dht: dhtImplementation, - pubsub: pubsubImplementation, -}; + pubsub: pubsubImplementation +} ``` Moreover, the majority of the modules can be customized via option parameters. This way, it is also possible to provide this options through a `config` object. This config object should have the property name of each building block to configure, the same way as the modules specification. Besides the `modules` and `config`, libp2p allows other internal options and configurations: - - `datastore`: an instance of [ipfs/interface-datastore](https://github.com/ipfs/js-ipfs-interfaces/tree/master/packages/interface-datastore) modules. - This is used in modules such as the DHT. If it is not provided, `js-libp2p` will use an in memory datastore. - `peerId`: the identity of the node, an instance of [libp2p/js-peer-id](https://github.com/libp2p/js-peer-id). @@ -236,23 +235,26 @@ Besides the `modules` and `config`, libp2p allows other internal options and con // dht: kad-dht // pubsub: gossipsub -import { createLibp2p } from "libp2p"; -import { tcp } from "@libp2p/tcp"; -import { webSockets } from "@libp2p/websockets"; -import { mplex } from "@libp2p/mplex"; -import { noise } from "@chainsafe/libp2p-noise"; -import { mdns } from "@libp2p/mdns"; -import { kadDHT } from "@libp2p/kad-dht"; -import { gossipsub } from "libp2p-gossipsub"; +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { webSockets } from '@libp2p/websockets' +import { mplex } from '@libp2p/mplex' +import { noise } from '@chainsafe/libp2p-noise' +import { mdns } from '@libp2p/mdns' +import { kadDHT } from '@libp2p/kad-dht' +import { gossipsub } from 'libp2p-gossipsub' const node = await createLibp2p({ - transports: [tcp(), webSockets()], + transports: [ + tcp(), + webSockets() + ], streamMuxers: [mplex()], connectionEncryption: [noise()], peerDiscovery: [MulticastDNS], dht: kadDHT(), - pubsub: gossipsub(), -}); + pubsub: gossipsub() +}) ``` #### Customizing Peer Discovery @@ -293,20 +295,29 @@ const node = await createLibp2p({ #### Setup webrtc transport and discovery ```js -import { createLibp2p } from "libp2p"; -import { webSockets } from "@libp2p/websockets"; -import { webRTCStar } from "@libp2p/webrtc-star"; -import { mplex } from "@libp2p/mplex"; -import { noise } from "@chainsafe/libp2p-noise"; +import { createLibp2p } from 'libp2p' +import { webSockets } from '@libp2p/websockets' +import { webRTCStar } from '@libp2p/webrtc-star' +import { mplex } from '@libp2p/mplex' +import { noise } from '@chainsafe/libp2p-noise' -const webRtc = webRTCStar(); +const webRtc = webRTCStar() const node = await createLibp2p({ - transports: [webSockets(), webRtc.transport], - peerDiscovery: [webRtc.discovery], - streamMuxers: [mplex()], - connectionEncryption: [noise()], -}); + transports: [ + webSockets(), + webRtc.transport + ], + peerDiscovery: [ + webRtc.discovery + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + noise() + ] +}) ``` #### Customizing Pubsub @@ -340,99 +351,101 @@ const node = await createLibp2p({ #### Customizing DHT ```js -import { createLibp2p } from "libp2p"; -import { tcp } from "@libp2p/tcp"; -import { mplex } from "@libp2p/mplex"; -import { noise } from "@chainsafe/libp2p-noise"; -import { kadDHT } from "@libp2p/kad-dht"; +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' +import { noise } from '@chainsafe/libp2p-noise' +import { kadDHT } from '@libp2p/kad-dht' const node = await createLibp2p({ - transports: [tcp()], - streamMuxers: [mplex()], - connectionEncryption: [noise()], + transports: [ + tcp() + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + noise() + ], dht: kadDHT({ kBucketSize: 20, - clientMode: false, // Whether to run the WAN DHT in client or server mode (default: client mode) - }), -}); + clientMode: false // Whether to run the WAN DHT in client or server mode (default: client mode) + }) +}) ``` #### Setup with Content and Peer Routing ```js -import { createLibp2p } from "libp2p"; -import { tcp } from "@libp2p/tcp"; -import { mplex } from "@libp2p/mplex"; -import { noise } from "@chainsafe/libp2p-noise"; -import { create as ipfsHttpClient } from "ipfs-http-client"; -import { DelegatedPeerRouting } from "@libp2p/delegated-peer-routing"; -import { DelegatedContentRouting } from "@libp2p/delegated-content-routing"; +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' +import { noise } from '@chainsafe/libp2p-noise' +import { create as ipfsHttpClient } from 'ipfs-http-client' +import { DelegatedPeerRouting } from '@libp2p/delegated-peer-routing' +import { DelegatedContentRouting} from '@libp2p/delegated-content-routing' // create a peerId -const peerId = await PeerId.create(); +const peerId = await PeerId.create() -const delegatedPeerRouting = new DelegatedPeerRouting( - ipfsHttpClient.create({ - host: "node0.delegate.ipfs.io", // In production you should setup your own delegates - protocol: "https", - port: 443, - }) -); +const delegatedPeerRouting = new DelegatedPeerRouting(ipfsHttpClient.create({ + host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates + protocol: 'https', + port: 443 +})) -const delegatedContentRouting = new DelegatedContentRouting( - peerId, - ipfsHttpClient.create({ - host: "node0.delegate.ipfs.io", // In production you should setup your own delegates - protocol: "https", - port: 443, - }) -); +const delegatedContentRouting = new DelegatedContentRouting(peerId, ipfsHttpClient.create({ + host: 'node0.delegate.ipfs.io', // In production you should setup your own delegates + protocol: 'https', + port: 443 +})) const node = await createLibp2p({ transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], - contentRouting: [delegatedContentRouting], - peerRouting: [delegatedPeerRouting], + contentRouting: [ + delegatedContentRouting + ], + peerRouting: [ + delegatedPeerRouting + ], peerId, - peerRouting: { - // Peer routing configuration - refreshManager: { - // Refresh known and connected closest peers + peerRouting: { // Peer routing configuration + refreshManager: { // Refresh known and connected closest peers enabled: true, // Should find the closest peers. interval: 6e5, // Interval for getting the new for closest peers of 10min - bootDelay: 10e3, // Delay for the initial query for closest peers - }, - }, -}); + bootDelay: 10e3 // Delay for the initial query for closest peers + } + } +}) ``` #### Setup with Relay ```js -import { createLibp2p } from "libp2p"; -import { tcp } from "@libp2p/tcp"; -import { mplex } from "@libp2p/mplex"; -import { noise } from "@chainsafe/libp2p-noise"; +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' +import { noise } from '@chainsafe/libp2p-noise' const node = await createLibp2p({ transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], - relay: { - // Circuit Relay options (this config is part of libp2p core configurations) - enabled: true, // Allows you to dial and accept relayed connections. Does not make you a relay. + relay: { // Circuit Relay options (this config is part of libp2p core configurations) + enabled: true, // Allows you to dial and accept relayed connections. Does not make you a relay. hop: { - enabled: true, // Allows you to be a relay for other peers - active: true, // You will attempt to dial destination peers if you are not connected to them + enabled: true, // Allows you to be a relay for other peers + active: true // You will attempt to dial destination peers if you are not connected to them }, advertise: { bootDelay: 15 * 60 * 1000, // Delay before HOP relay service is advertised on the network - enabled: true, // Allows you to disable the advertise of the Hop service - ttl: 30 * 60 * 1000, // Delay Between HOP relay service advertisements on the network - }, - }, -}); + enabled: true, // Allows you to disable the advertise of the Hop service + ttl: 30 * 60 * 1000 // Delay Between HOP relay service advertisements on the network + } + } +}) ``` #### Setup with Auto Relay @@ -461,45 +474,45 @@ const node = await createLibp2p({ Libp2p allows you to setup a secure keychain to manage your keys. The keychain configuration object should have the following properties: -| Name | Type | Description | -| --------- | -------- | -------------------------------------------------------------------------------------- | -| pass | `string` | Passphrase to use in the keychain (minimum of 20 characters). | +| Name | Type | Description | +|------|------|-------------| +| pass | `string` | Passphrase to use in the keychain (minimum of 20 characters). | | datastore | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) | ```js -import { createLibp2p } from "libp2p"; -import { tcp } from "@libp2p/tcp"; -import { mplex } from "@libp2p/mplex"; -import { noise } from "@chainsafe/libp2p-noise"; -import { LevelDatastore } from "datastore-level"; +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' +import { noise } from '@chainsafe/libp2p-noise' +import { LevelDatastore } from 'datastore-level' -const datastore = new LevelDatastore("path/to/store"); -await datastore.open(); +const datastore = new LevelDatastore('path/to/store') +await datastore.open() const node = await createLibp2p({ transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], keychain: { - pass: "notsafepassword123456789", + pass: 'notsafepassword123456789', datastore: dsInstant, - }, -}); + } +}) ``` #### Configuring Dialing Dialing in libp2p can be configured to limit the rate of dialing, and how long dials are allowed to take. The dialer configuration object should have the following properties: -| Name | Type | Description | -| ----------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| maxParallelDials | `number` | How many multiaddrs we can dial in parallel. | -| maxAddrsToDial | `number` | How many multiaddrs is the dial allowed to dial for a single peer. | -| maxDialsPerPeer | `number` | How many multiaddrs we can dial per peer, in parallel. | -| dialTimeout | `number` | Second dial timeout per peer in ms. | -| resolvers | `object` | Dial [Resolvers](https://github.com/multiformats/js-multiaddr/blob/master/src/resolvers/index.js) for resolving multiaddrs | -| addressSorter | `(Array
) => Array
` | Sort the known addresses of a peer before trying to dial. | -| startupReconnectTimeout | `number` | When a node is restarted, we try to connect to any peers marked with the `keep-alive` tag up until to this timeout in ms is reached (default: 60000) | +| Name | Type | Description | +|------|------|-------------| +| maxParallelDials | `number` | How many multiaddrs we can dial in parallel. | +| maxAddrsToDial | `number` | How many multiaddrs is the dial allowed to dial for a single peer. | +| maxDialsPerPeer | `number` | How many multiaddrs we can dial per peer, in parallel. | +| dialTimeout | `number` | Second dial timeout per peer in ms. | +| resolvers | `object` | Dial [Resolvers](https://github.com/multiformats/js-multiaddr/blob/master/src/resolvers/index.js) for resolving multiaddrs | +| addressSorter | `(Array
) => Array
` | Sort the known addresses of a peer before trying to dial. | +| startupReconnectTimeout | `number` | When a node is restarted, we try to connect to any peers marked with the `keep-alive` tag up until to this timeout in ms is reached (default: 60000) | The below configuration example shows how the dialer should be configured, with the current defaults: @@ -533,28 +546,27 @@ const node = await createLibp2p({ The Connection Manager prunes Connections in libp2p whenever certain limits are exceeded. If Metrics are enabled, you can also configure the Connection Manager to monitor the bandwidth of libp2p and prune connections as needed. You can read more about what Connection Manager does at [./CONNECTION_MANAGER.md](./CONNECTION_MANAGER.md). The configuration values below show the defaults for Connection Manager. See [./CONNECTION_MANAGER.md](./CONNECTION_MANAGER.md#options) for a full description of the parameters. ```js -import { createLibp2p } from "libp2p"; -import { tcp } from "@libp2p/tcp"; -import { mplex } from "@libp2p/mplex"; -import { noise } from "@chainsafe/libp2p-noise"; +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' +import { noise } from '@chainsafe/libp2p-noise' const node = await createLibp2p({ transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], connectionManager: { - maxIncomingConnections: Infinity, + maxConnections: Infinity, minConnections: 0, - maxOutgoingConnections: Infinity, pollInterval: 2000, // The below values will only be taken into account when Metrics are enabled maxData: Infinity, maxSentData: Infinity, maxReceivedData: Infinity, maxEventLoopDelay: Infinity, - movingAverageInterval: 60000, - }, -}); + movingAverageInterval: 60000 + } +}) ``` #### Configuring Connection Gater @@ -683,33 +695,33 @@ const node = await createLibp2p({ The Transport Manager is responsible for managing the libp2p transports life cycle. This includes starting listeners for the provided listen addresses, closing these listeners and dialing using the provided transports. By default, if a libp2p node has a list of multiaddrs for listening on and there are no valid transports for those multiaddrs, libp2p will throw an error on startup and shutdown. However, for some applications it is perfectly acceptable for libp2p nodes to start in dial only mode if all the listen multiaddrs failed. This error tolerance can be enabled as follows: ```js -import { createLibp2p } from "libp2p"; -import { tcp } from "@libp2p/tcp"; -import { mplex } from "@libp2p/mplex"; -import { noise } from "@chainsafe/libp2p-noise"; -import { FaultTolerance } from "@libp2p/interface-transport"; +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' +import { noise } from '@chainsafe/libp2p-noise' +import { FaultTolerance } from '@libp2p/interface-transport' const node = await createLibp2p({ transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], transportManager: { - faultTolerance: FaultTolerance.NO_FATAL, - }, -}); + faultTolerance: FaultTolerance.NO_FATAL + } +}) ``` #### Configuring Metrics Metrics are disabled in libp2p by default. You can enable and configure them as follows: -| Name | Type | Description | -| --------------------------- | --------------- | ---------------------------------------------------------------------------------------- | -| enabled | `boolean` | Enabled metrics collection. | -| computeThrottleMaxQueueSize | `number` | How many messages a stat will queue before processing. | -| computeThrottleTimeout | `number` | Time in milliseconds a stat will wait, after the last item was added, before processing. | -| movingAverageIntervals | `Array` | The moving averages that will be computed. | -| maxOldPeersRetention | `number` | How many disconnected peers we will retain stats for. | +| Name | Type | Description | +|------|------|-------------| +| enabled | `boolean` | Enabled metrics collection. | +| computeThrottleMaxQueueSize | `number` | How many messages a stat will queue before processing. | +| computeThrottleTimeout | `number` | Time in milliseconds a stat will wait, after the last item was added, before processing. | +| movingAverageIntervals | `Array` | The moving averages that will be computed. | +| maxOldPeersRetention | `number` | How many disconnected peers we will retain stats for. | The below configuration example shows how the metrics should be configured. Aside from enabled being `false` by default, the following default configuration options are listed below: @@ -743,22 +755,22 @@ PeerStore persistence is disabled in libp2p by default. You can enable and confi The threshold number represents the maximum number of "dirty peers" allowed in the PeerStore, i.e. peers that are not updated in the datastore. In this context, browser nodes should use a threshold of 1, since they might not "stop" properly in several scenarios and the PeerStore might end up with unflushed records when the window is closed. -| Name | Type | Description | -| ----------- | --------- | ------------------------------ | -| persistence | `boolean` | Is persistence enabled. | -| threshold | `number` | Number of dirty peers allowed. | +| Name | Type | Description | +|------|------|-------------| +| persistence | `boolean` | Is persistence enabled. | +| threshold | `number` | Number of dirty peers allowed. | The below configuration example shows how the PeerStore should be configured. Aside from persistence being `false` by default, the following default configuration options are listed below: ```js -import { createLibp2p } from "libp2p"; -import { tcp } from "@libp2p/tcp"; -import { mplex } from "@libp2p/mplex"; -import { noise } from "@chainsafe/libp2p-noise"; -import { LevelDatastore } from "datastore-level"; +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { mplex } from '@libp2p/mplex' +import { noise } from '@chainsafe/libp2p-noise' +import { LevelDatastore } from 'datastore-level' -const datastore = new LevelDatastore("path/to/store"); -await datastore.open(); // level database must be ready before node boot +const datastore = new LevelDatastore('path/to/store') +await datastore.open() // level database must be ready before node boot const node = await createLibp2p({ datastore, // pass the opened datastore @@ -767,9 +779,9 @@ const node = await createLibp2p({ connectionEncryption: [noise()], peerStore: { persistence: true, - threshold: 5, - }, -}); + threshold: 5 + } +}) ``` #### Customizing Transports @@ -777,47 +789,62 @@ const node = await createLibp2p({ Some Transports can be passed additional options when they are created. For example, `libp2p-webrtc-star` accepts an optional, custom `wrtc` implementation. In addition to libp2p passing itself and an `Upgrader` to handle connection upgrading, libp2p will also pass the options, if they are provided, from `config.transport`. ```js -import { createLibp2p } from "libp2p"; -import { webRTCStar } from "@libp2p/webrtc-star"; -import { mplex } from "@libp2p/mplex"; -import { noise } from "@chainsafe/libp2p-noise"; -import wrtc from "wrtc"; +import { createLibp2p } from 'libp2p' +import { webRTCStar } from '@libp2p/webrtc-star' +import { mplex } from '@libp2p/mplex' +import { noise } from '@chainsafe/libp2p-noise' +import wrtc from 'wrtc' const webRTC = webRTCStar({ - wrtc, -}); + wrtc +}) const node = await createLibp2p({ - transports: [webRTC.transport], - peerDiscovery: [webRTC.discovery], - streamMuxers: [mplex()], - connectionEncryption: [noise()], -}); + transports: [ + webRTC.transport + ], + peerDiscovery: [ + webRTC.discovery + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + noise() + ] +}) ``` -During Libp2p startup, transport listeners will be created for the configured listen multiaddrs. Some transports support custom listener options and you can set them using the `listenerOptions` in the transport configuration. For example, [libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) transport listener supports the configuration of its underlying [simple-peer](https://github.com/feross/simple-peer) ice server(STUN/TURN) config as follows: +During Libp2p startup, transport listeners will be created for the configured listen multiaddrs. Some transports support custom listener options and you can set them using the `listenerOptions` in the transport configuration. For example, [libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) transport listener supports the configuration of its underlying [simple-peer](https://github.com/feross/simple-peer) ice server(STUN/TURN) config as follows: ```js const webRTC = webRTCStar({ listenerOptions: { config: { iceServers: [ - { urls: ["turn:YOUR.TURN.SERVER:3478"], username: "YOUR.USER", credential: "YOUR.PASSWORD" }, - { urls: ["stun:YOUR.STUN.SERVER:3478"], username: "", credential: "" }, - ], - }, - }, -}); + {"urls": ["turn:YOUR.TURN.SERVER:3478"], "username": "YOUR.USER", "credential": "YOUR.PASSWORD"}, + {"urls": ["stun:YOUR.STUN.SERVER:3478"], "username": "", "credential": ""}] + } + } +}) const node = await createLibp2p({ - transports: [webRTC.transport], - peerDiscovery: [webRTC.discovery], - streamMuxers: [mplex()], - connectionEncryption: [noise()], + transports: [ + webRTC.transport + ], + peerDiscovery: [ + webRTC.discovery + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + noise() + ], addresses: { - listen: ["/dns4/your-wrtc-star.pub/tcp/443/wss/p2p-webrtc-star"], // your webrtc dns multiaddr - }, -}); + listen: ['/dns4/your-wrtc-star.pub/tcp/443/wss/p2p-webrtc-star'] // your webrtc dns multiaddr + } +}) ``` #### Configuring the NAT Manager @@ -831,15 +858,15 @@ const node = await createLibp2p({ config: { nat: { enabled: true, // defaults to true - description: "my-node", // set as the port mapping description on the router, defaults the current libp2p version and your peer id - gateway: "192.168.1.1", // leave unset to auto-discover - externalIp: "80.1.1.1", // leave unset to auto-discover - localAddress: "129.168.1.123", // leave unset to auto-discover + description: 'my-node', // set as the port mapping description on the router, defaults the current libp2p version and your peer id + gateway: '192.168.1.1', // leave unset to auto-discover + externalIp: '80.1.1.1', // leave unset to auto-discover + localAddress: '129.168.1.123', // leave unset to auto-discover ttl: 7200, // TTL for port mappings (min 20 minutes) keepAlive: true, // Refresh port mapping after TTL expires - }, - }, -}); + } + } +}) ``` ##### Browser support @@ -859,12 +886,12 @@ Changing the protocol name prefix can isolate default public network (IPFS) for ```js const node = await createLibp2p({ identify: { - protocolPrefix: "ipfs", // default + protocolPrefix: 'ipfs' // default }, ping: { - protocolPrefix: "ipfs", // default - }, -}); + protocolPrefix: 'ipfs' // default + } +}) /* protocols: [ "/ipfs/id/1.0.0", // identify service protocol (if we have multiplexers) diff --git a/doc/LIMITS.md b/doc/LIMITS.md index 3b8d8fa9a8..6173b413a7 100644 --- a/doc/LIMITS.md +++ b/doc/LIMITS.md @@ -21,7 +21,9 @@ This is important for [DoS](https://en.wikipedia.org/wiki/Denial-of-service_atta ## Connection limits -It's possible to limit the amount of incoming and outgoing connections a node is able to make. When this limit is reached and an attempt to open a new connection is made, existing connections may be closed to make room for the new connection. +It's possible to limit the total amount of connections a node is able to make (combining incoming and outgoing). When this limit is reached and an attempt to open a new connection is made, existing connections may be closed to make room for the new connection (see [Closing connections][#closing-connections]). + +* Note: there currently isn't a way to specify different limits for incoming vs. outgoing. Connection limits are applied across both incoming and outgoing connections combined. There is a backlog item for this [here](https://github.com/libp2p/js-libp2p/issues/1508). We can also limit the number of connections in a "pending" state. These connections have been opened by a remote peer but peer IDs have yet to be exchanged and/or connection encryption and multiplexing negotiated. Once this limit is hit further connections will be closed unless the remote peer has an address in the [allow list](#allowdeny-lists). @@ -31,27 +33,22 @@ All fields are optional. The default values are defined in [src/connection-manag const node = await createLibp2pNode({ connectionManager: { /** - * The total number of incoming connections allowed to be open at one time - */ - maxIncomingConnections: number, - - /** - * The total number of outgoing connections allowed to be open at one time + * The total number of connections allowed to be open at one time */ - maxOutgoingConnections: number, + maxConnections: number /** * If the number of open connections goes below this number, the node * will try to connect to randomly selected peers from the peer store */ - minConnections: number, + minConnections: number /** * How many connections can be open but not yet upgraded */ - maxIncomingPendingConnections: number, - }, -}); + maxIncomingPendingConnections: number + } +}) ``` ## Closing connections @@ -60,10 +57,10 @@ When choosing connections to close the connection manager sorts the list of conn ```js // tag a peer -await libp2p.peerStore.tagPeer(peerId, "my-tag", { +await libp2p.peerStore.tagPeer(peerId, 'my-tag', { value: 50, // 0-100 is the typical value range - ttl: 1000, // optional field, this tag will be deleted after this many ms -}); + ttl: 1000 // optional field, this tag will be deleted after this many ms +}) ``` ## Inbound connection threshold @@ -72,17 +69,17 @@ To prevent individual peers from opening multiple connections to a node, an `inb All fields are optional. The default values are defined in [src/connection-manager/index.ts](https://github.com/libp2p/js-libp2p/blob/master/src/connection-manager/index.ts) - please see that file for the current values. -````ts +```ts const node = await createLibp2pNode({ connectionManager: { /** * A remote peer may attempt to open up to this many connections per second, * any more than that will be automatically rejected */ - inboundConnectionThreshold: number, - }, -}); -o``` + inboundConnectionThreshold: number + } +}) +``` ## Data transfer and Event Loop limits @@ -134,7 +131,7 @@ const node = await createLibp2pNode({ maxEventLoopDelay: number } }) -```` +``` ## Stream limits @@ -256,21 +253,21 @@ const node = await createLibp2pNode({ outboundSocketInactivityTimeout: number /** - * Once this many incoming connections are open on this listener any further connections + * Once this many connections are open on this listener any further connections * will be rejected - this will have no effect if it is larger than the value - * configured for the ConnectionManager maxIncomingConnections parameter + * configured for the ConnectionManager maxConnections parameter */ - maxIncomingConnections: number, - }), - ], -}); + maxConnections: number + }) + ] +}) ``` ## Allow/deny lists It is possible to configure some hosts to always accept connections from and some to always reject connections from. -```ts +```js const node = await createLibp2pNode({ connectionManager: { /** @@ -279,9 +276,9 @@ const node = await createLibp2pNode({ * all connection limits */ allow: [ - "/ip4/43.123.5.23/tcp/3984", - "/ip4/234.243.64.2", - "/ip4/52.55", + '/ip4/43.123.5.23/tcp/3984', + '/ip4/234.243.64.2', + '/ip4/52.55', // etc ], @@ -289,14 +286,14 @@ const node = await createLibp2pNode({ * Any connection with a `remoteAddress` property that has any of these * addresses as a prefix will be immediately rejected */ - deny: [ - "/ip4/132.14.52.64/tcp/3984", - "/ip4/234.243.64.2", - "/ip4/34.42", + deny: [ + '/ip4/132.14.52.64/tcp/3984', + '/ip4/234.243.64.2', + '/ip4/34.42', // etc - ], - }, -}); + ] + } +}) ``` ## How much memory will be used for buffering? @@ -305,10 +302,10 @@ There is no a single config value to control the amount of memory js-libp2p uses Important details for ascertaining this are: -- Each connection has a multiplexer -- Each multiplexer has a buffer for raw incoming data (`muxer.maxUnprocessedMessageQueueSize`) -- The incoming data is parsed into messages for each stream and queued (`muxer.maxStreamBufferSize`) -- Each multiplexer has a stream limit for number of streams (`muxer.maxInboundStreams`, `muxer.maxOutboundStreams`). +* Each connection has a multiplexer +* Each multiplexer has a buffer for raw incoming data (`muxer.maxUnprocessedMessageQueueSize`) +* The incoming data is parsed into messages for each stream and queued (`muxer.maxStreamBufferSize`) +* Each multiplexer has a stream limit for number of streams (`muxer.maxInboundStreams`, `muxer.maxOutboundStreams`). As a result, the max amount of memory buffered by libp2p is approximately: @@ -319,7 +316,3 @@ connectionManager.maxConnections * + (muxer.maxOutboundStreams * muxer.maxStreamBufferSize) ) ``` - -``` - -``` From e2810db79c2accdf3b6461bf44131fe2e5257d75 Mon Sep 17 00:00:00 2001 From: chad Date: Wed, 18 Jan 2023 16:20:51 -0500 Subject: [PATCH 13/16] fixup! feat: Added the configuration capabilities on connection manager to seperate incoming and outgoing connection limits (#1508) --- src/connection-manager/index.ts | 97 +++++++++++++-------------------- 1 file changed, 39 insertions(+), 58 deletions(-) diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 6e3f3cfee8..0cd5dc2d33 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -105,7 +105,7 @@ export interface ConnectionManagerConfig { /** * A list of multiaddrs that will always be allowed (except if they are in the - * deny list) to open connections to this node even if we've reached maxIncomingConnections + * deny list) to open connections to this node even if we've reached maxnOutgoingConnections */ allow?: string[] @@ -126,12 +126,6 @@ export interface ConnectionManagerConfig { * complete the connection upgrade - e.g. choosing connection encryption, muxer, etc */ maxIncomingPendingConnections?: number - - /** - * The maximum number of parallel outgoing connections allowed that have yet to - * complete the connection upgrade - e.g. choosing connection encryption, muxer, etc - */ - maxOutgoingPendingConnections?: number } const defaultOptions: Partial = { @@ -214,7 +208,7 @@ export class DefaultConnectionManager extends EventEmitter multiaddr(ma)) - this.deny = (init.deny ?? []).map((ma) => multiaddr(ma)) + this.allow = (init.allow ?? []).map(ma => multiaddr(ma)) + this.deny = (init.deny ?? []).map(ma => multiaddr(ma)) this.inboundConnectionRateLimiter = new RateLimiterMemory({ points: this.opts.inboundConnectionThreshold, @@ -340,7 +334,7 @@ export class DefaultConnectionManager extends EventEmitter tag.name === KEEP_ALIVE).length > 0 + const hasKeepAlive = tags.filter(tag => tag.name === KEEP_ALIVE).length > 0 if (hasKeepAlive) { keepAlivePeers.push(peer.id) @@ -353,19 +347,20 @@ export class DefaultConnectionManager extends EventEmitter { + keepAlivePeers.map(async peer => { await this.openConnection(peer, { signal: this.connectOnStartupController?.signal - }).catch((err) => { - log.error(err) }) + .catch(err => { + log.error(err) + }) }) ) }) - .catch((err) => { + .catch(err => { log.error(err) }) .finally(() => { @@ -400,15 +395,13 @@ export class DefaultConnectionManager extends EventEmitter> = [] for (const connectionList of this.connections.values()) { for (const connection of connectionList) { - tasks.push( - (async () => { - try { - await connection.close() - } catch (err) { - log.error(err) - } - })() - ) + tasks.push((async () => { + try { + await connection.close() + } catch (err) { + log.error(err) + } + })()) } } @@ -418,7 +411,7 @@ export class DefaultConnectionManager extends EventEmitter) { - void this._onConnect(evt).catch((err) => { + void this._onConnect(evt).catch(err => { log.error(err) }) } @@ -450,9 +443,9 @@ export class DefaultConnectionManager extends EventEmitter('peer:connect', { detail: connection })) } @@ -528,7 +521,7 @@ export class DefaultConnectionManager extends EventEmitter { + connections.map(async connection => { return await connection.close() }) ) @@ -586,7 +579,7 @@ export class DefaultConnectionManager extends EventEmitter connection.stat.status === STATUS.OPEN) + return connections.filter(connection => connection.stat.status === STATUS.OPEN) } return [] @@ -598,9 +591,10 @@ export class DefaultConnectionManager extends EventEmitter) { const { detail: summary } = evt - this._checkMaxLimit('maxEventLoopDelay', summary.avgMs, 1).catch((err) => { - log.error(err) - }) + this._checkMaxLimit('maxEventLoopDelay', summary.avgMs, 1) + .catch(err => { + log.error(err) + }) } /** @@ -640,12 +634,9 @@ export class DefaultConnectionManager extends EventEmitter { - return acc + curr.value - }, 0) - ) + peerValues.set(remotePeer, tags.reduce((acc, curr) => { + return acc + curr.value + }, 0)) } // sort by value, lowest to highest @@ -681,15 +672,7 @@ export class DefaultConnectionManager extends EventEmitter { - return ma.getPeerId() === connection.remotePeer.toString() - }) - - // Connections in the allow list should be excluded from pruning - if (!connectionInAllowList) { - toClose.push(connection) - } + toClose.push(connection) if (toClose.length === toPrune) { break @@ -698,7 +681,7 @@ export class DefaultConnectionManager extends EventEmitter { + toClose.map(async connection => { try { await connection.close() } catch (err) { @@ -706,18 +689,16 @@ export class DefaultConnectionManager extends EventEmitter('connectionEnd', { - detail: connection - }) - ) + this.onDisconnect(new CustomEvent('connectionEnd', { + detail: connection + })) }) ) } async acceptIncomingConnection (maConn: MultiaddrConnection): Promise { // check deny list - const denyConnection = this.deny.some((ma) => { + const denyConnection = this.deny.some(ma => { return maConn.remoteAddr.toString().startsWith(ma.toString()) }) @@ -727,7 +708,7 @@ export class DefaultConnectionManager extends EventEmitter { + const allowConnection = this.allow.some(ma => { return maConn.remoteAddr.toString().startsWith(ma.toString()) }) @@ -760,7 +741,7 @@ export class DefaultConnectionManager extends EventEmitter Date: Mon, 5 Dec 2022 15:13:16 -0500 Subject: [PATCH 14/16] doc: Updated docs to seperate max/min incoming and outgoing connections (#1508) --- doc/CONFIGURATION.md | 3 ++- doc/LIMITS.md | 17 ++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index e16a21a3b0..096b5d9352 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -556,7 +556,8 @@ const node = await createLibp2p({ streamMuxers: [mplex()], connectionEncryption: [noise()], connectionManager: { - maxConnections: Infinity, + maxIncomingConnections: Infinity, + maxOutgoingConnections: Infinity, minConnections: 0, pollInterval: 2000, // The below values will only be taken into account when Metrics are enabled diff --git a/doc/LIMITS.md b/doc/LIMITS.md index 6173b413a7..b7b50f1494 100644 --- a/doc/LIMITS.md +++ b/doc/LIMITS.md @@ -23,8 +23,6 @@ This is important for [DoS](https://en.wikipedia.org/wiki/Denial-of-service_atta It's possible to limit the total amount of connections a node is able to make (combining incoming and outgoing). When this limit is reached and an attempt to open a new connection is made, existing connections may be closed to make room for the new connection (see [Closing connections][#closing-connections]). -* Note: there currently isn't a way to specify different limits for incoming vs. outgoing. Connection limits are applied across both incoming and outgoing connections combined. There is a backlog item for this [here](https://github.com/libp2p/js-libp2p/issues/1508). - We can also limit the number of connections in a "pending" state. These connections have been opened by a remote peer but peer IDs have yet to be exchanged and/or connection encryption and multiplexing negotiated. Once this limit is hit further connections will be closed unless the remote peer has an address in the [allow list](#allowdeny-lists). All fields are optional. The default values are defined in [src/connection-manager/index.ts](https://github.com/libp2p/js-libp2p/blob/master/src/connection-manager/index.ts) - please see that file for the current values. @@ -33,9 +31,14 @@ All fields are optional. The default values are defined in [src/connection-manag const node = await createLibp2pNode({ connectionManager: { /** - * The total number of connections allowed to be open at one time + * The total number of incoming connections allowed to be open at one time */ - maxConnections: number + maxIncomingConnections: number + + /** + * The total number of outgoing connections allowed to be open at one time + */ + maxOutgoingConnections: number /** * If the number of open connections goes below this number, the node @@ -255,9 +258,9 @@ const node = await createLibp2pNode({ /** * Once this many connections are open on this listener any further connections * will be rejected - this will have no effect if it is larger than the value - * configured for the ConnectionManager maxConnections parameter + * configured for the ConnectionManager maxIncomingConnections parameter */ - maxConnections: number + maxIncomingConnections: number }) ] }) @@ -310,7 +313,7 @@ Important details for ascertaining this are: As a result, the max amount of memory buffered by libp2p is approximately: ``` -connectionManager.maxConnections * +(connectionManager.maxOutgoingConnections + connectionManager.maxIncomingConnections) * (muxer.maxUnprocessedMessageQueueSize + (muxer.maxInboundStreams * muxer.maxStreamBufferSize) + (muxer.maxOutboundStreams * muxer.maxStreamBufferSize) From 9e75969e836bd9e630f4d8d08589b9ae358bc7aa Mon Sep 17 00:00:00 2001 From: chad Date: Mon, 23 Jan 2023 11:21:42 -0500 Subject: [PATCH 15/16] fix: updated connection limits to filter for inbound/outbound (#1511) --- src/connection-manager/index.ts | 71 ++++++++++++++++----------- test/connection-manager/index.spec.ts | 56 +++++++++------------ 2 files changed, 65 insertions(+), 62 deletions(-) diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 0cd5dc2d33..fd067fb631 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -173,16 +173,9 @@ export class DefaultConnectionManager extends EventEmitter { + if (connection.stat.direction === 'inbound') { + inboundConnections++ + } else { + outboundConnections++ + } + }) + + const numIncomingToPrune = inboundConnections - this.opts.maxIncomingConnections + const numOutgoingToPrune = outboundConnections - this.opts.maxOutgoingConnections + + await this._checkMaxLimit('maxOutgoingConnections', outboundConnections, numOutgoingToPrune) + await this._checkMaxLimit('maxIncomingConnections', inboundConnections, numIncomingToPrune) - await this._checkMaxLimit('maxOutgoingConnections', numConnections, toPrune) this.dispatchEvent(new CustomEvent('peer:connect', { detail: connection })) } @@ -505,9 +512,12 @@ export class DefaultConnectionManager extends EventEmitter this.opts.maxOutgoingConnections) { + const connections = this.getConnections() + const totalOutboundConnections = connections.filter(connection => connection.stat.direction === 'outbound').length + + if ((totalOutboundConnections + 1) > this.opts.maxOutgoingConnections) { throw errCode( - new Error('Connection Manager maxOutgoing connections exceeded'), + new Error('Connection Manager max connections exceeded'), codes.ERR_CONNECTION_DENIED ) } @@ -548,6 +558,8 @@ export class DefaultConnectionManager extends EventEmitter= Number(this.opts.maxIncomingPendingConnections)) { + log('connection from %s refused - incomingPendingConnections exceeded by peer %s', maConn.remoteAddr) + return false + } + + const connections = this.getConnections() + + const totalIncomingConnections = connections.filter(connection => connection.stat.direction === 'inbound').length + // check allow list const allowConnection = this.allow.some(ma => { return maConn.remoteAddr.toString().startsWith(ma.toString()) }) - if (allowConnection) { - this.incomingPendingConnections++ - - return true - } - - // check pending connections - if (this.incomingPendingConnections === this.opts.maxIncomingPendingConnections) { - log('connection from %s refused - incomingPendingConnections exceeded by peer %s', maConn.remoteAddr) + if ((totalIncomingConnections + 1 > this.opts.maxIncomingConnections) && !allowConnection) { + log('connection from %s refused - maxIncomingConnections exceeded', maConn.remoteAddr) return false } + // Check the rate limiter if (maConn.remoteAddr.isThinWaistAddress()) { const host = maConn.remoteAddr.nodeAddress().address @@ -735,14 +751,9 @@ export class DefaultConnectionManager extends EventEmitter { expect(spy).to.have.property('callCount', 1) }) - it('should fail if the connection manager has mismatched incoming connection limit options', async () => { + it('should fail if the connection manager has mismatched incoming + outgoing connection limit options', async () => { await expect(createNode({ config: createBaseOptions({ connectionManager: { maxIncomingConnections: 5, - minConnections: 6 + maxOutgoingConnections: 1, + minConnections: 7 } }), started: false })).to.eventually.rejected('maxIncomingConnections must be greater') }) - it('should fail if the connection manager has mismatched outgoing connection limit options', async () => { - await expect(createNode({ - config: createBaseOptions({ - connectionManager: { - maxOutgoingConnections: 5, - minConnections: 6 - } - }), - started: false - })).to.eventually.rejected('maxOutgoingConnections must be greater') - }) - it('should reconnect to important peers on startup', async () => { const peerId = await createEd25519PeerId() @@ -343,31 +332,34 @@ describe('Connection Manager', () => { }) it('should deny connections when maxIncomingConnections is exceeded', async () => { - const dialer = stubInterface() - dialer.dial.resolves(stubInterface()) - - const connectionManager = new DefaultConnectionManager({ - peerId: libp2p.peerId, - upgrader: stubInterface(), - peerStore: stubInterface(), - dialer - }, { - ...defaultOptions, - maxIncomingConnections: 1 + libp2p = await createNode({ + config: createBaseOptions({ + connectionManager: { + maxIncomingConnections: 1 + } + }), + started: false }) - // max out the connection limit - await connectionManager.openConnection(await createEd25519PeerId()) + await libp2p.start() + + const connectionManager = libp2p.connectionManager as DefaultConnectionManager + + // max out the connection limit by having an inbound connection already + const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) + connection.stat.direction = 'inbound' + await connectionManager._onConnect(new CustomEvent('connection', { detail: connection })) + expect(connectionManager.getConnections()).to.have.lengthOf(1) - // an inbound connection is opened - const remotePeer = await createEd25519PeerId() - const maConn = mockMultiaddrConnection({ + // another inbound connection is opened + const remotePeer2 = await createEd25519PeerId() + const maConn2 = mockMultiaddrConnection({ source: [], sink: async () => {} - }, remotePeer) + }, remotePeer2) - await expect(connectionManager.acceptIncomingConnection(maConn)) + await expect(connectionManager.acceptIncomingConnection(maConn2)) .to.eventually.be.false() }) From c8ebca5359d1832968343e21b0cdb7c60a6eaa3b Mon Sep 17 00:00:00 2001 From: chad Date: Tue, 24 Jan 2023 09:48:59 -0500 Subject: [PATCH 16/16] test: updated test for outbound connection limits (#1508) --- .vscode/settings.json | 13 ++++++++++++ src/connection-manager/index.ts | 2 -- test/connection-manager/index.spec.ts | 30 +++++++++++++++------------ 3 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..6c6f460db0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "[markdown]": { + "editor.unicodeHighlight.ambiguousCharacters": false, + "editor.unicodeHighlight.invisibleCharacters": false, + "editor.wordWrap": "on", + "editor.quickSuggestions": { + "comments": "off", + "strings": "off", + "other": "off" + }, + "editor.formatOnSave": false + } +} diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index fd067fb631..eb74810c8e 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -558,8 +558,6 @@ export class DefaultConnectionManager extends EventEmitter { }) it('should throw an error when attempting to connect and maxOutgoingConnections is exceeded', async () => { - const dialer = stubInterface() - dialer.dial.resolves(stubInterface()) - - const connectionManager = new DefaultConnectionManager({ - peerId: libp2p.peerId, - upgrader: stubInterface(), - peerStore: stubInterface(), - dialer - }, { - ...defaultOptions, - maxOutgoingConnections: 1 + libp2p = await createNode({ + config: createBaseOptions({ + connectionManager: { + maxOutgoingConnections: 1 + } + }), + started: false }) - // max out the connection limit - await connectionManager.openConnection(await createEd25519PeerId()) + await libp2p.start() + + const connectionManager = libp2p.connectionManager as DefaultConnectionManager + + // max out the connection limit by having an inbound connection already + const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) + connection.stat.direction = 'outbound' + await connectionManager._onConnect(new CustomEvent('connection', { detail: connection })) + expect(connectionManager.getConnections()).to.have.lengthOf(1) + // another outbound connection is opened await expect(connectionManager.openConnection(await createEd25519PeerId())).to.eventually.be.rejected() .and.to.have.property('code', ErrorCodes.ERR_CONNECTION_DENIED) })