ethstats-client/lib/node.js

719 lines
13 KiB
JavaScript
Raw Normal View History

2015-04-29 22:34:52 +02:00
'use strict';
2015-04-14 16:29:51 +02:00
var web3 = require('web3');
2015-05-04 22:16:02 +02:00
var async = require('async');
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-04-16 23:36:38 +02:00
var pjson = require('./../package.json');
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-04-29 22:34:52 +02:00
Socket, socket;
2015-02-16 22:30:21 +01:00
2015-03-22 22:09:34 +01:00
var ETH_VERSION,
NET_VERSION,
2015-04-17 00:25:47 +02:00
PROTOCOL_VERSION,
2015-04-29 22:34:52 +02:00
API_VERSION,
COINBASE;
2015-04-17 01:10:27 +02:00
2015-03-22 22:09:34 +01:00
var INSTANCE_NAME = process.env.INSTANCE_NAME;
2015-04-29 22:34:52 +02:00
var WS_SECRET = process.env.WS_SECRET || "eth-net-stats-has-a-secret";
2015-03-22 22:09:34 +01:00
2015-04-06 20:11:22 +02:00
var Contract = null;
2015-04-29 22:34:52 +02:00
var PENDING_WORKS = true;
var MAX_BLOCKS_HISTORY = 40;
var UPDATE_INTERVAL = 5000;
var PING_INTERVAL = 2000;
var MINERS_LIMIT = 5;
var MAX_HISTORY_UPDATE = 50;
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-12 13:26:47 +01:00
2015-02-12 22:59:01 +01:00
function Node()
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-04-16 23:36:38 +02:00
contact: (process.env.CONTACT_DETAILS || ""),
2015-04-29 22:34:52 +02:00
coinbase: null,
node: null,
net: null,
protocol: null,
api: null,
2015-04-16 23:36:38 +02:00
port: (process.env.LISTENING_PORT || 30303),
2015-02-12 13:26:47 +01:00
os: os.platform(),
2015-04-16 23:36:38 +02:00
os_v: os.release(),
2015-04-24 11:38:26 +02:00
client: pjson.version,
2015-04-28 09:41:48 +02:00
canUpdateHistory: true,
2015-02-11 23:54:33 +01:00
};
this.id = _.camelCase(this.info.name);
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-04-24 00:37:42 +02:00
hashrate: 0,
2015-02-12 13:26:47 +01:00
peers: 0,
pending: 0,
gasPrice: 0,
block: {},
2015-04-06 01:20:14 +02:00
miners: [],
2015-04-16 21:15:42 +02:00
uptime: 0
};
2015-04-29 11:14:40 +02:00
this._lastStats = JSON.stringify(this.stats);
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-04-06 20:11:22 +02:00
this._Registrar = null;
this._knownMiners = [];
2015-04-29 22:34:52 +02:00
this._web3 = false;
this._socket = false;
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-04-29 22:34:52 +02:00
this.connectionInterval = false;
2015-02-12 13:26:47 +01:00
2015-05-04 22:16:02 +02:00
this._lastChainLog = 0;
2015-04-28 06:05:04 +02:00
this._lastPendingLog = 0;
2015-04-29 22:34:52 +02:00
this._called = 0
this.startWeb3Connection();
2015-04-28 06:05:04 +02:00
2015-04-29 22:34:52 +02:00
return this;
}
Node.prototype.startWeb3Connection = function()
{
console.info("==> Starting eth connection");
web3.setProvider( new web3.providers.HttpProvider('http://' + (process.env.RPC_HOST || 'localhost') + ':' + (process.env.RPC_PORT || '8080')) );
this.checkWeb3Connection();
}
Node.prototype.checkWeb3Connection = function()
{
var self = this;
if(!this._web3)
{
try {
var tmp = web3.version.client;
if( !_.isUndefined(tmp) )
{
console.log('eth ', tmp);
console.info("==> Ethereum connection established");
this._web3 = true;
this.init();
return true;
}
}
catch(err)
{
2015-04-30 11:29:40 +02:00
console.error('xx> Ethereum connection attempt #' + this._called++ + ' failed;');
2015-04-29 22:34:52 +02:00
process.nextTick( function()
{
self.checkWeb3Connection();
});
}
}
}
Node.prototype.startSocketConnection = function()
{
console.info("==> Starting socket connection");
socket = new Socket( process.env.WS_SERVER || 'ws://localhost:3000' );
this.setupSockets();
}
Node.prototype.setupSockets = function()
{
var self = this;
// Setup events
2015-04-29 11:14:40 +02:00
socket.on('open', function open()
{
2015-04-29 22:34:52 +02:00
console.info('==> The connection has been opened.');
2015-03-27 12:38:41 +01:00
console.info('Trying to login');
2015-04-29 11:14:40 +02:00
socket.emit('hello', {
id: self.id,
info: self.info,
secret: WS_SECRET
});
2015-03-27 12:38:41 +01:00
})
2015-04-29 22:34:52 +02:00
.on('ready', function()
2015-04-29 11:14:40 +02:00
{
2015-04-29 22:34:52 +02:00
self._socket = true;
self.sendUpdate(true);
console.info('==> The connection has been established.');
2015-03-27 12:38:41 +01:00
})
2015-04-29 22:34:52 +02:00
.on('data', function incoming(data)
2015-04-29 11:14:40 +02:00
{
2015-04-29 22:34:52 +02:00
console.info('Received some data', data);
2015-03-27 12:38:41 +01:00
})
2015-04-29 22:34:52 +02:00
.on('history', function (data)
2015-04-29 11:14:40 +02:00
{
2015-04-29 22:34:52 +02:00
console.info('==> Getting history');
var reqHistory = self.getHistory( data );
socket.emit('history', {
id: self.id,
history: reqHistory
});
2015-03-27 12:38:41 +01:00
})
2015-04-29 11:14:40 +02:00
.on('node-pong', function(data)
{
var latency = Math.ceil( (_.now() - self._latency) / 2 );
socket.emit('latency', {
id: self.id,
latency: latency
});
2015-04-03 06:00:26 +02:00
})
2015-04-29 22:34:52 +02:00
.on('end', function end()
2015-04-29 11:14:40 +02:00
{
2015-04-29 22:34:52 +02:00
self._socket = false;
console.error('xx> Socket connection closed');
2015-04-28 09:41:48 +02:00
})
2015-04-29 22:34:52 +02:00
.on('error', function error(err)
{
2015-04-29 22:34:52 +02:00
console.error("socket:", err);
2015-04-28 09:41:48 +02:00
})
2015-04-29 22:34:52 +02:00
.on('reconnecting', function reconnecting(opts)
2015-04-28 09:41:48 +02:00
{
2015-04-29 22:34:52 +02:00
console.warn('We are scheduling a reconnect operation', opts);
2015-02-26 22:45:24 +01:00
});
2015-04-29 22:34:52 +02:00
}
2015-04-29 22:34:52 +02:00
Node.prototype.emit = function(message, payload)
{
if(this._socket)
{
try {
socket.emit(message, payload);
}
catch (err) {
console.error("socket.emit:", err);
}
}
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-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-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
}
2015-04-29 22:34:52 +02:00
Node.prototype.getInfo = function()
{
try {
this.info.coinbase = web3.eth.coinbase;
this.info.node = web3.version.client;
this.info.net = web3.version.network;
this.info.protocol = web3.toDecimal(web3.version.ethereum);
this.info.api = web3.version.api;
console.info(this.info);
return true;
}
catch (err) {
console.error("Couldn't get version");
}
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-04-30 11:29:40 +02:00
if( _.isUndefined(number) )
2015-04-29 22:34:52 +02:00
number = "latest";
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-05-04 22:16:02 +02:00
console.log('====> block:', block);
2015-02-12 13:26:47 +01:00
2015-05-04 22:16:02 +02:00
if( block.hash != '?' && !_.isUndefined(block.difficulty) && !_.isUndefined(block.totalDifficulty) )
2015-02-12 13:26:47 +01:00
{
2015-04-29 11:14:40 +02:00
block.difficulty = web3.toDecimal( block.difficulty );
2015-05-04 22:16:02 +02:00
block.totalDifficulty = web3.toDecimal( block.totalDifficulty );
2015-02-12 13:26:47 +01:00
}
2015-05-04 22:16:02 +02:00
console.log('====> block:', block);
2015-02-12 13:26:47 +01:00
}
catch (err) {
2015-04-16 22:39:52 +02:00
console.error("getBlock(" + number + "):", err);
2015-04-29 11:14:40 +02:00
if(number > 0)
{
try {
2015-04-29 11:14:40 +02:00
number--;
block = web3.eth.getBlock(number, true);
2015-05-04 22:16:02 +02:00
if( block.hash !== '?' && !_.isUndefined(block.difficulty) && !_.isUndefined(block.totalDifficulty) )
{
2015-04-29 11:14:40 +02:00
block.difficulty = web3.toDecimal( block.difficulty );
2015-05-04 22:16:02 +02:00
block.totalDifficulty = web3.toDecimal( block.totalDifficulty );
}
2015-04-29 11:14:40 +02:00
}
catch (err) {
console.error("getBlock(" + number + "):", err);
}
}
2015-02-12 13:26:47 +01:00
}
return block;
}
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
{
2015-04-24 11:38:26 +02:00
if(this._Registrar !== null)
2015-04-07 18:49:49 +02:00
{
2015-04-24 11:38:26 +02:00
var name = this._Registrar.name(miner);
if(name.length > 0)
{
this._knownMiners.push({miner: miner, name: name});
return name;
}
2015-04-07 18:49:49 +02:00
}
2015-04-24 11:38:26 +02:00
this._knownMiners.push({miner: miner, name: false});
2015-04-06 20:11:22 +02:00
}
return false;
}
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()
{
2015-05-04 22:16:02 +02:00
var self = this;
if(this._socket)
this._lastStats = JSON.stringify(this.stats);
2015-05-04 16:49:41 +02:00
if(this._web3)
2015-02-12 13:26:47 +01:00
{
2015-05-04 22:16:02 +02:00
async.parallel({
start: function (callback)
{
callback(null, _.now());
},
peers: function (callback)
{
setTimeout(function () {
var peers;
try {
peers = web3.toDecimal(web3.net.peerCount);
}
catch (err) {
console.error('xx> PeerCount failed: ', err);
callback(err, null);
}
callback(null, peers);
}, 1);
},
pending: function (callback)
{
setTimeout(function() {
try {
web3.eth.getBlockTransactionCount('pending', callback);
}
catch (err) {
console.error('xx> Pending failed: ', err);
callback(err, null);
}
}, 1);
},
mining: function (callback)
{
setTimeout(function () {
var mining;
try {
mining = web3.eth.mining;
}
catch (err) {
console.error('xx> Mining failed: ', err);
callback(err, null);
}
callback(null, mining);
}, 1);
},
hashrate: function (callback)
{
setTimeout(function () {
var hashrate;
try {
hashrate = web3.eth.hashrate;
}
catch (err) {
console.error('xx> Hashrate failed: ', err);
callback(err, null);
}
callback(null, hashrate);
}, 1);
},
gasprice: function (callback)
{
setTimeout(function () {
var gasPrice;
try {
gasPrice = web3.toBigNumber(web3.eth.gasPrice).toString(10);
}
catch (err) {
console.error('xx> gasPrice failed: ', err);
callback(err, null);
}
callback(null, gasPrice);
}, 1);
}
},
function (err, results)
2015-04-29 11:14:40 +02:00
{
2015-05-04 22:16:02 +02:00
self._tries++;
2015-05-04 16:49:41 +02:00
2015-05-04 22:16:02 +02:00
if (err) {
console.error('xx> getStats error: ', err);
2015-04-16 22:45:23 +02:00
2015-05-04 22:16:02 +02:00
self.stats.peers = 0;
self.stats.active = false;
self.stats.pending = 0;
self.stats.mining = false;
self.stats.hashrate = 0;
self.stats.gasPrice = 0;
self._down++;
return false;
2015-04-03 00:12:28 +02:00
}
2015-05-04 22:16:02 +02:00
results.end = _.now();
results.diff = results.end - results.start;
// console.log('==> Got getStats results: ', results);
console.log('==> Got getStats results in', results.diff, 'ms');
2015-04-24 00:37:42 +02:00
2015-05-04 22:16:02 +02:00
if(results.peers !== null)
2015-04-24 00:37:42 +02:00
{
2015-05-04 22:16:02 +02:00
self.stats.peers = results.peers;
self.stats.active = true;
self.stats.pending = results.pending;
self.stats.mining = results.mining;
self.stats.hashrate = results.hashrate;
self.stats.gasPrice = results.gasPrice;
}
else {
self.stats.peers = 0;
self.stats.active = false;
self.stats.pending = 0;
self.stats.mining = false;
self.stats.hashrate = 0;
self.stats.gasPrice = 0;
self._down++;
2015-04-24 00:37:42 +02:00
}
2015-05-04 22:16:02 +02:00
self.uptime();
self.sendUpdate();
});
}
}
Node.prototype.getStatsBlock = function ()
{
if(this._socket)
this._lastStats = JSON.stringify(this.stats);
if(this._web3)
{
console.log("==> Getting block");
var block = this.getBlock();
if( !_.isUndefined(block) && !_.isUndefined(block.number) && !_.isUndefined(block.hash) && block.hash !== '?' )
{
console.log("==> Got block:", block.number);
this.stats.block = block;
2015-04-29 11:14:40 +02:00
}
else
{
2015-05-04 22:16:02 +02:00
console.error("xx> getStatsBlock: couldn't fetch block...");
2015-04-16 22:45:23 +02:00
}
2015-02-12 13:26:47 +01:00
}
this.uptime();
2015-05-04 22:16:02 +02:00
this.sendUpdate();
2015-02-12 13:26:47 +01:00
}
2015-05-04 22:16:02 +02:00
Node.prototype.getHistory = function (range)
2015-04-28 09:41:48 +02:00
{
var history = [];
2015-04-29 11:14:40 +02:00
var interv = {};
if( _.isUndefined(range) || range === null)
2015-04-28 09:41:48 +02:00
{
2015-04-29 11:14:40 +02:00
interv = {
2015-04-28 09:41:48 +02:00
min: this.stats.block.number - MAX_HISTORY_UPDATE,
max: this.stats.block.number - 1
2015-04-29 11:14:40 +02:00
};
}
if( !_.isUndefined(range.list) )
{
interv = {
min: 0,
max: range.list.length - 1
};
2015-04-28 09:41:48 +02:00
}
2015-05-04 22:16:02 +02:00
for (var i = interv.min; i <= interv.max; i++)
2015-04-28 09:41:48 +02:00
{
2015-04-29 11:14:40 +02:00
var block = this.getBlock(( !_.isUndefined(range.list) ? range.list[i] : i));
2015-04-28 09:41:48 +02:00
2015-04-29 11:14:40 +02:00
if( block !== null && !_.isUndefined(block.number) )
2015-04-28 09:41:48 +02:00
{
2015-04-29 11:14:40 +02:00
history.push( block );
2015-04-28 09:41:48 +02:00
}
}
return history.reverse();
}
2015-05-04 22:16:02 +02:00
Node.prototype.updateBlock = function()
{
2015-05-04 22:16:02 +02:00
console.log('==> Getting block stats');
2015-02-18 03:59:03 +01:00
2015-05-04 22:16:02 +02:00
this.getStatsBlock();
2015-02-18 03:59:03 +01:00
2015-05-04 22:16:02 +02:00
return this;
};
Node.prototype.update = function()
{
console.log('==> Getting stats');
this.getStats();
return this;
};
Node.prototype.changed = function ()
{
var changed = ! _.isEqual( this._lastStats, JSON.stringify(this.stats) );
2015-02-18 03:59:03 +01:00
return changed;
}
2015-05-04 22:16:02 +02:00
Node.prototype.prepareStats = function ()
2015-02-12 15:02:44 +01:00
{
return {
id: this.id,
2015-02-12 15:02:44 +01:00
stats: this.stats
};
}
2015-05-04 22:16:02 +02:00
Node.prototype.sendUpdate = function (force)
{
2015-04-29 11:14:40 +02:00
if( this.changed() || force )
this.emit('update', this.prepareStats());
}
2015-04-02 17:22:33 +02:00
Node.prototype.ping = function()
{
2015-04-29 11:14:40 +02:00
this._latency = _.now();
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-04-24 11:38:26 +02:00
try {
2015-05-04 22:16:02 +02:00
this.chainFilter = web3.eth.filter('chain');
this.chainFilter.watch( function (log)
2015-04-29 11:14:40 +02:00
{
2015-05-04 22:16:02 +02:00
var now = _.now();
var time = now - self._lastChainLog;
self._lastChainLog = now;
2015-04-28 06:05:04 +02:00
2015-05-04 22:16:02 +02:00
console.log('>>> Chain Filter triggered: ', now);
console.log('>>> Last chain Filter triggered: ', time);
2015-04-28 06:05:04 +02:00
2015-05-04 22:16:02 +02:00
if(time > 50)
{
self.updateBlock();
}
else
{
debounce(function() {
self.updateBlock();
}, 50);
2015-04-24 11:38:26 +02:00
}
});
}
catch (err)
{
2015-05-04 22:16:02 +02:00
console.error("Couldn't set up chain filter");
2015-04-24 11:38:26 +02:00
console.error(err);
}
2015-05-04 22:16:02 +02:00
// try {
// this.pendingFilter = web3.eth.filter('pending');
// this.pendingFilter.watch( function (log)
// {
// var now = _.now();
// var time = now - self._lastPendingLog;
// console.log('>>> Pending Filter triggered: ', now);
// if(time > 50)
// {
// self.update();
// }
// else
// {
// debounce(function() {
// self.update();
// }, 50);
// }
// self._lastPendingLog = now;
// });
// }
// catch (err)
// {
// console.error("Couldn't set up pending filter");
// console.error(err);
// }
2015-04-29 11:14:40 +02:00
this.updateInterval = setInterval( function(){
2015-02-12 13:26:47 +01:00
self.update();
2015-04-02 17:22:33 +02:00
}, UPDATE_INTERVAL);
2015-04-29 11:14:40 +02:00
this.pingInterval = setInterval( function(){
2015-04-02 17:22:33 +02:00
self.ping();
}, PING_INTERVAL);
2015-02-12 13:26:47 +01:00
}
2015-04-06 20:11:22 +02:00
Node.prototype.installContract = function()
{
2015-04-24 11:38:26 +02:00
try {
2015-04-29 11:14:40 +02:00
Contract = web3.eth.contract( registrar.desc );
this._Registrar = new Contract( registrar.address );
2015-04-24 11:38:26 +02:00
}
catch (err)
{
console.error("Couldn't set up registrar contract");
console.error(err);
}
2015-04-06 20:11:22 +02:00
}
2015-02-16 17:44:26 +01:00
Node.prototype.init = function()
{
2015-04-29 22:34:52 +02:00
this.getInfo();
this.startSocketConnection();
2015-04-06 20:11:22 +02:00
this.installContract();
2015-02-12 13:26:47 +01:00
this.setWatches();
2015-05-04 22:16:02 +02:00
this.updateBlock();
2015-04-29 22:34:52 +02:00
this.update();
2015-02-12 13:26:47 +01:00
}
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-04-29 11:14:40 +02:00
module.exports = Node;