ethstats-client/lib/node.js

851 lines
18 KiB
JavaScript
Raw Normal View History

2015-04-29 22:34:52 +02:00
'use strict';
2015-05-05 05:59:48 +02:00
require('./utils/logger.js');
var os = require('os');
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-04-03 18:34:31 +02:00
var debounce = require('debounce');
2015-04-16 23:36:38 +02:00
var pjson = require('./../package.json');
2015-05-05 02:12:41 +02:00
var chalk = require('chalk');
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-29 22:34:52 +02:00
var PENDING_WORKS = true;
var MAX_BLOCKS_HISTORY = 40;
var UPDATE_INTERVAL = 5000;
2015-06-02 15:47:47 +02:00
var PING_INTERVAL = 3000;
2015-04-29 22:34:52 +02:00
var MINERS_LIMIT = 5;
var MAX_HISTORY_UPDATE = 50;
2015-06-17 02:01:45 +02:00
var MAX_CONNECTION_ATTEMPTS = 30;
2015-05-26 22:02:45 +02:00
var CONNECTION_ATTEMPTS_TIMEOUT = 1000;
2015-03-22 22:09:34 +01:00
2015-02-16 22:30:21 +01:00
Socket = Primus.createSocket({
transformer: 'websockets',
pathname: '/api',
2015-05-11 15:24:18 +02:00
timeout: 120000,
2015-05-11 19:55:12 +02:00
strategy: 'disconnect,online,timeout',
reconnect: {
retries: 30
},
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-06-25 00:59:12 +02:00
console.error("No instance name specified!");
process.exit(1);
2015-02-17 23:15:34 +01:00
}
2015-06-25 00:59:12 +02:00
console.info(' ');
console.info(' ', 'NET STATS CLIENT');
console.success(' ', 'v' + pjson.version);
console.info(' ');
console.info(' ');
2015-02-12 13:26:47 +01:00
2015-05-05 02:12:41 +02: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,
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,
2015-05-26 22:02:45 +02:00
block: {
number: 0,
hash: '?',
difficulty: 0,
totalDifficulty: 0,
transactions: [],
uncles: []
},
2015-04-16 21:15:42 +02:00
uptime: 0
};
2015-04-29 11:14:40 +02:00
2015-05-27 11:19:02 +02:00
this._lastBlock = 0;
this._lastStats = JSON.stringify(this.stats);
2015-05-04 23:51:16 +02:00
this._lastFetch = 0;
2015-06-02 16:15:34 +02:00
this._lastPending = 0;
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-29 22:34:52 +02:00
this._web3 = false;
this._socket = false;
this._latestQueue = 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-04-29 22:34:52 +02:00
this.connectionInterval = false;
2015-02-12 13:26:47 +01:00
this._lastBlockSentAt = 0;
2015-05-04 22:16:02 +02:00
this._lastChainLog = 0;
2015-04-28 06:05:04 +02:00
this._lastPendingLog = 0;
2015-06-02 03:25:28 +02:00
this._chainDebouncer = 0;
this._chan_min_time = 50;
this._max_chain_debouncer = 20;
this._chain_debouncer_cnt = 0;
this._connection_attempts = 0;
2015-06-02 03:25:28 +02:00
this._timeOffset = null;
2015-04-29 22:34:52 +02:00
this.startWeb3Connection();
2015-04-28 06:05:04 +02:00
2015-04-29 22:34:52 +02:00
return this;
}
Node.prototype.startWeb3Connection = function()
{
2015-05-05 05:59:48 +02:00
console.info('Starting web3 connection');
2015-04-29 22:34:52 +02:00
2015-05-08 15:23:54 +02:00
web3.setProvider( new web3.providers.HttpProvider('http://' + (process.env.RPC_HOST || 'localhost') + ':' + (process.env.RPC_PORT || '8545')) );
2015-04-29 22:34:52 +02:00
this.checkWeb3Connection();
}
Node.prototype.checkWeb3Connection = function()
{
var self = this;
2015-05-05 08:04:27 +02:00
if (!this._web3)
2015-04-29 22:34:52 +02:00
{
try {
2015-06-25 00:59:12 +02:00
var eth_version = web3.version.client;
2015-04-29 22:34:52 +02:00
2015-06-25 00:59:12 +02:00
if( !_.isUndefined(eth_version) )
2015-04-29 22:34:52 +02:00
{
2015-06-25 00:59:12 +02:00
console.success(eth_version);
2015-05-05 05:59:48 +02:00
console.success('Web3 connection established');
2015-04-29 22:34:52 +02:00
this._web3 = true;
this.init();
return true;
}
}
2015-05-05 08:04:27 +02:00
catch (err)
2015-04-29 22:34:52 +02:00
{
2015-05-13 15:39:29 +02:00
if(this._connection_attempts < MAX_CONNECTION_ATTEMPTS)
{
console.error('Web3 connection attempt', chalk.cyan('#' + this._connection_attempts++), 'failed');
2015-05-13 15:46:47 +02:00
console.error('Trying again in', chalk.cyan(500 * this._connection_attempts + ' ms'));
2015-04-29 22:34:52 +02:00
2015-05-13 15:39:29 +02:00
setTimeout(function ()
{
self.checkWeb3Connection();
2015-05-26 22:02:45 +02:00
}, CONNECTION_ATTEMPTS_TIMEOUT * this._connection_attempts);
2015-05-13 15:39:29 +02:00
}
else
2015-04-29 22:34:52 +02:00
{
2015-05-13 15:46:47 +02:00
console.error('Web3 connection failed', chalk.cyan(MAX_CONNECTION_ATTEMPTS), 'times. Aborting...');
2015-05-13 15:39:29 +02:00
}
2015-04-29 22:34:52 +02:00
}
}
}
2015-06-12 21:03:40 +02:00
Node.prototype.reconnectWeb3 = function()
{
2015-06-12 21:46:47 +02:00
console.warn("Uninstalling filters and update interval");
2015-06-12 21:41:39 +02:00
2015-06-12 21:03:40 +02:00
this._web3 = false;
2015-06-12 21:41:39 +02:00
this._connection_attempts = 0;
2015-06-12 21:03:40 +02:00
if(this.updateInterval)
clearInterval(this.updateInterval);
try
{
web3.reset();
}
catch (err)
{
console.error("Web3 reset error:", err);
}
2015-06-12 21:46:47 +02:00
console.info("Web3 reconnect attempts started");
2015-06-12 21:03:40 +02:00
this.checkWeb3Connection();
}
2015-04-29 22:34:52 +02:00
Node.prototype.startSocketConnection = function()
{
2015-06-12 21:03:40 +02:00
if( !this._socket )
{
console.info('wsc', 'Starting socket connection');
2015-04-29 22:34:52 +02:00
2015-06-12 21:03:40 +02:00
socket = new Socket( process.env.WS_SERVER || 'ws://localhost:3000' );
2015-04-29 22:34:52 +02:00
2015-06-12 21:03:40 +02:00
this.setupSockets();
}
2015-04-29 22:34:52 +02:00
}
Node.prototype.setupSockets = function()
{
var self = this;
// Setup events
2015-04-29 11:14:40 +02:00
socket.on('open', function open()
{
2015-05-11 19:55:12 +02:00
console.info('wsc', 'The socket connection has been opened.');
2015-06-25 00:59:12 +02: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;
2015-05-11 19:55:12 +02:00
console.success('wsc', 'The socket connection has been established.');
2015-05-05 00:31:19 +02:00
2015-05-27 09:15:22 +02:00
self.getLatestBlock();
2015-06-01 22:04:43 +02:00
self.getPending();
2015-05-27 09:15:22 +02:00
self.getStats(true);
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-06-25 00:59:12 +02:00
console.stats('Socket 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-06-25 00:59:12 +02:00
console.stats('his', 'Got history request');
2015-04-29 22:34:52 +02:00
2015-05-05 08:24:27 +02:00
self.getHistory( data );
2015-03-27 12:38:41 +01:00
})
2015-04-29 11:14:40 +02:00
.on('node-pong', function(data)
{
2015-06-02 03:25:28 +02:00
var now = _.now();
var latency = Math.ceil( (now - data.clientTime) / 2 );
2015-04-29 11:14:40 +02:00
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;
2015-05-11 19:55:12 +02:00
console.error('wsc', 'Socket connection end received');
2015-04-28 09:41:48 +02:00
})
2015-04-29 22:34:52 +02:00
.on('error', function error(err)
{
2015-05-05 05:59:48 +02:00
console.error('wsc', 'Socket error:', err);
2015-04-28 09:41:48 +02:00
})
2015-05-11 19:55:12 +02:00
.on('timeout', function ()
2015-04-28 09:41:48 +02:00
{
2015-06-01 23:14:28 +02:00
self._socket = false;
2015-05-11 19:55:12 +02:00
console.error('wsc', 'Socket connection timeout');
})
.on('close', function ()
{
self._socket = false;
console.error('wsc', 'Socket connection has been closed');
})
.on('offline', function ()
{
self._socket = false;
console.error('wsc', 'Network connection is offline');
})
.on('online', function ()
{
2015-06-01 23:14:28 +02:00
self._socket = true;
2015-05-11 19:55:12 +02:00
console.info('wsc', 'Network connection is online');
})
.on('reconnect', function ()
{
console.info('wsc', 'Socket reconnect attempt started');
})
.on('reconnect scheduled', function (opts)
{
2015-06-01 23:14:28 +02:00
self._socket = false;
2015-05-11 19:55:12 +02:00
console.warn('wsc', 'Reconnecting in', opts.scheduled, 'ms');
console.warn('wsc', 'This is attempt', opts.attempt, 'out of', opts.retries);
})
.on('reconnected', function (opts)
{
2015-06-01 23:14:28 +02:00
self._socket = true;
2015-05-11 19:55:12 +02:00
console.success('wsc', 'Socket reconnected successfully after', opts.duration, 'ms');
self.getLatestBlock();
self.getPending();
self.getStats(true);
2015-05-11 19:55:12 +02:00
})
.on('reconnect timeout', function (err, opts)
{
2015-06-01 23:14:28 +02:00
self._socket = false;
2015-05-11 19:55:12 +02:00
console.error('wsc', 'Socket reconnect atempt took too long:', err.message);
})
.on('reconnect failed', function (err, opts)
{
2015-06-01 23:14:28 +02:00
self._socket = false;
2015-05-11 19:55:12 +02:00
console.error('wsc', 'Socket reconnect failed:', err.message);
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);
2015-06-25 00:59:12 +02:00
console.sstats('wsc', 'Socket emited message:', chalk.reset.cyan(message));
2015-05-27 11:19:02 +02:00
// console.success('wsc', payload);
2015-04-29 22:34:52 +02:00
}
catch (err) {
2015-05-05 05:59:48 +02:00
console.error('wsc', 'Socket emit error:', err);
2015-04-29 22:34:52 +02:00
}
}
2015-02-12 14:38:09 +01:00
}
2015-04-29 22:34:52 +02:00
Node.prototype.getInfo = function()
{
2015-05-05 00:31:19 +02:00
console.info('==>', 'Getting info');
2015-05-05 05:59:48 +02:00
console.time('Got info');
2015-05-05 00:31:19 +02:00
2015-04-29 22:34:52 +02:00
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;
2015-05-05 05:59:48 +02:00
console.timeEnd('Got info');
console.info(this.info);
2015-04-29 22:34:52 +02:00
return true;
}
catch (err) {
console.error("Couldn't get version");
}
return false;
}
2015-05-05 08:04:27 +02:00
Node.prototype.setInactive = function()
{
this.stats.active = false;
this.stats.peers = 0;
this.stats.mining = false;
this.stats.hashrate = 0;
this._down++;
2015-06-12 21:03:40 +02:00
this.setUptime();
this.sendStatsUpdate(true);
// Schedule web3 reconnect
this.reconnectWeb3();
2015-05-05 08:04:27 +02:00
return this;
}
Node.prototype.setUptime = function()
{
this.stats.uptime = ((this._tries - this._down) / this._tries) * 100;
}
Node.prototype.formatBlock = function (block)
2015-04-06 20:11:22 +02:00
{
2015-06-02 01:43:23 +02:00
if( !_.isNull(block) && !_.isUndefined(block) && !_.isUndefined(block.number) && block.number >= 0 && !_.isUndefined(block.difficulty) && !_.isUndefined(block.totalDifficulty) )
2015-04-06 20:11:22 +02:00
{
2015-05-26 23:36:48 +02:00
block.difficulty = block.difficulty.toString(10);
block.totalDifficulty = block.totalDifficulty.toString(10);
2015-04-24 11:38:26 +02:00
if( !_.isUndefined(block.logsBloom) )
{
2015-05-27 11:19:02 +02:00
delete block.logsBloom;
}
2015-05-05 08:04:27 +02:00
return block;
2015-04-06 20:11:22 +02:00
}
return false;
}
2015-05-27 09:15:22 +02:00
Node.prototype.getLatestBlock = function ()
2015-02-12 13:26:47 +01:00
{
2015-05-26 20:45:33 +02:00
var self = this;
2015-05-05 08:04:27 +02:00
if(this._web3)
{
2015-05-28 12:40:51 +02:00
var timeString = 'Got block in' + chalk.reset.red('');
console.time('==>', timeString);
2015-05-27 11:19:02 +02:00
2015-05-26 20:45:33 +02:00
web3.eth.getBlock('latest', false, function(error, result) {
2015-05-28 12:40:51 +02:00
self.validateLatestBlock(error, result, timeString);
2015-05-26 20:45:33 +02:00
});
}
}
2015-05-05 08:04:27 +02:00
Node.prototype.validateLatestBlock = function (error, result, timeString)
2015-05-26 20:45:33 +02:00
{
2015-05-28 12:30:53 +02:00
console.timeEnd('==>', timeString);
2015-05-27 11:19:02 +02:00
if( error )
2015-05-26 20:45:33 +02:00
{
2015-05-27 11:19:02 +02:00
console.error("xx>", "getLatestBlock couldn't fetch block...");
console.error("xx>", error);
2015-05-26 22:02:45 +02:00
2015-05-27 11:19:02 +02:00
return false;
2015-05-26 20:45:33 +02:00
}
2015-05-27 11:19:02 +02:00
var block = this.formatBlock(result);
if(block === false)
2015-05-26 20:45:33 +02:00
{
console.error("xx>", "Got bad block:", chalk.reset.cyan(result));
2015-05-27 11:19:02 +02:00
return false;
}
if( this.stats.block.number === block.number )
{
console.warn("==>", "Got same block:", chalk.reset.cyan(block.number));
2015-05-27 11:19:02 +02:00
2015-06-01 23:21:09 +02:00
if( _.isEqual(JSON.stringify(this.stats.block), JSON.stringify(block)) )
return false;
2015-06-25 00:59:12 +02:00
console.stats(this.stats.block);
console.stats(block);
2015-06-01 23:21:09 +02:00
console.warn("Blocks are different... updating block");
2015-05-27 11:19:02 +02:00
}
2015-06-25 00:59:12 +02:00
console.sstats("==>", "Got block:", chalk.reset.red(block.number));
2015-05-27 11:19:02 +02:00
this.stats.block = block;
2015-06-01 22:23:45 +02:00
this.sendBlockUpdate();
2015-05-27 11:19:02 +02:00
if(this.stats.block.number - this._lastBlock > 1)
{
var range = _.range( Math.max(this.stats.block.number - MAX_BLOCKS_HISTORY, this._lastBlock + 1), Math.max(this.stats.block.number, 0), 1 );
if( this._latestQueue.idle() )
this.getHistory({ list: range });
2015-05-27 11:19:02 +02:00
}
if(this.stats.block.number > this._lastBlock)
{
this._lastBlock = this.stats.block.number;
2015-05-05 08:04:27 +02:00
}
2015-02-12 13:26:47 +01:00
}
2015-05-04 23:51:16 +02:00
Node.prototype.getStats = function(forced)
2015-02-12 13:26:47 +01:00
{
2015-05-04 22:16:02 +02:00
var self = this;
2015-05-04 23:51:16 +02:00
var now = _.now();
var lastFetchAgo = now - this._lastFetch;
this._lastFetch = now;
2015-05-04 22:16:02 +02:00
2015-05-04 23:51:16 +02:00
if (this._socket)
this._lastStats = JSON.stringify(this.stats);
2015-05-04 23:51:16 +02:00
if (this._web3 && (lastFetchAgo >= UPDATE_INTERVAL || forced === true))
2015-02-12 13:26:47 +01:00
{
2015-06-25 00:59:12 +02:00
console.stats('==>', 'Getting stats')
console.stats(' ', 'last update:', chalk.reset.cyan(lastFetchAgo));
console.stats(' ', 'forced:', chalk.reset.cyan(forced === true));
2015-05-04 23:51:16 +02:00
2015-05-04 22:16:02 +02:00
async.parallel({
peers: function (callback)
{
2015-05-26 20:45:33 +02:00
web3.net.getPeerCount(callback);
2015-05-04 22:16:02 +02:00
},
mining: function (callback)
{
2015-05-26 20:45:33 +02:00
web3.eth.getMining(callback);
2015-05-04 22:16:02 +02:00
},
hashrate: function (callback)
{
2015-05-26 20:45:33 +02:00
web3.eth.getHashrate(callback);
2015-05-04 22:16:02 +02:00
},
2015-05-04 23:51:16 +02:00
gasPrice: function (callback)
2015-05-04 22:16:02 +02:00
{
2015-05-26 20:45:33 +02:00
web3.eth.getGasPrice(callback);
}
2015-05-04 22:16:02 +02:00
},
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) {
2015-05-05 05:59:48 +02:00
console.error('xx>', 'getStats error: ', err);
2015-04-16 22:45:23 +02:00
2015-05-04 23:51:16 +02:00
self.setInactive();
2015-05-04 22:16:02 +02:00
return false;
2015-04-03 00:12:28 +02:00
}
2015-05-04 22:16:02 +02:00
results.end = _.now();
2015-05-27 05:46:32 +02:00
results.diff = results.end - self._lastFetch;
2015-05-04 22:16:02 +02:00
2015-06-25 00:59:12 +02:00
console.sstats('==>', 'Got getStats results in', chalk.reset.cyan(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.active = true;
2015-05-26 23:36:48 +02:00
self.stats.peers = results.peers;
2015-05-04 22:16:02 +02:00
self.stats.mining = results.mining;
self.stats.hashrate = results.hashrate;
2015-05-26 23:36:48 +02:00
self.stats.gasPrice = results.gasPrice.toString(10);
2015-05-04 22:16:02 +02:00
}
else {
2015-05-04 23:51:16 +02:00
self.setInactive();
2015-04-24 00:37:42 +02:00
}
2015-05-05 08:04:27 +02:00
self.setUptime();
2015-05-04 22:16:02 +02:00
2015-06-01 22:50:26 +02:00
self.sendStatsUpdate(forced);
2015-05-04 22:16:02 +02:00
});
}
}
2015-06-01 20:00:14 +02:00
Node.prototype.getPending = function()
{
var self = this;
var now = _.now();
if (this._web3)
{
2015-06-25 00:59:12 +02:00
console.stats('==>', 'Getting Pending')
2015-06-01 20:00:14 +02:00
web3.eth.getBlockTransactionCount('pending', function (err, pending)
2015-06-01 20:00:14 +02:00
{
if (err) {
console.error('xx>', 'getPending error: ', err);
return false;
}
var results = {};
2015-06-01 20:00:14 +02:00
results.end = _.now();
results.diff = results.end - now;
2015-06-25 00:59:12 +02:00
console.sstats('==>', 'Got', chalk.reset.red(pending) , chalk.reset.bold.green('pending tx'+ (pending === 1 ? '' : 's') + ' in'), chalk.reset.cyan(results.diff, 'ms'));
2015-06-01 20:00:14 +02:00
self.stats.pending = pending;
2015-06-01 20:00:14 +02:00
2015-06-02 16:15:34 +02:00
if(self._lastPending !== pending)
self.sendPendingUpdate();
self._lastPending = pending;
2015-06-01 20:00:14 +02:00
});
}
}
2015-05-04 22:16:02 +02:00
Node.prototype.getHistory = function (range)
2015-04-28 09:41:48 +02:00
{
2015-05-05 08:24:27 +02:00
var self = this;
2015-04-28 09:41:48 +02:00
var history = [];
2015-04-29 11:14:40 +02:00
var interv = {};
2015-05-05 05:59:48 +02:00
console.time('=H=', 'his', 'Got history in');
2015-05-05 02:12:41 +02:00
2015-05-05 08:04:27 +02:00
if ( _.isUndefined(range) || range === null)
2015-05-27 11:19:02 +02:00
interv = _.range(this.stats.block.number - 1, this.stats.block.number - MAX_HISTORY_UPDATE);
2015-04-29 11:14:40 +02:00
2015-05-05 08:04:27 +02:00
if (!_.isUndefined(range.list))
interv = range.list;
2015-06-25 00:59:12 +02:00
console.stats('his', 'Getting history from', chalk.reset.cyan(interv[0]), 'to', chalk.reset.cyan(interv[interv.length - 1]));
2015-04-28 09:41:48 +02:00
2015-05-05 08:24:27 +02:00
async.mapSeries(interv, function (number, callback)
2015-04-28 09:41:48 +02:00
{
2015-05-26 22:02:45 +02:00
web3.eth.getBlock(number, false, callback);
2015-05-05 08:24:27 +02:00
},
function (err, results)
{
if (err) {
console.error('his', 'history fetch failed:', err);
results = false;
}
2015-05-26 22:02:45 +02:00
else
{
for(var i=0; i < results.length; i++)
{
results[i] = self.formatBlock(results[i]);
}
}
2015-05-05 08:24:27 +02:00
2015-05-27 09:00:23 +02:00
self.emit('history', {
2015-05-05 08:24:27 +02:00
id: self.id,
history: results.reverse()
});
2015-05-27 09:15:22 +02:00
console.timeEnd('=H=', 'his', 'Got history in');
2015-05-05 08:24:27 +02:00
});
2015-04-28 09:41:48 +02:00
}
2015-05-04 22:16:02 +02:00
Node.prototype.changed = function ()
{
var changed = ! _.isEqual( this._lastStats, JSON.stringify(this.stats) );
2015-02-18 03:59:03 +01:00
return changed;
}
2015-06-01 22:50:26 +02:00
Node.prototype.prepareBlock = function ()
2015-02-12 15:02:44 +01:00
{
return {
id: this.id,
2015-06-01 22:50:26 +02:00
block: this.stats.block
2015-02-12 15:02:44 +01:00
};
}
2015-06-01 22:50:26 +02:00
Node.prototype.preparePending = function ()
2015-06-01 20:00:14 +02:00
{
return {
id: this.id,
2015-06-01 22:50:26 +02:00
stats: {
pending: this.stats.pending
}
2015-06-01 20:00:14 +02:00
};
}
2015-06-01 22:50:26 +02:00
Node.prototype.prepareStats = function ()
2015-06-01 20:00:14 +02:00
{
return {
id: this.id,
stats: {
active: this.stats.active,
mining: this.stats.mining,
hashrate: this.stats.hashrate,
peers: this.stats.peers,
gasPrice: this.stats.gasPrice,
uptime: this.stats.uptime
}
};
}
2015-06-01 22:23:45 +02:00
Node.prototype.sendBlockUpdate = function()
{
this._lastBlockSentAt = _.now();
2015-06-25 00:59:12 +02:00
console.stats("wsc", "Sending", chalk.reset.red("block"), chalk.bold.white("update"));
2015-06-01 22:23:45 +02:00
this.emit('block', this.prepareBlock());
}
2015-06-01 20:00:14 +02:00
Node.prototype.sendPendingUpdate = function()
{
2015-06-25 00:59:12 +02:00
console.stats("wsc", "Sending pending update");
2015-06-01 22:50:26 +02:00
this.emit('pending', this.preparePending());
}
Node.prototype.sendStatsUpdate = function (force)
{
if( this.changed() || force ) {
2015-06-25 00:59:12 +02:00
console.stats("wsc", "Sending", chalk.reset.blue((force ? "forced" : "changed")), chalk.bold.white("update"));
2015-06-01 23:00:04 +02:00
this.emit('stats', this.prepareStats());
2015-06-01 20:00:14 +02:00
}
}
2015-04-02 17:22:33 +02:00
Node.prototype.ping = function()
{
2015-04-29 11:14:40 +02:00
this._latency = _.now();
2015-06-02 03:25:28 +02:00
socket.emit('node-ping', {
id: this.id,
clientTime: _.now()
});
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
this._latestQueue = async.queue(function (hash, callback)
{
2015-05-29 22:39:18 +02:00
var timeString = 'Got block ' + chalk.reset.red(hash) + chalk.reset.bold.white(' in') + chalk.reset.green('');
2015-05-28 12:40:51 +02:00
console.time('==>', timeString);
web3.eth.getBlock(hash, false, function (error, result)
{
2015-05-28 12:40:51 +02:00
self.validateLatestBlock(error, result, timeString);
callback();
});
}, 1);
this._latestQueue.drain = function()
{
2015-06-25 00:59:12 +02:00
console.sstats("Finished processing", 'latest', 'queue');
2015-06-01 21:41:35 +02:00
self.getPending();
}
2015-06-24 02:22:00 +02:00
this._debouncedChain = debounce(function(hash) {
2015-06-25 00:59:12 +02:00
console.stats('>>>', 'Debounced');
2015-06-24 02:22:00 +02:00
self._latestQueue.push(hash);
2015-06-24 02:26:18 +02:00
}, 120);
2015-06-24 02:22:00 +02:00
2015-06-24 02:23:28 +02:00
this._debouncedPending = debounce(function() {
self.getPending();
}, 5);
2015-04-24 11:38:26 +02:00
try {
2015-05-05 02:12:41 +02:00
this.chainFilter = web3.eth.filter('latest');
this.chainFilter.watch( function (err, hash)
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-06-12 21:03:40 +02:00
if(hash === null)
{
hash = web3.eth.blockNumber;
}
2015-06-25 00:59:12 +02:00
console.stats('>>>', 'Chain Filter triggered: ', chalk.reset.red(hash), '- last trigger:', chalk.reset.cyan(time));
2015-04-28 06:05:04 +02:00
if(time < self._chan_min_time)
2015-06-01 23:49:25 +02:00
{
2015-06-01 23:52:54 +02:00
self._chainDebouncer++;
self._chain_debouncer_cnt++;
if(self._chain_debouncer_cnt > 100)
{
self._chan_min_time = Math.max(self._chan_min_time + 1, 200);
self._max_chain_debouncer = Math.max(self._max_chain_debouncer - 1, 5);
}
}
else
{
if(time > 5000)
{
self._chan_min_time = 50;
self._max_chain_debouncer = 20;
self._chain_debouncer_cnt = 0;
}
// reset local chain debouncer
2015-06-01 23:52:54 +02:00
self._chainDebouncer = 0;
2015-06-01 23:49:25 +02:00
}
if(self._chainDebouncer < self._max_chain_debouncer || now - self._lastBlockSentAt > 5000)
2015-06-01 23:49:25 +02:00
{
if(now - self._lastBlockSentAt > 5000)
{
self._lastBlockSentAt = now;
}
2015-06-01 23:49:25 +02:00
self._latestQueue.push(hash);
}
else
{
2015-06-24 02:22:00 +02:00
self._debouncedChain(hash);
2015-06-01 23:49:25 +02:00
}
2015-04-24 11:38:26 +02:00
});
2015-06-01 22:04:43 +02:00
console.success("Installed chain filter");
2015-04-24 11:38:26 +02:00
}
catch (err)
{
this.chainFilter = false;
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-05 00:00:01 +02:00
try {
this.pendingFilter = web3.eth.filter('pending');
this.pendingFilter.watch( function (err, hash)
2015-05-05 00:00:01 +02:00
{
var now = _.now();
var time = now - self._lastPendingLog;
2015-05-05 00:31:19 +02:00
self._lastPendingLog = now;
2015-05-05 00:00:01 +02:00
2015-06-25 00:59:12 +02:00
console.stats('>>>', 'Pending Filter triggered:', chalk.reset.red(hash), '- last trigger:', chalk.reset.cyan(time));
2015-05-05 00:00:01 +02:00
if(time > 50)
{
2015-06-01 21:41:35 +02:00
self.getPending();
2015-05-05 00:00:01 +02:00
}
else
{
2015-06-24 02:23:28 +02:00
self._debouncedPending();
2015-05-05 00:00:01 +02:00
}
});
2015-06-01 23:23:27 +02:00
console.success("Installed pending filter");
2015-05-05 00:00:01 +02:00
}
catch (err)
{
this.pendingFilter = false;
2015-05-05 00:00:01 +02:00
console.error("Couldn't set up pending filter");
console.error(err);
}
2015-05-04 22:16:02 +02:00
2015-04-29 11:14:40 +02:00
this.updateInterval = setInterval( function(){
2015-05-27 09:15:22 +02:00
self.getStats();
2015-04-02 17:22:33 +02:00
}, UPDATE_INTERVAL);
2015-06-12 21:46:47 +02:00
if( !this.pingInterval )
{
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.init = function()
{
2015-05-04 23:51:16 +02:00
// Fetch node info
2015-04-29 22:34:52 +02:00
this.getInfo();
2015-05-04 23:51:16 +02:00
2015-05-05 00:31:19 +02:00
// Start socket connection
this.startSocketConnection();
2015-05-04 23:51:16 +02:00
// Set filters
2015-02-12 13:26:47 +01:00
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-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;