ethstats-client/lib/node.js

516 lines
10 KiB
JavaScript
Raw Normal View History

2015-02-12 13:26:47 +01:00
var web3 = require('ethereum.js');
2015-02-17 02:07:40 +01:00
var _ = require('lodash');
2015-02-12 13:26:47 +01:00
var os = require('os');
2015-02-17 23:15:34 +01:00
var shelljs = require('shelljs');
2015-04-03 18:34:31 +02:00
var debounce = require('debounce');
2015-04-06 20:11:22 +02:00
var registrar = require('./registrar.js');
2015-02-12 13:26:47 +01:00
2015-02-16 22:30:21 +01:00
var Primus = require('primus'),
Emitter = require('primus-emit'),
2015-02-23 14:57:59 +01:00
Latency = require('primus-spark-latency'),
2015-02-16 22:30:21 +01:00
Socket;
2015-03-22 22:09:34 +01:00
var ETH_VERSION,
NET_VERSION,
API_VERSION;
var INSTANCE_NAME = process.env.INSTANCE_NAME;
2015-04-06 20:11:22 +02:00
var Contract = null;
2015-03-27 12:56:11 +01:00
web3.setProvider(new web3.providers.HttpProvider('http://' + (process.env.RPC_HOST || 'localhost') + ':' + (process.env.RPC_PORT || '8080')));
2015-03-22 22:09:34 +01:00
2015-02-16 22:30:21 +01:00
Socket = Primus.createSocket({
transformer: 'websockets',
pathname: '/api',
2015-04-08 23:58:23 +02:00
timeout: 60000,
2015-04-05 20:29:34 +02:00
strategy: 'disconnect,online',
2015-02-23 14:57:59 +01:00
plugin: {emitter: Emitter, sparkLatency: Latency}
2015-02-16 22:30:21 +01:00
});
2015-03-22 22:09:34 +01:00
if(process.env.NODE_ENV === 'production' && INSTANCE_NAME === "")
2015-02-17 23:15:34 +01:00
{
2015-03-22 22:09:34 +01:00
INSTANCE_NAME = shelljs.exec('ec2metadata --instance-id', {silent: true}).output;
2015-02-17 23:15:34 +01:00
}
2015-02-16 22:30:21 +01:00
var socket = new Socket(process.env.WS_SERVER || 'ws://localhost:3000');
2015-02-26 22:58:46 +01:00
var WS_SECRET = process.env.WS_SECRET || "eth-net-stats-has-a-secret";
2015-02-16 17:44:26 +01:00
2015-04-03 00:12:28 +02:00
var PENDING_WORKS = true;
var MAX_BLOCKS_HISTORY = 36;
2015-04-02 17:22:33 +02:00
var UPDATE_INTERVAL = 5000;
var PING_INTERVAL = 2000;
2015-04-06 01:26:43 +02:00
var MINERS_LIMIT = 5;
2015-02-12 13:26:47 +01:00
2015-02-12 22:59:01 +01:00
function Node()
2015-02-11 23:54:33 +01:00
{
2015-02-16 17:44:26 +01:00
var self = this;
2015-03-22 21:26:55 +01:00
try {
2015-03-22 21:55:15 +01:00
ETH_VERSION = web3.version.client;
NET_VERSION = web3.version.network;
API_VERSION = web3.version.api;
2015-03-22 21:26:55 +01:00
}
catch (err) {
2015-03-27 12:56:11 +01:00
console.error("Couldn't get version");
2015-03-22 21:26:55 +01:00
}
2015-03-20 07:36:52 +01:00
2015-02-11 23:54:33 +01:00
this.info = {
2015-02-17 23:15:34 +01:00
name: INSTANCE_NAME || (process.env.EC2_INSTANCE_ID || os.hostname()),
2015-03-22 21:55:15 +01:00
node: ETH_VERSION,
net: NET_VERSION,
api: API_VERSION,
2015-02-12 13:26:47 +01:00
os: os.platform(),
os_v: os.release()
2015-02-11 23:54:33 +01:00
};
this.id = _.camelCase(this.info.name);
2015-02-17 02:07:40 +01:00
2015-03-27 12:56:11 +01:00
console.info(this.info);
2015-02-17 02:07:40 +01:00
2015-02-12 13:26:47 +01:00
this.stats = {
2015-02-11 23:54:33 +01:00
active: false,
2015-02-12 13:26:47 +01:00
listening: false,
2015-02-11 23:54:33 +01:00
mining: false,
2015-02-12 13:26:47 +01:00
peers: 0,
pending: 0,
gasPrice: 0,
block: {},
2015-02-17 02:07:40 +01:00
blocktimeAvg: 0,
2015-02-12 13:26:47 +01:00
difficulty: [],
2015-02-17 22:42:52 +01:00
txDensity: [],
2015-02-23 15:47:18 +01:00
blockTimes: [],
2015-03-28 19:06:46 +01:00
gasSpending: [],
2015-04-06 01:20:14 +02:00
miners: [],
uptime: 0,
2015-02-12 13:26:47 +01:00
errors: []
};
this._lastStats = JSON.stringify(this.stats);
2015-04-09 00:33:36 +02:00
this._coinbase = web3.eth.coinbase;
this._tries = 0;
this._down = 0;
2015-02-18 03:59:03 +01:00
this._lastSent = 0;
2015-04-03 06:00:26 +02:00
this._latency = 0;
2015-02-11 23:54:33 +01:00
2015-02-17 02:07:40 +01:00
this.blocks = [];
2015-04-06 20:11:22 +02:00
this._Registrar = null;
this._knownMiners = [];
2015-02-16 22:53:32 +01:00
this._socket = null;
2015-03-27 20:11:21 +01:00
this.pendingFilter = false;
this.chainFilter = false;
2015-02-12 13:26:47 +01:00
this.updateInterval = false;
2015-04-02 17:22:33 +02:00
this.pingInterval = false;
2015-02-12 13:26:47 +01:00
2015-02-16 22:30:21 +01:00
socket.on('open', function open() {
2015-02-26 22:58:46 +01:00
socket.emit('hello', { id: self.id, info: self.info, secret: WS_SECRET });
2015-03-27 12:38:41 +01:00
console.info('The connection has been opened.');
console.info('Trying to login');
})
.on('end', function end() {
2015-02-16 22:53:32 +01:00
self._socket = false;
2015-03-27 12:38:41 +01:00
console.error('Socket connection closed');
})
.on('error', function error(err) {
2015-04-02 20:11:55 +02:00
console.error("socket:", err);
2015-03-27 12:38:41 +01:00
})
.on('reconnecting', function reconnecting(opts) {
console.warn('We are scheduling a reconnect operation', opts);
})
2015-04-03 06:00:26 +02:00
.on('node-pong', function(data) {
2015-04-06 03:51:19 +02:00
var latency = Math.ceil(((new Date()).getTime() - self._latency)/2);
2015-04-03 06:00:26 +02:00
socket.emit('latency', { id: self.id, latency: latency });
})
2015-03-27 12:38:41 +01:00
.on('data', function incoming(data) {
console.info('Received some data', data);
2015-02-16 17:44:26 +01:00
});
2015-02-11 23:54:33 +01:00
socket.on('ready', function()
{
2015-02-18 03:32:03 +01:00
self._socket = true;
self.sendUpdate(true);
2015-03-27 12:38:41 +01:00
console.info('The connection has been established.');
2015-02-26 22:45:24 +01:00
});
2015-02-17 02:07:40 +01:00
this.init();
2015-02-11 23:54:33 +01:00
2015-02-17 02:07:40 +01:00
return this;
2015-02-12 14:38:09 +01:00
}
2015-02-12 13:26:47 +01:00
Node.prototype.isActive = function()
2015-02-11 23:54:33 +01:00
{
this._tries++;
2015-02-12 13:26:47 +01:00
this.stats.errors = [];
2015-02-11 23:54:33 +01:00
try {
2015-03-22 22:16:30 +01:00
var peers = web3.toDecimal(web3.net.peerCount);
2015-02-12 13:26:47 +01:00
2015-02-18 03:32:03 +01:00
if(peers !== null)
{
this.stats.peers = peers;
this.stats.active = true;
return true;
}
2015-02-11 23:54:33 +01:00
}
catch (err) {
2015-02-12 13:26:47 +01:00
this.stats.errors.push({
code: '1',
msg: err
});
2015-04-02 20:11:55 +02:00
console.error("peerCount:", err);
2015-02-11 23:54:33 +01:00
}
2015-02-18 03:32:03 +01:00
this.stats.active = false;
this.stats.listening = false;
this.stats.mining = false;
this.stats.peers = 0;
this._down++;
return false;
2015-02-12 13:26:47 +01:00
}
Node.prototype.getBlock = function(number)
{
var block = {
number: 0,
hash: '?',
difficulty: 0,
2015-04-07 18:31:43 +02:00
timestamp: 0
2015-02-12 13:26:47 +01:00
};
2015-02-11 23:54:33 +01:00
2015-02-12 13:26:47 +01:00
if(typeof number === 'undefined'){
try {
2015-03-27 12:38:41 +01:00
number = web3.eth.blockNumber;
2015-02-12 13:26:47 +01:00
if(number === this.stats.block.number)
2015-02-12 13:26:47 +01:00
return this.stats.block;
}
catch (err) {
this.stats.errors.push({
code: '3',
msg: err
});
2015-04-02 20:11:55 +02:00
console.error("blockNumber:", err);
2015-02-11 23:54:33 +01:00
}
}
2015-02-12 13:26:47 +01:00
try {
2015-03-22 22:09:34 +01:00
block = web3.eth.getBlock(number, true);
2015-02-12 13:26:47 +01:00
if(block.hash != '?' && typeof block.difficulty !== 'undefined')
{
block.difficulty = web3.toDecimal(block.difficulty);
}
}
catch (err) {
this.stats.errors.push({
code: '2',
msg: err
});
2015-04-02 20:11:55 +02:00
console.error("getBlock:", err);
2015-02-12 13:26:47 +01:00
}
return block;
}
Node.prototype.getLatestBlocks = function()
{
var bestBlock = this.stats.block.number;
var maxIterations = MAX_BLOCKS_HISTORY;
var minBlock = 0;
2015-02-17 02:07:40 +01:00
if(this.blocks.length > 0)
2015-02-12 13:26:47 +01:00
{
2015-02-17 02:07:40 +01:00
maxIterations = Math.min(bestBlock - this.blocks[0].number, MAX_BLOCKS_HISTORY);
2015-02-12 13:26:47 +01:00
}
minBlock = Math.max(0, parseInt(bestBlock) - maxIterations);
for (var i = minBlock; i < bestBlock; i++)
{
this.addBlockHistory(this.getBlock(i));
};
this.addBlockHistory(this.stats.block);
2015-02-23 15:47:18 +01:00
this.stats.blockTimes = this.calculateBlockTimes();
2015-02-12 13:26:47 +01:00
this.stats.blocktimeAvg = this.blockTimesAvg();
this.stats.difficulty = this.difficultyChart();
2015-02-17 22:42:52 +01:00
this.stats.txDensity = this.txDensityChart();
2015-03-28 19:06:46 +01:00
this.stats.gasSpending = this.gasSpendingChart();
2015-04-06 01:20:14 +02:00
this.stats.miners = this.minersChart();
2015-02-12 13:26:47 +01:00
}
Node.prototype.addBlockHistory = function(block)
{
2015-04-05 20:29:34 +02:00
if(this.blocks.length === 0 || (block !== null && block.number !== this.blocks[0].number))
2015-02-12 13:26:47 +01:00
{
2015-02-17 02:07:40 +01:00
if(this.blocks.length === MAX_BLOCKS_HISTORY)
{
this.blocks.pop();
}
2015-02-12 13:26:47 +01:00
2015-02-17 02:07:40 +01:00
this.blocks.unshift(block);
}
2015-02-12 13:26:47 +01:00
}
2015-02-11 23:54:33 +01:00
2015-02-12 13:26:47 +01:00
Node.prototype.calculateBlockTimes = function()
{
var self = this;
2015-02-17 02:07:40 +01:00
var blockTimes = _.map(this.blocks, function(block, key, list)
2015-02-12 13:26:47 +01:00
{
2015-02-18 09:55:50 +01:00
var diff = (key > 0 ? list[key - 1].timestamp : Math.floor(Date.now()/1000)) - block.timestamp;
2015-02-12 13:26:47 +01:00
2015-04-07 19:07:23 +02:00
diff = Math.max(diff, 0);
2015-02-12 13:26:47 +01:00
return diff;
});
2015-02-23 15:18:43 +01:00
blockTimes.shift();
2015-02-12 13:26:47 +01:00
return blockTimes;
}
Node.prototype.blockTimesAvg = function()
{
2015-02-23 15:47:18 +01:00
var sum = _.reduce(this.stats.blockTimes, function(memo, time) { return memo + time;}, 0);
2015-02-12 13:26:47 +01:00
2015-02-23 15:47:18 +01:00
return sum/this.stats.blockTimes.length;
2015-02-12 13:26:47 +01:00
}
Node.prototype.difficultyChart = function()
{
2015-02-17 02:07:40 +01:00
return difficulty = _.map(this.blocks, function(block)
2015-02-12 13:26:47 +01:00
{
return block.difficulty;
});
}
2015-02-17 22:42:52 +01:00
Node.prototype.txDensityChart = function()
{
return txDensity = _.map(this.blocks, function(block)
{
2015-03-22 22:09:34 +01:00
return block.transactions.length;
2015-02-17 22:42:52 +01:00
});
}
2015-03-28 19:06:46 +01:00
Node.prototype.gasSpendingChart = function()
{
return gasSpending = _.map(this.blocks, function(block)
{
return block.gasUsed;
});
}
2015-04-06 20:11:22 +02:00
Node.prototype.getMinerName = function(miner)
{
var result = _.find(this._knownMiners, {miner: miner});
if(result !== undefined)
{
return result.name;
}
else
{
var name = this._Registrar.call({from: this._coinbase}).name(miner);
2015-04-06 20:11:22 +02:00
if(name.length > 0)
{
this._knownMiners.push({miner: miner, name: name});
return name;
}
2015-04-07 18:49:49 +02:00
else
{
this._knownMiners.push({miner: miner, name: false});
return false;
}
2015-04-06 20:11:22 +02:00
}
return false;
}
2015-04-06 01:20:14 +02:00
Node.prototype.minersChart = function()
{
2015-04-06 20:11:22 +02:00
var self = this;
2015-04-06 01:20:14 +02:00
var miners = _.countBy(this.blocks, function(block)
{
return block.miner;
});
var minersArray = [];
2015-04-06 20:11:22 +02:00
_.forEach(miners, function(cnt, miner)
{
var name = self.getMinerName(miner);
minersArray.push({miner: miner, name: name, blocks: cnt});
2015-04-06 01:20:14 +02:00
});
2015-04-06 01:26:43 +02:00
var minersArray = _.sortBy(minersArray, 'blocks').reverse();
return minersArray.slice(0, MINERS_LIMIT);
2015-04-06 01:20:14 +02:00
}
2015-02-12 13:26:47 +01:00
Node.prototype.uptime = function()
{
this.stats.uptime = ((this._tries - this._down) / this._tries) * 100;
2015-02-12 13:26:47 +01:00
}
Node.prototype.getStats = function()
{
if(this._socket)
this._lastStats = JSON.stringify(this.stats);
2015-02-12 13:26:47 +01:00
if(this.isActive())
{
this.stats.block = this.getBlock();
// Get last MAX_BLOCKS_HISTORY blocks for calculations
if(this.stats.block.number > 0)
this.getLatestBlocks();
2015-04-03 00:12:28 +02:00
if(PENDING_WORKS) {
try {
this.stats.pending = web3.eth.getBlockTransactionCount('pending');
} catch (err) {
PENDING_WORKS = false;
console.error("getBlockTransactionCount('pending'):", err);
}
2015-04-02 22:52:55 +02:00
}
2015-04-03 00:12:28 +02:00
2015-02-12 13:26:47 +01:00
this.stats.mining = web3.eth.mining;
2015-04-06 01:43:34 +02:00
this.stats.gasPrice = web3.toBigNumber(web3.eth.gasPrice).toString(10);
2015-02-12 13:26:47 +01:00
}
this.uptime();
}
Node.prototype.changed = function()
{
var changed = ! _.isEqual(this._lastStats, JSON.stringify(this.stats));
2015-02-18 03:59:03 +01:00
if(this._tries - this._lastSent > 5)
{
this._lastSent = this._tries;
return true;
}
return changed;
}
2015-02-12 15:02:44 +01:00
Node.prototype.prepareStats = function()
{
return {
id: this.id,
2015-02-12 15:02:44 +01:00
stats: this.stats
};
}
Node.prototype.sendUpdate = function(force)
{
if(this.changed() || force)
this.emit('update', this.prepareStats());
}
2015-02-12 13:26:47 +01:00
Node.prototype.update = function()
{
this.getStats();
this.sendUpdate();
2015-02-12 15:02:44 +01:00
2015-02-12 13:26:47 +01:00
return this.stats;
2015-02-11 23:54:33 +01:00
};
2015-04-07 18:46:05 +02:00
Node.prototype.updatePending = function()
{
if(PENDING_WORKS) {
try {
this.stats.pending = web3.eth.getBlockTransactionCount('pending');
this.sendUpdate();
} catch (err) {
PENDING_WORKS = false;
console.error("getBlockTransactionCount('pending'):", err);
}
}
}
2015-04-02 17:22:33 +02:00
Node.prototype.ping = function()
{
2015-04-03 06:00:26 +02:00
this._latency = (new Date()).getTime();
2015-04-03 05:08:54 +02:00
this.emit('node-ping', { id: this.id });
2015-04-02 17:22:33 +02:00
};
2015-02-12 13:26:47 +01:00
Node.prototype.setWatches = function()
{
var self = this;
2015-02-17 02:07:40 +01:00
2015-03-28 18:16:52 +01:00
this.pendingFilter = web3.eth.filter('pending');
this.pendingFilter.watch( function(log) {
2015-04-03 00:13:22 +02:00
if(PENDING_WORKS) {
2015-04-03 18:34:59 +02:00
debounce(function() {
2015-04-07 18:46:05 +02:00
self.updatePending();
2015-04-03 18:34:59 +02:00
}, 50);
2015-04-03 00:13:22 +02:00
}
2015-03-28 18:16:52 +01:00
});
this.chainFilter = web3.eth.filter('latest');
this.chainFilter.watch(function(log) {
2015-04-03 18:34:31 +02:00
debounce(function() {
self.update();
}, 50);
2015-03-28 18:16:52 +01:00
});
2015-02-12 13:26:47 +01:00
this.updateInterval = setInterval(function(){
self.update();
2015-04-02 17:22:33 +02:00
}, UPDATE_INTERVAL);
this.pingInterval = setInterval(function(){
self.ping();
}, PING_INTERVAL);
2015-02-12 13:26:47 +01:00
}
2015-02-16 17:44:26 +01:00
Node.prototype.emit = function(message, payload)
2015-02-12 13:26:47 +01:00
{
2015-02-16 22:53:32 +01:00
if(this._socket){
2015-02-16 17:44:26 +01:00
try {
socket.emit(message, payload);
}
catch (err) {
2015-04-02 20:11:55 +02:00
console.error("socket.emit:", err);
2015-02-16 17:44:26 +01:00
}
}
}
2015-02-12 15:02:44 +01:00
2015-04-06 20:11:22 +02:00
Node.prototype.installContract = function()
{
Contract = web3.eth.contract(registrar.desc);
this._Registrar = new Contract(registrar.address);
}
2015-02-16 17:44:26 +01:00
Node.prototype.init = function()
{
2015-04-06 20:11:22 +02:00
this.installContract();
2015-02-12 13:26:47 +01:00
this.update();
this.setWatches();
}
Node.prototype.stop = function()
{
2015-02-16 22:53:32 +01:00
if(this._socket)
2015-02-18 07:05:58 +01:00
socket.end();
2015-02-12 15:02:44 +01:00
2015-03-22 21:26:55 +01:00
2015-02-12 13:26:47 +01:00
if(this.updateInterval)
clearInterval(this.updateInterval);
2015-04-02 17:22:33 +02:00
if(this.pingInterval)
clearInterval(this.pingInterval);
2015-03-22 22:22:08 +01:00
web3.reset();
2015-02-12 13:26:47 +01:00
}
2015-02-11 23:54:33 +01:00
module.exports = Node;