ntrnetwork
Version:
Transledger peer to peer wire communication
279 lines (254 loc) • 11.3 kB
JavaScript
//----------------------------------------------------------------------------------------
// 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;