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(enr): add quic support #293

Merged
merged 1 commit into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
113 changes: 97 additions & 16 deletions packages/enr/src/enr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ export function decodeTxt(encoded: string): ENRData {

// IP / Protocol

export type Protocol = "udp" | "tcp" | "udp4" | "udp6" | "tcp4" | "tcp6";
/** Protocols automagically supported by this library */
export type Protocol = "udp" | "tcp" | "quic" | "udp4" | "udp6" | "tcp4" | "tcp6" | "quic4" | "quic6";

export function getIPValue(kvs: ReadonlyMap<ENRKey, ENRValue>, key: string, multifmtStr: string): string | undefined {
const raw = kvs.get(key);
Expand Down Expand Up @@ -191,6 +192,57 @@ export function portToBuf(port: number): Uint8Array {
return buf;
}

export function parseLocationMultiaddr(ma: Multiaddr): {
family: 4 | 6;
ip: Uint8Array;
protoName: "udp" | "tcp" | "quic";
protoVal: Uint8Array;
} {
const protoNames = ma.protoNames();
const tuples = ma.tuples();
let family: 4 | 6;
let protoName: "udp" | "tcp" | "quic";

if (protoNames[0] === "ip4") {
family = 4;
} else if (protoNames[0] === "ip6") {
family = 6;
} else {
throw new Error("Invalid multiaddr: must start with ip4 or ip6");
}
if (tuples[0][1] == null) {
throw new Error("Invalid multiaddr: ip address is missing");
}
const ip = tuples[0][1];

if (protoNames[1] === "udp") {
protoName = "udp";
} else if (protoNames[1] === "tcp") {
protoName = "tcp";
} else {
throw new Error("Invalid multiaddr: must have udp or tcp protocol");
}
if (tuples[1][1] == null) {
throw new Error("Invalid multiaddr: udp or tcp port is missing");
}
const protoVal = tuples[1][1];

if (protoNames.length === 3) {
if (protoNames[2] === "quic-v1") {
if (protoName !== "udp") {
throw new Error("Invalid multiaddr: quic protocol must be used with udp");
}
protoName = "quic";
} else {
throw new Error("Invalid multiaddr: unknown protocol");
}
} else if (protoNames.length > 2) {
throw new Error("Invalid multiaddr: unknown protocol");
}

return { family, ip, protoName, protoVal };
}

// Classes

export abstract class BaseENR {
Expand Down Expand Up @@ -228,6 +280,9 @@ export abstract class BaseENR {
get udp(): number | undefined {
return getProtocolValue(this.kvs, "udp");
}
get quic(): number | undefined {
return getProtocolValue(this.kvs, "quic");
}
get ip6(): string | undefined {
return getIPValue(this.kvs, "ip6", "ip6");
}
Expand All @@ -237,13 +292,19 @@ export abstract class BaseENR {
get udp6(): number | undefined {
return getProtocolValue(this.kvs, "udp6");
}
get quic6(): number | undefined {
return getProtocolValue(this.kvs, "quic6");
}
getLocationMultiaddr(protocol: Protocol): Multiaddr | undefined {
if (protocol === "udp") {
return this.getLocationMultiaddr("udp4") || this.getLocationMultiaddr("udp6");
}
if (protocol === "tcp") {
return this.getLocationMultiaddr("tcp4") || this.getLocationMultiaddr("tcp6");
}
if (protocol === "quic") {
return this.getLocationMultiaddr("quic4") || this.getLocationMultiaddr("quic6");
}
const isIpv6 = protocol.endsWith("6");
const ipVal = this.kvs.get(isIpv6 ? "ip6" : "ip");
if (!ipVal) {
Expand All @@ -252,13 +313,17 @@ export abstract class BaseENR {

const isUdp = protocol.startsWith("udp");
const isTcp = protocol.startsWith("tcp");
const isQuic = protocol.startsWith("quic");
let protoName, protoVal;
if (isUdp) {
protoName = "udp";
protoVal = isIpv6 ? this.kvs.get("udp6") : this.kvs.get("udp");
} else if (isTcp) {
protoName = "tcp";
protoVal = isIpv6 ? this.kvs.get("tcp6") : this.kvs.get("tcp");
} else if (isQuic) {
protoName = "udp";
protoVal = isIpv6 ? this.kvs.get("quic6") : this.kvs.get("quic");
} else {
return undefined;
}
Expand All @@ -282,7 +347,11 @@ export abstract class BaseENR {
maBuf.set(protoBuf, 1 + ipByteLen);
maBuf.set(protoVal, 1 + ipByteLen + protoBuf.length);

return multiaddr(maBuf);
const ma = multiaddr(maBuf);
if (isQuic) {
return ma.encapsulate("/quic-v1");
}
return ma;
}
async getFullMultiaddr(protocol: Protocol): Promise<Multiaddr | undefined> {
const locationMultiaddr = this.getLocationMultiaddr(protocol);
Expand Down Expand Up @@ -504,6 +573,16 @@ export class SignableENR extends BaseENR {
this.set("udp", portToBuf(port));
}
}
get quic(): number | undefined {
return getProtocolValue(this.kvs, "quic");
}
set quic(port: number | undefined) {
if (port === undefined) {
this.delete("quic");
} else {
this.set("quic", portToBuf(port));
}
}
get ip6(): string | undefined {
return getIPValue(this.kvs, "ip6", "ip6");
}
Expand Down Expand Up @@ -534,23 +613,25 @@ export class SignableENR extends BaseENR {
this.set("udp6", portToBuf(port));
}
}
setLocationMultiaddr(multiaddr: Multiaddr): void {
const protoNames = multiaddr.protoNames();
if (protoNames.length !== 2 && protoNames[1] !== "udp" && protoNames[1] !== "tcp") {
throw new Error("Invalid multiaddr");
}
const tuples = multiaddr.tuples();
if (!tuples[0][1] || !tuples[1][1]) {
throw new Error("Invalid multiaddr");
get quic6(): number | undefined {
return getProtocolValue(this.kvs, "quic6");
}
set quic6(port: number | undefined) {
if (port === undefined) {
this.delete("quic6");
} else {
this.set("quic6", portToBuf(port));
}
}
setLocationMultiaddr(multiaddr: Multiaddr): void {
const { family, ip, protoName, protoVal } = parseLocationMultiaddr(multiaddr);

// IPv4
if (tuples[0][0] === 4) {
this.set("ip", tuples[0][1]);
this.set(protoNames[1], tuples[1][1]);
if (family === 4) {
this.set("ip", ip);
this.set(protoName, protoVal);
} else {
this.set("ip6", tuples[0][1]);
this.set(protoNames[1] + "6", tuples[1][1]);
this.set("ip6", ip);
this.set(protoName + "6", protoVal);
}
}

Expand Down
47 changes: 47 additions & 0 deletions packages/enr/test/unit/enr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,47 @@ describe("ENR multiaddr support", () => {
expect(record.kvs.get("ip")).to.deep.equal(tuples1[0][1]);
expect(record.kvs.get("tcp")).to.deep.equal(tuples1[1][1]);
});
it("should get / set QUIC multiaddr", () => {
const multi0 = multiaddr("/ip4/127.0.0.1/udp/30303/quic-v1");
const tuples0 = multi0.tuples();

if (!tuples0[0][1] || !tuples0[1][1]) {
throw new Error("invalid multiaddr");
}

// set underlying records
record.set("ip", tuples0[0][1]);
record.set("quic", tuples0[1][1]);
// and get the multiaddr
expect(record.getLocationMultiaddr("quic")!.toString()).to.equal(multi0.toString());
// set the multiaddr
const multi1 = multiaddr("/ip4/0.0.0.0/udp/30300/quic-v1");
record.setLocationMultiaddr(multi1);
// and get the multiaddr
expect(record.getLocationMultiaddr("quic")!.toString()).to.equal(multi1.toString());
// and get the underlying records
const tuples1 = multi1.tuples();
expect(record.kvs.get("ip")).to.deep.equal(tuples1[0][1]);
expect(record.kvs.get("quic")).to.deep.equal(tuples1[1][1]);
});

describe("location multiaddr", async () => {
const ip4 = "127.0.0.1";
const ip6 = "::1";
const tcp = 8080;
const udp = 8080;
const quic = 8081;

const peerId = await createSecp256k1PeerId();
const enr = SignableENR.createFromPeerId(peerId);
enr.ip = ip4;
enr.ip6 = ip6;
enr.tcp = tcp;
enr.udp = udp;
enr.quic = quic;
enr.tcp6 = tcp;
enr.udp6 = udp;
enr.quic6 = quic;

it("should properly create location multiaddrs - udp4", () => {
expect(enr.getLocationMultiaddr("udp4")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${udp}`));
Expand All @@ -141,6 +167,10 @@ describe("ENR multiaddr support", () => {
expect(enr.getLocationMultiaddr("tcp4")).to.deep.equal(multiaddr(`/ip4/${ip4}/tcp/${tcp}`));
});

it("should properly create location multiaddrs - quic4", () => {
expect(enr.getLocationMultiaddr("quic4")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${quic}/quic-v1`));
});

it("should properly create location multiaddrs - udp6", () => {
expect(enr.getLocationMultiaddr("udp6")).to.deep.equal(multiaddr(`/ip6/${ip6}/udp/${udp}`));
});
Expand All @@ -149,6 +179,10 @@ describe("ENR multiaddr support", () => {
expect(enr.getLocationMultiaddr("tcp6")).to.deep.equal(multiaddr(`/ip6/${ip6}/tcp/${tcp}`));
});

it("should properly create location multiaddrs - quic6", () => {
expect(enr.getLocationMultiaddr("quic6")).to.deep.equal(multiaddr(`/ip6/${ip6}/udp/${quic}/quic-v1`));
});

it("should properly create location multiaddrs - udp", () => {
// default to ip4
expect(enr.getLocationMultiaddr("udp")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${udp}`));
Expand All @@ -174,6 +208,19 @@ describe("ENR multiaddr support", () => {
expect(enr.getLocationMultiaddr("tcp")).to.deep.equal(multiaddr(`/ip4/${ip4}/tcp/${tcp}`));
enr.ip6 = ip6;
});

it("should properly create location multiaddrs - quic", () => {
// default to ip4
expect(enr.getLocationMultiaddr("quic")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${quic}/quic-v1`));
// if ip6 is set, use it
enr.ip = undefined;
expect(enr.getLocationMultiaddr("quic")).to.deep.equal(multiaddr(`/ip6/${ip6}/udp/${quic}/quic-v1`));
// if ip6 does not exist, use ip4
enr.ip6 = undefined;
enr.ip = ip4;
expect(enr.getLocationMultiaddr("quic")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${quic}/quic-v1`));
enr.ip6 = ip6;
});
});
});

Expand Down
Loading