Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add IPv6 support #5758

Merged
merged 5 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
},
Comment on lines +51 to +54
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discv5 5.0.0 now requires bindAddrs to be passed in like so.
So we thread this through to the cli layer.

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.0",
"@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
90 changes: 84 additions & 6 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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this allows us to fail fast if the user sets an incorrect ip address or port
eg: --listenAddress :: and --listenAddress6 0.0.0.0 should error

}

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[],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

libp2p was already configured to listen to an array of multiaddrs.
So nothing further than passing both multiaddrs here needs to be done.

deterministicLongLivedAttnets: args["deterministicLongLivedAttnets"],
subscribeAllSubnets: args["subscribeAllSubnets"],
disablePeerScoring: args["disablePeerScoring"],
Expand Down Expand Up @@ -93,7 +150,7 @@ 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",
},
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 --discovery-port6 flag.",
wemeetagain marked this conversation as resolved.
Show resolved Hide resolved
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