UNPKG

ntrnetwork

Version:

Transledger peer to peer wire communication

279 lines (254 loc) 11.3 kB
//---------------------------------------------------------------------------------------- // internode library // // Author: Didier PH Martin // updates: // October 17th 2018 // --------------------------------------------------------------------------------------- const MESSAGE_CODES = require('../src/monitor.js').MESSAGE_CODES;// INTERnodes message codes const rlpx = require('../src/monitor.js').rlpx; // rlpx library const dpt = require('../src/monitor.js').dpt; const { EventEmitter } = require('events'); const getPeerAddr = (peer) => `${peer._socket.remoteAddress}:${peer._socket.remotePort}`; const getPeerIP = (peer) => `${peer._socket.remoteAddress}`; const status = require('../src/monitor.js').status; const environment = require('../src/environment'); const txRequests = {}; const txRequestsByID = {}; const ip = require('ip'); const server = require('../src/dpt/server.js'); const nodeID = require('./environment').nodeID; const debug = (process.env.BROADCASTER) ? process.env.BROADCASTER : require('./environment').debug.broadcaster; class Broadcaster { constructor(channelID) { if (channelID || channelID == 0x00) { dpt.server.channelID = channelID; rlpx.channelID = channelID; this._channelID = channelID; } // RLPX-INTERBLOCKCHAIN PROTOCOL // ======================================================================================== // When a peer is added in the DPT table, an event is fired. The corresponding // event handler process it and adds two event handlers associated to that particular peer: // - onstatus: To receive status information from the connected peer // - onmessage: to process message coming from the connected peer. // Initially, when the event 'peer:added' is fired, a status object (i.e. string) is sent to // the connected peer. rlpx.on('peer:added', (peer) => { const addr = getPeerAddr(peer); const channelID = peer.channelID; const ip = getPeerIP(peer); const ntr = peer.getProtocols()[0]; const requests = { headers: [], bodies: [], msgTypes: {} }; const _peer = peer; const clientId = peer.getHelloMessage().clientId debug ? console.log(`${Date().toString().substring(0, 24)} rlpx.on('peer:added') ${addr} ${clientId} (eth${ntr.getVersion()}) (total: ${rlpx.getPeers().length})`) : null; /* In this code there is sequence of two events: - status events fired when a new peer is added to the peer list. - message events either as string or as array (including array of arrays. when a status event is received, the status event handler sends a message consisting of an object packaged as JSON. The JSON object is also a string and therefore is sent and received in conformity to the RLP protocol. The message event handler expects to receive a JSON object. Messages are identified by the message Id: NTR.MESSAGE_CODES.TX. Actually, the Interblockchain accept only one message type identified by the code NTR.MESSAGE_CODES.TX. */ ntr.sendStatus(status); ntr.once('status', (status) => { _peer.clientId = status.nodeID; debug ? console.log(`${Date().toString().substring(0, 24)} ntr.on('status'): received status from ${addr}`) : null; }) /* This event handler is fired for each message received. The payload can be: - An array or an array of arrays - A string - Objects are to formatted as JSON objects. (i.e strings) Messages passed between interblockchain nodes are objects packaged as encoded safe Buffers (see README). To decode messages, use the payload.toString('ascii') function. This transforms the received buffer into an ascii string. In the case of JSON object, since it is also a string, to transform it back as an object, use the JSON.parse(payload) function. Replica of TX or AUDIT transaction ID are not broadcasted. To prevent uneeded broadcasts, the requestbyID map contains, for a few seconds a list of recently broadcasted messages. Every 3 seconds, a garbagge collector clean the recorded transactions from the requestByID map. */ ntr.on('message', async (code, payload) => { // count how many message for each code type if (code in requests.msgTypes) { requests.msgTypes[code] += 1 } else { requests.msgTypes[code] = 1 } if (code == 0) // status message is handled in interblockchain layer return; else { const content = JSON.parse(payload.toString()); let transactionID = ""; let codeType = ""; switch (code) { case MESSAGE_CODES.TX: transactionID = content.data.transactionID; codeType = "TX"; break; case MESSAGE_CODES.AUDIT: transactionID = `${content.TR.transferRequest.transactionID}:${content.nodeID}`; codeType = "AUDIT"; break; } debug ? console.log(`${Date().toString().substring(0, 24)} ntr.on(message) id:${transactionID} from network -> receive data from ${ip} with code: ${codeType}`) : null; if (txRequestsByID[transactionID] == null) { let timestamp = Date.now(); txRequestsByID[transactionID] = timestamp; txRequests[timestamp] = content; if (this.callback) this.callback(code, content); debug ? console.log(`${Date().toString().substring(0, 24)} ntr.on(message) id:${transactionID} to network -> broadcast data from ${ip} with code: ${codeType}`) : null; rlpx._broadcast(code, ip, payload); } } }) }); setInterval( this.garbaggeCollector, 30000); } get channelID() { return this._channelID; } get dptSize() { return dpt.getPeers().length } get dptList() { const nodes = dpt.getPeers(); let list = []; nodes.forEach( (item) => { list.push({ address: item.address, channelID: item.channelID, ucpPort: item.udpPort, tcpPort: item.tcpPort }) }); return list; } // A new clientID is generated each time // the client server is started. get clientID() { return rlpx._clientId; } set clientID(clientId) { rlpx._clientId = clientId; } get peers() { const peers = rlpx.getPeers(); let list = []; peers.forEach ( (item) => { let capabilities = []; item._capabilities.forEach( (protocol) => { capabilities.push({ name: protocol.name, version: protocol.version, opCodes: protocol.length }); }); list.push( { clientID: item.clientId, channelID: item.channelID? item.channelID: 'unknown', closed: item._closed, connected: item._connected, address: item._socket._peername.address, capabilities: capabilities }); }); return list; } get _peers() { let list = []; if (rlpx._peers) { rlpx._peers.forEach ( (item) => { if (item._socket) list.push(item._socket._peername.address); else "Unknown object"; }); } return list; } get maxPeers() { return environment.maxPeers; } get slots() { const slots = rlpx._peers; let list = []; slots.forEach( (item) => { if (item._socket) list.push(`address:${item._socket._peername.address}:${item._socket._peername.port}, channel:${item.channelID?item.channelID:'unknown'}`); }) return list; } get peerSize() { return rlpx._peers.size; } get OpenSlots() { return rlpx._getOpenSlots(); } get peersQueue() { const peersQueue = rlpx._peersQueue; let list = []; peersQueue.forEach( (item) => { list.push({ clientID: item._clientId ? item._clientId.toString() : "none", closed: item._closed, connected: item._connected, address: item._socket._peername ? item._socket._peername.address: "none", }) }); return list; } getTransactions() { return txRequests; } garbaggeCollector() { const ttl = 30*1000; for (let tx in txRequests) { if (Date.now() - tx > ttl) { delete txRequestsByID[txRequests[tx].transactionID]; delete txRequests[tx]; } }; } // We broadcast a message to all peers // we keep track of the transactionID to indicate // later on, in ntr.on("message") if we already broadcasted the message // it won't be broadcasted again. async publish(code, content) { Object.assign(content, {ip: ip.address(), nodeID: environment.nodeID}); let identifier = ""; let codeType = ""; const transactionID = environment.uuidv4(); switch (code) { case MESSAGE_CODES.TX: identifier = content.data.transactionID; codeType = "TX"; break; case MESSAGE_CODES.AUDIT: Object.assign(content, {transactionID: transactionID}); identifier = `${content.TR.transferRequest.transactionID}:${content.nodeID}`; codeType = "AUDIT"; break; } let timestamp = Date.now(); txRequestsByID[identifier]= timestamp; txRequests[timestamp] = content; if (this.callback) this.callback(code, content); debug ? console.log(`${Date().toString().substring(0, 24)} broadcast.publish() id:${identifier} with code: ${codeType}`) : null; rlpx.broadcast(code, JSON.stringify(content)); } subscribe(callback) { this.callback = callback; } } exports.Broadcaster = Broadcaster; exports.MESSAGE_CODES = MESSAGE_CODES;