@ethereumjs/devp2p
Version:
A JavaScript implementation of ÐΞVp2p
180 lines • 7.28 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Server = void 0;
const dgram = require("dgram");
const util_1 = require("@ethereumjs/util");
const debug_1 = require("debug");
const eventemitter3_1 = require("eventemitter3");
const lru_cache_1 = require("lru-cache");
const util_ts_1 = require("../util.js");
const message_ts_1 = require("./message.js");
const DEBUG_BASE_NAME = 'dpt:server';
const verbose = (0, debug_1.default)('verbose').enabled;
const VERSION = 0x04;
class Server {
constructor(dpt, privateKey, options) {
this.events = new eventemitter3_1.EventEmitter();
this._dpt = dpt;
this._privateKey = privateKey;
this._timeout = options.timeout ?? 4000; // 4 * 1000
this._endpoint = options.endpoint ?? { address: '0.0.0.0', udpPort: null, tcpPort: null };
this._requests = new Map();
this._requestsCache = new lru_cache_1.LRUCache({ max: 1000, ttl: 1000 }); // 1 sec * 1000
const createSocket = options.createSocket ?? dgram.createSocket.bind(null, { type: 'udp4' });
this._socket = createSocket();
this._debug = util_ts_1.devp2pDebug.extend(DEBUG_BASE_NAME);
if (this._socket) {
this._socket.once('listening', () => this.events.emit('listening'));
this._socket.once('close', () => this.events.emit('close'));
this._socket.on('error', (err) => this.events.emit('error', err));
this._socket.on('message', (msg, rinfo) => {
try {
this._handler(msg, rinfo);
}
catch (err) {
this.events.emit('error', err);
}
});
}
this._common = options.common;
this.DEBUG =
typeof window === 'undefined' ? (process?.env?.DEBUG?.includes('ethjs') ?? false) : false;
}
bind(...args) {
this._isAliveCheck();
if (this.DEBUG) {
this._debug('call .bind');
}
if (this._socket)
this._socket.bind(...args);
}
destroy(...args) {
this._isAliveCheck();
if (this.DEBUG) {
this._debug('call .destroy');
}
if (this._socket) {
this._socket.close(...args);
this._socket = null;
}
}
async ping(peer) {
this._isAliveCheck();
const rcKey = `${peer.address}:${peer.udpPort}`;
const promise = this._requestsCache.get(rcKey);
if (promise !== undefined)
return promise;
const hash = this._send(peer, 'ping', {
version: VERSION,
from: this._endpoint,
to: peer,
});
const deferred = (0, util_ts_1.createDeferred)();
const rKey = (0, util_1.bytesToUnprefixedHex)(hash);
this._requests.set(rKey, {
peer,
deferred,
timeoutId: setTimeout(() => {
if (this._requests.get(rKey) !== undefined) {
if (this.DEBUG) {
this._debug(`ping timeout: ${peer.address}:${peer.udpPort} ${peer.id ? (0, util_ts_1.formatLogId)((0, util_1.bytesToHex)(peer.id), verbose) : '-'}`);
}
this._requests.delete(rKey);
deferred.reject((0, util_1.EthereumJSErrorWithoutCode)(`Timeout error: ping ${peer.address}:${peer.udpPort}`));
}
else {
return deferred.promise;
}
}, this._timeout),
});
this._requestsCache.set(rcKey, deferred.promise);
return deferred.promise;
}
findneighbours(peer, id) {
this._isAliveCheck();
this._send(peer, 'findneighbours', { id });
}
_isAliveCheck() {
if (this._socket === null)
throw (0, util_1.EthereumJSErrorWithoutCode)('Server already destroyed');
}
_send(peer, typename, data) {
if (this.DEBUG) {
this.debug(typename, `send ${typename} to ${peer.address}:${peer.udpPort} (peerId: ${peer.id ? (0, util_ts_1.formatLogId)((0, util_1.bytesToHex)(peer.id), verbose) : '-'})`);
}
const msg = (0, message_ts_1.encode)(typename, data, this._privateKey, this._common);
if (this._socket && typeof peer.udpPort === 'number')
this._socket.send(msg, 0, msg.length, peer.udpPort, peer.address);
return msg.subarray(0, 32); // message id
}
_handler(msg, rinfo) {
const info = (0, message_ts_1.decode)(msg, this._common); // Dgram serializes everything to `Uint8Array`
const peerId = (0, util_ts_1.pk2id)(info.publicKey);
if (this.DEBUG) {
this.debug(info.typename.toString(), `received ${info.typename} from ${rinfo.address}:${rinfo.port} (peerId: ${(0, util_ts_1.formatLogId)((0, util_1.bytesToHex)(peerId), verbose)})`);
}
// add peer if not in our table
const peer = this._dpt.getPeer(peerId);
if (peer === null && info.typename === 'ping' && info.data.from.udpPort !== null) {
setTimeout(() => this.events.emit('peers', [info.data.from]), 100); // 100 ms
}
switch (info.typename) {
case 'ping': {
const remote = {
id: peerId,
udpPort: rinfo.port,
address: rinfo.address,
};
this._send(remote, 'pong', {
to: {
address: rinfo.address,
udpPort: rinfo.port,
tcpPort: info.data.from.tcpPort,
},
hash: msg.subarray(0, 32),
});
break;
}
case 'pong': {
const rKey = (0, util_1.bytesToUnprefixedHex)(info.data.hash);
const request = this._requests.get(rKey);
if (request !== undefined) {
this._requests.delete(rKey);
request.deferred.resolve({
id: peerId,
address: request.peer.address,
udpPort: request.peer.udpPort,
tcpPort: request.peer.tcpPort,
});
}
break;
}
case 'findneighbours': {
const remote = {
id: peerId,
udpPort: rinfo.port,
address: rinfo.address,
};
this._send(remote, 'neighbours', {
peers: this._dpt.getClosestPeers(info.data.id),
});
break;
}
case 'neighbours': {
this.events.emit('peers', info.data.peers.map((peer) => peer.endpoint));
break;
}
}
}
/**
* Debug message both on the generic as well as the
* per-message debug logger
* @param messageName Lower capital message name (e.g. `findneighbours`)
* @param msg Message text to debug
*/
debug(messageName, msg) {
this._debug.extend(messageName)(msg);
}
}
exports.Server = Server;
//# sourceMappingURL=server.js.map