diff --git a/README.md b/README.md index 532aee9ed..21653fa74 100644 --- a/README.md +++ b/README.md @@ -318,3 +318,70 @@ npm run start ``` The dashboard will be made available at: `http://localhost:8000/dashboard/` + +## Networking in cloud environments or DMZ + +In order for your node to join the network, the others nodes needs to be able to connect to it. +All options can be controlled using [environment +variables](docs/environment-variables.md#p2p) + +To quickly start your node, you can keep all of the default values,but most likely it will hurt performance. If you want a customised approach, here are the full steps: + +- decide what IP version to use (IPV4 or/and IPv6). You should use both if available. +- decide if you want to filter private ips (if you run multiple nodes in a LAN or cloud environment, leave them on) +- if you already have an external ip configured on your machine, you are good to go. +- if you have a private ip, but an UPNP gateway, you should be fine as well. +- if you have a private ip and you can forward external ports from your gateway, use P2P_ANNOUNCE_ADDRESSES and let other nodes know your external IP/port. +- if you cannot forward ports on your gateway, the only choice is to use a circuit relay server (then all traffic will go through that node and it will proxy) + +In order to check connectivity, you can do the following: + +### On your node, check and observe how your node sees itself: + +```bash +curl http://localhost:8000/getP2pPeer?peerId=16Uiu2HAkwWe6BFQXZWg6zE9X7ExynvXEe9BRTR5Wn3udNs7JpUDx +``` + +and observe the addresses section: + +```json +{ + "addresses": [ + { "multiaddr": "/ip4/127.0.0.1/tcp/34227", "isCertified": false }, + { "multiaddr": "/ip4/127.0.0.1/tcp/36913/ws", "isCertified": false }, + { "multiaddr": "/ip4/172.15.0.1/tcp/34227", "isCertified": false }, + { "multiaddr": "/ip4/172.15.0.1/tcp/36913/ws", "isCertified": false }, + { "multiaddr": "/ip4/172.26.53.25/tcp/34227", "isCertified": false }, + { "multiaddr": "/ip4/172.26.53.25/tcp/36913/ws", "isCertified": false }, + { "multiaddr": "/ip6/::1/tcp/41157", "isCertified": false } + ], + "protocols": [ + "/floodsub/1.0.0", + "/ipfs/id/1.0.0", + "/ipfs/id/push/1.0.0", + "/ipfs/ping/1.0.0", + "/libp2p/autonat/1.0.0", + "/libp2p/circuit/relay/0.2.0/hop", + "/libp2p/circuit/relay/0.2.0/stop", + "/libp2p/dcutr", + "/meshsub/1.0.0", + "/meshsub/1.1.0", + "/ocean/nodes/1.0.0", + "/ocean/nodes/1.0.0/kad/1.0.0", + "/ocean/nodes/1.0.0/lan/kad/1.0.0" + ], + "metadata": {}, + "tags": {}, + "id": "16Uiu2HAkwWe6BFQXZWg6zE9X7ExynvXEe9BRTR5Wn3udNs7JpUDx", + "publicKey": "08021221021efd24150c233d689ade0f9f467aa6a5a2969a5f52d70c85caac8681925093e3" +} +``` + +Are any of those IPs reachable from other nodes? + +### To observe how your node is seen by others, start your node, wait a bit and then ask another node to give you details about you: + +```bash + curl http://node2.oceanprotocol.com:8000/getP2pPeer?peerId=16Uiu2HAk +wWe6BFQXZWg6zE9X7ExynvXEe9BRTR5Wn3udNs7JpUDx +``` diff --git a/dashboard/src/components/NodePeers/index.tsx b/dashboard/src/components/NodePeers/index.tsx index 52107c617..1da18b423 100644 --- a/dashboard/src/components/NodePeers/index.tsx +++ b/dashboard/src/components/NodePeers/index.tsx @@ -32,7 +32,7 @@ export default function NodePeers() { return (
-
Node Peers
+
Connected Nodes
{isLoadingNodePeers && (
diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 1682d9e3f..679d2c5fc 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -3,29 +3,40 @@ **Warning**: the names of some of these environment variables might change at some point in the future. This page lists the environment variables used by `ocean-node` and what effect -they have. - +they have. ## Core + - `PRIVATE_KEY` : Private key used by this node (applies to p2p peer id, asset encryption key, etc) -- `RPCS` : List of RPC URL for each chain. Example: +- `RPCS` : List of RPC URL for each chain. Example: + ```bash export RPC="{ \"1\": \"https://rpc.eth.gateway.fm\", \"137\": \"https://polygon.meowrpc.com\", \"80001\": \"https://rpc-mumbai.maticvigil.com\" }" ``` ## P2P + +- `P2P_ENABLE_IPV4` : Enable IPv4 conectivity. Defaults: True +- `P2P_ENABLE_IPV6` : Enable IPv6 conectivity. Defaults: True - `P2P_ipV4BindAddress` : Bind address for IPV4. Defaults to `0.0.0.0` - `P2P_ipV4BindTcpPort` : Port used on IPv4 TCP connections. Defaults to `0` (Use whatever port is free. When running as docker, please set it explicitly) - `P2P_ipV4BindWsPort` : Port used on IPv4 WS connections. Defaults to `0` (Use whatever port is free. When running as docker, please set it explicitly) - `P2P_ipV6BindAddress` : Bind address for IPV6. Defaults to `::1` - `P2P_ipV6BindTcpPort` : Port used on IPv6 TCP connections. Defaults to `0` (Use whatever port is free. When running as docker, please set it explicitly) - `P2P_ipV6BindWsPort` : Port used on IPv6 WS connections. Defaults to `0` (Use whatever port is free. When running as docker, please set it explicitly) -- `P2P_pubsubPeerDiscoveryInterval` : Interval (in ms) for discovery using pubsub. Defaults to `1000` (one second) +- `P2P_ANNOUNCE_ADDRESSES` : List of addresses to announce to the network. Example: ["/ip4/1.2.3.4/tcp/8000"] +- `P2P_ANNOUNCE_PRIVATE`: Announce private IPs. Default: True +- `P2P_pubsubPeerDiscoveryInterval` : Interval (in ms) for discovery using pubsub. Defaults to `1000` (one second) - `P2P_dhtMaxInboundStreams` : Maximum no of DHT inbound streams. Defaults to `500` - `P2P_dhtMaxOutboundStreams` : Maximum no of DHT outbound streams. Defaults to `500` -- `P2P_mDNSInterval` : Interval (in ms) for discovery using mDNS. Defaults to `20000` (20 seconds) +- `P2P_mDNSInterval` : Interval (in ms) for discovery using mDNS. Defaults to `20000` (20 seconds) - `P2P_connectionsMaxParallelDials` : Maximum no of parallel dials. Defaults to `150` -- `P2P_connectionsDialTimeout`: Timeout for dial commands. Defaults to `10000` (10 seconds) +- `P2P_connectionsDialTimeout`: Timeout for dial commands. Defaults to `10000` (10 seconds) +- `P2P_ENABLE_UPNP`: Enable UPNP gateway discovery. Default: True +- `P2P_ENABLE_AUTONAT`: Enable AutoNAT discovery. Default: True +- `P2P_ENABLE_CIRCUIT_RELAY_SERVER`: Enable Circuit Relay Server. It will help network, but increase your bandwith usage. Should be disabled for edge nodes. Default: True +- `P2P_ENABLE_CIRCUIT_RELAY_CLIENT`: Enable conections through relay servers. Default: True ## HTTP -- `HTTP_API_PORT` : Port used for HTTP interface. Defaults to `8000` \ No newline at end of file + +- `HTTP_API_PORT` : Port used for HTTP interface. Defaults to `8000` diff --git a/src/@types/OceanNode.ts b/src/@types/OceanNode.ts index e05912ef1..64f13059c 100644 --- a/src/@types/OceanNode.ts +++ b/src/@types/OceanNode.ts @@ -24,6 +24,8 @@ export interface OceanNodeKeys { export interface OceanNodeP2PConfig { bootstrapNodes: string[] + enableIPV4: boolean + enableIPV6: boolean ipV4BindAddress: string | null ipV4BindTcpPort: number | null ipV4BindWsPort: number | null @@ -36,6 +38,12 @@ export interface OceanNodeP2PConfig { mDNSInterval: number connectionsMaxParallelDials: number connectionsDialTimeout: number + announceAddresses: string[] + autoNat: boolean + upnp: boolean + enableCircuitRelayServer: boolean + enableCircuitRelayClient: boolean + announcePrivateIp: boolean } export interface OceanNodeConfig { diff --git a/src/components/P2P/handlers.ts b/src/components/P2P/handlers.ts index 551560777..e4dde59cd 100644 --- a/src/components/P2P/handlers.ts +++ b/src/components/P2P/handlers.ts @@ -1,54 +1,2 @@ export * from './handleBroadcasts.js' export * from './handleProtocolCommands.js' - -export function handlePeerConnect(details: any) { - if (details) { - // const peerId = details.detail - // console.log('Connection established to:', peerId.toString()) // Emitted when a peer has been found - /* - try{ - this._libp2p.services.pubsub.connect(peerId.toString()) - } - catch(e){ - console.log(e) - console.log("Failed to connect pubsub") - } - */ - } - // else{ - // console.log("Null evt ") - // } -} - -export function handlePeerDisconnect(details: any) { - // const peerId = details.detail - // console.log('Connection closed to:', peerId.toString()) // Emitted when a peer has been found -} - -export function handlePeerDiscovery(details: any) { - // const peerInfo = details.detail - // console.log('Discovered new peer:', peerInfo.id.toString()) - // console.log(details.detail) - /* - try{ - //this._libp2p.services.pubsub.connect(peerInfo.id.toString()) - this._libp2p.services.dht.connect(peerInfo.id.toString()) - } - catch(e){ - console.log(e) - console.log("Failed to connect pubsub") - } - */ -} - -export function handlePeerJoined(details: any) { - // console.log('New peer joined us:', details) -} - -export function handlePeerLeft(details: any) { - // console.log('New peer joined us:', details) -} - -export function handleSubscriptionCHange(details: any) { - // console.log('subscription-change:', details.detail) -} diff --git a/src/components/P2P/index.ts b/src/components/P2P/index.ts index b1adee9b7..888300755 100644 --- a/src/components/P2P/index.ts +++ b/src/components/P2P/index.ts @@ -5,9 +5,9 @@ import clone from 'lodash.clonedeep' import { handleBroadcasts, - handlePeerConnect, - handlePeerDiscovery, - handlePeerDisconnect, + // handlePeerConnect, + // handlePeerDiscovery, + // handlePeerDisconnect, handleProtocolCommands } from './handlers.js' @@ -32,7 +32,6 @@ import { autoNAT } from '@libp2p/autonat' import { uPnPNAT } from '@libp2p/upnp-nat' import { ping } from '@libp2p/ping' import { dcutr } from '@libp2p/dcutr' - import { kadDHT } from '@libp2p/kad-dht' import { gossipsub } from '@chainsafe/libp2p-gossipsub' @@ -40,7 +39,8 @@ import { EVENTS, cidFromRawString } from '../../utils/index.js' import { Transform } from 'stream' import { Database } from '../database' import { OceanNodeConfig, FindDDOResponse } from '../../@types/OceanNode' - +// eslint-disable-next-line camelcase +import is_ip_private from 'private-ip' import { GENERIC_EMOJIS, LOG_LEVELS_STR, @@ -49,6 +49,7 @@ import { import { INDEXER_DDO_EVENT_EMITTER } from '../Indexer/index.js' import { P2P_LOGGER } from '../../utils/logging/common.js' import { CoreHandlersRegistry } from '../core/handler/coreHandlersRegistry' +import { multiaddr } from '@multiformats/multiaddr' const DEFAULT_OPTIONS = { pollInterval: 1000 @@ -110,6 +111,15 @@ export class OceanP2P extends EventEmitter { this._topic = 'oceanprotocol' this._libp2p = await this.createNode(this._config) + this._libp2p.addEventListener('peer:connect', (evt: any) => { + this.handlePeerConnect(evt) + }) + this._libp2p.addEventListener('peer:disconnect', (evt: any) => { + this.handlePeerDisconnect(evt) + }) + this._libp2p.addEventListener('peer:discovery', (evt: any) => { + this.handlePeerDiscovery(evt) + }) this._options = Object.assign({}, clone(DEFAULT_OPTIONS), clone(options)) this._peers = [] this._connections = {} @@ -134,92 +144,204 @@ export class OceanP2P extends EventEmitter { }) } + handlePeerConnect(details: any) { + if (details) { + const peerId = details.detail + P2P_LOGGER.debug('Connection established to:' + peerId.toString()) // Emitted when a peer has been found + try { + this._libp2p.services.pubsub.connect(peerId.toString()) + } catch (e) {} + } else { + /* empty */ + } + } + + handlePeerDisconnect(details: any) { + const peerId = details.detail + P2P_LOGGER.debug('Connection closed to:' + peerId.toString()) // Emitted when a peer has been found + } + + handlePeerDiscovery(details: any) { + const peerInfo = details.detail + P2P_LOGGER.debug('Discovered new peer:' + peerInfo.id.toString()) + } + + handlePeerJoined(details: any) { + P2P_LOGGER.debug('New peer joined us:' + details) + } + + handlePeerLeft(details: any) { + P2P_LOGGER.debug('Peer left us:' + details) + } + + handlePeerMessage(details: any) { + P2P_LOGGER.debug('peer joined us:' + details) + } + + handleSubscriptionCHange(details: any) { + P2P_LOGGER.debug('subscription-change:' + details.detail) + } + + shouldAnnounce(addr: any) { + const maddr = multiaddr(addr) + if ( + this._config.p2pConfig.announcePrivateIp === false && + is_ip_private(maddr.nodeAddress().address) + ) { + // disabled logs because of flooding + // P2P_LOGGER.debug('Deny announcment of ' + maddr.nodeAddress().address) + return false + } else { + // disabled logs because of flooding + // P2P_LOGGER.debug('Allow announcment of ' + maddr.nodeAddress().address) + return true + } + } + async createNode(config: OceanNodeConfig): Promise { try { this._publicAddress = config.keys.peerId.toString() this._publicKey = config.keys.publicKey this._privateKey = config.keys.privateKey - /** @type {import('libp2p').Libp2pOptions} */ // start with some default, overwrite based on config later - const options = { + let servicesConfig = { + identify: identify(), + pubsub: gossipsub({ + allowPublishToZeroPeers: true + // canRelayMessage: true, + // enabled: true + }), + dht: kadDHT({ + // this is necessary because this node is not connected to the public network + // it can be removed if, for example bootstrappers are configured + allowQueryWithZeroPeers: true, + maxInboundStreams: config.p2pConfig.dhtMaxInboundStreams, + maxOutboundStreams: config.p2pConfig.dhtMaxOutboundStreams, + + clientMode: false, // this should be true for edge devices + kBucketSize: 20, + protocolPrefix: '/ocean/nodes/1.0.0' + // randomWalk: { + // enabled: true, // Allows to disable discovery (enabled by default) + // interval: 300e3, + // timeout: 10e3 + // } + }), + ping: ping(), + dcutr: dcutr() + } + // eslint-disable-next-line no-constant-condition, no-self-compare + if (config.p2pConfig.enableCircuitRelayServer) { + servicesConfig = { ...servicesConfig, ...{ circuitRelay: circuitRelayServer() } } + } + // eslint-disable-next-line no-constant-condition, no-self-compare + if (config.p2pConfig.upnp) { + servicesConfig = { ...servicesConfig, ...{ upnpNAT: uPnPNAT() } } + } + // eslint-disable-next-line no-constant-condition, no-self-compare + if (config.p2pConfig.autoNat) { + servicesConfig = { ...servicesConfig, ...{ autoNAT: autoNAT() } } + } + const bindInterfaces = [] + if (config.p2pConfig.enableIPV4) { + bindInterfaces.push( + `/ip4/${config.p2pConfig.ipV4BindAddress}/tcp/${config.p2pConfig.ipV4BindTcpPort}` + ) + bindInterfaces.push( + `/ip4/${config.p2pConfig.ipV4BindAddress}/tcp/${config.p2pConfig.ipV4BindWsPort}/ws` + ) + } + if (config.p2pConfig.enableIPV4) { + bindInterfaces.push( + `/ip6/${config.p2pConfig.ipV6BindAddress}/tcp/${config.p2pConfig.ipV6BindTcpPort}` + ) + bindInterfaces.push( + `/ip6/${config.p2pConfig.ipV6BindAddress}/tcp/${config.p2pConfig.ipV6BindWsPort}/ws` + ) + } + let transports = [] + if (config.p2pConfig.enableCircuitRelayClient) { + transports = [ + webSockets(), + tcp(), + circuitRelayTransport(/* { + discoverRelays: 2 + } */) + ] + } else { + transports = [webSockets(), tcp()] + } + let options = { addresses: { - listen: [ - `/ip4/${config.p2pConfig.ipV4BindAddress}/tcp/${config.p2pConfig.ipV4BindTcpPort}`, - `/ip4/${config.p2pConfig.ipV4BindAddress}/tcp/${config.p2pConfig.ipV4BindWsPort}/ws`, - `/ip6/${config.p2pConfig.ipV6BindAddress}/tcp/${config.p2pConfig.ipV6BindTcpPort}`, - `/ip6/${config.p2pConfig.ipV6BindAddress}/tcp/${config.p2pConfig.ipV6BindWsPort}/ws` - ] + listen: bindInterfaces, + announceFilter: (multiaddrs: any[]) => + multiaddrs.filter((m) => this.shouldAnnounce(m)) }, peerId: config.keys.peerId, - transports: [webSockets(), tcp(), circuitRelayTransport()], + transports, streamMuxers: [yamux(), mplex()], connectionEncryption: [ noise() // plaintext() ], - peerDiscovery: [ - bootstrap({ - list: config.p2pConfig.bootstrapNodes - }), - pubsubPeerDiscovery({ - interval: config.p2pConfig.pubsubPeerDiscoveryInterval, - topics: [ - 'oceanprotocoldiscovery', - `oceanprotocol._peer-discovery._p2p._pubsub`, // It's recommended but not required to extend the global space - '_peer-discovery._p2p._pubsub' // Include if you want to participate in the global space - ], - listenOnly: false - }), - mdns({ - interval: config.p2pConfig.mDNSInterval - }) - ], - services: { - identify: identify(), - pubsub: gossipsub({ - allowPublishToZeroPeers: true - // canRelayMessage: true, - // enabled: true - }), - dht: kadDHT({ - // this is necessary because this node is not connected to the public network - // it can be removed if, for example bootstrappers are configured - allowQueryWithZeroPeers: true, - maxInboundStreams: config.p2pConfig.dhtMaxInboundStreams, - maxOutboundStreams: config.p2pConfig.dhtMaxOutboundStreams, - - clientMode: false, // this should be true for edge devices - kBucketSize: 20, - protocolPrefix: '/ocean/nodes/1.0.0' - // randomWalk: { - // enabled: true, // Allows to disable discovery (enabled by default) - // interval: 300e3, - // timeout: 10e3 - // } - }), - autoNAT: autoNAT(), - upnpNAT: uPnPNAT(), - ping: ping(), - dcutr: dcutr(), - circuitRelay: circuitRelayServer() - }, + services: servicesConfig, connectionManager: { maxParallelDials: config.p2pConfig.connectionsMaxParallelDials, // 150 total parallel multiaddr dials dialTimeout: config.p2pConfig.connectionsDialTimeout // 10 second dial timeout per peer dial } } + if (config.p2pConfig.bootstrapNodes && config.p2pConfig.bootstrapNodes.length > 0) { + options = { + ...options, + ...{ + peerDiscovery: [ + bootstrap({ + list: config.p2pConfig.bootstrapNodes, + timeout: 1000, // in ms, + tagName: 'bootstrap', + tagValue: 50, + tagTTL: 10000000000 + }), + mdns({ + interval: config.p2pConfig.mDNSInterval + }), + pubsubPeerDiscovery({ + interval: config.p2pConfig.pubsubPeerDiscoveryInterval, + topics: [ + 'oceanprotocoldiscovery', + `oceanprotocol._peer-discovery._p2p._pubsub`, // It's recommended but not required to extend the global space + '_peer-discovery._p2p._pubsub' // Include if you want to participate in the global space + ], + listenOnly: false + }) + ] + } + } + } else { + // only mdns & pubsubPeerDiscovery + options = { + ...options, + ...{ + peerDiscovery: [ + mdns({ + interval: config.p2pConfig.mDNSInterval + }), + pubsubPeerDiscovery({ + interval: config.p2pConfig.pubsubPeerDiscoveryInterval, + topics: [ + 'oceanprotocoldiscovery', + `oceanprotocol._peer-discovery._p2p._pubsub`, // It's recommended but not required to extend the global space + '_peer-discovery._p2p._pubsub' // Include if you want to participate in the global space + ], + listenOnly: false + }) + ] + } + } + } const node = await createLibp2p(options) await node.start() - node.addEventListener('peer:connect', (evt: any) => { - handlePeerConnect(evt) - }) - node.addEventListener('peer:disconnect', (evt: any) => { - handlePeerDisconnect(evt) - }) - node.addEventListener('peer:discovery', (evt: any) => { - handlePeerDiscovery(evt) - }) // node.services.pubsub.addEventListener( 'peer joined', (evt:any) => {handlePeerJoined(evt)}) // node.services.pubsub.addEventListener('peer left', (evt:any) => {handlePeerLeft(evt)}) @@ -245,10 +367,14 @@ export class OceanP2P extends EventEmitter { node.services.pubsub.subscribe(this._topic) node.services.pubsub.publish(this._topic, encoding('online')) // ;(node.services.upnpNAT as any).mapIpAddresses() - ;(node.services.upnpNAT as any).mapIpAddresses().catch((err: any) => { - // hole punching errors are non-fatal - console.error(err) - }) + const upnpService = (node.services as any).upnpNAT + if (config.p2pConfig.upnp && upnpService) { + ;(upnpService as any).mapIpAddresses().catch((err: any) => { + // hole punching errors are non-fatal + P2P_LOGGER.info('Failed to configure UPNP Gateway(if you have one)') + P2P_LOGGER.debug(err) + }) + } return node } catch (e) { P2P_LOGGER.logMessageWithEmoji( @@ -269,12 +395,42 @@ export class OceanP2P extends EventEmitter { // } } - getPeers() { - return this._peers.slice(0) + async getRunningOceanPeers() { + return await this.getOceanPeers() } - hasPeer(peer: any) { - return Boolean(this._peers.find((p) => p.toString() === peer.toString())) + async getKnownOceanPeers() { + return await this.getOceanPeers(false, true) + } + + async getAllOceanPeers() { + return await this.getOceanPeers(true, true) + } + + async getOceanPeers(running: boolean = true, known: boolean = false) { + const peers: string[] = [] + + // get pubsub peers + for (const peer of this._peers.slice(0)) { + if (!peers.includes(peer.toString)) peers.push(peer.toString()) + } + // get p2p peers and filter them by protocol + for (const peer of await this._libp2p.peerStore.all()) { + if (peer && peer.protocols) { + for (const protocol of peer.protocols) { + if (protocol === this._protocol) { + if (!peers.includes(peer.id.toString())) peers.push(peer.id.toString()) + } + } + } + } + + return peers + } + + async hasPeer(peer: any) { + const s = await this._libp2p.peerStore.all() + return Boolean(s.find((p: any) => p.toString() === peer.toString())) } async broadcast(_message: any) { @@ -390,7 +546,6 @@ export class OceanP2P extends EventEmitter { async _pollPeers() { const node = this._libp2p const newPeers = (await node.services.pubsub.getSubscribers(this._topic)).sort() - if (this._emitChanges(newPeers)) { const addedNew = newPeers.length > this._peers.length this._peers = newPeers @@ -413,8 +568,8 @@ export class OceanP2P extends EventEmitter { differences.added.forEach((peer: any) => this.emit('peer joined', peer)) differences.removed.forEach((peer: any) => this.emit('peer left', peer)) - - return differences.added.length > 0 || differences.removed.length > 0 + const x = differences.added.length > 0 || differences.removed.length > 0 + return x } _onMessage(event: any) { @@ -428,14 +583,14 @@ export class OceanP2P extends EventEmitter { async advertiseDid(did: string) { P2P_LOGGER.logMessage('Advertising ' + did, true) try { - const x = this._peers.length + const x = (await this.getRunningOceanPeers()).length if (x > 0) { const cid = await cidFromRawString(did) const multiAddrs = this._libp2p.components.addressManager.getAddresses() // console.log('multiaddrs: ', multiAddrs) await this._libp2p.contentRouting.provide(cid, multiAddrs) } else { - P2P_LOGGER.warn( + P2P_LOGGER.verbose( 'Could not find any Ocean peers. Nobody is listening at the moment, skipping...' ) // save it for retry later diff --git a/src/components/httpRoutes/getOceanPeers.ts b/src/components/httpRoutes/getOceanPeers.ts index 156aec5fa..17ee156b7 100644 --- a/src/components/httpRoutes/getOceanPeers.ts +++ b/src/components/httpRoutes/getOceanPeers.ts @@ -9,7 +9,7 @@ getOceanPeersRoute.get( '/getOceanPeers', async (req: Request, res: Response): Promise => { if (hasP2PInterface) { - const peers = await req.oceanNode.getP2PNode().getPeers() + const peers = await req.oceanNode.getP2PNode().getRunningOceanPeers() P2P_LOGGER.log(getDefaultLevel(), `getOceanPeers: ${peers}`, true) res.json(peers) } else { diff --git a/src/test/unit/oceanP2P.test.ts b/src/test/unit/oceanP2P.test.ts index adee3b9b5..c0b023d93 100644 --- a/src/test/unit/oceanP2P.test.ts +++ b/src/test/unit/oceanP2P.test.ts @@ -15,6 +15,7 @@ describe('OceanP2P Test', () => { let node2: OceanP2P let config1: any let config2: any + const mDNSInterval: number = 1 const envOverrides = buildEnvOverrideConfig( [ @@ -36,6 +37,11 @@ describe('OceanP2P Test', () => { process.env.PRIVATE_KEY = process.env.NODE1_PRIVATE_KEY config1 = await getConfiguration(true) config1.p2pConfig.ipV4BindTcpPort = 0 + // we don't need bootstrap nodes, we rely on Multicast DNS + config1.p2pConfig.mDNSInterval = mDNSInterval * 1e3 + config1.p2pConfig.bootstrapNodes = [] + // enable private IP + config1.p2pConfig.announcePrivateIp = true node1 = new OceanP2P(config1, null) await node1.start() assert(node1, 'Failed to create P2P Node instance') @@ -44,6 +50,11 @@ describe('OceanP2P Test', () => { process.env.PRIVATE_KEY = process.env.NODE2_PRIVATE_KEY config2 = await getConfiguration(true) config2.p2pConfig.ipV4BindTcpPort = 0 + // we don't need bootstrap nodes, we rely on Multicast DNS + config2.p2pConfig.mDNSInterval = mDNSInterval * 1e3 + config2.p2pConfig.bootstrapNodes = [] + // enable private IP + config2.p2pConfig.announcePrivateIp = true node2 = new OceanP2P(config2, null) await node2.start() assert(node2, 'Failed to create P2P Node instance') @@ -58,7 +69,7 @@ describe('OceanP2P Test', () => { 'Peer missmatch for node2' ) }) - delay(1000) + delay(mDNSInterval * 1e3 * 2) it('Start check if nodes are connected', async () => { const allPeers1 = await node1.getAllPeerStore() const peers1 = allPeers1.map((a: any) => a.id.toString()) @@ -74,16 +85,14 @@ describe('OceanP2P Test', () => { ) }) it('Start check if nodes are connected with pubsub', async () => { - let peers = await node1.getPeers() - const peers1 = peers.map((p) => p.toString()) + let peers = await node1.getOceanPeers() assert( - peers1.includes(config2.keys.peerId.toString()), + peers.includes(config2.keys.peerId.toString()), 'Node2 not found in node1 peer list' ) - peers = await node2.getPeers() - const peers2 = peers.map((p) => p.toString()) + peers = await node2.getOceanPeers() assert( - peers2.includes(config1.keys.peerId.toString()), + peers.includes(config1.keys.peerId.toString()), 'Node1 not found in node2 peer list' ) }) diff --git a/src/utils/config.ts b/src/utils/config.ts index 65add4f08..a5457765d 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -82,6 +82,9 @@ function getSupportedChains(): RPCS | null { return supportedNetworks } +function getP2PAnnounceAddresses(isStartup?: boolean): string[] { + return readListFromEnvVariable(ENVIRONMENT_VARIABLES.P2P_ANNOUNCE_ADDRESSES, isStartup) +} // valid decrypthers function getAuthorizedDecrypters(isStartup?: boolean): string[] { return readAddressListFromEnvVariable( @@ -101,8 +104,8 @@ export function getAllowedAdmins(isStartup?: boolean): string[] { return readAddressListFromEnvVariable(ENVIRONMENT_VARIABLES.ALLOWED_ADMINS, isStartup) } -// whenever we want to read an array of addresses from an env variable, use this common function -function readAddressListFromEnvVariable(envVariable: any, isStartup?: boolean): string[] { +// whenever we want to read an array of strings from an env variable, use this common function +function readListFromEnvVariable(envVariable: any, isStartup?: boolean): string[] { const { name } = envVariable try { if (!existsEnvironmentVariable(envVariable, isStartup)) { @@ -118,7 +121,7 @@ function readAddressListFromEnvVariable(envVariable: any, isStartup?: boolean): ) return [] } - return addressesRaw.map((address) => getAddress(address)) + return addressesRaw } catch (error) { CONFIG_LOGGER.logMessageWithEmoji( `Missing or Invalid address(es) in ${name} env variable`, @@ -130,6 +133,11 @@ function readAddressListFromEnvVariable(envVariable: any, isStartup?: boolean): } } +// whenever we want to read an array of addresses from an env variable, use this common function +function readAddressListFromEnvVariable(envVariable: any, isStartup?: boolean): string[] { + const addressesRaw: string[] = readListFromEnvVariable(envVariable, isStartup) + return addressesRaw.map((address) => getAddress(address)) +} /** * get default values for provider fee tokens * @param supportedNetworks chains that we support @@ -422,26 +430,29 @@ async function getEnvConfig(isStartup?: boolean): Promise { hasP2P: interfaces.includes('P2P'), p2pConfig: { bootstrapNodes: [ + // Public IPFS bootstraps '/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', '/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', '/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', - // '/ip4/127.0.0.12/tcp/49100/p2p/12D3KooWLktGvbzuDK7gv1kS4pq6DNWxmxEREKVtBEhVFQmDNni7' + // OPF nodes + // '/dns4/node1.oceanprotocol.com/tcp/9000/p2p/' + '/dns4/node2.oceanprotocol.com/tcp/9000/p2p/16Uiu2HAm6u88XuC4Xke7J9NmT7qLNL4zMYEyLxqdVgAc7Rnr95o6', + // '/dns4/node3.oceanprotocol.com/tcp/9000/p2p/' + // OPF developer nodes '/ip4/35.198.125.13/tcp/8000/p2p/16Uiu2HAmKZuuY2Lx3JiY938rJWZrYQh6kjBZCNrh3ALkodtwFRdF', // paulo - '/ip4/34.159.64.236/tcp/8000/p2p/16Uiu2HAmAy1GcZGhzFT3cbARTmodg9c3M4EAmtBZyDgu5cSL1NPr', // jaime + '/ip4/35.209.77.64/tcp/8000/p2p/16Uiu2HAmFxPwhW5dmoLZnbqXFyUvr6j1PzCB1mBxRUZHGsoqQoSQ', '/ip4/34.107.3.14/tcp/8000/p2p/16Uiu2HAm4DWmX56ZX2bKjvARJQZPMUZ9xsdtAfrMmd7P8czcN4UT', // maria - // LOCAL - // TODO check: we might need to have an option to use local node as a bootstrap one - // '/ip4/127.0.0.1/tcp/8000/p2p/16Uiu2HAkuYfgjXoGcSSLSpRPD6XtUgV71t5RqmTmcqdbmrWY9MJo', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp', - '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt' + '/dnsaddr/ocean-node3.oceanprotocol.io/tcp/8000/p2p/16Uiu2HAm96Sx6o8XCEifPL9MtJiZCSzKqiBQApnZ6JWd7be4zwNK' // bogdan ], + enableIPV4: process.env.P2P_ENABLE_IPV4 !== 'false', + enableIPV6: process.env.P2P_ENABLE_IPV6 !== 'false', ipV4BindAddress: getEnvValue(process.env.P2P_ipV4BindAddress, '0.0.0.0'), ipV4BindTcpPort: getIntEnvValue(process.env.P2P_ipV4BindTcpPort, 0), ipV4BindWsPort: getIntEnvValue(process.env.P2P_ipV4BindWsPort, 0), ipV6BindAddress: getEnvValue(process.env.P2P_ipV6BindAddress, '::1'), ipV6BindTcpPort: getIntEnvValue(process.env.P2P_ipV6BindTcpPort, 0), ipV6BindWsPort: getIntEnvValue(process.env.P2P_ipV6BindWsPort, 0), + announceAddresses: getP2PAnnounceAddresses(isStartup), pubsubPeerDiscoveryInterval: getIntEnvValue( process.env.P2P_pubsubPeerDiscoveryInterval, 1000 @@ -453,7 +464,15 @@ async function getEnvConfig(isStartup?: boolean): Promise { process.env.P2P_connectionsMaxParallelDials, 150 ), - connectionsDialTimeout: getIntEnvValue(process.env.P2P_connectionsDialTimeout, 10e3) // 10 seconds + connectionsDialTimeout: getIntEnvValue( + process.env.P2P_connectionsDialTimeout, + 30e3 + ), // 10 seconds + upnp: process.env.P2P_ENABLE_UPNP !== 'false', + autoNat: process.env.P2P_ENABLE_AUTONAT !== 'false', + enableCircuitRelayServer: process.env.P2P_ENABLE_CIRCUIT_RELAY_SERVER !== 'false', + enableCircuitRelayClient: process.env.P2P_ENABLE_CIRCUIT_RELAY_CLIENT !== 'false', + announcePrivateIp: process.env.P2P_ANNOUNCE_PRIVATE !== 'false' }, // Only enable provider if we have a DB_URL hasProvider: !!getEnvValue(process.env.DB_URL, ''), diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 5bd5852f1..f10aff2b5 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -173,6 +173,12 @@ export const ENVIRONMENT_VARIABLES: Record = { value: process.env.ADDRESS_FILE, required: false }, + // p2p specific + P2P_ANNOUNCE_ADDRESSES: { + name: 'P2P_ANNOUNCE_ADDRESSES', + value: process.env.P2P_ANNOUNCE_ADDRESSES, + required: false + }, // node specific NODE_ENV: { name: 'NODE_ENV', value: process.env.NODE_ENV, required: false }, AUTHORIZED_DECRYPTERS: {