Skip to content

Commit

Permalink
feat: add IPv6 support (#5758)
Browse files Browse the repository at this point in the history
* feat: add IPv6 support

* fix: ip -> ip4

* fix: add parens

* fix: update cli flag descriptions
  • Loading branch information
wemeetagain authored Jul 17, 2023
1 parent e3eb055 commit ec81531
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 47 deletions.
2 changes: 1 addition & 1 deletion packages/beacon-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"@chainsafe/as-sha256": "^0.3.1",
"@chainsafe/bls": "7.1.1",
"@chainsafe/blst": "^0.2.9",
"@chainsafe/discv5": "^4.0.0",
"@chainsafe/discv5": "^5.0.0",
"@chainsafe/libp2p-gossipsub": "^9.1.0",
"@chainsafe/libp2p-noise": "^12.0.1",
"@chainsafe/persistent-merkle-tree": "^0.5.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/network/discv5/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class Discv5Worker extends (EventEmitter as {new (): StrictEventEmitter<E
const workerData: Discv5WorkerData = {
enr: opts.discv5.enr,
peerIdProto: exportToProtobuf(opts.peerId),
bindAddr: opts.discv5.bindAddr,
bindAddrs: opts.discv5.bindAddrs,
config: opts.discv5.config ?? {},
bootEnrs: opts.discv5.bootEnrs,
metrics: Boolean(opts.metrics),
Expand Down
18 changes: 16 additions & 2 deletions packages/beacon-node/src/network/discv5/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,32 @@ import {LoggerNodeOpts} from "@lodestar/logger/node";
// TODO export IDiscv5Config so we don't need this convoluted type
type Discv5Config = Parameters<(typeof Discv5)["create"]>[0]["config"];

type BindAddrs =
| {
ip4: string;
ip6?: string;
}
| {
ip4?: string;
ip6: string;
}
| {
ip4: string;
ip6: string;
};

export type LodestarDiscv5Opts = {
config?: Discv5Config;
enr: string;
bindAddr: string;
bindAddrs: BindAddrs;
bootEnrs: string[];
};

/** discv5 worker constructor data */
export interface Discv5WorkerData {
enr: string;
peerIdProto: Uint8Array;
bindAddr: string;
bindAddrs: BindAddrs;
config: Discv5Config;
bootEnrs: string[];
metrics: boolean;
Expand Down
17 changes: 12 additions & 5 deletions packages/beacon-node/src/network/discv5/worker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import worker from "node:worker_threads";
import {createFromProtobuf} from "@libp2p/peer-id-factory";
import {multiaddr} from "@multiformats/multiaddr";
import {Multiaddr, multiaddr} from "@multiformats/multiaddr";
import {Gauge} from "prom-client";
import {expose} from "@chainsafe/threads/worker";
import {Observable, Subject} from "@chainsafe/threads/observable";
Expand Down Expand Up @@ -48,7 +48,10 @@ const config = createBeaconConfig(workerData.chainConfig, workerData.genesisVali
const discv5 = Discv5.create({
enr: SignableENR.decodeTxt(workerData.enr, keypair),
peerId,
multiaddr: multiaddr(workerData.bindAddr),
bindAddrs: {
ip4: (workerData.bindAddrs.ip4 ? multiaddr(workerData.bindAddrs.ip4) : undefined) as Multiaddr,
ip6: workerData.bindAddrs.ip6 ? multiaddr(workerData.bindAddrs.ip6) : undefined,
},
config: workerData.config,
metricsRegistry,
});
Expand Down Expand Up @@ -105,8 +108,12 @@ const module: Discv5WorkerApi = {

expose(module);

logger.info("discv5 worker started", {
const logData: Record<string, string> = {
peerId: peerId.toString(),
listenAddr: workerData.bindAddr,
initialENR: workerData.enr,
});
};

if (workerData.bindAddrs.ip4) logData.bindAddr4 = workerData.bindAddrs.ip4;
if (workerData.bindAddrs.ip6) logData.bindAddr6 = workerData.bindAddrs.ip6;

logger.info("discv5 worker started", logData);
4 changes: 3 additions & 1 deletion packages/beacon-node/test/e2e/network/mdns.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ describe.skip("mdns", function () {
discv5FirstQueryDelayMs: 0,
discv5: {
enr: enr.encodeTxt(),
bindAddr: bindAddrUdp,
bindAddrs: {
ip4: bindAddrUdp,
},
bootEnrs: [],
},
};
Expand Down
8 changes: 6 additions & 2 deletions packages/beacon-node/test/unit/network/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ describe("createNodeJsLibp2p", () => {
connectToDiscv5Bootnodes: true,
discv5: {
enr: SignableENR.createV4(keypair).encodeTxt(),
bindAddr: "/ip4/127.0.0.1/udp/0",
bindAddrs: {
ip4: "/ip4/127.0.0.1/udp/0",
},
bootEnrs: enrWithTcp,
},
bootMultiaddrs,
Expand Down Expand Up @@ -81,7 +83,9 @@ describe("createNodeJsLibp2p", () => {
connectToDiscv5Bootnodes: true,
discv5: {
enr: SignableENR.createV4(keypair).encodeTxt(),
bindAddr: "/ip4/127.0.0.1/udp/0",
bindAddrs: {
ip4: "/ip4/127.0.0.1/udp/0",
},
bootEnrs: enrWithoutTcp,
},
bootMultiaddrs,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"@chainsafe/bls-keygen": "^0.3.0",
"@chainsafe/bls-keystore": "^2.0.0",
"@chainsafe/blst": "^0.2.9",
"@chainsafe/discv5": "^4.0.0",
"@chainsafe/discv5": "^5.0.0",
"@chainsafe/ssz": "^0.10.2",
"@chainsafe/threads": "^1.11.1",
"@libp2p/crypto": "^1.0.0",
Expand Down
50 changes: 28 additions & 22 deletions packages/cli/src/cmds/beacon/initPeerIdAndEnr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {createKeypairFromPeerId, SignableENR} from "@chainsafe/discv5";
import {Logger} from "@lodestar/utils";
import {exportToJSON, readPeerId} from "../../config/index.js";
import {writeFile600Perm} from "../../util/file.js";
import {defaultP2pPort} from "../../options/beaconNodeOptions/network.js";
import {parseListenArgs} from "../../options/beaconNodeOptions/network.js";
import {BeaconArgs} from "./options.js";

/**
Expand Down Expand Up @@ -53,40 +53,46 @@ export function isLocalMultiAddr(multiaddr: Multiaddr | undefined): boolean {
return false;
}

export function clearMultiaddrUDP(enr: SignableENR): void {
// enr.multiaddrUDP = undefined in new version
enr.delete("ip");
enr.delete("udp");
enr.delete("ip6");
enr.delete("udp6");
}

export function overwriteEnrWithCliArgs(enr: SignableENR, args: BeaconArgs, logger: Logger): void {
// TODO: Not sure if we should propagate port/defaultP2pPort options to the ENR
enr.tcp = args["enr.tcp"] ?? args.port ?? defaultP2pPort;
const udpPort = args["enr.udp"] ?? args.discoveryPort ?? args.port ?? defaultP2pPort;
if (udpPort != null) enr.udp = udpPort;
if (args["enr.ip"] != null) enr.ip = args["enr.ip"];
if (args["enr.ip6"] != null) enr.ip6 = args["enr.ip6"];
if (args["enr.tcp6"] != null) enr.tcp6 = args["enr.tcp6"];
if (args["enr.udp6"] != null) enr.udp6 = args["enr.udp6"];
const {listenAddress, port, discoveryPort, listenAddress6, port6, discoveryPort6} = parseListenArgs(args);
enr.ip = args["enr.ip"] ?? listenAddress;
enr.tcp = args["enr.tcp"] ?? port;
enr.udp = args["enr.udp"] ?? discoveryPort;
enr.ip6 = args["enr.ip6"] ?? listenAddress6;
enr.tcp6 = args["enr.tcp6"] ?? port6;
enr.udp6 = args["enr.udp6"] ?? discoveryPort6;

const udpMultiaddr = enr.getLocationMultiaddr("udp");
if (udpMultiaddr) {
const isLocal = isLocalMultiAddr(udpMultiaddr);
function testMultiaddrForLocal(mu: Multiaddr, ip4: boolean): void {
const isLocal = isLocalMultiAddr(mu);
if (args.nat) {
if (isLocal) {
logger.warn("--nat flag is set with no purpose");
}
} else {
if (!isLocal) {
logger.warn(
"Configured ENR IP address is not local, clearing ENR IP and UDP. Set the --nat flag to prevent this"
`Configured ENR ${ip4 ? "IPv4" : "IPv6"} address is not local, clearing ENR ${ip4 ? "ip" : "ip6"} and ${
ip4 ? "udp" : "udp6"
}. Set the --nat flag to prevent this`
);
clearMultiaddrUDP(enr);
if (ip4) {
enr.delete("ip");
enr.delete("udp");
} else {
enr.delete("ip6");
enr.delete("udp6");
}
}
}
}
const udpMultiaddr4 = enr.getLocationMultiaddr("udp4");
if (udpMultiaddr4) {
testMultiaddrForLocal(udpMultiaddr4, true);
}
const udpMultiaddr6 = enr.getLocationMultiaddr("udp6");
if (udpMultiaddr6) {
testMultiaddrForLocal(udpMultiaddr6, false);
}
}

/**
Expand Down
92 changes: 85 additions & 7 deletions packages/cli/src/options/beaconNodeOptions/network.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import {multiaddr} from "@multiformats/multiaddr";
import {defaultOptions, IBeaconNodeOptions} from "@lodestar/beacon-node";
import {CliCommandOptions, YargsError} from "../../util/index.js";

const defaultListenAddress = "0.0.0.0";
export const defaultP2pPort = 9000;
export const defaultP2pPort6 = 9090;

export type NetworkArgs = {
discv5?: boolean;
listenAddress?: string;
port?: number;
discoveryPort?: number;
listenAddress6?: string;
port6?: number;
discoveryPort6?: number;
bootnodes?: string[];
targetPeers?: number;
deterministicLongLivedAttnets?: boolean;
Expand Down Expand Up @@ -38,10 +43,59 @@ export type NetworkArgs = {
"network.rateTrackerTimeoutMs"?: number;
};

function validateMultiaddrArg<T extends Record<string, string | undefined>>(args: T, key: keyof T): void {
if (args[key]) {
try {
multiaddr(args[key]);
} catch (e) {
throw new YargsError(`Invalid ${key as string}: ${(e as Error).message}`);
}
}
}

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function parseListenArgs(args: NetworkArgs) {
// If listenAddress is explicitly set, use it
// If listenAddress6 is not set, use defaultListenAddress
const listenAddress = args.listenAddress ?? (args.listenAddress6 ? undefined : defaultListenAddress);
const port = listenAddress ? args.port ?? defaultP2pPort : undefined;
const discoveryPort = listenAddress ? args.discoveryPort ?? args.port ?? defaultP2pPort : undefined;

// Only use listenAddress6 if it is explicitly set
const listenAddress6 = args.listenAddress6;
const port6 = listenAddress6 ? args.port6 ?? defaultP2pPort6 : undefined;
const discoveryPort6 = listenAddress6 ? args.discoveryPort6 ?? args.port6 ?? defaultP2pPort6 : undefined;

return {listenAddress, port, discoveryPort, listenAddress6, port6, discoveryPort6};
}

export function parseArgs(args: NetworkArgs): IBeaconNodeOptions["network"] {
const listenAddress = args.listenAddress || defaultListenAddress;
const udpPort = args.discoveryPort ?? args.port ?? defaultP2pPort;
const tcpPort = args.port ?? defaultP2pPort;
const {listenAddress, port, discoveryPort, listenAddress6, port6, discoveryPort6} = parseListenArgs(args);
// validate ip, ip6, ports
const muArgs = {
listenAddress: listenAddress ? `/ip4/${listenAddress}` : undefined,
port: listenAddress ? `/tcp/${port}` : undefined,
discoveryPort: listenAddress ? `/udp/${discoveryPort}` : undefined,
listenAddress6: listenAddress6 ? `/ip6/${listenAddress6}` : undefined,
port6: listenAddress6 ? `/tcp/${port6}` : undefined,
discoveryPort6: listenAddress6 ? `/udp/${discoveryPort6}` : undefined,
};

for (const key of [
"listenAddress",
"port",
"discoveryPort",
"listenAddress6",
"port6",
"discoveryPort6",
] as (keyof typeof muArgs)[]) {
validateMultiaddrArg(muArgs, key);
}

const bindMu = listenAddress ? `${muArgs.listenAddress}${muArgs.discoveryPort}` : undefined;
const localMu = listenAddress ? `${muArgs.listenAddress}${muArgs.port}` : undefined;
const bindMu6 = listenAddress6 ? `${muArgs.listenAddress6}${muArgs.discoveryPort6}` : undefined;
const localMu6 = listenAddress6 ? `${muArgs.listenAddress6}${muArgs.port6}` : undefined;

const targetPeers = args["targetPeers"];
const maxPeers = args["network.maxPeers"] ?? (targetPeers !== undefined ? Math.floor(targetPeers * 1.1) : undefined);
Expand All @@ -54,7 +108,10 @@ export function parseArgs(args: NetworkArgs): IBeaconNodeOptions["network"] {
discv5: enableDiscv5
? {
config: {},
bindAddr: `/ip4/${listenAddress}/udp/${udpPort}`,
bindAddrs: {
ip4: bindMu as string,
ip6: bindMu6,
},
// TODO: Okay to set to empty array?
bootEnrs: args["bootnodes"] ?? [],
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
Expand All @@ -63,7 +120,7 @@ export function parseArgs(args: NetworkArgs): IBeaconNodeOptions["network"] {
: null,
maxPeers: maxPeers ?? defaultOptions.network.maxPeers,
targetPeers: targetPeers ?? defaultOptions.network.targetPeers,
localMultiaddrs: [`/ip4/${listenAddress}/tcp/${tcpPort}`],
localMultiaddrs: [localMu, localMu6].filter(Boolean) as string[],
deterministicLongLivedAttnets: args["deterministicLongLivedAttnets"],
subscribeAllSubnets: args["subscribeAllSubnets"],
disablePeerScoring: args["disablePeerScoring"],
Expand Down Expand Up @@ -93,13 +150,13 @@ export const options: CliCommandOptions<NetworkArgs> = {

listenAddress: {
type: "string",
description: "The address to listen for p2p UDP and TCP connections",
description: "The IPv4 address to listen for p2p UDP and TCP connections",
defaultDescription: defaultListenAddress,
group: "network",
},

port: {
description: "The TCP/UDP port to listen on. The UDP port can be modified by the --discovery-port flag.",
description: "The TCP/UDP port to listen on. The UDP port can be modified by the --discoveryPort flag.",
type: "number",
// TODO: Derive from BeaconNode defaults
defaultDescription: String(defaultP2pPort),
Expand All @@ -113,6 +170,27 @@ export const options: CliCommandOptions<NetworkArgs> = {
group: "network",
},

listenAddress6: {
type: "string",
description: "The IPv6 address to listen for p2p UDP and TCP connections",
group: "network",
},

port6: {
description: "The TCP/UDP port to listen on. The UDP port can be modified by the --discoveryPort6 flag.",
type: "number",
// TODO: Derive from BeaconNode defaults
defaultDescription: String(defaultP2pPort6),
group: "network",
},

discoveryPort6: {
description: "The UDP port that discovery will listen on. Defaults to `port6`",
type: "number",
defaultDescription: "`port6`",
group: "network",
},

bootnodes: {
type: "array",
description: "Bootnodes for discv5 discovery",
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/test/unit/options/beaconNodeOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ describe("options / beaconNodeOptions", () => {
network: {
discv5: {
config: {},
bindAddr: "/ip4/127.0.0.1/udp/9002",
bindAddrs: {
ip4: "/ip4/127.0.0.1/udp/9002",
},
bootEnrs: ["enr:-somedata"],
},
maxPeers: 30,
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -505,10 +505,10 @@
node-fetch "^2.6.1"
node-gyp "^8.4.0"

"@chainsafe/discv5@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@chainsafe/discv5/-/discv5-4.0.0.tgz#41b9876ce0d209a4abcf844cfcdb6c4c8c338fe5"
integrity sha512-4dsfSAAa2rSRgrYM7YdvJRYNMSZ0NZzzBJw7e+PQMURvCZBRtDxh51/WS0qFX2sFrmjkT+i+xmLD8EsS4P5pUw==
"@chainsafe/discv5@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@chainsafe/discv5/-/discv5-5.0.0.tgz#d8d4eadc0ce7f649d5c1b141bb0d51efbfca4c6d"
integrity sha512-e+TbRrs1wukZgVmFuQL4tm0FRqSFRWlV7+MEbApbIenzFI7Pds1B4U5CDUFF8UE9QlkY5GEI/vXkT480Rhn6Rg==
dependencies:
"@libp2p/crypto" "^1.0.0"
"@libp2p/interface-peer-discovery" "^2.0.0"
Expand Down

0 comments on commit ec81531

Please sign in to comment.