UNPKG

ntrnetwork

Version:

Transledger peer to peer wire communication

208 lines (181 loc) 6.88 kB
const { EventEmitter } = require('events') const dgram = require('dgram') const ms = require('ms') const LRUCache = require('lru-cache') const message = require('./message') const { keccak256, pk2id, createDeferred } = require('../util') const VERSION = 0x010; let interblockchainKey = 0x00; const createSocketUDP4 = dgram.createSocket.bind(null, 'udp4'); const debug = (process.env.SERVER) ? process.env.SERVER : require('../environment').debug.server; // channelID is set by default to 0x16; // the channelID used to discriminate the channels can be overloaded. class Server extends EventEmitter { constructor (dpt, privateKey, options) { super() this._dpt = dpt; this._privateKey = privateKey; if (!this._channelID) this._channelID = interblockchainKey; if (!this._monitor) this._monitor = false; this._timeout = options.timeout || ms('10s') this._endpoint = options.endpoint || { address: '0.0.0.0', udpPort: null, tcpPort: null } this._requests = new Map() this._parityRequestMap = new Map() this._requestsCache = new LRUCache({ max: 1000, maxAge: ms('1s'), stale: false }) const createSocket = options.createSocket || createSocketUDP4 this._socket = createSocket() this._socket.once('listening', () => this.emit('listening')) this._socket.once('close', () => this.emit('close')) this._socket.on('error', (err) => this.emit('error', err)) this._socket.on('message', (msg, rinfo) => { try { if (msg) this._handler(msg, rinfo); } catch (err) { this.emit('error', err); } }) } set channelID (channel) { this._channelID = channel; } get channelID () { return this._channelID; } bind (...args) { this._isAliveCheck() debug ? console.log(`${Date().toString().substring(0, 24)} dpt/server.bind`) : null; this._socket.bind(...args) } destroy (...args) { this._isAliveCheck() debug ? console.log(`${Date().toString().substring(0, 24)} dpt/server.destroy`) : null; 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, interblockchain: this._channelID }) const deferred = createDeferred() const rkey = hash.toString('hex') this._requests.set(rkey, { peer, deferred, timeoutId: setTimeout(() => { if (this._requests.get(rkey) !== undefined) { debug ? console.log(`${Date().toString().substring(0, 24)} dpt/server.ping timeout: ${peer.address}:${peer.udpPort}`) : null; this._requests.delete(rkey) deferred.reject(new Error(`${Date().toString().substring(0, 24)} dpt/server.ping: 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', { version: VERSION, from: this._endpoint, to: peer, interblockchain: this._channelID }) } _isAliveCheck () { if (this._socket === null) throw new Error('Server already destroyed') } _send (peer, typename, data) { if (peer && typename && data) { debug ? console.log(`${Date().toString().substring(0, 24)} dpt/server.send ${typename} to ${peer.address}:${peer.udpPort}`) : null; const msg = message.encode(typename, data, this._privateKey) ; this._socket.send(msg, 0, msg.length, peer.udpPort, peer.address); return msg.slice(0, 32); // message id } } _handler (msg, rinfo) { const info = message.decode(msg); const peerId = pk2id(info.publicKey); debug ? console.log(`${Date().toString().substring(0, 24)} dpt/server._handler: received ${info.typename} from ${rinfo.address}:${rinfo.port}`) : null; // 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.emit('peers', [ info.data.from ]), ms('100ms')) } // all handled messages are filtered with a channelID switch (info.typename) { case 'ping': if (info.data.interblockchain == this.channelID || this.channelID == 0x00 || info.data.interblockchain == 0x00) { Object.assign(rinfo, { id: peerId, udpPort: rinfo.port }); this._send(rinfo, 'pong', { to: { address: rinfo.address, udpPort: rinfo.port, tcpPort: info.data.from.tcpPort }, version: VERSION, interblockchain: this._channelID, hash: msg.slice(0, 32) }); } break case 'pong': if (info.data.interblockchain == this.channelID || this.channelID == 0x00 || info.data.interblockchain == 0x00) { var rkey = info.data.hash.toString('hex'); const request = this._requests.get(rkey); if (request) { this._requests.delete(rkey) request.deferred.resolve({ id: peerId, address: request.peer.address, udpPort: request.peer.udpPort, tcpPort: request.peer.tcpPort }) } } break; case 'findneighbours': if (info.data.interblockchain == this.channelID || this.channelID == 0x00 || info.data.interblockchain == 0x00) { Object.assign(rinfo, { id: peerId, udpPort: rinfo.port }) this._send(rinfo, 'neighbours', { version: VERSION, interblockchain: this._channelID, peers: this._dpt.getClosestPeers(info.data.id) }) } break case 'neighbours': if (info.data.interblockchain == this.channelID || this.channelID == 0x00 || info.data.interblockchain == 0x00) { this.emit('peers', info.data.peers.map((peer) => peer.endpoint)) } break } } } module.exports = Server;