diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1ab7e7b7..efe3952d 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -48,6 +48,7 @@ module.exports = { "prefer-const": "error", "quotes": ["error", "double"], "@chainsafe/node/file-extension-in-import": ["error", "always", {esm: true}], + "@typescript-eslint/no-floating-promises": "error", }, "overrides": [ { diff --git a/packages/discv5/src/service/service.ts b/packages/discv5/src/service/service.ts index 68b87820..485ab55f 100644 --- a/packages/discv5/src/service/service.ts +++ b/packages/discv5/src/service/service.ts @@ -15,7 +15,7 @@ import { import { BindAddrs, IPMode, ITransportService, UDPTransportService } from "../transport/index.js"; import { MAX_PACKET_SIZE } from "../packet/index.js"; -import { ConnectionDirection, RequestErrorType, SessionService } from "../session/index.js"; +import { ConnectionDirection, RequestErrorType, ResponseErrorType, SessionService } from "../session/index.js"; import { IKeypair, createKeypair } from "../keypair/index.js"; import { EntryStatus, @@ -48,7 +48,7 @@ import { toBuffer } from "../util/index.js"; import { IDiscv5Config, defaultConfig } from "../config/index.js"; import { createNodeContact, getNodeAddress, getNodeId, INodeAddress, NodeContact } from "../session/nodeInfo.js"; import { - BufferCallback, + CodeError, ConnectionStatus, ConnectionStatusType, Discv5EventEmitter, @@ -56,7 +56,9 @@ import { IActiveRequest, INodesResponse, PongResponse, + ResponseType, SignableENRInput, + toResponseType, } from "./types.js"; import { RateLimiter, RateLimiterOpts } from "../rateLimit/index.js"; import { @@ -388,16 +390,16 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { * Send FINDNODE message to remote and returns response */ public async sendFindNode(remote: ENR | Multiaddr, distances: number[]): Promise { + const contact = createNodeContact(remote, this.ipMode); + const request = createFindNodeMessage(distances); + return await new Promise((resolve, reject) => { this.sendRpcRequest({ - contact: createNodeContact(remote), - request: createFindNodeMessage(distances), - callback: (err: RequestErrorType | null, res: ENR[] | null): void => { - if (err !== null) { - reject(err); - return; - } - resolve(res as ENR[]); + contact, + request, + callbackPromise: { + resolve: resolve as (val: ENR[]) => void, + reject, }, }); }); @@ -407,16 +409,16 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { * Send TALKREQ message to dstId and returns response */ public async sendTalkReq(remote: ENR | Multiaddr, payload: Buffer, protocol: string | Uint8Array): Promise { + const contact = createNodeContact(remote, this.ipMode); + const request = createTalkRequestMessage(payload, protocol); + return await new Promise((resolve, reject) => { this.sendRpcRequest({ - contact: createNodeContact(remote), - request: createTalkRequestMessage(payload, protocol), - callback: (err: RequestErrorType | null, res: Buffer | null): void => { - if (err !== null) { - reject(err); - return; - } - resolve(res as Buffer); + contact, + request, + callbackPromise: { + resolve: resolve as (val: Buffer) => void, + reject, }, }); }); @@ -441,16 +443,16 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { * Sends a PING request to a node and returns response */ public async sendPing(nodeAddr: ENR | Multiaddr): Promise { + const contact = createNodeContact(nodeAddr, this.ipMode); + const request = createPingMessage(this.enr.seq); + return await new Promise((resolve, reject) => { this.sendRpcRequest({ - contact: createNodeContact(nodeAddr), - request: createPingMessage(this.enr.seq), - callback: (err: RequestErrorType | null, res: PongResponse | null): void => { - if (err !== null) { - reject(err); - return; - } - resolve(res as PongResponse); + contact, + request, + callbackPromise: { + resolve: resolve as (val: PongResponse) => void, + reject, }, }); }); @@ -462,7 +464,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { private pingConnectedPeers(): void { for (const entry of this.kbuckets.rawValues()) { if (entry.status === EntryStatus.Connected) { - this.sendPing(entry.value); + this.sendPing(entry.value).catch((e) => log("Error pinging peer %o: %s", entry.value, (e as Error).message)); } } } @@ -470,8 +472,8 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { /** * Request an external node's ENR */ - private requestEnr(contact: NodeContact, callback?: (err: RequestErrorType | null, res: ENR[] | null) => void): void { - this.sendRpcRequest({ request: createFindNodeMessage([0]), contact, callback }); + private requestEnr(contact: NodeContact): void { + this.sendRpcRequest({ request: createFindNodeMessage([0]), contact }); } /** @@ -486,7 +488,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { } this.sendRpcRequest({ - contact: createNodeContact(enr), + contact: createNodeContact(enr, this.ipMode), request, lookupId, }); @@ -498,10 +500,13 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { * * Returns true if the request was sent successfully */ - private sendRpcRequest(activeRequest: IActiveRequest): void { - this.activeRequests.set(activeRequest.request.id, activeRequest); + private sendRpcRequest(activeRequest: IActiveRequest): void { + this.activeRequests.set( + activeRequest.request.id, + activeRequest as unknown as IActiveRequest + ); - const nodeAddr = getNodeAddress(activeRequest.contact, this.ipMode); + const nodeAddr = getNodeAddress(activeRequest.contact); log("Sending %s to node: %o", MessageType[activeRequest.request.type], nodeAddr); try { this.sessionService.sendRequest(activeRequest.contact, activeRequest.request); @@ -545,7 +550,9 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { setInterval(() => { // If the node is in the routing table, keep pinging if (this.kbuckets.getValue(nodeId)) { - this.sendPing(newStatus.enr); + this.sendPing(newStatus.enr).catch((e) => + log("Error pinging peer %o: %s", newStatus.enr, (e as Error).message) + ); } else { clearInterval(this.connectedPeers.get(nodeId) as NodeJS.Timeout); this.connectedPeers.delete(nodeId); @@ -566,7 +573,9 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { setInterval(() => { // If the node is in the routing table, keep pinging if (this.kbuckets.getValue(nodeId)) { - this.sendPing(newStatus.enr); + this.sendPing(newStatus.enr).catch((e) => + log("Error pinging peer %o: %s", newStatus.enr, (e as Error).message) + ); } else { clearInterval(this.connectedPeers.get(nodeId) as NodeJS.Timeout); this.connectedPeers.delete(nodeId); @@ -679,7 +688,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { // process kad updates private onPendingEviction = (enr: ENR): void => { - this.sendPing(enr); + this.sendPing(enr).catch((e) => log("Error pinging peer %o: %s", enr, (e as Error).message)); }; private onAppliedEviction = (inserted: ENR, evicted?: ENR): void => { @@ -723,18 +732,24 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { * Requests respond to the received socket address, rather than the IP of the known ENR. */ private handleRpcRequest = (nodeAddr: INodeAddress, request: RequestMessage): void => { - this.metrics?.rcvdMessageCount.inc({ type: MessageType[request.type] }); - switch (request.type) { - case MessageType.PING: - return this.handlePing(nodeAddr, request as IPingMessage); - case MessageType.FINDNODE: - return this.handleFindNode(nodeAddr, request as IFindNodeMessage); - case MessageType.TALKREQ: - return this.handleTalkReq(nodeAddr, request as ITalkReqMessage); - default: - log("Received request which is unimplemented"); - // TODO Implement all RPC methods - return; + const requestType = MessageType[request.type]; + this.metrics?.rcvdMessageCount.inc({ type: requestType }); + + try { + switch (request.type) { + case MessageType.PING: + return this.handlePing(nodeAddr, request as IPingMessage); + case MessageType.FINDNODE: + return this.handleFindNode(nodeAddr, request as IFindNodeMessage); + case MessageType.TALKREQ: + return this.handleTalkReq(nodeAddr, request as ITalkReqMessage); + default: + log("Received request type which is unimplemented: %s", request.type); + // TODO Implement all RPC methods + return; + } + } catch (e) { + log("Error handling rpc request: node: %o, requestType: %s", nodeAddr, requestType); } }; @@ -743,7 +758,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { const entry = this.kbuckets.getWithPending(nodeAddr.nodeId); if (entry) { if (entry.value.seq < message.enrSeq) { - this.requestEnr(createNodeContact(entry.value)); + this.requestEnr(createNodeContact(entry.value, this.ipMode)); } } @@ -829,7 +844,8 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { * Processes an RPC response from a peer. */ private handleRpcResponse = (nodeAddr: INodeAddress, response: ResponseMessage): void => { - this.metrics?.rcvdMessageCount.inc({ type: MessageType[response.type] }); + const responseType = MessageType[response.type]; + this.metrics?.rcvdMessageCount.inc({ type: responseType }); // verify we know of the rpc id @@ -841,7 +857,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { this.activeRequests.delete(response.id); // Check that the responder matches the expected request - const requestNodeAddr = getNodeAddress(activeRequest.contact, this.ipMode); + const requestNodeAddr = getNodeAddress(activeRequest.contact); if (requestNodeAddr.nodeId !== nodeAddr.nodeId || !requestNodeAddr.socketAddr.equals(nodeAddr.socketAddr)) { log( "Received a response from an unexpected address. Expected %o, received %o, request id: %s", @@ -849,29 +865,38 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { nodeAddr, response.id ); + activeRequest.callbackPromise?.reject(new CodeError(ResponseErrorType.WrongAddress)); return; } // Check that the response type matches the request if (!requestMatchesResponse(activeRequest.request, response)) { log("Node gave an incorrect response type. Ignoring response from: %o", nodeAddr); + activeRequest.callbackPromise?.reject(new CodeError(ResponseErrorType.WrongResponseType)); return; } - switch (response.type) { - case MessageType.PONG: - return this.handlePong(nodeAddr, activeRequest, response as IPongMessage); - case MessageType.NODES: - return this.handleNodes(nodeAddr, activeRequest, response as INodesMessage); - case MessageType.TALKRESP: - return this.handleTalkResp( - nodeAddr, - activeRequest as IActiveRequest, - response as ITalkRespMessage - ); - default: - // TODO Implement all RPC methods - return; + try { + switch (response.type) { + case MessageType.PONG: + this.handlePong(nodeAddr, activeRequest, response as IPongMessage); + break; + case MessageType.NODES: + if (!this.handleNodes(nodeAddr, activeRequest as IActiveRequest, response as INodesMessage)) + return; + break; + case MessageType.TALKRESP: + this.handleTalkResp(nodeAddr, activeRequest as IActiveRequest, response as ITalkRespMessage); + break; + default: + // TODO Implement all RPC methods + return; + } + + activeRequest.callbackPromise?.resolve(toResponseType(response)); + } catch (e) { + log("Error handling rpc response: node: %o response: %s", nodeAddr, responseType); + activeRequest.callbackPromise?.reject(new CodeError(ResponseErrorType.InternalError, (e as Error).message)); } }; @@ -910,8 +935,15 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { } } - private handleNodes(nodeAddr: INodeAddress, activeRequest: IActiveRequest, message: INodesMessage): void { - const { request, lookupId } = activeRequest as { request: IFindNodeMessage; lookupId: number }; + /** + * Return true if this nodes message is the final in the response + */ + private handleNodes( + nodeAddr: INodeAddress, + activeRequest: IActiveRequest, + message: INodesMessage + ): boolean { + const { request, lookupId } = activeRequest; // Currently a maximum of 16 peers can be returned. // Datagrams have a max size of 1280 and ENRs have a max size of 300 bytes. // There should be no more than 5 responses to return 16 peers @@ -934,9 +966,9 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { if (currentResponse.count < 5 && currentResponse.count < message.total) { currentResponse.count += 1; currentResponse.enrs.push(...message.enrs); - this.activeRequests.set(message.id, activeRequest); + this.activeRequests.set(message.id, activeRequest as IActiveRequest); this.activeNodesResponses.set(message.id, currentResponse); - return; + return false; } // Have received all the Nodes responses we are willing to accept @@ -952,18 +984,17 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { this.activeNodesResponses.delete(message.id); this.discovered(nodeAddr.nodeId, message.enrs, lookupId); + + return true; } private handleTalkResp = ( nodeAddr: INodeAddress, - activeRequest: IActiveRequest, + activeRequest: IActiveRequest, message: ITalkRespMessage ): void => { log("Received TALKRESP message from Node: %o", nodeAddr); this.emit("talkRespReceived", nodeAddr, this.findEnr(nodeAddr.nodeId) ?? null, message); - if (activeRequest.callback) { - activeRequest.callback(null, message.response); - } }; /** @@ -975,14 +1006,9 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { if (!req) { return; } - const { request, contact, lookupId, callback } = req; + const { request, contact, lookupId, callbackPromise } = req; this.activeRequests.delete(request.id); - // If this is initiated by the user, return an error on the callback. - if (callback) { - callback(error, null); - } - const nodeId = getNodeId(contact); // If a failed FindNodes Request, ensure we haven't partially received responses. // If so, process the partially found nodes @@ -1017,5 +1043,8 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) { // report the node as being disconnected this.connectionUpdated(nodeId, { type: ConnectionStatusType.Disconnected }); + + // If this is initiated by the user, return the error on the callback. + callbackPromise?.reject(new CodeError(error)); }; } diff --git a/packages/discv5/src/service/types.ts b/packages/discv5/src/service/types.ts index 635302a0..6f8f164c 100644 --- a/packages/discv5/src/service/types.ts +++ b/packages/discv5/src/service/types.ts @@ -3,9 +3,16 @@ import StrictEventEmitter from "strict-event-emitter-types"; import { Multiaddr } from "@multiformats/multiaddr"; import { ENR, SequenceNumber, SignableENR } from "@chainsafe/enr"; -import { ITalkReqMessage, ITalkRespMessage, RequestMessage } from "../message/index.js"; +import { + INodesMessage, + IPongMessage, + ITalkReqMessage, + ITalkRespMessage, + MessageType, + RequestMessage, +} from "../message/index.js"; import { INodeAddress, NodeContact } from "../session/nodeInfo.js"; -import { ConnectionDirection, RequestErrorType } from "../session/index.js"; +import { ConnectionDirection, RequestErrorType, ResponseErrorType } from "../session/index.js"; import { SocketAddress } from "../util/ip.js"; export interface IDiscv5Events { @@ -57,7 +64,7 @@ export interface INodesResponse { /** * Active RPC request awaiting a response */ -export interface IActiveRequest { +export interface IActiveRequest { /** * The address the request was sent to. */ @@ -73,19 +80,39 @@ export interface IActiveRequest void; + reject: (err: CodeError) => void; + }; +} + +export class CodeError extends Error { + code: T; + + constructor(code: T, message?: string) { + super(message); + + this.code = code; + } } -export type BufferCallback = (err: RequestErrorType | null, res: Buffer | null) => void; -export type ENRCallback = (err: RequestErrorType | null, res: ENR[] | null) => void; export type PongResponse = { enrSeq: SequenceNumber; addr: SocketAddress; }; -export type PongCallback = (err: RequestErrorType | null, res: PongResponse | null) => void; -export type Callback = BufferCallback | ENRCallback | PongCallback; -export type CallbackResponseType = Buffer | ENR; +export type ResponseType = Buffer | ENR[] | PongResponse; + +export function toResponseType(response: IPongMessage | INodesMessage | ITalkRespMessage): ResponseType { + switch (response.type) { + case MessageType.PONG: + return { enrSeq: response.enrSeq, addr: response.addr }; + case MessageType.NODES: + return response.enrs; + case MessageType.TALKRESP: + return response.response; + } +} export enum ConnectionStatusType { Connected, diff --git a/packages/discv5/src/session/crypto.ts b/packages/discv5/src/session/crypto.ts index 1390cab9..60848620 100644 --- a/packages/discv5/src/session/crypto.ts +++ b/packages/discv5/src/session/crypto.ts @@ -5,7 +5,6 @@ import { NodeId } from "@chainsafe/enr"; import { generateKeypair, IKeypair, createKeypair } from "../keypair/index.js"; import { fromHex } from "../util/index.js"; -import { getNodeId, getPublicKey, NodeContact } from "./nodeInfo.js"; // Implementation for generating session keys in the Discv5 protocol. // Currently, Diffie-Hellman key agreement is performed with known public key types. Session keys @@ -25,23 +24,19 @@ export const MAC_LENGTH = 16; // Returns [initiatorKey, responderKey, ephemPK] export function generateSessionKeys( localId: NodeId, - remoteContact: NodeContact, + remoteId: NodeId, + remotePubkey: IKeypair, challengeData: Buffer ): [Buffer, Buffer, Buffer] { - const remoteKeypair = getPublicKey(remoteContact); - const ephemKeypair = generateKeypair(remoteKeypair.type); - const secret = ephemKeypair.deriveSecret(remoteKeypair); + const ephemKeypair = generateKeypair(remotePubkey.type); + const secret = ephemKeypair.deriveSecret(remotePubkey); /* TODO possibly not needed, check tests const ephemPubkey = remoteKeypair.type === "secp256k1" ? secp256k1PublicKeyToCompressed(ephemKeypair.publicKey) : ephemKeypair.publicKey; */ - return [...deriveKey(secret, localId, getNodeId(remoteContact), challengeData), ephemKeypair.publicKey] as [ - Buffer, - Buffer, - Buffer - ]; + return [...deriveKey(secret, localId, remoteId, challengeData), ephemKeypair.publicKey] as [Buffer, Buffer, Buffer]; } export function deriveKey(secret: Buffer, firstId: NodeId, secondId: NodeId, challengeData: Buffer): [Buffer, Buffer] { diff --git a/packages/discv5/src/session/nodeInfo.ts b/packages/discv5/src/session/nodeInfo.ts index 1d89eb28..2218d8e7 100644 --- a/packages/discv5/src/session/nodeInfo.ts +++ b/packages/discv5/src/session/nodeInfo.ts @@ -22,7 +22,7 @@ export function nodeAddressToString(nodeAddr: INodeAddress): string { } /** - * This type relaxes the requirement of having an ENR to connect to a node, to allow for unsigned + * This type abstracts the requirement of having an ENR to connect to a node, to allow for unsigned * connection types, such as multiaddrs. */ export enum INodeContactType { @@ -30,7 +30,6 @@ export enum INodeContactType { ENR, /** * We don't have an ENR, but have enough information to start a handshake. - * * The handshake will request the ENR at the first opportunity. * The public key can be derived from multiaddr's whose keys can be inlined. */ @@ -38,26 +37,45 @@ export enum INodeContactType { } /** - * This type relaxes the requirement of having an ENR to connect to a node, to allow for unsigned + * This type abstracts the requirement of having an ENR to connect to a node, to allow for unsigned * connection types, such as multiaddrs. + * + * Either: + * + * * We know the ENR of the node we are contacting, or + * + * * We don't have an ENR, but have enough information to start a handshake. + * + * The handshake will request the ENR at the first opportunity. + * The public key can be derived from multiaddr's whose keys can be inlined. */ export type NodeContact = | { - type: INodeContactType.ENR; - enr: ENR; + type: INodeContactType.Raw; + publicKey: IKeypair; + nodeAddress: INodeAddress; } | { - type: INodeContactType.Raw; + type: INodeContactType.ENR; publicKey: IKeypair; nodeAddress: INodeAddress; + enr: ENR; }; -export function createNodeContact(input: ENR | Multiaddr): NodeContact { +/** + * Convert an ENR or Multiaddr into a NodeContact. + * + * Note: this function may error if the input can't derive a public key or a valid socket address + */ +export function createNodeContact(input: ENR | Multiaddr, ipMode: IPMode): NodeContact { if (isMultiaddr(input)) { const options = input.toOptions(); if (options.transport !== "udp") { throw new Error("Multiaddr must specify a UDP port"); } + if ((options.family === 4 && !ipMode.ip4) || (options.family === 6 && !ipMode.ip6)) { + throw new Error("Multiaddr family not supported by IP mode"); + } const peerIdStr = input.getPeerId(); if (!peerIdStr) { throw new Error("Multiaddr must specify a peer id"); @@ -75,44 +93,30 @@ export function createNodeContact(input: ENR | Multiaddr): NodeContact { }, }; } else { + const socketAddr = getSocketAddressMultiaddrOnENR(input, ipMode); + if (!socketAddr) { + throw new Error("ENR has no suitable udp multiaddr given the IP mode"); + } return { type: INodeContactType.ENR, + publicKey: createKeypair({ type: input.keypairType, publicKey: input.publicKey }), + nodeAddress: { + socketAddr, + nodeId: input.nodeId, + }, enr: input, }; } } export function getNodeId(contact: NodeContact): NodeId { - switch (contact.type) { - case INodeContactType.ENR: - return contact.enr.nodeId; - case INodeContactType.Raw: - return contact.nodeAddress.nodeId; - } + return contact.nodeAddress.nodeId; } -export function getNodeAddress(contact: NodeContact, ipMode: IPMode): INodeAddress { - switch (contact.type) { - case INodeContactType.ENR: { - const socketAddr = getSocketAddressMultiaddrOnENR(contact.enr, ipMode); - if (!socketAddr) { - throw new Error("ENR has no udp multiaddr"); - } - return { - socketAddr, - nodeId: contact.enr.nodeId, - }; - } - case INodeContactType.Raw: - return contact.nodeAddress; - } +export function getNodeAddress(contact: NodeContact): INodeAddress { + return contact.nodeAddress; } export function getPublicKey(contact: NodeContact): IKeypair { - switch (contact.type) { - case INodeContactType.ENR: - return createKeypair({ type: contact.enr.keypairType, publicKey: contact.enr.publicKey }); - case INodeContactType.Raw: - return contact.publicKey; - } + return contact.publicKey; } diff --git a/packages/discv5/src/session/service.ts b/packages/discv5/src/session/service.ts index aa808e9a..4d1c921d 100644 --- a/packages/discv5/src/session/service.ts +++ b/packages/discv5/src/session/service.ts @@ -150,7 +150,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte this.transport = transport; this.activeRequests = new TimeoutMap(config.requestTimeout, (k, v) => - this.handleRequestTimeout(getNodeAddress(v.contact, this.ipMode), v) + this.handleRequestTimeout(getNodeAddress(v.contact), v) ); this.activeRequestsNonceMapping = new Map(); this.pendingRequests = new Map(); @@ -190,7 +190,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte * Sends an RequestMessage to a node. */ public sendRequest(contact: NodeContact, request: RequestMessage): void { - const nodeAddr = getNodeAddress(contact, this.ipMode); + const nodeAddr = getNodeAddress(contact); const nodeAddrStr = nodeAddressToString(nodeAddr); if (this.transport.bindAddrs.some((bindAddr) => nodeAddr.socketAddr.equals(bindAddr))) { @@ -745,7 +745,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte * Inserts a request and associated authTag mapping */ private insertActiveRequest(requestCall: IRequestCall): void { - const nodeAddr = getNodeAddress(requestCall.contact, this.ipMode); + const nodeAddr = getNodeAddress(requestCall.contact); const nodeAddrStr = nodeAddressToString(nodeAddr); this.activeRequestsNonceMapping.set(requestCall.packet.header.nonce.toString("hex"), nodeAddr); this.activeRequests.set(nodeAddrStr, requestCall); @@ -823,7 +823,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte // Fail the current request this.emit("requestFailed", requestCall.request.id, error); - const nodeAddr = getNodeAddress(requestCall.contact, this.ipMode); + const nodeAddr = getNodeAddress(requestCall.contact); this.failSession(nodeAddr, error, removeSession); } @@ -847,6 +847,8 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte } private send(nodeAddr: INodeAddress, packet: IPacket): void { - this.transport.send(nodeAddr.socketAddr, nodeAddr.nodeId, packet); + this.transport + .send(nodeAddr.socketAddr, nodeAddr.nodeId, packet) + .catch((e) => log("Error sending packet to node %o: %s", nodeAddr, (e as Error).message)); } } diff --git a/packages/discv5/src/session/session.ts b/packages/discv5/src/session/session.ts index b99533c9..6ea27601 100644 --- a/packages/discv5/src/session/session.ts +++ b/packages/discv5/src/session/session.ts @@ -22,7 +22,7 @@ import { IKeypair, createKeypair } from "../keypair/index.js"; import { randomBytes } from "crypto"; import { RequestId } from "../message/index.js"; import { IChallenge } from "."; -import { getNodeId, NodeContact } from "./nodeInfo.js"; +import { getNodeId, getPublicKey, NodeContact } from "./nodeInfo.js"; // The `Session` struct handles the stages of creating and establishing a handshake with a // peer. @@ -136,7 +136,12 @@ export class Session { message: Buffer ): [IPacket, Session] { // generate session keys - const [encryptionKey, decryptionKey, ephPubkey] = generateSessionKeys(localNodeId, remoteContact, challengeData); + const [encryptionKey, decryptionKey, ephPubkey] = generateSessionKeys( + localNodeId, + getNodeId(remoteContact), + getPublicKey(remoteContact), + challengeData + ); const keys = { encryptionKey, decryptionKey }; // construct nonce signature diff --git a/packages/discv5/src/session/types.ts b/packages/discv5/src/session/types.ts index d43d68b6..9258f748 100644 --- a/packages/discv5/src/session/types.ts +++ b/packages/discv5/src/session/types.ts @@ -57,6 +57,14 @@ export enum RequestErrorType { InvalidMultiaddr, } +export enum ResponseErrorType { + /** The responder address does not match the expected address */ + WrongAddress, + /** The response type does not match the expected response type */ + WrongResponseType, + /** The response handler threw */ + InternalError, +} export interface IKeys { encryptionKey: Buffer; decryptionKey: Buffer; diff --git a/packages/discv5/test/e2e/connect.test.ts b/packages/discv5/test/e2e/connect.test.ts index 5b812476..cfed08bf 100644 --- a/packages/discv5/test/e2e/connect.test.ts +++ b/packages/discv5/test/e2e/connect.test.ts @@ -80,7 +80,7 @@ describe("discv5 integration test", function () { // test a TALKRESP with a response const expectedResp = Buffer.from([4, 5, 6, 7]); node1.discv5.on("talkReqReceived", (nodeAddr, enr, request) => { - node1.discv5.sendTalkResp(nodeAddr, request.id, expectedResp); + void node1.discv5.sendTalkResp(nodeAddr, request.id, expectedResp); }); const resp = await node0.discv5.sendTalkReq(node1.enr.toENR(), Buffer.from([0, 1, 2, 3]), "foo"); expect(resp).to.deep.equal(expectedResp); diff --git a/packages/discv5/test/unit/session/crypto.test.ts b/packages/discv5/test/unit/session/crypto.test.ts index 29d2bd03..db85abcb 100644 --- a/packages/discv5/test/unit/session/crypto.test.ts +++ b/packages/discv5/test/unit/session/crypto.test.ts @@ -14,7 +14,6 @@ import { decryptMessage, } from "../../../src/session/index.js"; import { createKeypair, generateKeypair } from "../../../src/keypair/index.js"; -import { createNodeContact } from "../../../src/session/nodeInfo.js"; describe("session crypto", () => { it("ecdh should produce expected secret", () => { @@ -55,7 +54,12 @@ describe("session crypto", () => { const enr1 = SignableENR.createV4(kp1.privateKey); const enr2 = SignableENR.createV4(kp2.privateKey); const nonce = randomBytes(32); - const [a1, b1, pk] = generateSessionKeys(enr1.nodeId, createNodeContact(enr2.toENR()), nonce); + const [a1, b1, pk] = generateSessionKeys( + enr1.nodeId, + enr2.nodeId, + createKeypair({ type: enr2.keypairType, publicKey: enr2.publicKey }), + nonce + ); const [a2, b2] = deriveKeysFromPubkey(kp2, enr2.nodeId, enr1.nodeId, pk, nonce); expect(a1).to.deep.equal(a2); diff --git a/packages/discv5/test/unit/session/service.test.ts b/packages/discv5/test/unit/session/service.test.ts index af190da6..6016a361 100644 --- a/packages/discv5/test/unit/session/service.test.ts +++ b/packages/discv5/test/unit/session/service.test.ts @@ -84,11 +84,11 @@ describe("session service", () => { resolve(); }) ); - service0.sendRequest(createNodeContact(enr1.toENR()), createFindNodeMessage([0])); + service0.sendRequest(createNodeContact(enr1.toENR(), { ip4: true, ip6: true }), createFindNodeMessage([0])); await Promise.all([receivedRandom, receivedWhoAreYou, establishedSession, receivedMsg]); }); it("receiver should drop WhoAreYou packets from destinations without existing pending requests", async () => { - transport0.send(addr1, enr1.nodeId, createWhoAreYouPacket(Buffer.alloc(12), BigInt(0))); + void transport0.send(addr1, enr1.nodeId, createWhoAreYouPacket(Buffer.alloc(12), BigInt(0))); transport0.on("packet", () => expect.fail("transport0 should not receive any packets")); }); it("should only accept WhoAreYou packets from destinations with existing pending requests", async () => {