@waku/enr
Version:
ENR (EIP-778) for Waku
224 lines (191 loc) • 5.84 kB
text/typescript
import type { Multiaddr } from "@multiformats/multiaddr";
import {
convertToBytes,
convertToString
} from "@multiformats/multiaddr/convert";
import type {
ENRKey,
ENRValue,
SequenceNumber,
ShardInfo,
Waku2
} from "@waku/interfaces";
import { decodeRelayShard } from "@waku/utils";
import { bytesToUtf8 } from "@waku/utils/bytes";
import { ERR_INVALID_ID } from "./constants.js";
import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec.js";
import { decodeWaku2, encodeWaku2 } from "./waku2_codec.js";
export class RawEnr extends Map<ENRKey, ENRValue> {
public seq: SequenceNumber;
public signature?: Uint8Array;
protected constructor(
kvs: Record<ENRKey, ENRValue> = {},
seq: SequenceNumber = BigInt(1),
signature?: Uint8Array
) {
super(Object.entries(kvs));
this.seq = seq;
this.signature = signature;
}
public set(k: ENRKey, v: ENRValue): this {
this.signature = undefined;
this.seq++;
return super.set(k, v);
}
public get id(): string {
const id = this.get("id");
if (!id) throw new Error("id not found.");
return bytesToUtf8(id);
}
public get publicKey(): Uint8Array | undefined {
switch (this.id) {
case "v4":
return this.get("secp256k1");
default:
throw new Error(ERR_INVALID_ID);
}
}
public get rs(): ShardInfo | undefined {
const rs = this.get("rs");
if (!rs) return undefined;
return decodeRelayShard(rs);
}
public get rsv(): ShardInfo | undefined {
const rsv = this.get("rsv");
if (!rsv) return undefined;
return decodeRelayShard(rsv);
}
public get ip(): string | undefined {
return getStringValue(this, "ip", "ip4");
}
public set ip(ip: string | undefined) {
setStringValue(this, "ip", "ip4", ip);
}
public get tcp(): number | undefined {
return getNumberAsStringValue(this, "tcp", "tcp");
}
public set tcp(port: number | undefined) {
setNumberAsStringValue(this, "tcp", "tcp", port);
}
public get udp(): number | undefined {
return getNumberAsStringValue(this, "udp", "udp");
}
public set udp(port: number | undefined) {
setNumberAsStringValue(this, "udp", "udp", port);
}
public get ip6(): string | undefined {
return getStringValue(this, "ip6", "ip6");
}
public set ip6(ip: string | undefined) {
setStringValue(this, "ip6", "ip6", ip);
}
public get tcp6(): number | undefined {
return getNumberAsStringValue(this, "tcp6", "tcp");
}
public set tcp6(port: number | undefined) {
setNumberAsStringValue(this, "tcp6", "tcp", port);
}
public get udp6(): number | undefined {
return getNumberAsStringValue(this, "udp6", "udp");
}
public set udp6(port: number | undefined) {
setNumberAsStringValue(this, "udp6", "udp", port);
}
/**
* Get the `multiaddrs` field from ENR.
*
* This field is used to store multiaddresses that cannot be stored with the current ENR pre-defined keys.
* These can be a multiaddresses that include encapsulation (e.g. wss) or do not use `ip4` nor `ip6` for the host
* address (e.g. `dns4`, `dnsaddr`, etc)..
*
* If the peer information only contains information that can be represented with the ENR pre-defined keys
* (ip, tcp, etc) then the usage of { @link ENR.getLocationMultiaddr } should be preferred.
*
* The multiaddresses stored in this field are expected to be location multiaddresses, ie, peer id less.
*/
public get multiaddrs(): Multiaddr[] | undefined {
const raw = this.get("multiaddrs");
if (raw) return decodeMultiaddrs(raw);
return;
}
/**
* Set the `multiaddrs` field on the ENR.
*
* This field is used to store multiaddresses that cannot be stored with the current ENR pre-defined keys.
* These can be a multiaddresses that include encapsulation (e.g. wss) or do not use `ip4` nor `ip6` for the host
* address (e.g. `dns4`, `dnsaddr`, etc)..
*
* If the peer information only contains information that can be represented with the ENR pre-defined keys
* (ip, tcp, etc) then the usage of { @link ENR.setLocationMultiaddr } should be preferred.
* The multiaddresses stored in this field must be location multiaddresses,
* ie, without a peer id.
*/
public set multiaddrs(multiaddrs: Multiaddr[] | undefined) {
deleteUndefined(this, "multiaddrs", multiaddrs, encodeMultiaddrs);
}
/**
* Get the `waku2` field from ENR.
*/
public get waku2(): Waku2 | undefined {
const raw = this.get("waku2");
if (raw) return decodeWaku2(raw[0]);
return;
}
/**
* Set the `waku2` field on the ENR.
*/
public set waku2(waku2: Waku2 | undefined) {
deleteUndefined(
this,
"waku2",
waku2,
(w) => new Uint8Array([encodeWaku2(w)])
);
}
}
function getStringValue(
map: Map<ENRKey, ENRValue>,
key: ENRKey,
proto: string
): string | undefined {
const raw = map.get(key);
if (!raw) return;
return convertToString(proto, raw);
}
function getNumberAsStringValue(
map: Map<ENRKey, ENRValue>,
key: ENRKey,
proto: string
): number | undefined {
const raw = map.get(key);
if (!raw) return;
return Number(convertToString(proto, raw));
}
function setStringValue(
map: Map<ENRKey, ENRValue>,
key: ENRKey,
proto: string,
value: string | undefined
): void {
deleteUndefined(map, key, value, convertToBytes.bind({}, proto));
}
function setNumberAsStringValue(
map: Map<ENRKey, ENRValue>,
key: ENRKey,
proto: string,
value: number | undefined
): void {
setStringValue(map, key, proto, value?.toString(10));
}
function deleteUndefined<K, V, W>(
map: Map<K, W>,
key: K,
value: V | undefined,
transform: (v: V) => W
): void {
if (value !== undefined) {
map.set(key, transform(value));
} else {
map.delete(key);
}
}