diff --git a/.gitignore b/.gitignore
index 84c82379..52c956b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,5 @@ ws_secret.json
stats.json
package-lock.json
+# IdeaIDE
+.idea
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 787c4141..fab0c376 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,11 @@
FROM node
-RUN git clone https://github.com/goerli/netstats-server /netstats-server
-WORKDIR /netstats-server
+ADD . /celostats-server
+WORKDIR /celostats-server
RUN npm install
RUN npm install -g grunt-cli
RUN grunt
EXPOSE 3000
CMD ["npm", "start"]
+
diff --git a/Gruntfile.js b/Gruntfile.js
index b026119f..99d64f2c 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -40,6 +40,20 @@ module.exports = function(grunt) {
js: ['dist/js/*.*', '!dist/js/netstats.*'],
css: ['dist/css/*.css', '!dist/css/netstats.*.css']
},
+ watch: {
+ css: {
+ files: ['src/css/*.css'],
+ tasks: ['default']
+ },
+ js: {
+ files: ['src/js/*.js'],
+ tasks: ['default']
+ },
+ html: {
+ files: ['src/views/*.jade'],
+ tasks: ['default']
+ }
+ },
jade: {
build: {
options: {
@@ -51,17 +65,6 @@ module.exports = function(grunt) {
files: {
'dist/index.html': 'src/views/index.jade'
}
- },
- build_pow: {
- options: {
- data: {
- debug: false,
- pretty: true
- }
- },
- files: {
- 'dist/index.html': 'src/pow/views/index.jade'
- }
}
},
copy: {
@@ -96,37 +99,6 @@ module.exports = function(grunt) {
}
]
},
- build_pow: {
- files: [
- {
- expand: true,
- cwd: 'src/fonts/',
- src: ['*.*'],
- dest: 'dist/fonts/',
- filter: 'isFile'
- },
- {
- expand: true,
- cwd: 'src/images/',
- src: ['*.*'],
- dest: 'dist/',
- filter: 'isFile'
- },
- {
- expand: true,
- cwd: 'src/pow/css/',
- src: styles,
- dest: 'dist/css/',
- filter: 'isFile'
- },
- {
- expand: true,
- cwd: 'src/js/lib/',
- src: ['*.*'],
- dest: 'dist/js/lib'
- }
- ]
- }
},
cssmin: {
build: {
@@ -189,6 +161,6 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-jade');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-uglify');
-
+ grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['clean:build', 'clean:js', 'clean:css', 'jade:build', 'copy:build', 'cssmin:build', 'concat:vendor', 'concat:scripts', 'uglify:app', 'concat:netstats', 'concat:css', 'clean:js', 'clean:css']);
};
diff --git a/README.md b/README.md
index 3287f01d..bdfd4b01 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,5 @@
Celo Network Stats
===============================================
-[![Build Status][travis-image]][travis-url] [![dependency status][dep-image]][dep-url]
This is a visual interface for tracking proof-of-work ("mainnet") and proof-of-authority ("testnet") network status. It uses WebSockets to receive stats from running nodes and output them through an angular interface. It is the front-end implementation for [ethstats-client](https://github.com/goerli/ethstats-client).
@@ -15,7 +14,7 @@ This is a visual interface for tracking proof-of-work ("mainnet") and proof-of-a
* npm
#### Installation
-Make sure you have node.js and npm installed.
+Make sure you have node.js (10 or above) and npm installed.
Clone the repository and install the dependencies:
@@ -23,7 +22,7 @@ Clone the repository and install the dependencies:
git clone https://github.com/goerli/ethstats-server
cd ethstats-server
npm install
-sudo npm install -g grunt-cli
+npm install -g grunt-cli
```
#### Build
@@ -36,33 +35,13 @@ grunt
To build the static files for a network other than Ethereum copy and change src/js/defaultConfig.js and run the following command.
```bash
-grunt --configPath="src/js/someOtherConfig.js"
+grunt --configPath="src/js/celoConfig.js"
```
#### Run
-Start a node process and pass the websocket secret to it.
+Start a node process and pass a trusted node to it or edit the list of trusted nodes in [the server config](/lib/utils/config.js).
```bash
-WS_SECRET="asdf" npm start
+TRUSTED_NODE=0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95 npm start
```
Find the interface at http://localhost:3000
-
-## Proof-of-Work (Legacy)
-
-
-
-* Demo: https://ropsten-stats.parity.io/
-
-Same as above, just run the `pow` build task in Grunt.
-
-```bash
-grunt pow
-WS_SECRET="asdf" npm start
-```
-
-:-)
-
-[travis-image]: https://travis-ci.org/goerli/ethstats-server.svg
-[travis-url]: https://travis-ci.org/goerli/ethstats-server
-[dep-image]: https://david-dm.org/goerli/ethstats-server.svg
-[dep-url]: https://david-dm.org/goerli/ethstats-server
diff --git a/app.js b/app.js
index 0e0cdd0f..38d52a79 100644
--- a/app.js
+++ b/app.js
@@ -1,45 +1,24 @@
var _ = require('lodash');
+const { Keccak } = require('sha3');
+const EC = require('elliptic').ec;
var logger = require('./lib/utils/logger');
var chalk = require('chalk');
var http = require('http');
-// Init WS SECRET
-var WS_SECRET;
-
-if( !_.isUndefined(process.env.WS_SECRET) && !_.isNull(process.env.WS_SECRET) )
-{
- if( process.env.WS_SECRET.indexOf('|') > 0 )
- {
- WS_SECRET = process.env.WS_SECRET.split('|');
- }
- else
- {
- WS_SECRET = [process.env.WS_SECRET];
- }
-}
-else
-{
- try {
- var tmp_secret_json = require('./ws_secret.json');
- WS_SECRET = _.values(tmp_secret_json);
- }
- catch (e)
- {
- console.error("WS_SECRET NOT SET!!!");
- }
-}
+let banned = require('./lib/utils/config').banned;
+let reserved = require('./lib/utils/config').reserved;
+let trusted = require('./lib/utils/config').trusted
-var banned = require('./lib/utils/config').banned;
-var reserved = require('./lib/utils/config').reserved;
+if (process.env.TRUSTED_NODE) {
+ trusted.push(process.env.TRUSTED_NODE)
+}
// Init http server
-if( process.env.NODE_ENV !== 'production' )
-{
- var app = require('./lib/express');
- server = http.createServer(app);
-}
-else
- server = http.createServer();
+if (process.env.NODE_ENV !== 'production') {
+ var app = require('./lib/express');
+ server = http.createServer(app);
+} else
+ server = http.createServer();
// Init socket vars
var Primus = require('primus');
@@ -50,9 +29,9 @@ var server;
// Init API Socket connection
api = new Primus(server, {
- transformer: 'websockets',
- pathname: '/api',
- parser: 'JSON'
+ transformer: 'websockets',
+ pathname: '/api',
+ parser: 'JSON'
});
api.plugin('emit', require('primus-emit'));
@@ -61,9 +40,9 @@ api.plugin('spark-latency', require('primus-spark-latency'));
// Init Client Socket connection
client = new Primus(server, {
- transformer: 'websockets',
- pathname: '/primus',
- parser: 'JSON'
+ transformer: 'websockets',
+ pathname: '/primus',
+ parser: 'JSON'
});
client.plugin('emit', require('primus-emit'));
@@ -71,9 +50,9 @@ client.plugin('emit', require('primus-emit'));
// Init external API
external = new Primus(server, {
- transformer: 'websockets',
- pathname: '/external',
- parser: 'JSON'
+ transformer: 'websockets',
+ pathname: '/external',
+ parser: 'JSON'
});
external.plugin('emit', require('primus-emit'));
@@ -82,332 +61,339 @@ external.plugin('emit', require('primus-emit'));
var Collection = require('./lib/collection');
var Nodes = new Collection(external);
-Nodes.setChartsCallback(function (err, charts)
-{
- if(err !== null)
- {
- console.error('COL', 'CHR', 'Charts error:', err);
- }
- else
- {
- client.write({
- action: 'charts',
- data: charts
- });
- }
+Nodes.setChartsCallback(function (err, charts) {
+ if (err !== null) {
+ console.error('COL', 'CHR', 'Charts error:', err);
+ } else {
+ client.write({
+ action: 'charts',
+ data: charts
+ });
+ }
});
+const authorize = (proof, stats) => {
+ let isAuthorized = false
+ if (!_.isUndefined(proof)
+ && !_.isUndefined(proof.publicKey)
+ && !_.isUndefined(proof.signature)
+ && !_.isUndefined(stats)) {
+ const hasher = new Keccak(256)
+ hasher.update(JSON.stringify(stats))
+ const msgHash = hasher.digest('hex')
+ const ec = new EC('secp256k1')
+ const pubkeyNoZeroX = proof.publicKey.substr(2)
+ let pubkey
+ try {
+ pubkey = ec.keyFromPublic(pubkeyNoZeroX, 'hex')
+ } catch (e) {
+ console.error('API', 'SIG', 'Public Key Error', e.message)
+ return false
+ }
+ const addressHasher = new Keccak(256)
+ addressHasher.update(pubkeyNoZeroX.substr(2), 'hex')
+ const addressHash = addressHasher.digest("hex").substr(24)
+ if (!(addressHash.toLowerCase() === proof.address.substr(2).toLowerCase())) {
+ console.error('API', 'SIG', 'Address hash did not match', addressHash, proof.address.substr(2))
+ }
+ const signature = {
+ r: proof.signature.substr(2, 64),
+ s: proof.signature.substr(66, 64)
+ }
+ if (!(msgHash === proof.msgHash.substr(2))) {
+ console.error('API', 'SIG', 'Message hash did not match', msgHash, proof.msgHash.substr(2))
+ return false
+ }
+ try {
+ isAuthorized = pubkey.verify(msgHash, signature)
+ } catch (e) {
+ console.error('API', 'SIG', 'Signature Error', e.message)
+ return false
+ }
+ }
+ if (!isAuthorized) {
+ console.error('API', 'SIG', 'Signature did not verify')
+ }
+ return isAuthorized
+}
+
// Init API Socket events
-api.on('connection', function (spark)
-{
- console.info('API', 'CON', 'Open:', spark.address.ip);
-
- spark.on('hello', function (data)
- {
- console.info('API', 'CON', 'Hello', data['id']);
-
- if( _.isUndefined(data.secret) || WS_SECRET.indexOf(data.secret) === -1 || banned.indexOf(spark.address.ip) >= 0 || _.isUndefined(data.id) || reserved.indexOf(data.id) >= 0 )
- {
- spark.end(undefined, { reconnect: false });
- console.error('API', 'CON', 'Closed - wrong auth', data);
-
- return false;
- }
-
- if( !_.isUndefined(data.id) && !_.isUndefined(data.info) )
- {
- data.ip = spark.address.ip;
- data.spark = spark.id;
- data.latency = spark.latency || 0;
-
- Nodes.add( data, function (err, info)
- {
- if(err !== null)
- {
- console.error('API', 'CON', 'Connection error:', err);
- return false;
- }
-
- if(info !== null)
- {
- spark.emit('ready');
-
- console.success('API', 'CON', 'Connected', data.id);
-
- client.write({
- action: 'add',
- data: info
- });
- }
- });
- }
- });
-
-
- spark.on('update', function (data)
- {
- if( !_.isUndefined(data.id) && !_.isUndefined(data.stats) )
- {
- Nodes.update(data.id, data.stats, function (err, stats)
- {
- if(err !== null)
- {
- console.error('API', 'UPD', 'Update error:', err);
- }
- else
- {
- if(stats !== null)
- {
- client.write({
- action: 'update',
- data: stats
- });
-
- console.info('API', 'UPD', 'Update from:', data.id, 'for:', stats);
-
- Nodes.getCharts();
- }
- }
- });
- }
- else
- {
- console.error('API', 'UPD', 'Update error:', data);
- }
- });
-
-
- spark.on('block', function (data)
- {
- if( !_.isUndefined(data.id) && !_.isUndefined(data.block) )
- {
- Nodes.addBlock(data.id, data.block, function (err, stats)
- {
- if(err !== null)
- {
- console.error('API', 'BLK', 'Block error:', err);
- }
- else
- {
- if(stats !== null)
- {
- client.write({
- action: 'block',
- data: stats
- });
-
- console.success('API', 'BLK', 'Block:', data.block['number'], 'td:', data.block['totalDifficulty'], 'from:', data.id, 'ip:', spark.address.ip);
-
- Nodes.getCharts();
- }
- }
- });
- }
- else
- {
- console.error('API', 'BLK', 'Block error:', data);
- }
- });
-
-
- spark.on('pending', function (data)
- {
- if( !_.isUndefined(data.id) && !_.isUndefined(data.stats) )
- {
- Nodes.updatePending(data.id, data.stats, function (err, stats) {
- if(err !== null)
- {
- console.error('API', 'TXS', 'Pending error:', err);
- }
-
- if(stats !== null)
- {
- client.write({
- action: 'pending',
- data: stats
- });
-
- console.success('API', 'TXS', 'Pending:', data.stats['pending'], 'from:', data.id);
- }
- });
- }
- else
- {
- console.error('API', 'TXS', 'Pending error:', data);
- }
- });
-
-
- spark.on('stats', function (data)
- {
- if( !_.isUndefined(data.id) && !_.isUndefined(data.stats) )
- {
-
- Nodes.updateStats(data.id, data.stats, function (err, stats)
- {
- if(err !== null)
- {
- console.error('API', 'STA', 'Stats error:', err);
- }
- else
- {
- if(stats !== null)
- {
- client.write({
- action: 'stats',
- data: stats
- });
-
- console.success('API', 'STA', 'Stats from:', data.id);
- }
- }
- });
- }
- else
- {
- console.error('API', 'STA', 'Stats error:', data);
- }
- });
-
-
- spark.on('history', function (data)
- {
- console.success('API', 'HIS', 'Got history from:', data.id);
-
- var time = chalk.reset.cyan((new Date()).toJSON()) + " ";
- console.time(time, 'COL', 'CHR', 'Got charts in');
-
- Nodes.addHistory(data.id, data.history, function (err, history)
- {
- console.timeEnd(time, 'COL', 'CHR', 'Got charts in');
-
- if(err !== null)
- {
- console.error('COL', 'CHR', 'History error:', err);
- }
- else
- {
- client.write({
- action: 'charts',
- data: history
- });
- }
- });
- });
-
-
- spark.on('node-ping', function (data)
- {
- var start = (!_.isUndefined(data) && !_.isUndefined(data.clientTime) ? data.clientTime : null);
-
- spark.emit('node-pong', {
- clientTime: start,
- serverTime: _.now()
- });
-
- console.info('API', 'PIN', 'Ping from:', data['id']);
- });
-
-
- spark.on('latency', function (data)
- {
- if( !_.isUndefined(data.id) )
- {
- Nodes.updateLatency(data.id, data.latency, function (err, latency)
- {
- if(err !== null)
- {
- console.error('API', 'PIN', 'Latency error:', err);
- }
-
- if(latency !== null)
- {
- // client.write({
- // action: 'latency',
- // data: latency
- // });
-
- console.info('API', 'PIN', 'Latency:', latency, 'from:', data.id);
- }
- });
-
- if( Nodes.requiresUpdate(data.id) )
- {
- var range = Nodes.getHistory().getHistoryRequestRange();
-
- spark.emit('history', range);
- console.info('API', 'HIS', 'Asked:', data.id, 'for history:', range.min, '-', range.max);
-
- Nodes.askedForHistory(true);
- }
- }
- });
-
-
- spark.on('end', function (data)
- {
- Nodes.inactive(spark.id, function (err, stats)
- {
- if(err !== null)
- {
- console.error('API', 'CON', 'Connection end error:', err);
- }
- else
- {
- client.write({
- action: 'inactive',
- data: stats
- });
-
- console.warn('API', 'CON', 'Connection with:', spark.id, 'ended:', data);
- }
- });
- });
+api.on('connection', function (spark) {
+ console.info('API', 'CON', 'Open:', spark.address.ip);
+
+ spark.on('hello', function (data) {
+ const { stats, proof } = data
+ console.info('API', 'CON', 'Hello', stats['id']);
+ if (banned.indexOf(spark.address.ip) >= 0
+ || _.isUndefined(stats.id)
+ || reserved.indexOf(stats.id) >= 0
+ || _.isUndefined(proof)
+ || _.isUndefined(proof.publicKey)
+ || trusted.map(address => address.toLowerCase()).indexOf(proof.address) < 0
+ || !authorize(proof, stats)) {
+
+ spark.end(undefined, { reconnect: false });
+ console.error('API', 'CON', 'Closed - wrong auth', data);
+
+ return false;
+ }
+
+ if (!_.isUndefined(stats.id) && !_.isUndefined(stats.info)) {
+ stats.ip = spark.address.ip;
+ stats.spark = spark.id;
+ stats.latency = spark.latency || 0;
+
+ Nodes.add(stats, function (err, info) {
+ if (err !== null) {
+ console.error('API', 'CON', 'Connection error:', err);
+ return false;
+ }
+
+ if (info !== null) {
+ spark.emit('ready');
+
+ console.success('API', 'CON', 'Connected', stats.id);
+
+ client.write({
+ action: 'add',
+ data: info
+ });
+ }
+ });
+ }
+ });
+
+
+ spark.on('update', function (data) {
+ if (!_.isUndefined(data.id) && !_.isUndefined(data.stats)) {
+ Nodes.update(data.id, data.stats, function (err, stats) {
+ if (err !== null) {
+ console.error('API', 'UPD', 'Update error:', err);
+ } else {
+ if (stats !== null) {
+ client.write({
+ action: 'update',
+ data: stats
+ });
+
+ console.info('API', 'UPD', 'Update from:', data.id, 'for:', stats);
+
+ Nodes.getCharts();
+ }
+ }
+ });
+ } else {
+ console.error('API', 'UPD', 'Update error:', data);
+ }
+ });
+
+
+ spark.on('block', function (data) {
+ const { stats, proof } = data
+ if (authorize(proof, stats)
+ && !_.isUndefined(stats.id)
+ && !_.isUndefined(stats.block)) {
+
+ if (stats.block.validators && stats.block.validators.registered) {
+ stats.block.validators.registered.forEach(validator => {
+ validator.registered = true
+ const node = Nodes.getNodeOrNew({ id: validator.address }, validator)
+ // TODO: only if new node
+ node.setValidatorData(validator)
+ return node.name
+ })
+ }
+
+ Nodes.addBlock(stats.id, stats.block, function (err, stats) {
+ if (err !== null) {
+ console.error('API', 'BLK', 'Block error:', err);
+ } else {
+ if (stats !== null) {
+ client.write({
+ action: 'block',
+ data: stats
+ });
+
+ console.success('API', 'BLK',
+ 'Block:', stats.block['number'],
+ 'td:', stats.block['totalDifficulty'],
+ 'from:', stats.id, 'ip:', spark.address.ip);
+
+ Nodes.getCharts();
+ }
+ }
+ });
+ } else {
+ console.error('API', 'BLK', 'Block error:', data);
+ }
+ });
+
+
+ spark.on('pending', function (data) {
+ const { stats, proof } = data
+ if (authorize(proof, stats)
+ && !_.isUndefined(stats.id)
+ && !_.isUndefined(stats.stats)) {
+ Nodes.updatePending(stats.id, stats.stats, function (err, pending) {
+ if (err !== null) {
+ console.error('API', 'TXS', 'Pending error:', err);
+ }
+
+ if (pending !== null) {
+ client.write({
+ action: 'pending',
+ data: pending
+ });
+
+ console.success('API', 'TXS', 'Pending:', pending['pending'], 'from:', pending.id);
+ }
+ });
+ } else {
+ console.error('API', 'TXS', 'Pending error:', data);
+ }
+ });
+
+
+ spark.on('stats', function (data) {
+ const { stats, proof } = data
+ if (authorize(proof, stats)
+ && !_.isUndefined(stats.id)
+ && !_.isUndefined(stats.stats)) {
+
+ Nodes.updateStats(stats.id, stats.stats, function (err, stats) {
+ if (err !== null) {
+ console.error('API', 'STA', 'Stats error:', err);
+ } else {
+ if (stats !== null) {
+ client.write({
+ action: 'stats',
+ data: stats
+ });
+
+ console.success('API', 'STA', 'Stats from:', stats.id);
+ }
+ }
+ });
+ }
+ });
+
+
+ spark.on('history', function (data) {
+ const { stats, proof } = data
+ if (authorize(proof, stats)) {
+ console.success('API', 'HIS', 'Got history from:', stats.id);
+
+ var time = chalk.reset.cyan((new Date()).toJSON()) + " ";
+ console.time(time, 'COL', 'CHR', 'Got charts in');
+ // Nodes.addHistory(stats.id, stats.history, function (err, history) {
+ // console.timeEnd(time, 'COL', 'CHR', 'Got charts in');
+ // if (err !== null) {
+ // console.error('COL', 'CHR', 'History error:', err);
+ // } else {
+ // client.write({
+ // action: 'charts',
+ // data: history
+ // });
+ // }
+ // });
+ }
+ });
+
+
+ spark.on('node-ping', function (data) {
+ const { stats, proof } = data
+ if (authorize(proof, stats)) {
+ const start = (!_.isUndefined(stats) && !_.isUndefined(stats.clientTime) ? stats.clientTime : null);
+
+ spark.emit('node-pong', {
+ clientTime: start,
+ serverTime: _.now()
+ });
+
+ console.success('API', 'PIN', 'Ping from:', stats['id']);
+ }
+ });
+
+
+ spark.on('latency', function (data) {
+ const { stats, proof } = data
+ if (authorize(proof, stats)
+ && !_.isUndefined(stats.id)) {
+ Nodes.updateLatency(stats.id, stats.latency, function (err, latency) {
+ if (err !== null) {
+ console.error('API', 'PIN', 'Latency error:', err);
+ }
+
+ if (latency !== null) {
+ console.success('API', 'PIN', 'Latency:', latency, 'from:', stats.id);
+ }
+ });
+
+ if (Nodes.requiresUpdate(stats.id)) {
+ var range = Nodes.getHistory().getHistoryRequestRange();
+
+ spark.emit('history', range);
+ console.success('API', 'HIS', 'Asked:', stats.id, 'for history:', range.min, '-', range.max);
+
+ Nodes.askedForHistory(true);
+ }
+ }
+ });
+
+
+ spark.on('end', function (data) {
+ Nodes.inactive(spark.id, function (err, stats) {
+ if (err !== null) {
+ console.error('API', 'CON', 'Connection end error:', err);
+ } else {
+ client.write({
+ action: 'inactive',
+ data: stats
+ });
+
+ console.warn('API', 'CON', 'Connection with:', spark.id, 'ended:', data);
+ }
+ });
+ });
});
+client.on('connection', function (clientSpark) {
+ clientSpark.on('ready', function (data) {
+ clientSpark.emit('init', { nodes: Nodes.all() });
-client.on('connection', function (clientSpark)
-{
- clientSpark.on('ready', function (data)
- {
- clientSpark.emit('init', { nodes: Nodes.all() });
-
- Nodes.getCharts();
- });
+ Nodes.getCharts();
+ });
- clientSpark.on('client-pong', function (data)
- {
- var serverTime = _.get(data, "serverTime", 0);
- var latency = Math.ceil( (_.now() - serverTime) / 2 );
+ clientSpark.on('client-pong', function (data) {
+ var serverTime = _.get(data, "serverTime", 0);
+ var latency = Math.ceil((_.now() - serverTime) / 2);
- clientSpark.emit('client-latency', { latency: latency });
- });
+ clientSpark.emit('client-latency', { latency: latency });
+ });
});
-var latencyTimeout = setInterval( function ()
-{
- client.write({
- action: 'client-ping',
- data: {
- serverTime: _.now()
- }
- });
+var latencyTimeout = setInterval(function () {
+ client.write({
+ action: 'client-ping',
+ data: {
+ serverTime: _.now()
+ }
+ });
}, 5000);
// Cleanup old inactive nodes
-var nodeCleanupTimeout = setInterval( function ()
-{
- client.write({
- action: 'init',
- data: Nodes.all()
- });
+var nodeCleanupTimeout = setInterval(function () {
+ client.write({
+ action: 'init',
+ data: Nodes.all()
+ });
- Nodes.getCharts();
+ Nodes.getCharts();
-}, 1000*60*60);
+}, 1000 * 60 * 60);
server.listen(process.env.PORT || 3000);
diff --git a/lib/collection.js b/lib/collection.js
index 22973815..a4635b0a 100644
--- a/lib/collection.js
+++ b/lib/collection.js
@@ -2,332 +2,274 @@ var _ = require('lodash');
var Blockchain = require('./history');
var Node = require('./node');
-var Collection = function Collection(externalAPI)
-{
- this._items = [];
- this._blockchain = new Blockchain();
- this._askedForHistory = false;
- this._askedForHistoryTime = 0;
- this._debounced = null;
- this._externalAPI = externalAPI;
- this._highestBlock = 0;
-
- return this;
+var Collection = function Collection(externalAPI) {
+ this._items = [];
+ this._blockchain = new Blockchain();
+ this._askedForHistory = false;
+ this._askedForHistoryTime = 0;
+ this._debounced = null;
+ this._externalAPI = externalAPI;
+ this._highestBlock = 0;
+
+ return this;
}
-Collection.prototype.setupSockets = function()
-{
- this._externalAPI.on('connection', function (spark)
- {
- this._externalAPI.on('latestBlock', function (data)
- {
- spark.emit('latestBlock', {
- number: this._highestBlock
- });
- });
- });
+Collection.prototype.setupSockets = function () {
+ this._externalAPI.on('connection', function (spark) {
+ this._externalAPI.on('latestBlock', function (data) {
+ spark.emit('latestBlock', {
+ number: this._highestBlock
+ });
+ });
+ });
}
-Collection.prototype.add = function(data, callback)
-{
- var node = this.getNodeOrNew({ id : data.id }, data);
- node.setInfo(data, callback);
+Collection.prototype.add = function (data, callback) {
+ var node = this.getNodeOrNew({ id: data.id }, data);
+ node.setInfo(data, callback);
}
-Collection.prototype.update = function(id, stats, callback)
-{
- var node = this.getNode({ id: id });
-
- if (!node)
- {
- callback('Node not found', null);
- }
- else
- {
- // this._blockchain.clean(this.getBestBlockFromItems());
-
- var block = this._blockchain.add(stats.block, id, node.trusted);
-
- if (!block)
- {
- callback('Block data wrong', null);
- }
- else
- {
- var propagationHistory = this._blockchain.getNodePropagation(id);
-
- stats.block.arrived = block.block.arrived;
- stats.block.received = block.block.received;
- stats.block.propagation = block.block.propagation;
-
- node.setStats(stats, propagationHistory, callback);
- }
- }
+Collection.prototype.update = function (id, stats, callback) {
+ var node = this.getNode({ id: id });
+
+ if (!node) {
+ callback('Node not found', null);
+ } else {
+ // this._blockchain.clean(this.getBestBlockFromItems());
+
+ var block = this._blockchain.add(stats.block, id, node.trusted);
+
+ if (!block) {
+ callback('Block data wrong', null);
+ } else {
+ var propagationHistory = this._blockchain.getNodePropagation(id);
+
+ stats.block.arrived = block.block.arrived;
+ stats.block.received = block.block.received;
+ stats.block.propagation = block.block.propagation;
+
+ node.setStats(stats, propagationHistory, callback);
+ }
+ }
}
-Collection.prototype.addBlock = function(id, stats, callback)
-{
- var node = this.getNode({ id: id });
-
- if (!node)
- {
- callback('Node not found', null);
- }
- else
- {
- // this._blockchain.clean(this.getBestBlockFromItems());
-
- var block = this._blockchain.add(stats, id, node.trusted);
-
- if (!block)
- {
- callback('Block undefined', null);
- }
- else
- {
- var propagationHistory = this._blockchain.getNodePropagation(id);
-
- stats.arrived = block.block.arrived;
- stats.received = block.block.received;
- stats.propagation = block.block.propagation;
-
- if(block.block.number > this._highestBlock)
- {
- this._highestBlock = block.block.number;
- this._externalAPI.write({
- action:"lastBlock",
- number: this._highestBlock
- });
- }
-
- node.setBlock(stats, propagationHistory, callback);
- }
- }
+Collection.prototype.addBlock = function (id, stats, callback) {
+ var node = this.getNode({ id: id });
+
+ if (!node) {
+ callback('Node not found', null);
+ } else {
+ // this._blockchain.clean(this.getBestBlockFromItems());
+
+ var block = this._blockchain.add(stats, id, node.trusted);
+
+ if (!block) {
+ callback('Block undefined', null);
+ } else {
+ var propagationHistory = this._blockchain.getNodePropagation(id);
+
+ stats.arrived = block.block.arrived;
+ stats.received = block.block.received;
+ stats.propagation = block.block.propagation;
+ stats.validators = block.block.validators;
+
+ if (block.block.number > this._highestBlock) {
+ this._highestBlock = block.block.number;
+ this._externalAPI.write({
+ action: "lastBlock",
+ number: this._highestBlock
+ });
+ }
+
+ node.setBlock(stats, propagationHistory, callback);
+ }
+ }
}
-Collection.prototype.updatePending = function(id, stats, callback)
-{
- var node = this.getNode({ id: id });
+Collection.prototype.updatePending = function (id, stats, callback) {
+ var node = this.getNode({ id: id });
- if (!node)
- return false;
+ if (!node)
+ return false;
- node.setPending(stats, callback);
+ node.setPending(stats, callback);
}
-Collection.prototype.updateStats = function(id, stats, callback)
-{
- var node = this.getNode({ id: id });
-
- if (!node)
- {
- callback('Node not found', null);
- }
- else
- {
- node.setBasicStats(stats, callback);
- }
+Collection.prototype.updateStats = function (id, stats, callback) {
+ var node = this.getNode({ id: id });
+
+ if (!node) {
+ callback('Node not found', null);
+ } else {
+ node.setBasicStats(stats, callback);
+ }
}
// TODO: Async series
-Collection.prototype.addHistory = function(id, blocks, callback)
-{
- var node = this.getNode({ id: id });
+Collection.prototype.addHistory = function (id, blocks, callback) {
+ var node = this.getNode({ id: id });
- if (!node)
- {
- callback('Node not found', null)
- }
- else
- {
- blocks = blocks.reverse();
+ if (!node) {
+ callback('Node not found', null)
+ } else {
+ blocks = blocks.reverse();
- // this._blockchain.clean(this.getBestBlockFromItems());
+ // this._blockchain.clean(this.getBestBlockFromItems());
- for (var i = 0; i <= blocks.length - 1; i++)
- {
- this._blockchain.add(blocks[i], id, node.trusted, true);
- };
+ for (var i = 0; i <= blocks.length - 1; i++) {
+ this._blockchain.add(blocks[i], id, true, true);
+ }
- this.getCharts();
- }
+ this.getCharts();
+ }
- this.askedForHistory(false);
+ this.askedForHistory(false);
}
-Collection.prototype.updateLatency = function(id, latency, callback)
-{
- var node = this.getNode({ id: id });
+Collection.prototype.updateLatency = function (id, latency, callback) {
+ var node = this.getNode({ id: id });
- if (!node)
- return false;
+ if (!node)
+ return false;
- node.setLatency(latency, callback);
+ node.setLatency(latency, callback);
}
-Collection.prototype.inactive = function(id, callback)
-{
- var node = this.getNode({ spark: id });
-
- if (!node)
- {
- callback('Node not found', null);
- }
- else
- {
- node.setState(false);
- callback(null, node.getStats());
- }
+Collection.prototype.inactive = function (id, callback) {
+ var node = this.getNode({ spark: id });
+
+ if (!node) {
+ callback('Node not found', null);
+ } else {
+ node.setState(false);
+ callback(null, node.getStats());
+ }
}
-Collection.prototype.getIndex = function(search)
-{
- return _.findIndex(this._items, search);
+Collection.prototype.getIndex = function (search) {
+ return _.findIndex(this._items, search);
}
-Collection.prototype.getNode = function(search)
-{
- var index = this.getIndex(search);
+Collection.prototype.getNode = function (search) {
+ var index = this.getIndex(search);
- if(index >= 0)
- return this._items[index];
+ if (index >= 0)
+ return this._items[index];
- return false;
+ return false;
}
-Collection.prototype.getNodeByIndex = function(index)
-{
- if(this._items[index])
- return this._items[index];
+Collection.prototype.getNodeByIndex = function (index) {
+ if (this._items[index])
+ return this._items[index];
- return false;
+ return false;
}
-Collection.prototype.getIndexOrNew = function(search, data)
-{
- var index = this.getIndex(search);
+Collection.prototype.getIndexOrNew = function (search, data) {
+ var index = this.getIndex(search);
- return (index >= 0 ? index : this._items.push(new Node(data)) - 1);
+ return (index >= 0 ? index : this._items.push(new Node(data)) - 1);
}
-Collection.prototype.getNodeOrNew = function(search, data)
-{
- return this.getNodeByIndex(this.getIndexOrNew(search, data));
+Collection.prototype.getNodeOrNew = function (search, data) {
+ return this.getNodeByIndex(this.getIndexOrNew(search, data));
}
-Collection.prototype.all = function()
-{
- this.removeOldNodes();
+Collection.prototype.all = function () {
+ this.removeOldNodes();
- return this._items;
+ return this._items;
}
-Collection.prototype.removeOldNodes = function()
-{
- var deleteList = []
-
- for(var i = this._items.length - 1; i >= 0; i--)
- {
- if( this._items[i].isInactiveAndOld() )
- {
- deleteList.push(i);
- }
- }
-
- if(deleteList.length > 0)
- {
- for(var i = 0; i < deleteList.length; i++)
- {
- this._items.splice(deleteList[i], 1);
- }
- }
+Collection.prototype.removeOldNodes = function () {
+ var deleteList = []
+
+ for (var i = this._items.length - 1; i >= 0; i--) {
+ if (this._items[i].isInactiveAndOld()) {
+ deleteList.push(i);
+ }
+ }
+
+ if (deleteList.length > 0) {
+ for (var i = 0; i < deleteList.length; i++) {
+ this._items.splice(deleteList[i], 1);
+ }
+ }
}
-Collection.prototype.blockPropagationChart = function()
-{
- return this._blockchain.getBlockPropagation();
+Collection.prototype.blockPropagationChart = function () {
+ return this._blockchain.getBlockPropagation();
}
-Collection.prototype.getUncleCount = function()
-{
- return this._blockchain.getUncleCount();
+Collection.prototype.getUncleCount = function () {
+ return this._blockchain.getUncleCount();
}
-Collection.prototype.setChartsCallback = function(callback)
-{
- this._blockchain.setCallback(callback);
+Collection.prototype.setChartsCallback = function (callback) {
+ this._blockchain.setCallback(callback);
}
-Collection.prototype.getCharts = function()
-{
- this.getChartsDebounced();
+Collection.prototype.getCharts = function () {
+ this.getChartsDebounced();
}
-Collection.prototype.getChartsDebounced = function()
-{
- var self = this;
-
- if( this._debounced === null) {
- this._debounced = _.debounce(function(){
- self._blockchain.getCharts();
- }, 1000, {
- leading: false,
- maxWait: 5000,
- trailing: true
- });
- }
-
- this._debounced();
+Collection.prototype.getChartsDebounced = function () {
+ var self = this;
+
+ if (this._debounced === null) {
+ this._debounced = _.debounce(function () {
+ self._blockchain.getCharts();
+ }, 500, {
+ leading: false,
+ maxWait: 2000,
+ trailing: true
+ });
+ }
+
+ this._debounced();
}
-Collection.prototype.getHistory = function()
-{
- return this._blockchain;
+Collection.prototype.getHistory = function () {
+ return this._blockchain;
}
-Collection.prototype.getBestBlockFromItems = function()
-{
- return Math.max(this._blockchain.bestBlockNumber(), _.result(_.max(this._items, function(item) {
- // return ( !item.trusted ? 0 : item.stats.block.number );
- return ( item.stats.block.number );
- }), 'stats.block.number', 0));
+Collection.prototype.getBestBlockFromItems = function () {
+ return Math.max(this._blockchain.bestBlockNumber(), _.result(_.max(this._items, function (item) {
+ // return ( !item.trusted ? 0 : item.stats.block.number );
+ return (item.stats.block.number);
+ }), 'stats.block.number', 0));
}
-Collection.prototype.canNodeUpdate = function(id)
-{
- var node = this.getNode({id: id});
+Collection.prototype.canNodeUpdate = function (id) {
+ var node = this.getNode({ id: id });
- if(!node)
- return false;
+ if (!node)
+ return false;
- if(node.canUpdate())
- {
- var diff = node.getBlockNumber() - this._blockchain.bestBlockNumber();
+ if (node.canUpdate()) {
+ var diff = node.getBlockNumber() - this._blockchain.bestBlockNumber();
- return Boolean(diff >= 0);
- }
+ return Boolean(diff >= 0);
+ }
- return false;
+ return false;
}
-Collection.prototype.requiresUpdate = function(id)
-{
- return ( this.canNodeUpdate(id) && this._blockchain.requiresUpdate() && (!this._askedForHistory || _.now() - this._askedForHistoryTime > 2*60*1000) );
+Collection.prototype.requiresUpdate = function (id) {
+ return (this.canNodeUpdate(id) && this._blockchain.requiresUpdate() && (!this._askedForHistory || _.now() - this._askedForHistoryTime > 2 * 60 * 1000));
}
-Collection.prototype.askedForHistory = function(set)
-{
- if( !_.isUndefined(set) )
- {
- this._askedForHistory = set;
+Collection.prototype.askedForHistory = function (set) {
+ if (!_.isUndefined(set)) {
+ this._askedForHistory = set;
- if(set === true)
- {
- this._askedForHistoryTime = _.now();
- }
- }
+ if (set === true) {
+ this._askedForHistoryTime = _.now();
+ }
+ }
- return (this._askedForHistory || _.now() - this._askedForHistoryTime < 2*60*1000);
+ return (this._askedForHistory || _.now() - this._askedForHistoryTime < 2 * 60 * 1000);
}
module.exports = Collection;
diff --git a/lib/express.js b/lib/express.js
index bd37f5fd..7647d2fc 100644
--- a/lib/express.js
+++ b/lib/express.js
@@ -10,33 +10,33 @@ app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, (process.env.LITE === 'true' ? '../dist-lite' : '../dist'))));
-app.get('/', function(req, res) {
+app.get('/', function (req, res) {
res.render('index');
});
// catch 404 and forward to error handler
-app.use(function(req, res, next) {
- var err = new Error('Not Found');
- err.status = 404;
- next(err);
+app.use(function (req, res, next) {
+ var err = new Error('Not Found');
+ err.status = 404;
+ next(err);
});
// error handlers
-app.use(function(err, req, res, next) {
- res.status(err.status || 500);
- res.render('error', {
- message: err.message,
- error: err
- });
+app.use(function (err, req, res, next) {
+ res.status(err.status || 500);
+ res.render('error', {
+ message: err.message,
+ error: err
+ });
});
// production error handler
-app.use(function(err, req, res, next) {
- res.status(err.status || 500);
- res.render('error', {
- message: err.message,
- error: {}
- });
+app.use(function (err, req, res, next) {
+ res.status(err.status || 500);
+ res.render('error', {
+ message: err.message,
+ error: {}
+ });
});
module.exports = app;
\ No newline at end of file
diff --git a/lib/history.js b/lib/history.js
index a60123e6..32764cac 100644
--- a/lib/history.js
+++ b/lib/history.js
@@ -11,651 +11,560 @@ var MAX_UNCLES = 1000;
var MAX_UNCLES_PER_BIN = 25;
var MAX_BINS = 40;
-var History = function History(data)
-{
- this._items = [];
- this._callback = null;
+var History = function History(data) {
+ this._items = [];
+ this._callback = null;
}
-History.prototype.add = function(block, id, trusted, addingHistory)
-{
- var changed = false;
-
- if( !_.isUndefined(block) && !_.isUndefined(block.number) && !_.isUndefined(block.uncles) && !_.isUndefined(block.transactions) && !_.isUndefined(block.difficulty) && block.number > 0 )
- {
- trusted = (process.env.LITE === 'true' ? true : trusted);
- var historyBlock = this.search(block.number);
- var forkIndex = -1;
-
- var now = _.now();
-
- block.trusted = trusted;
- block.arrived = now;
- block.received = now;
- block.propagation = 0;
- block.fork = 0;
-
- if( historyBlock )
- {
- // We already have a block with this height in collection
-
- // Check if node already checked this block height
- var propIndex = _.findIndex( historyBlock.propagTimes, { node: id } );
-
- // Check if node already check a fork with this height
- forkIndex = compareForks(historyBlock, block);
-
- if( propIndex === -1 )
- {
- // Node didn't submit this block before
- if( forkIndex >= 0 && !_.isUndefined(historyBlock.forks[forkIndex]) )
- {
- // Found fork => update data
- block.arrived = historyBlock.forks[forkIndex].arrived;
- block.propagation = now - historyBlock.forks[forkIndex].received;
- }
- else
- {
- // No fork found => add a new one
- var prevBlock = this.prevMaxBlock(block.number);
-
- if( prevBlock )
- {
- block.time = Math.max(block.arrived - prevBlock.block.arrived, 0);
-
- if(block.number < this.bestBlock().height)
- block.time = Math.max((block.timestamp - prevBlock.block.timestamp) * 1000, 0);
- }
- else
- {
- block.time = 0;
- }
-
- forkIndex = historyBlock.forks.push(block) - 1;
- historyBlock.forks[forkIndex].fork = forkIndex;
- }
-
- // Push propagation time
- historyBlock.propagTimes.push({
- node: id,
- trusted: trusted,
- fork: forkIndex,
- received: now,
- propagation: block.propagation
- });
- }
- else
- {
- // Node submited the block before
- if( forkIndex >= 0 && !_.isUndefined(historyBlock.forks[forkIndex]) )
- {
- // Matching fork found => update data
- block.arrived = historyBlock.forks[forkIndex].arrived;
-
- if( forkIndex === historyBlock.propagTimes[propIndex].fork )
- {
- // Fork index is the same
- block.received = historyBlock.propagTimes[propIndex].received;
- block.propagation = historyBlock.propagTimes[propIndex].propagation;
- }
- else
- {
- // Fork index is different
- historyBlock.propagTimes[propIndex].fork = forkIndex;
- historyBlock.propagTimes[propIndex].propagation = block.propagation = now - historyBlock.forks[forkIndex].received;
- }
-
- }
- else
- {
- // No matching fork found => replace old one
- block.received = historyBlock.propagTimes[propIndex].received;
- block.propagation = historyBlock.propagTimes[propIndex].propagation;
-
- var prevBlock = this.prevMaxBlock(block.number);
-
- if( prevBlock )
- {
- block.time = Math.max(block.arrived - prevBlock.block.arrived, 0);
-
- if(block.number < this.bestBlock().height)
- block.time = Math.max((block.timestamp - prevBlock.block.timestamp) * 1000, 0);
- }
- else
- {
- block.time = 0;
- }
-
- forkIndex = historyBlock.forks.push(block) - 1;
- historyBlock.forks[forkIndex].fork = forkIndex;
- }
- }
-
- if( trusted && !compareBlocks(historyBlock.block, historyBlock.forks[forkIndex]) )
- {
- // If source is trusted update the main block
- historyBlock.forks[forkIndex].trusted = trusted;
- historyBlock.block = historyBlock.forks[forkIndex];
- }
-
- block.fork = forkIndex;
-
- changed = true;
-
- }
- else
- {
- // Couldn't find block with this height
-
- // Getting previous max block
- var prevBlock = this.prevMaxBlock(block.number);
-
- if( prevBlock )
- {
- block.time = Math.max(block.arrived - prevBlock.block.arrived, 0);
-
- if(block.number < this.bestBlock().height)
- block.time = Math.max((block.timestamp - prevBlock.block.timestamp) * 1000, 0);
- }
- else
- {
- block.time = 0;
- }
-
- var item = {
- height: block.number,
- block: block,
- forks: [block],
- propagTimes: []
- }
-
- if( this._items.length === 0 || (this._items.length > 0 && block.number > this.worstBlockNumber()) || (this._items.length < MAX_HISTORY && block.number < this.bestBlockNumber() && addingHistory) )
- {
- item.propagTimes.push({
- node: id,
- trusted: trusted,
- fork: 0,
- received: now,
- propagation: block.propagation
- });
-
- this._save(item);
-
- changed = true;
- }
- }
-
- return {
- block: block,
- changed: changed
- };
- }
-
- return false;
+History.prototype.add = function (block, id, trusted, addingHistory) {
+ var changed = false;
+
+ if (!_.isUndefined(block) && !_.isUndefined(block.number) && !_.isUndefined(block.uncles) && !_.isUndefined(block.transactions) && !_.isUndefined(block.difficulty) && block.number > 0) {
+ trusted = (process.env.LITE === 'true' ? true : trusted);
+ var historyBlock = this.search(block.number);
+ var forkIndex = -1;
+
+ var now = _.now();
+
+ block.trusted = trusted;
+ block.arrived = now;
+ block.received = now;
+ block.propagation = 0;
+ block.fork = 0;
+
+ if (historyBlock) {
+ // We already have a block with this height in collection
+
+ // Check if node already checked this block height
+ var propIndex = _.findIndex(historyBlock.propagTimes, { node: id });
+
+ // Check if node already check a fork with this height
+ forkIndex = compareForks(historyBlock, block);
+
+ if (propIndex === -1) {
+ // Node didn't submit this block before
+ if (forkIndex >= 0 && !_.isUndefined(historyBlock.forks[forkIndex])) {
+ // Found fork => update data
+ block.arrived = historyBlock.forks[forkIndex].arrived;
+ block.propagation = now - historyBlock.forks[forkIndex].received;
+ } else {
+ // No fork found => add a new one
+ var prevBlock = this.prevMaxBlock(block.number);
+
+ if (prevBlock) {
+ block.time = Math.max(block.arrived - prevBlock.block.arrived, 0);
+
+ if (block.number < this.bestBlock().height)
+ block.time = Math.max((block.timestamp - prevBlock.block.timestamp) * 1000, 0);
+ } else {
+ block.time = 0;
+ }
+
+ forkIndex = historyBlock.forks.push(block) - 1;
+ historyBlock.forks[forkIndex].fork = forkIndex;
+ }
+
+ // Push propagation time
+ historyBlock.propagTimes.push({
+ node: id,
+ trusted: trusted,
+ fork: forkIndex,
+ received: now,
+ propagation: block.propagation
+ });
+ } else {
+ // Node submited the block before
+ if (forkIndex >= 0 && !_.isUndefined(historyBlock.forks[forkIndex])) {
+ // Matching fork found => update data
+ block.arrived = historyBlock.forks[forkIndex].arrived;
+
+ if (forkIndex === historyBlock.propagTimes[propIndex].fork) {
+ // Fork index is the same
+ block.received = historyBlock.propagTimes[propIndex].received;
+ block.propagation = historyBlock.propagTimes[propIndex].propagation;
+ } else {
+ // Fork index is different
+ historyBlock.propagTimes[propIndex].fork = forkIndex;
+ historyBlock.propagTimes[propIndex].propagation = block.propagation = now - historyBlock.forks[forkIndex].received;
+ }
+
+ } else {
+ // No matching fork found => replace old one
+ block.received = historyBlock.propagTimes[propIndex].received;
+ block.propagation = historyBlock.propagTimes[propIndex].propagation;
+
+ var prevBlock = this.prevMaxBlock(block.number);
+
+ if (prevBlock) {
+ block.time = Math.max(block.arrived - prevBlock.block.arrived, 0);
+
+ if (block.number < this.bestBlock().height)
+ block.time = Math.max((block.timestamp - prevBlock.block.timestamp) * 1000, 0);
+ } else {
+ block.time = 0;
+ }
+
+ forkIndex = historyBlock.forks.push(block) - 1;
+ historyBlock.forks[forkIndex].fork = forkIndex;
+ }
+ }
+
+ if (trusted && !compareBlocks(historyBlock.block, historyBlock.forks[forkIndex])) {
+ // If source is trusted update the main block
+ historyBlock.forks[forkIndex].trusted = trusted;
+ historyBlock.block = historyBlock.forks[forkIndex];
+ }
+
+ block.fork = forkIndex;
+
+ changed = true;
+
+ } else {
+ // Couldn't find block with this height
+
+ // Getting previous max block
+ var prevBlock = this.prevMaxBlock(block.number);
+
+ if (prevBlock) {
+ block.time = Math.max(block.arrived - prevBlock.block.arrived, 0);
+
+ if (block.number < this.bestBlock().height)
+ block.time = Math.max((block.timestamp - prevBlock.block.timestamp) * 1000, 0);
+ } else {
+ block.time = 0;
+ }
+
+ var item = {
+ height: block.number,
+ block: block,
+ forks: [block],
+ propagTimes: []
+ }
+
+ if (
+ this._items.length === 0
+ || (
+ this._items.length > 0
+ && block.number > this.worstBlockNumber())
+ || (
+ this._items.length < MAX_HISTORY
+ && block.number < this.bestBlockNumber()
+ && addingHistory)) {
+ item.propagTimes.push({
+ node: id,
+ trusted: trusted,
+ fork: 0,
+ received: now,
+ propagation: block.propagation
+ });
+
+ this._save(item);
+
+ changed = true;
+ }
+ }
+
+ return {
+ block: block,
+ changed: changed
+ };
+ }
+
+ return false;
}
-function compareBlocks(block1, block2)
-{
- if( block1.hash !== block2.hash ||
- block1.parentHash !== block2.parentHash ||
- block1.sha3Uncles !== block2.sha3Uncles ||
- block1.transactionsRoot !== block2.transactionsRoot ||
- block1.stateRoot !== block2.stateRoot ||
- block1.miner !== block2.miner ||
- block1.difficulty !== block2.difficulty ||
- block1.totalDifficulty !== block2.totalDifficulty)
- return false;
-
- return true;
+function compareBlocks(block1, block2) {
+ if (block1.hash !== block2.hash ||
+ block1.parentHash !== block2.parentHash ||
+ block1.sha3Uncles !== block2.sha3Uncles ||
+ block1.transactionsRoot !== block2.transactionsRoot ||
+ block1.stateRoot !== block2.stateRoot ||
+ block1.miner !== block2.miner ||
+ block1.difficulty !== block2.difficulty ||
+ block1.totalDifficulty !== block2.totalDifficulty)
+ return false;
+
+ return true;
}
-function compareForks(historyBlock, block2)
-{
- if( _.isUndefined(historyBlock) )
- return -1;
+function compareForks(historyBlock, block2) {
+ if (_.isUndefined(historyBlock))
+ return -1;
- if( _.isUndefined(historyBlock.forks) || historyBlock.forks.length === 0 )
- return -1;
+ if (_.isUndefined(historyBlock.forks) || historyBlock.forks.length === 0)
+ return -1;
- for(var x = 0; x < historyBlock.forks.length; x++)
- if(compareBlocks(historyBlock.forks[x], block2))
- return x;
+ for (var x = 0; x < historyBlock.forks.length; x++)
+ if (compareBlocks(historyBlock.forks[x], block2))
+ return x;
- return -1;
+ return -1;
}
-History.prototype._save = function(block)
-{
- this._items.unshift(block);
+History.prototype._save = function (block) {
+ this._items.unshift(block);
- this._items = _.sortBy( this._items, 'height', false );
+ this._items = _.sortBy(this._items, 'height', false).reverse();
- if(this._items.length > MAX_HISTORY)
- {
- this._items.pop();
- }
+ if (this._items.length > MAX_HISTORY) {
+ this._items.pop();
+ }
}
-History.prototype.clean = function(max)
-{
- if(max > 0 && this._items.length > 0 && max < this.bestBlockNumber())
- {
- console.log("MAX:", max);
+History.prototype.clean = function (max) {
+ if (max > 0 && this._items.length > 0 && max < this.bestBlockNumber()) {
+ console.log("MAX:", max);
- console.log("History items before:", this._items.length);
+ console.log("History items before:", this._items.length);
- this._items = _(this._items).filter(function(item) {
- return (item.height <= max && item.block.trusted === false);
- }).value();
+ this._items = _(this._items).filter(function (item) {
+ return (item.height <= max && item.block.trusted === false);
+ }).value();
- console.log("History items after:", this._items.length);
- }
+ console.log("History items after:", this._items.length);
+ }
}
-History.prototype.search = function(number)
-{
- var index = _.findIndex( this._items, { height: number } );
+History.prototype.search = function (number) {
+ var index = _.findIndex(this._items, { height: number });
- if(index < 0)
- return false;
+ if (index < 0)
+ return false;
- return this._items[index];
+ return this._items[index];
}
-History.prototype.prevMaxBlock = function(number)
-{
- var index = _.findIndex(this._items, function (item) {
- return item.height < number;
- });
+History.prototype.prevMaxBlock = function (number) {
+ const heights = this._items.map(item=>item.height)
+ const index = heights.indexOf(Math.max(...heights))
+
+ if (index < 0)
+ return false;
- if(index < 0)
- return false;
-
- return this._items[index];
+ return this._items[index];
}
-History.prototype.bestBlock = function()
-{
- return _.maxBy(this._items, 'height');
+History.prototype.bestBlock = function () {
+ return _.maxBy(this._items, 'height');
}
-History.prototype.bestBlockNumber = function()
-{
- var best = this.bestBlock();
+History.prototype.bestBlockNumber = function () {
+ var best = this.bestBlock();
- if( !_.isUndefined(best) && !_.isUndefined(best.height) )
- return best.height;
+ if (!_.isUndefined(best) && !_.isUndefined(best.height))
+ return best.height;
- return 0;
+ return 0;
}
-History.prototype.worstBlock = function()
-{
- return _.minBy(this._items, 'height');
+History.prototype.worstBlock = function () {
+ return _.minBy(this._items, 'height');
}
-History.prototype.worstBlockNumber = function(trusted)
-{
- var worst = this.worstBlock();
+History.prototype.worstBlockNumber = function (trusted) {
+ var worst = this.worstBlock();
- if( !_.isUndefined(worst) && !_.isUndefined(worst.height) )
- return worst.height;
+ if (!_.isUndefined(worst) && !_.isUndefined(worst.height))
+ return worst.height;
- return 0;
+ return 0;
}
-History.prototype.getNodePropagation = function(id)
-{
- var propagation = new Array( MAX_PEER_PROPAGATION );
- var bestBlock = this.bestBlockNumber();
- var lastBlocktime = _.now();
-
- _.fill(propagation, -1);
-
- var sorted = _( this._items )
- .sortBy( 'height', false )
- .slice( 0, MAX_PEER_PROPAGATION )
- .forEach(function (item, key)
- {
- var index = MAX_PEER_PROPAGATION - 1 - bestBlock + item.height;
-
- if(index >= 0)
- {
- var tmpPropagation = _.result(_.find(item.propagTimes, 'node', id), 'propagation', false);
-
- if (_.result(_.find(item.propagTimes, 'node', id), 'propagation', false) !== false)
- {
- propagation[index] = tmpPropagation;
- lastBlocktime = item.block.arrived;
- }
- else
- {
- propagation[index] = Math.max(0, lastBlocktime - item.block.arrived);
- }
- }
- })
- .reverse();
-
- return propagation;
+History.prototype.getNodePropagation = function (id) {
+ const propagation = this._items
+ .slice(0, MAX_PEER_PROPAGATION)
+ .map(item => {
+ matches = item.propagTimes.filter(item => item.node === id)
+ if (matches.length > 0)
+ return matches[0].propagation
+ return -1
+ })
+ return propagation;
}
-History.prototype.getBlockPropagation = function()
-{
- var propagation = [];
- var avgPropagation = 0;
-
- _.forEach(this._items, function (n, key)
- {
- _.forEach(n.propagTimes, function (p, i)
- {
- var prop = Math.min(MAX_PROPAGATION_RANGE, _.result(p, 'propagation', -1));
-
- if(prop >= 0)
- propagation.push(prop);
- });
- });
-
- if(propagation.length > 0)
- {
- var avgPropagation = Math.round( _.sum(propagation) / propagation.length );
- }
-
- var data = d3.layout.histogram()
- .frequency( false )
- .range([ MIN_PROPAGATION_RANGE, MAX_PROPAGATION_RANGE ])
- .bins( MAX_BINS )
- ( propagation );
-
- var freqCum = 0;
- var histogram = data.map(function (val) {
- freqCum += val.length;
- var cumPercent = ( freqCum / Math.max(1, propagation.length) );
-
- return {
- x: val.x,
- dx: val.dx,
- y: val.y,
- frequency: val.length,
- cumulative: freqCum,
- cumpercent: cumPercent
- };
- });
-
- return {
- histogram: histogram,
- avg: avgPropagation
- };
+History.prototype.getBlockPropagation = function () {
+ var propagation = [];
+ var avgPropagation = 0;
+
+ _.forEach(this._items, function (n, key) {
+ _.forEach(n.propagTimes, function (p, i) {
+ var prop = Math.min(MAX_PROPAGATION_RANGE, _.result(p, 'propagation', -1));
+
+ if (prop >= 0)
+ propagation.push(prop);
+ });
+ });
+
+ if (propagation.length > 0) {
+ var avgPropagation = Math.round(_.sum(propagation) / propagation.length);
+ }
+
+ var data = d3.layout.histogram()
+ .frequency(false)
+ .range([MIN_PROPAGATION_RANGE, MAX_PROPAGATION_RANGE])
+ .bins(MAX_BINS)
+ (propagation);
+
+ var freqCum = 0;
+ var histogram = data.map(function (val) {
+ freqCum += val.length;
+ var cumPercent = (freqCum / Math.max(1, propagation.length));
+
+ return {
+ x: val.x,
+ dx: val.dx,
+ y: val.y,
+ frequency: val.length,
+ cumulative: freqCum,
+ cumpercent: cumPercent
+ };
+ });
+
+ return {
+ histogram: histogram,
+ avg: avgPropagation
+ };
}
-History.prototype.getUncleCount = function()
-{
- var uncles = _( this._items )
- .sortBy( 'height', false )
- // .filter(function (item)
- // {
- // return item.block.trusted;
- // })
- .slice(0, MAX_UNCLES)
- .map(function (item)
- {
- return item.block.uncles.length;
- })
- .value();
-
- var uncleBins = _.fill( Array(MAX_BINS), 0 );
-
- var sumMapper = function (array, key)
- {
- uncleBins[key] = _.sum(array);
- return _.sum(array);
- };
-
- _.map(_.chunk( uncles, MAX_UNCLES_PER_BIN ), sumMapper);
-
- return uncleBins;
+History.prototype.getUncleCount = function () {
+ var uncles = _(this._items)
+ .sortBy('height', false)
+ // .filter(function (item)
+ // {
+ // return item.block.trusted;
+ // })
+ .slice(0, MAX_UNCLES)
+ .map(function (item) {
+ return item.block.uncles.length;
+ })
+ .value();
+
+ var uncleBins = _.fill(Array(MAX_BINS), 0);
+
+ var sumMapper = function (array, key) {
+ uncleBins[key] = _.sum(array);
+ return _.sum(array);
+ };
+
+ _.map(_.chunk(uncles, MAX_UNCLES_PER_BIN), sumMapper);
+
+ return uncleBins;
}
-History.prototype.getBlockTimes = function()
-{
- var blockTimes = _( this._items )
- .sortBy( 'height', false )
- // .filter(function (item)
- // {
- // return item.block.trusted;
- // })
- .slice(0, MAX_BINS)
- .reverse()
- .map(function (item)
- {
- return item.block.time / 1000;
- })
- .value();
-
- return blockTimes;
+History.prototype.getBlockTimes = function () {
+ var blockTimes = _(this._items)
+ .sortBy('height', false)
+ // .filter(function (item)
+ // {
+ // return item.block.trusted;
+ // })
+ .slice(0, MAX_BINS)
+ .reverse()
+ .map(function (item) {
+ return item.block.time / 1000;
+ })
+ .value();
+
+ return blockTimes;
}
-History.prototype.getAvgBlocktime = function()
-{
- var blockTimes = _( this._items )
- .sortBy( 'height', false )
- // .filter(function (item)
- // {
- // return item.block.trusted;
- // })
- // .slice(0, MAX_BINS)
- .reverse()
- .map(function (item)
- {
- return item.block.time / 1000;
- })
- .value();
-
- return _.sum(blockTimes) / (blockTimes.length === 0 ? 1 : blockTimes.length);
+History.prototype.getAvgBlocktime = function () {
+ var blockTimes = _(this._items)
+ .sortBy('height', false)
+ // .filter(function (item)
+ // {
+ // return item.block.trusted;
+ // })
+ // .slice(0, MAX_BINS)
+ .reverse()
+ .map(function (item) {
+ return item.block.time / 1000;
+ })
+ .value();
+
+ return _.sum(blockTimes) / (blockTimes.length === 0 ? 1 : blockTimes.length);
}
-History.prototype.getGasLimit = function()
-{
- var gasLimitHistory = _( this._items )
- .sortBy( 'height', false )
- // .filter(function (item)
- // {
- // return item.block.trusted;
- // })
- .slice(0, MAX_BINS)
- .reverse()
- .map(function (item)
- {
- return item.block.gasLimit;
- })
- .value();
-
- return gasLimitHistory;
+History.prototype.getGasLimit = function () {
+ var gasLimitHistory = _(this._items)
+ .sortBy('height', false)
+ // .filter(function (item)
+ // {
+ // return item.block.trusted;
+ // })
+ .slice(0, MAX_BINS)
+ .reverse()
+ .map(function (item) {
+ return item.block.gasLimit;
+ })
+ .value();
+
+ return gasLimitHistory;
}
-History.prototype.getDifficulty = function()
-{
- var difficultyHistory = _( this._items )
- .sortBy( 'height', false )
- .filter(function (item)
- {
- return item.block.trusted;
- })
- .slice(0, MAX_BINS)
- .reverse()
- .map(function (item)
- {
- return item.block.difficulty;
- })
- .value();
-
- return difficultyHistory;
+History.prototype.getDifficulty = function () {
+ var difficultyHistory = _(this._items)
+ .sortBy('height', false)
+ .filter(function (item) {
+ return item.block.trusted;
+ })
+ .slice(0, MAX_BINS)
+ .reverse()
+ .map(function (item) {
+ return item.block.difficulty;
+ })
+ .value();
+
+ return difficultyHistory;
}
-History.prototype.getTransactionsCount = function()
-{
- var txCount = _( this._items )
- .sortBy( 'height', false )
- .filter(function (item)
- {
- return item.block.trusted;
- })
- .slice(0, MAX_BINS)
- .reverse()
- .map(function (item)
- {
- return item.block.transactions.length;
- })
- .value();
-
- return txCount;
+History.prototype.getTransactionsCount = function () {
+ var txCount = _(this._items)
+ .sortBy('height', false)
+ .filter(function (item) {
+ return item.block.trusted;
+ })
+ .slice(0, MAX_BINS)
+ .reverse()
+ .map(function (item) {
+ return item.block.transactions.length;
+ })
+ .value();
+
+ return txCount;
}
-History.prototype.getGasSpending = function()
-{
- var gasSpending = _( this._items )
- .sortBy( 'height', false )
- .filter(function (item)
- {
- return item.block.trusted;
- })
- .slice(0, MAX_BINS)
- .reverse()
- .map(function (item)
- {
- return item.block.gasUsed;
- })
- .value();
-
- return gasSpending;
+History.prototype.getGasSpending = function () {
+ var gasSpending = _(this._items)
+ .sortBy('height', false)
+ .filter(function (item) {
+ return item.block.trusted;
+ })
+ .slice(0, MAX_BINS)
+ .reverse()
+ .map(function (item) {
+ return item.block.gasUsed;
+ })
+ .value();
+
+ return gasSpending;
}
-History.prototype.getAvgHashrate = function()
-{
- if( _.isEmpty(this._items) )
- return 0;
-
- var blocktimeHistory = _( this._items )
- .sortBy( 'height', false )
- // .filter(function (item)
- // {
- // return item.block.trusted;
- // })
- .slice(0, 64)
- .map(function (item)
- {
- return item.block.time;
- })
- .value();
-
- var avgBlocktime = (_.sum(blocktimeHistory) / blocktimeHistory.length)/1000;
-
- return this.bestBlock().block.difficulty / avgBlocktime;
+History.prototype.getAvgHashrate = function () {
+ if (_.isEmpty(this._items))
+ return 0;
+
+ var blocktimeHistory = _(this._items)
+ .sortBy('height', false)
+ // .filter(function (item)
+ // {
+ // return item.block.trusted;
+ // })
+ .slice(0, 64)
+ .map(function (item) {
+ return item.block.time;
+ })
+ .value();
+
+ var avgBlocktime = (_.sum(blocktimeHistory) / blocktimeHistory.length) / 1000;
+
+ return this.bestBlock().block.difficulty / avgBlocktime;
}
-History.prototype.getMinersCount = function()
-{
- var miners = _( this._items )
- .sortBy( 'height', false )
- // .filter(function (item)
- // {
- // return item.block.trusted;
- // })
- .slice(0, MAX_BINS)
- .map(function (item)
- {
- return item.block.miner;
- })
- .value();
-
- var minerCount = [];
-
- _.forEach( _.countBy(miners), function (cnt, miner)
- {
- minerCount.push({ miner: miner, name: false, blocks: cnt });
- });
-
- return _(minerCount)
- .sortBy( 'blocks', false )
- .slice(0, 2)
- .value();
+History.prototype.getMinersCount = function () {
+ var miners = _(this._items)
+ .sortBy('height', false)
+ // .filter(function (item)
+ // {
+ // return item.block.trusted;
+ // })
+ .slice(0, MAX_BINS)
+ .map(function (item) {
+ return item.block.miner;
+ })
+ .value();
+
+ var minerCount = [];
+
+ _.forEach(_.countBy(miners), function (cnt, miner) {
+ minerCount.push({ miner: miner, name: false, blocks: cnt });
+ });
+
+ return _(minerCount)
+ .sortBy('blocks', false)
+ .slice(0, 2)
+ .value();
}
-History.prototype.setCallback = function(callback)
-{
- this._callback = callback;
+History.prototype.setCallback = function (callback) {
+ this._callback = callback;
}
-History.prototype.getCharts = function()
-{
- if(this._callback !== null)
- {
- var chartHistory = _( this._items )
- .sortBy( 'height', false )
- // .filter(function (item)
- // {
- // return item.block.trusted;
- // })
- .slice(0, MAX_BINS)
- .reverse()
- .map(function (item)
- {
- return {
- height: item.height,
- blocktime: item.block.time / 1000,
- difficulty: item.block.difficulty,
- uncles: item.block.uncles.length,
- transactions: item.block.transactions ? item.block.transactions.length : 0,
- gasSpending: item.block.gasUsed,
- gasLimit: item.block.gasLimit,
- miner: item.block.miner
- };
- })
- .value();
-
- this._callback(null, {
- height : _.map( chartHistory, 'height' ),
- blocktime : _.map( chartHistory, 'blocktime' ),
- // avgBlocktime : _.sum(_.map( chartHistory, 'blocktime' )) / (chartHistory.length === 0 ? 1 : chartHistory.length),
- avgBlocktime : this.getAvgBlocktime(),
- difficulty : _.map( chartHistory, 'difficulty' ),
- uncles : _.map( chartHistory, 'uncles' ),
- transactions : _.map( chartHistory, 'transactions' ),
- gasSpending : _.map( chartHistory, 'gasSpending' ),
- gasLimit : _.map( chartHistory, 'gasLimit' ),
- miners : this.getMinersCount(),
- propagation : this.getBlockPropagation(),
- uncleCount : this.getUncleCount(),
- avgHashrate : this.getAvgHashrate()
- });
- }
+History.prototype.getCharts = function () {
+ if (this._callback !== null) {
+ var chartHistory = _(this._items)
+ .sortBy('height', false)
+ .reverse()
+ .slice(0, MAX_BINS)
+ .map(function (item) {
+ return {
+ height: item.height,
+ blocktime: item.block.time / 1000,
+ difficulty: item.block.difficulty,
+ uncles: item.block.uncles.length,
+ transactions: item.block.transactions ? item.block.transactions.length : 0,
+ gasSpending: item.block.gasUsed,
+ gasLimit: item.block.gasLimit,
+ miner: item.block.miner
+ };
+ })
+ .value();
+ const padArray = function(arr,len,fill) {
+ return arr.concat(Array(len).fill(fill)).slice(0,len);
+ }
+
+ this._callback(null, {
+ height: _.map(chartHistory, 'height'),
+ blocktime: padArray(_.map(chartHistory, 'blocktime'), MAX_BINS, 0),
+ // avgBlocktime : _.sum(_.map( chartHistory, 'blocktime' )) / (chartHistory.length === 0 ? 1 : chartHistory.length),
+ avgBlocktime: this.getAvgBlocktime(),
+ difficulty: _.map(chartHistory, 'difficulty'),
+ uncles: _.map(chartHistory, 'uncles'),
+ transactions: _.map(chartHistory, 'transactions'),
+ gasSpending: padArray(_.map(chartHistory, 'gasSpending'), MAX_BINS, 0),
+ gasLimit: padArray(_.map(chartHistory, 'gasLimit'), MAX_BINS, 0),
+ miners: this.getMinersCount(),
+ propagation: this.getBlockPropagation(),
+ uncleCount: this.getUncleCount(),
+ avgHashrate: this.getAvgHashrate()
+ });
+ }
}
-History.prototype.requiresUpdate = function()
-{
- // return ( this._items.length < MAX_HISTORY && !_.isEmpty(this._items) );
- return ( this._items.length < MAX_HISTORY );
+History.prototype.requiresUpdate = function () {
+ // return ( this._items.length < MAX_HISTORY && !_.isEmpty(this._items) );
+ return (this._items.length < MAX_HISTORY);
}
-History.prototype.getHistoryRequestRange = function()
-{
- if( this._items.length < 2 )
- return false;
+History.prototype.getHistoryRequestRange = function () {
+ if (this._items.length < 2)
+ return false;
- var blocks = _.map( this._items, 'height' );
- var best = _.max( blocks );
- var range = _.range( _.max([ 0, best - MAX_HISTORY ]), best + 1);
+ var blocks = _.map(this._items, 'height');
+ var best = _.max(blocks);
+ var range = _.range(_.max([0, best - MAX_HISTORY]), best + 1);
- var missing = _.difference( range, blocks );
+ var missing = _.difference(range, blocks);
- var max = _.max(missing);
- var min = max - Math.min( 50, (MAX_HISTORY - this._items.length + 1) ) + 1;
+ var max = _.max(missing);
+ var min = max - Math.min(50, (MAX_HISTORY - this._items.length + 1)) + 1;
- return {
- max: max,
- min: min,
- list: _( missing ).reverse().slice(0, 50).reverse().value()
- };
+ return {
+ max: max,
+ min: min,
+ list: _(missing).reverse().slice(0, 50).reverse().value()
+ };
}
module.exports = History;
diff --git a/lib/node.js b/lib/node.js
index 454fa544..f4cd0fa0 100644
--- a/lib/node.js
+++ b/lib/node.js
@@ -3,395 +3,370 @@ var _ = require('lodash');
var trusted = require('./utils/config').trusted;
var MAX_HISTORY = 40;
-var MAX_INACTIVE_TIME = 1000*60*60*4;
-
-var Node = function(data)
-{
- this.id = null;
- this.trusted = false;
- this.info = {};
- this.geo = {}
- this.stats = {
- active: false,
- mining: false,
- hashrate: 0,
- peers: 0,
- pending: 0,
- gasPrice: 0,
- block: {
- number: 0,
- hash: '0x0000000000000000000000000000000000000000000000000000000000000000',
- difficulty: 0,
- totalDifficulty: 0,
- gasLimit: 0,
- timestamp: 0,
- time: 0,
- arrival: 0,
- received: 0,
- propagation: 0,
- transactions: [],
- uncles: []
- },
- syncing: false,
- propagationAvg: 0,
- latency: 0,
- uptime: 100
- };
-
- this.history = new Array(MAX_HISTORY);
-
- this.uptime = {
- started: null,
- up: 0,
- down: 0,
- lastStatus: null,
- lastUpdate: null
- };
-
- this.init(data);
-
- return this;
+var MAX_INACTIVE_TIME = 1000 * 60 * 60 * 4;
+
+var Node = function (data) {
+ this.id = null;
+ this.address = null;
+ this.validatorData = {
+ name: null,
+ url: null,
+ affiliation: null,
+ }
+ this.trusted = false;
+ this.info = {};
+ this.geo = {}
+ this.stats = {
+ active: false,
+ mining: false,
+ elected: false,
+ hashrate: 0,
+ peers: 0,
+ pending: 0,
+ gasPrice: 0,
+ block: {
+ number: 0,
+ hash: '0x0000000000000000000000000000000000000000000000000000000000000000',
+ difficulty: 0,
+ totalDifficulty: 0,
+ gasLimit: 0,
+ timestamp: 0,
+ time: 0,
+ arrival: 0,
+ received: 0,
+ propagation: 0,
+ transactions: [],
+ uncles: []
+ },
+ syncing: false,
+ propagationAvg: 0,
+ latency: 0,
+ uptime: 100
+ };
+
+ this.history = new Array(MAX_HISTORY);
+
+ this.uptime = {
+ started: null,
+ up: 0,
+ down: 0,
+ lastStatus: null,
+ lastUpdate: null
+ };
+
+ if (!data.registered) {
+ this.init(data);
+ } else {
+ this.setValidatorData(data)
+ }
+
+ if (!!data.address) {
+ this.address = data.address
+ }
+
+ return this;
}
-Node.prototype.init = function(data)
-{
- _.fill(this.history, -1);
+Node.prototype.init = function (data) {
+ _.fill(this.history, -1);
- if( this.id === null && this.uptime.started === null )
- this.setState(true);
+ if (this.id === null && this.uptime.started === null)
+ this.setState(true);
- this.id = _.result(data, 'id', this.id);
+ this.id = _.result(data, 'id', this.id);
- if( !_.isUndefined(data.latency) )
- this.stats.latency = data.latency;
+ if (!_.isUndefined(data.latency))
+ this.stats.latency = data.latency;
- this.setInfo(data, null);
+ this.setInfo(data, null);
}
-Node.prototype.setInfo = function(data, callback)
-{
- if( !_.isUndefined(data.info) )
- {
- this.info = data.info;
+Node.prototype.setInfo = function (data, callback) {
+ if (!_.isUndefined(data.info)) {
+ this.info = data.info;
- if( !_.isUndefined(data.info.canUpdateHistory) )
- {
- this.info.canUpdateHistory = _.result(data, 'info.canUpdateHistory', false);
- }
- }
+ if (!_.isUndefined(data.info.canUpdateHistory)) {
+ this.info.canUpdateHistory = _.result(data, 'info.canUpdateHistory', false);
+ }
+ }
- if( !_.isUndefined(data.ip) )
- {
- if( trusted.indexOf(data.ip) >= 0 || process.env.LITE === 'true')
- {
- this.trusted = true;
- }
+ if (!_.isUndefined(data.ip)) {
+ if (trusted.indexOf(data.ip) >= 0 || process.env.LITE === 'true') {
+ this.trusted = true;
+ }
+ this.trusted = true;
+ this.setGeo(data.ip);
+ }
- this.setGeo(data.ip);
- }
+ this.spark = _.result(data, 'spark', null);
- this.spark = _.result(data, 'spark', null);
+ this.setState(true);
- this.setState(true);
-
- if(callback !== null)
- {
- callback(null, this.getInfo());
- }
+ if (callback !== null) {
+ callback(null, this.getInfo());
+ }
}
-Node.prototype.setGeo = function(ip)
-{
- if (ip.substr(0, 7) == "::ffff:") {
- ip = ip.substr(7)
- }
- this.info.ip = ip;
- this.geo = geoip.lookup(ip);
+Node.prototype.setGeo = function (ip) {
+ if (ip.substr(0, 7) == "::ffff:") {
+ ip = ip.substr(7)
+ }
+ this.info.ip = ip;
+ this.geo = geoip.lookup(ip);
}
-Node.prototype.getInfo = function(callback)
-{
- return {
- id: this.id,
- info: this.info,
- stats: {
- active: this.stats.active,
- mining: this.stats.mining,
- syncing: this.stats.syncing,
- hashrate: this.stats.hashrate,
- peers: this.stats.peers,
- gasPrice: this.stats.gasPrice,
- block: this.stats.block,
- propagationAvg: this.stats.propagationAvg,
- uptime: this.stats.uptime,
- latency: this.stats.latency,
- pending: this.stats.pending,
- },
- history: this.history,
- geo: this.geo
- };
+Node.prototype.getInfo = function (callback) {
+ return {
+ id: this.id,
+ info: this.info,
+ stats: {
+ active: this.stats.active,
+ mining: this.stats.mining,
+ syncing: this.stats.syncing,
+ hashrate: this.stats.hashrate,
+ peers: this.stats.peers,
+ gasPrice: this.stats.gasPrice,
+ block: this.stats.block,
+ propagationAvg: this.stats.propagationAvg,
+ uptime: this.stats.uptime,
+ latency: this.stats.latency,
+ pending: this.stats.pending,
+ },
+ history: this.history,
+ geo: this.geo
+ };
}
-Node.prototype.setStats = function(stats, history, callback)
-{
- if( !_.isUndefined(stats) )
- {
- this.setBlock( _.result(stats, 'block', this.stats.block), history, function (err, block) {} );
+Node.prototype.setStats = function (stats, history, callback) {
+ if (!_.isUndefined(stats)) {
+ this.setBlock(_.result(stats, 'block', this.stats.block), history, function (err, block) {
+ });
- this.setBasicStats(stats, function (err, stats) {});
+ this.setBasicStats(stats, function (err, stats) {
+ });
- this.setPending( _.result(stats, 'pending', this.stats.pending), function (err, stats) {} );
+ this.setPending(_.result(stats, 'pending', this.stats.pending), function (err, stats) {
+ });
- callback(null, this.getStats());
- }
+ callback(null, this.getStats());
+ }
- callback('Stats undefined', null);
+ callback('Stats undefined', null);
}
-Node.prototype.setBlock = function(block, history, callback)
-{
- if( !_.isUndefined(block) && !_.isUndefined(block.number) )
- {
- if ( !_.isEqual(history, this.history) || !_.isEqual(block, this.stats.block) )
- {
- if(block.number !== this.stats.block.number || block.hash !== this.stats.block.hash)
- {
- this.stats.block = block;
- }
-
- this.setHistory(history);
-
- callback(null, this.getBlockStats());
- }
- else
- {
- callback(null, null);
- }
- }
- else
- {
- callback('Block undefined', null);
- }
+Node.prototype.setValidatorData = function (validatorData) {
+ if (!_.isUndefined(validatorData)) {
+ this.validatorData = validatorData
+ this.info.name = validatorData.name
+ this.info.contact = validatorData.url
+ this.stats.mining = true
+ this.trusted = true
+ this.id = validatorData.address
+ }
+}
+
+Node.prototype.setBlock = function (block, history, callback) {
+ if (!_.isUndefined(block) && !_.isUndefined(block.number)) {
+ if (!_.isEqual(history, this.history) || !_.isEqual(block, this.stats.block)) {
+ if (block.number !== this.stats.block.number || block.hash !== this.stats.block.hash) {
+ if (!block.validators.registered) {
+ block.validators = this.stats.block.validators
+ }
+ this.stats.block = block;
+ }
+
+ this.setHistory(history);
+
+ callback(null, this.getBlockStats());
+ } else {
+ callback(null, null);
+ }
+ } else {
+ callback('Block undefined', null);
+ }
}
-Node.prototype.setHistory = function(history)
-{
- if( _.isEqual(history, this.history) )
- {
- return false;
- }
+Node.prototype.setHistory = function (history) {
+ if (_.isEqual(history, this.history)) {
+ return false;
+ }
- if( !_.isArray(history) )
- {
- this.history = _.fill( new Array(MAX_HISTORY), -1 );
- this.stats.propagationAvg = 0;
+ if (!_.isArray(history)) {
+ this.history = _.fill(new Array(MAX_HISTORY), -1);
+ this.stats.propagationAvg = 0;
- return true;
- }
+ return true;
+ }
- this.history = history;
+ this.history = history;
- var positives = _.filter(history, function(p) {
- return p >= 0;
- });
+ var positives = _.filter(history, function (p) {
+ return p >= 0;
+ });
- this.stats.propagationAvg = ( positives.length > 0 ? Math.round( _.sum(positives) / positives.length ) : 0 );
- positives = null;
+ this.stats.propagationAvg = (positives.length > 0 ? Math.round(_.sum(positives) / positives.length) : 0);
+ positives = null;
- return true;
+ return true;
}
-Node.prototype.setPending = function(stats, callback)
-{
- if( !_.isUndefined(stats) && !_.isUndefined(stats.pending))
- {
- if(!_.isEqual(stats.pending, this.stats.pending))
- {
- this.stats.pending = stats.pending;
-
- callback(null, {
- id: this.id,
- pending: this.stats.pending
- });
- }
- else
- {
- callback(null, null);
- }
- }
- else
- {
- callback('Stats undefined', null);
- }
+Node.prototype.setPending = function (stats, callback) {
+ if (!_.isUndefined(stats) && !_.isUndefined(stats.pending)) {
+ if (!_.isEqual(stats.pending, this.stats.pending)) {
+ this.stats.pending = stats.pending;
+
+ callback(null, {
+ id: this.id,
+ pending: this.stats.pending
+ });
+ } else {
+ callback(null, null);
+ }
+ } else {
+ callback('Stats undefined', null);
+ }
}
-Node.prototype.setBasicStats = function(stats, callback)
-{
- if( !_.isUndefined(stats) )
- {
- if( !_.isEqual(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
- }) )
- {
- this.stats.active = stats.active;
- this.stats.mining = stats.mining;
- this.stats.syncing = (!_.isUndefined(stats.syncing) ? stats.syncing : false);
- this.stats.hashrate = stats.hashrate;
- this.stats.peers = stats.peers;
- this.stats.gasPrice = stats.gasPrice;
- this.stats.uptime = stats.uptime;
-
- callback(null, this.getBasicStats());
- }
- else
- {
- callback(null, null);
- }
- }
- else
- {
- callback('Stats undefined', null);
- }
+Node.prototype.setBasicStats = function (stats, callback) {
+ if (!_.isUndefined(stats)) {
+ if (!_.isEqual(stats, {
+ active: this.stats.active,
+ mining: this.stats.mining,
+ elected: this.stats.elected,
+ hashrate: this.stats.hashrate,
+ peers: this.stats.peers,
+ gasPrice: this.stats.gasPrice,
+ uptime: this.stats.uptime
+ })) {
+ this.stats.active = stats.active;
+ this.stats.mining = stats.mining;
+ this.stats.elected = stats.elected;
+ this.stats.syncing = (!_.isUndefined(stats.syncing) ? stats.syncing : false);
+ this.stats.hashrate = stats.hashrate;
+ this.stats.peers = stats.peers;
+ this.stats.gasPrice = stats.gasPrice;
+ this.stats.uptime = stats.uptime;
+
+ callback(null, this.getBasicStats());
+ } else {
+ callback(null, null);
+ }
+ } else {
+ callback('Stats undefined', null);
+ }
}
-Node.prototype.setLatency = function(latency, callback)
-{
- if( !_.isUndefined(latency) )
- {
- if( !_.isEqual(latency, this.stats.latency) )
- {
- this.stats.latency = latency;
-
- callback(null, {
- id: this.id,
- latency: latency
- });
- }
- else
- {
- callback(null, null);
- }
- }
- else
- {
- callback('Latency undefined', null);
- }
+Node.prototype.setLatency = function (latency, callback) {
+ if (!_.isUndefined(latency)) {
+ if (!_.isEqual(latency, this.stats.latency)) {
+ this.stats.latency = latency;
+
+ callback(null, {
+ id: this.id,
+ latency: latency
+ });
+ } else {
+ callback(null, null);
+ }
+ } else {
+ callback('Latency undefined', null);
+ }
}
-Node.prototype.getStats = function()
-{
- return {
- id: this.id,
- stats: {
- active: this.stats.active,
- mining: this.stats.mining,
- syncing: this.stats.syncing,
- hashrate: this.stats.hashrate,
- peers: this.stats.peers,
- gasPrice: this.stats.gasPrice,
- block: this.stats.block,
- propagationAvg: this.stats.propagationAvg,
- uptime: this.stats.uptime,
- pending: this.stats.pending,
- latency: this.stats.latency
- },
- history: this.history
- };
+Node.prototype.getStats = function () {
+ return {
+ id: this.id,
+ stats: {
+ active: this.stats.active,
+ mining: this.stats.mining,
+ syncing: this.stats.syncing,
+ hashrate: this.stats.hashrate,
+ peers: this.stats.peers,
+ gasPrice: this.stats.gasPrice,
+ block: this.stats.block,
+ propagationAvg: this.stats.propagationAvg,
+ uptime: this.stats.uptime,
+ pending: this.stats.pending,
+ latency: this.stats.latency
+ },
+ history: this.history
+ };
}
-Node.prototype.getBlockStats = function()
-{
- return {
- id: this.id,
- block: this.stats.block,
- propagationAvg: this.stats.propagationAvg,
- history: this.history
- };
+Node.prototype.getBlockStats = function () {
+ return {
+ id: this.id,
+ block: this.stats.block,
+ propagationAvg: this.stats.propagationAvg,
+ history: this.history
+ };
}
-Node.prototype.getBasicStats = function()
-{
- return {
- id: this.id,
- stats: {
- active: this.stats.active,
- mining: this.stats.mining,
- syncing: this.stats.syncing,
- hashrate: this.stats.hashrate,
- peers: this.stats.peers,
- gasPrice: this.stats.gasPrice,
- uptime: this.stats.uptime,
- latency: this.stats.latency
- }
- };
+Node.prototype.getBasicStats = function () {
+ return {
+ id: this.id,
+ stats: {
+ active: this.stats.active,
+ mining: this.stats.mining,
+ elected: this.stats.elected,
+ syncing: this.stats.syncing,
+ hashrate: this.stats.hashrate,
+ peers: this.stats.peers,
+ gasPrice: this.stats.gasPrice,
+ uptime: this.stats.uptime,
+ latency: this.stats.latency
+ }
+ };
}
-Node.prototype.setState = function(active)
-{
- var now = _.now();
-
- if(this.uptime.started !== null)
- {
- if(this.uptime.lastStatus === active)
- {
- this.uptime[(active ? 'up' : 'down')] += now - this.uptime.lastUpdate;
- }
- else
- {
- this.uptime[(active ? 'down' : 'up')] += now - this.uptime.lastUpdate;
- }
- }
- else
- {
- this.uptime.started = now;
- }
-
- this.stats.active = active;
- this.uptime.lastStatus = active;
- this.uptime.lastUpdate = now;
-
- this.stats.uptime = this.calculateUptime();
-
- now = undefined;
+Node.prototype.setState = function (active) {
+ var now = _.now();
+
+ if (this.uptime.started !== null) {
+ if (this.uptime.lastStatus === active) {
+ this.uptime[(active ? 'up' : 'down')] += now - this.uptime.lastUpdate;
+ } else {
+ this.uptime[(active ? 'down' : 'up')] += now - this.uptime.lastUpdate;
+ }
+ } else {
+ this.uptime.started = now;
+ }
+
+ this.stats.active = active;
+ this.uptime.lastStatus = active;
+ this.uptime.lastUpdate = now;
+
+ this.stats.uptime = this.calculateUptime();
+
+ now = undefined;
}
-Node.prototype.calculateUptime = function()
-{
- if(this.uptime.lastUpdate === this.uptime.started)
- {
- return 100;
- }
+Node.prototype.calculateUptime = function () {
+ if (this.uptime.lastUpdate === this.uptime.started) {
+ return 100;
+ }
- return Math.round( this.uptime.up / (this.uptime.lastUpdate - this.uptime.started) * 100);
+ return Math.round(this.uptime.up / (this.uptime.lastUpdate - this.uptime.started) * 100);
}
-Node.prototype.getBlockNumber = function()
-{
- return this.stats.block.number;
+Node.prototype.getBlockNumber = function () {
+ return this.stats.block.number;
}
-Node.prototype.canUpdate = function()
-{
- if (this.trusted) {
- return true;
- }
- // return (this.info.canUpdateHistory && this.trusted) || false;
- return (this.info.canUpdateHistory || (this.stats.syncing === false && this.stats.peers > 0)) || false;
+Node.prototype.canUpdate = function () {
+ if (this.trusted) {
+ return true;
+ }
+ // return (this.info.canUpdateHistory && this.trusted) || false;
+ return (this.info.canUpdateHistory || (this.stats.syncing === false && this.stats.peers > 0)) || false;
}
-Node.prototype.isInactiveAndOld = function()
-{
- if( this.uptime.lastStatus === false && this.uptime.lastUpdate !== null && (_.now() - this.uptime.lastUpdate) > MAX_INACTIVE_TIME )
- {
- return true;
- }
+Node.prototype.isInactiveAndOld = function () {
+ if (this.uptime.lastStatus === false && this.uptime.lastUpdate !== null && (_.now() - this.uptime.lastUpdate) > MAX_INACTIVE_TIME) {
+ return true;
+ }
- return false;
+ return false;
}
module.exports = Node;
diff --git a/lib/utils/config.js b/lib/utils/config.js
index 13587f22..b86fd35d 100644
--- a/lib/utils/config.js
+++ b/lib/utils/config.js
@@ -1,4 +1,8 @@
-var trusted = [];
+var trusted = [
+ "0x47e172f6cfb6c7d01c1574fa3e2be7cc73269D95",
+ "0xa42c9b0d1a30722aea8b81e72957134897e7a11a",
+ "0xa0af2e71cecc248f4a7fd606f203467b500dd53b",
+]
var banned = [];
diff --git a/lib/utils/logger.js b/lib/utils/logger.js
index 604a1e07..b8f16486 100644
--- a/lib/utils/logger.js
+++ b/lib/utils/logger.js
@@ -17,7 +17,8 @@ var types = [
'TXS',
'STA',
'HIS',
- 'PIN'
+ 'PIN',
+ 'SIG'
];
var typeColors = {
@@ -29,6 +30,7 @@ var typeColors = {
'STA': chalk.reset.bold.red,
'HIS': chalk.reset.bold.magenta,
'PIN': chalk.reset.bold.yellow,
+ 'SIG': chalk.reset.bold.green,
};
var verbosity = [
diff --git a/package.json b/package.json
index 7d2a64d8..f58c0262 100644
--- a/package.json
+++ b/package.json
@@ -16,21 +16,17 @@
"chalk": "^2.4.1",
"d3": "3.5.17",
"debug": "^4.1.0",
+ "elliptic": "^6.5.1",
"express": "^4.16.4",
"geoip-lite": "^1.3.5",
"grunt": "^1.0.3",
"grunt-cli": "^1.3.2",
- "grunt-contrib-clean": "^2.0.0",
- "grunt-contrib-concat": "^1.0.1",
- "grunt-contrib-copy": "^1.0.0",
- "grunt-contrib-cssmin": "^3.0.0",
- "grunt-contrib-jade": "^1.0.0",
- "grunt-contrib-uglify": "^4.0.0",
"jade": "^1.11.0",
"lodash": "4.17.11",
"primus": "^7.3.1",
"primus-emit": "^1.0.0",
"primus-spark-latency": "^0.1.1",
+ "sha3": "^2.0.7",
"ws": "^6.1.2"
},
"repository": {
@@ -54,5 +50,13 @@
}
],
"license": "LGPL-3.0",
- "devDependencies": {}
+ "devDependencies": {
+ "grunt-contrib-clean": "^2.0.0",
+ "grunt-contrib-concat": "^1.0.1",
+ "grunt-contrib-copy": "^1.0.0",
+ "grunt-contrib-cssmin": "^3.0.0",
+ "grunt-contrib-jade": "^1.0.0",
+ "grunt-contrib-uglify": "^4.0.0",
+ "grunt-contrib-watch": "^1.1.0"
+ }
}
diff --git a/src/css/style.css b/src/css/style.css
index eb02bc72..18866b5d 100644
--- a/src/css/style.css
+++ b/src/css/style.css
@@ -8,6 +8,7 @@ body {
font-smooth: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
+ background-color: #2E3338;
}
@font-face {
@@ -19,7 +20,7 @@ body {
@font-face {
font-family: 'Source Sans Pro';
- font-style: normal;
+ font-style: normal;
font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url(../fonts/SourceSansPro-Light.woff2) format('woff2');
}
@@ -52,6 +53,10 @@ table td {
-moz-osx-font-smoothing: auto;
}
+table tr {
+ border: 1px solid rgba(255,255,255, 0.1);
+}
+
.propagationBox {
position: relative;
width: 8px;
@@ -65,12 +70,18 @@ table td {
.bg-success,
.text-success .propagationBox {
- background: #50fa7b;
+ background: #35D07F;
+}
+.text-success{
+ color:#35D07F
}
.bg-info,
.text-info .propagationBox {
- background: #8be9fd;
+ background: #73DDFF;
+}
+.text-info {
+ color: #73DDFF;
}
.bg-highlight,
@@ -80,7 +91,7 @@ table td {
.bg-warning,
.text-warning .propagationBox {
- background: #f1fa8c;
+ background: #FFB765;
}
.bg-orange,
@@ -103,7 +114,7 @@ table td {
.bg-warning,
.bg-orange,
.bg-danger {
- color: #000;
+ color: #2E3338;
}
.text-gray {
@@ -120,8 +131,8 @@ table td {
}
.stat-holder {
- background: #090909;
- border: 1px solid rgba(255,255,255,0.05);
+ background: #2E3338;
+ border: 1px solid rgba(255,255,255, 0.1);
}
.big-info {
@@ -151,7 +162,7 @@ div.small-title-miner {
line-height: 20px;
letter-spacing: 1px;
text-transform: uppercase;
- color: #aaa;
+ /* color: #aaa; */
}
span.small-title span.small {
@@ -194,8 +205,8 @@ span.small-title span.small {
}
.big-info.chart {
- height: 120px;
- -webkit-box-sizing: border-box
+ height: 130px;
+ -webkit-box-sizing: border-box;
box-sizing: border-box;
}
@@ -297,7 +308,7 @@ table i {
table th,
table td {
- border-color: #222 !important;
+ border-color: rgba(255,255,255, 0.1) !important;
}
table td {
@@ -343,7 +354,15 @@ nodepropagchart {
}
.th-nodename {
- width: 300px;
+ width: 200px;
+}
+
+.nodeInfo {
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.nodeInfo>span {
text-overflow: ellipsis;
}
@@ -556,3 +575,6 @@ svg .y.axis .tick:first-child text {
font-size: 10px;
}
}
+
+
+.table-striped>tbody>tr:nth-child(odd){background-color:#2E3338}
\ No newline at end of file
diff --git a/src/js/controllers.js b/src/js/controllers.js
index 33fa96f7..6db3d22c 100644
--- a/src/js/controllers.js
+++ b/src/js/controllers.js
@@ -1,654 +1,528 @@
-
/* Controllers */
+netStatsApp.controller('StatsCtrl', function ($scope, $filter, $localStorage, socket, _, toastr) {
+
+ var MAX_BINS = 40;
+
+ // Main Stats init
+ // ---------------
+
+ $scope.frontierHash = '0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa';
+ $scope.nodesTotal = 0;
+ $scope.nodesActive = 0;
+ $scope.bestBlock = 0;
+ $scope.lastBlock = 0;
+ $scope.lastDifficulty = 0;
+ $scope.upTimeTotal = 0;
+ $scope.avgBlockTime = 0;
+ $scope.blockPropagationAvg = 0;
+ $scope.avgHashrate = 0;
+ $scope.uncleCount = 0;
+ $scope.bestStats = {};
+
+ $scope.lastGasLimit = _.fill(Array(MAX_BINS), 2);
+ $scope.lastBlocksTime = _.fill(Array(MAX_BINS), 2);
+ $scope.difficultyChart = _.fill(Array(MAX_BINS), 2);
+ $scope.transactionDensity = _.fill(Array(MAX_BINS), 2);
+ $scope.gasSpending = _.fill(Array(MAX_BINS), 2);
+ $scope.miners = [];
+ $scope.validators = {
+ 'elected': [],
+ 'registered': []
+ };
+
+
+ $scope.nodes = [];
+ $scope.map = [];
+ $scope.blockPropagationChart = [];
+ $scope.uncleCountChart = _.fill(Array(MAX_BINS), 2);
+ $scope.coinbases = [];
+
+ $scope.latency = 0;
+
+ $scope.currentApiVersion = "0.1.1";
+
+ $scope.predicate = $localStorage.predicate || ['-pinned', '-stats.active', '-stats.block.number', 'stats.block.propagation'];
+ $scope.reverse = $localStorage.reverse || false;
+ $scope.pinned = $localStorage.pinned || [];
+
+ $scope.prefixPredicate = ['-pinned', '-stats.active'];
+ $scope.originalPredicate = ['-stats.block.number', 'stats.block.propagation'];
+
+ $scope.orderTable = function (predicate, reverse) {
+ if (!_.isEqual(predicate, $scope.originalPredicate)) {
+ $scope.reverse = reverse;
+ $scope.originalPredicate = predicate;
+ $scope.predicate = _.union($scope.prefixPredicate, predicate);
+ } else {
+ $scope.reverse = !$scope.reverse;
+
+ if ($scope.reverse === true) {
+ _.forEach(predicate, function (value, key) {
+ predicate[key] = (value[0] === '-' ? value.replace('-', '') : '-' + value);
+ });
+ }
+
+ $scope.predicate = _.union($scope.prefixPredicate, predicate);
+ }
+
+ $localStorage.predicate = $scope.predicate;
+ $localStorage.reverse = $scope.reverse;
+ }
+
+ $scope.pinNode = function (id) {
+ index = findIndex({ id: id });
+
+ if (!_.isUndefined($scope.nodes[index])) {
+ $scope.nodes[index].pinned = !$scope.nodes[index].pinned;
+
+ if ($scope.nodes[index].pinned) {
+ $scope.pinned.push(id);
+ } else {
+ $scope.pinned.splice($scope.pinned.indexOf(id), 1);
+ }
+ }
+
+ $localStorage.pinned = $scope.pinned;
+ }
+
+ var timeout = setInterval(function () {
+ $scope.$apply();
+ }, 300);
+
+ $scope.getNumber = function (num) {
+ return new Array(num);
+ }
+
+ // Socket listeners
+ // ----------------
+
+ socket.on('open', function open() {
+ socket.emit('ready');
+ console.log('The connection has been opened.');
+ })
+ .on('end', function end() {
+ console.log('Socket connection ended.')
+ })
+ .on('error', function error(err) {
+ console.log(err);
+ })
+ .on('reconnecting', function reconnecting(opts) {
+ console.log('We are scheduling a reconnect operation', opts);
+ })
+ .on('data', function incoming(data) {
+ $scope.$apply(socketAction(data.action, data.data));
+ });
+
+ socket.on('init', function (data) {
+ $scope.$apply(socketAction("init", data.nodes));
+ });
+
+ socket.on('client-latency', function (data) {
+ $scope.latency = data.latency;
+ })
+
+ function socketAction(action, data) {
+ // filter data
+ data = xssFilter(data);
+
+ switch (action) {
+ case "init":
+ $scope.nodes = data;
+ _.forEach($scope.nodes, function (node, index) {
+
+ // Init hashrate
+ if (_.isUndefined(node.stats.hashrate))
+ node.stats.hashrate = 0;
+
+ // Init latency
+ latencyFilter(node);
+
+ // Init history
+ if (_.isUndefined(data.history)) {
+ data.history = new Array(40);
+ _.fill(data.history, -1);
+ }
+
+ // Init or recover pin
+ node.pinned = ($scope.pinned.indexOf(node.id) >= 0 ? true : false);
+ });
+
+ if ($scope.nodes.length > 0) {
+ toastr['success']("Got nodes list", "Got nodes!");
+
+ updateActiveNodes();
+ }
+
+ break;
+
+ case "add":
+ var index = findIndex({ id: data.id });
+
+ // if( addNewNode(data) )
+ // toastr['success']("New node "+ $scope.nodes[findIndex({id: data.id})].info.name +" connected!", "New node!");
+ // else
+ // toastr['info']("Node "+ $scope.nodes[index].info.name +" reconnected!", "Node is back!");
+
+ break;
+
+ // TODO: Remove when everybody updates api client to 0.0.12
+ case "update":
+ var index = findIndex({ id: data.id });
+
+ if (index >= 0 && !_.isUndefined($scope.nodes[index]) && !_.isUndefined($scope.nodes[index].stats)) {
+ if (!_.isUndefined($scope.nodes[index].stats.latency))
+ data.stats.latency = $scope.nodes[index].stats.latency;
+
+ if (_.isUndefined(data.stats.hashrate))
+ data.stats.hashrate = 0;
+
+ if ($scope.nodes[index].stats.block.number < data.stats.block.number) {
+ var best = _.max($scope.nodes, function (node) {
+ return parseInt(node.stats.block.number);
+ }).stats.block;
+
+ if (data.stats.block.number > best.number) {
+ data.stats.block.arrived = _.now();
+ } else {
+ data.stats.block.arrived = best.arrived;
+ }
+ $scope.nodes[index].history = data.history;
+ }
+
+ $scope.nodes[index].stats = data.stats;
+
+ if (!_.isUndefined(data.stats.latency) && _.get($scope.nodes[index], 'stats.latency', 0) !== data.stats.latency) {
+ $scope.nodes[index].stats.latency = data.stats.latency;
+
+ latencyFilter($scope.nodes[index]);
+ }
+
+ updateBestBlock();
+ }
+
+ break;
-netStatsApp.controller('StatsCtrl', function($scope, $filter, $localStorage, socket, _, toastr) {
-
- var MAX_BINS = 40;
-
- // Main Stats init
- // ---------------
-
- $scope.frontierHash = '0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa';
- $scope.nodesTotal = 0;
- $scope.nodesActive = 0;
- $scope.bestBlock = 0;
- $scope.lastBlock = 0;
- $scope.lastDifficulty = 0;
- $scope.upTimeTotal = 0;
- $scope.avgBlockTime = 0;
- $scope.blockPropagationAvg = 0;
- $scope.avgHashrate = 0;
- $scope.uncleCount = 0;
- $scope.bestStats = {};
-
- $scope.lastGasLimit = _.fill(Array(MAX_BINS), 2);
- $scope.lastBlocksTime = _.fill(Array(MAX_BINS), 2);
- $scope.difficultyChart = _.fill(Array(MAX_BINS), 2);
- $scope.transactionDensity = _.fill(Array(MAX_BINS), 2);
- $scope.gasSpending = _.fill(Array(MAX_BINS), 2);
- $scope.miners = [];
-
-
- $scope.nodes = [];
- $scope.map = [];
- $scope.blockPropagationChart = [];
- $scope.uncleCountChart = _.fill(Array(MAX_BINS), 2);
- $scope.coinbases = [];
-
- $scope.latency = 0;
-
- $scope.currentApiVersion = "0.1.1";
-
- $scope.predicate = $localStorage.predicate || ['-pinned', '-stats.active', '-stats.block.number', 'stats.block.propagation'];
- $scope.reverse = $localStorage.reverse || false;
- $scope.pinned = $localStorage.pinned || [];
-
- $scope.prefixPredicate = ['-pinned', '-stats.active'];
- $scope.originalPredicate = ['-stats.block.number', 'stats.block.propagation'];
-
- $scope.orderTable = function(predicate, reverse)
- {
- if(!_.isEqual(predicate, $scope.originalPredicate))
- {
- $scope.reverse = reverse;
- $scope.originalPredicate = predicate;
- $scope.predicate = _.union($scope.prefixPredicate, predicate);
- }
- else
- {
- $scope.reverse = !$scope.reverse;
-
- if($scope.reverse === true){
- _.forEach(predicate, function (value, key) {
- predicate[key] = (value[0] === '-' ? value.replace('-', '') : '-' + value);
- });
- }
-
- $scope.predicate = _.union($scope.prefixPredicate, predicate);
- }
-
- $localStorage.predicate = $scope.predicate;
- $localStorage.reverse = $scope.reverse;
- }
-
- $scope.pinNode = function(id)
- {
- index = findIndex({id: id});
-
- if( !_.isUndefined($scope.nodes[index]) )
- {
- $scope.nodes[index].pinned = !$scope.nodes[index].pinned;
-
- if($scope.nodes[index].pinned)
- {
- $scope.pinned.push(id);
- }
- else
- {
- $scope.pinned.splice($scope.pinned.indexOf(id), 1);
- }
- }
-
- $localStorage.pinned = $scope.pinned;
- }
-
- var timeout = setInterval(function ()
- {
- $scope.$apply();
- }, 300);
-
- $scope.getNumber = function (num) {
- return new Array(num);
- }
-
- // Socket listeners
- // ----------------
-
- socket.on('open', function open() {
- socket.emit('ready');
- console.log('The connection has been opened.');
- })
- .on('end', function end() {
- console.log('Socket connection ended.')
- })
- .on('error', function error(err) {
- console.log(err);
- })
- .on('reconnecting', function reconnecting(opts) {
- console.log('We are scheduling a reconnect operation', opts);
- })
- .on('data', function incoming(data) {
- $scope.$apply(socketAction(data.action, data.data));
- });
-
- socket.on('init', function(data)
- {
- $scope.$apply(socketAction("init", data.nodes));
- });
-
- socket.on('client-latency', function(data)
- {
- $scope.latency = data.latency;
- })
-
- function socketAction(action, data)
- {
- // filter data
- data = xssFilter(data);
-
- // console.log('Action: ', action);
- // console.log('Data: ', data);
-
- switch(action)
- {
- case "init":
- $scope.nodes = data;
-
- _.forEach($scope.nodes, function (node, index) {
-
- // Init hashrate
- if( _.isUndefined(node.stats.hashrate) )
- node.stats.hashrate = 0;
-
- // Init latency
- latencyFilter(node);
-
- // Init history
- if( _.isUndefined(data.history) )
- {
- data.history = new Array(40);
- _.fill(data.history, -1);
- }
-
- // Init or recover pin
- node.pinned = ($scope.pinned.indexOf(node.id) >= 0 ? true : false);
- });
-
- if( $scope.nodes.length > 0 )
- {
- toastr['success']("Got nodes list", "Got nodes!");
-
- updateActiveNodes();
- }
-
- break;
-
- case "add":
- var index = findIndex({id: data.id});
-
- // if( addNewNode(data) )
- // toastr['success']("New node "+ $scope.nodes[findIndex({id: data.id})].info.name +" connected!", "New node!");
- // else
- // toastr['info']("Node "+ $scope.nodes[index].info.name +" reconnected!", "Node is back!");
-
- break;
-
- // TODO: Remove when everybody updates api client to 0.0.12
- case "update":
- var index = findIndex({id: data.id});
-
- if( index >= 0 && !_.isUndefined($scope.nodes[index]) && !_.isUndefined($scope.nodes[index].stats) )
- {
- if( !_.isUndefined($scope.nodes[index].stats.latency) )
- data.stats.latency = $scope.nodes[index].stats.latency;
-
- if( _.isUndefined(data.stats.hashrate) )
- data.stats.hashrate = 0;
-
- if( $scope.nodes[index].stats.block.number < data.stats.block.number )
- {
- var best = _.max($scope.nodes, function (node) {
- return parseInt(node.stats.block.number);
- }).stats.block;
+ case "block":
+ var index = findIndex({ id: data.id });
- if (data.stats.block.number > best.number) {
- data.stats.block.arrived = _.now();
- } else {
- data.stats.block.arrived = best.arrived;
- }
+ if (index >= 0 && !_.isUndefined($scope.nodes[index]) && !_.isUndefined($scope.nodes[index].stats)) {
+ if ($scope.nodes[index].stats.block.number < data.block.number) {
+ var best = _.max($scope.nodes, function (node) {
+ return parseInt(node.stats.block.number);
+ }).stats.block;
- $scope.nodes[index].history = data.history;
- }
+ if (data.block.number > best.number) {
+ data.block.arrived = _.now();
+ } else {
+ data.block.arrived = best.arrived;
+ }
- $scope.nodes[index].stats = data.stats;
+ $scope.nodes[index].history = data.history;
+ }
+ $scope.nodes[index].stats.block = data.block;
+ $scope.nodes[index].stats.propagationAvg = data.propagationAvg;
+ $scope.validators = data.block.validators;
+ updateBestBlock();
+ }
- if( !_.isUndefined(data.stats.latency) && _.get($scope.nodes[index], 'stats.latency', 0) !== data.stats.latency )
- {
- $scope.nodes[index].stats.latency = data.stats.latency;
+ break;
- latencyFilter($scope.nodes[index]);
- }
+ case "pending":
+ var index = findIndex({ id: data.id });
- updateBestBlock();
- }
+ if (!_.isUndefined(data.id) && index >= 0) {
+ var node = $scope.nodes[index];
- break;
+ if (!_.isUndefined(node) && !_.isUndefined(node.stats.pending) && !_.isUndefined(data.pending))
+ $scope.nodes[index].stats.pending = data.pending;
+ }
- case "block":
- var index = findIndex({id: data.id});
+ break;
- if( index >= 0 && !_.isUndefined($scope.nodes[index]) && !_.isUndefined($scope.nodes[index].stats) )
- {
- if( $scope.nodes[index].stats.block.number < data.block.number )
- {
- var best = _.max($scope.nodes, function (node) {
- return parseInt(node.stats.block.number);
- }).stats.block;
+ case "stats":
+ var index = findIndex({ id: data.id });
- if (data.block.number > best.number) {
- data.block.arrived = _.now();
- } else {
- data.block.arrived = best.arrived;
- }
+ if (!_.isUndefined(data.id) && index >= 0) {
+ var node = $scope.nodes[index];
- $scope.nodes[index].history = data.history;
- }
+ if (!_.isUndefined(node) && !_.isUndefined(node.stats)) {
+ $scope.nodes[index].stats.active = data.stats.active;
+ $scope.nodes[index].stats.mining = data.stats.mining;
+ $scope.nodes[index].stats.hashrate = data.stats.hashrate;
+ $scope.nodes[index].stats.peers = data.stats.peers;
+ $scope.nodes[index].stats.gasPrice = data.stats.gasPrice;
+ $scope.nodes[index].stats.uptime = data.stats.uptime;
+ $scope.nodes[index].stats.address = data.stats.address;
+ console.log($scope.nodes[index].stats.address)
+ if (!_.isUndefined(data.stats.latency) && _.get($scope.nodes[index], 'stats.latency', 0) !== data.stats.latency) {
+ $scope.nodes[index].stats.latency = data.stats.latency;
- $scope.nodes[index].stats.block = data.block;
- $scope.nodes[index].stats.propagationAvg = data.propagationAvg;
+ latencyFilter($scope.nodes[index]);
+ }
- updateBestBlock();
- }
+ updateActiveNodes();
+ }
+ }
- break;
+ break;
- case "pending":
- var index = findIndex({id: data.id});
+ case "info":
+ var index = findIndex({ id: data.id });
- if( !_.isUndefined(data.id) && index >= 0 )
- {
- var node = $scope.nodes[index];
+ if (index >= 0) {
+ $scope.nodes[index].info = data.info;
- if( !_.isUndefined(node) && !_.isUndefined(node.stats.pending) && !_.isUndefined(data.pending) )
- $scope.nodes[index].stats.pending = data.pending;
- }
+ if (_.isUndefined($scope.nodes[index].pinned))
+ $scope.nodes[index].pinned = false;
- break;
+ // Init latency
+ latencyFilter($scope.nodes[index]);
- case "stats":
- var index = findIndex({id: data.id});
+ updateActiveNodes();
+ }
- if( !_.isUndefined(data.id) && index >= 0 )
- {
- var node = $scope.nodes[index];
+ break;
- if( !_.isUndefined(node) && !_.isUndefined(node.stats) )
- {
- $scope.nodes[index].stats.active = data.stats.active;
- $scope.nodes[index].stats.mining = data.stats.mining;
- $scope.nodes[index].stats.hashrate = data.stats.hashrate;
- $scope.nodes[index].stats.peers = data.stats.peers;
- $scope.nodes[index].stats.gasPrice = data.stats.gasPrice;
- $scope.nodes[index].stats.uptime = data.stats.uptime;
+ case "blockPropagationChart":
+ $scope.blockPropagationChart = data.histogram;
+ $scope.blockPropagationAvg = data.avg;
- if( !_.isUndefined(data.stats.latency) && _.get($scope.nodes[index], 'stats.latency', 0) !== data.stats.latency )
- {
- $scope.nodes[index].stats.latency = data.stats.latency;
+ break;
- latencyFilter($scope.nodes[index]);
- }
+ case "uncleCount":
+ $scope.uncleCount = data[0] + data[1];
+ data.reverse();
+ $scope.uncleCountChart = data;
- updateActiveNodes();
- }
- }
+ break;
- break;
+ case "charts":
+ if (!_.isEqual($scope.avgBlockTime, data.avgBlocktime))
+ $scope.avgBlockTime = data.avgBlocktime;
- case "info":
- var index = findIndex({id: data.id});
+ if (!_.isEqual($scope.avgHashrate, data.avgHashrate))
+ $scope.avgHashrate = data.avgHashrate;
- if( index >= 0 )
- {
- $scope.nodes[index].info = data.info;
+ if (!_.isEqual($scope.lastGasLimit, data.gasLimit) && data.gasLimit.length >= MAX_BINS)
+ $scope.lastGasLimit = data.gasLimit;
+
+ if (!_.isEqual($scope.lastBlocksTime, data.blocktime) && data.blocktime.length >= MAX_BINS)
+ $scope.lastBlocksTime = data.blocktime;
- if( _.isUndefined($scope.nodes[index].pinned) )
- $scope.nodes[index].pinned = false;
+ if (!_.isEqual($scope.difficultyChart, data.difficulty) && data.difficulty.length >= MAX_BINS)
+ $scope.difficultyChart = data.difficulty;
- // Init latency
- latencyFilter($scope.nodes[index]);
+ if (!_.isEqual($scope.blockPropagationChart, data.propagation.histogram)) {
+ $scope.blockPropagationChart = data.propagation.histogram;
+ $scope.blockPropagationAvg = data.propagation.avg;
+ }
- updateActiveNodes();
- }
+ data.uncleCount.reverse();
- break;
+ if (!_.isEqual($scope.uncleCountChart, data.uncleCount) && data.uncleCount.length >= MAX_BINS) {
+ $scope.uncleCount = data.uncleCount[data.uncleCount.length - 2] + data.uncleCount[data.uncleCount.length - 1];
+ $scope.uncleCountChart = data.uncleCount;
+ }
- case "blockPropagationChart":
- $scope.blockPropagationChart = data.histogram;
- $scope.blockPropagationAvg = data.avg;
+ if (!_.isEqual($scope.transactionDensity, data.transactions) && data.transactions.length >= MAX_BINS)
+ $scope.transactionDensity = data.transactions;
- break;
+ if (!_.isEqual($scope.gasSpending, data.gasSpending) && data.gasSpending.length >= MAX_BINS)
+ $scope.gasSpending = data.gasSpending;
- case "uncleCount":
- $scope.uncleCount = data[0] + data[1];
- data.reverse();
- $scope.uncleCountChart = data;
+ if (!_.isEqual($scope.miners, data.miners)) {
+ $scope.miners = data.miners;
+ getMinersNames();
+ }
- break;
+ break;
- case "charts":
- if( !_.isEqual($scope.avgBlockTime, data.avgBlocktime) )
- $scope.avgBlockTime = data.avgBlocktime;
+ case "inactive":
+ var index = findIndex({ id: data.id });
- if( !_.isEqual($scope.avgHashrate, data.avgHashrate) )
- $scope.avgHashrate = data.avgHashrate;
+ if (index >= 0) {
+ if (!_.isUndefined(data.stats))
+ $scope.nodes[index].stats = data.stats;
- if( !_.isEqual($scope.lastGasLimit, data.gasLimit) && data.gasLimit.length >= MAX_BINS )
- $scope.lastGasLimit = data.gasLimit;
+ // toastr['error']("Node "+ $scope.nodes[index].info.name +" went away!", "Node connection was lost!");
- if( !_.isEqual($scope.lastBlocksTime, data.blocktime) && data.blocktime.length >= MAX_BINS )
- $scope.lastBlocksTime = data.blocktime;
+ updateActiveNodes();
+ }
- if( !_.isEqual($scope.difficultyChart, data.difficulty) && data.difficulty.length >= MAX_BINS )
- $scope.difficultyChart = data.difficulty;
+ break;
- if( !_.isEqual($scope.blockPropagationChart, data.propagation.histogram) ) {
- $scope.blockPropagationChart = data.propagation.histogram;
- $scope.blockPropagationAvg = data.propagation.avg;
- }
+ case "latency":
+ if (!_.isUndefined(data.id) && !_.isUndefined(data.latency)) {
+ var index = findIndex({ id: data.id });
- data.uncleCount.reverse();
+ if (index >= 0) {
+ var node = $scope.nodes[index];
- if( !_.isEqual($scope.uncleCountChart, data.uncleCount) && data.uncleCount.length >= MAX_BINS ) {
- $scope.uncleCount = data.uncleCount[data.uncleCount.length-2] + data.uncleCount[data.uncleCount.length-1];
- $scope.uncleCountChart = data.uncleCount;
- }
+ if (!_.isUndefined(node) && !_.isUndefined(node.stats) && !_.isUndefined(node.stats.latency) && node.stats.latency !== data.latency) {
+ node.stats.latency = data.latency;
+ latencyFilter(node);
+ }
+ }
+ }
- if( !_.isEqual($scope.transactionDensity, data.transactions) && data.transactions.length >= MAX_BINS )
- $scope.transactionDensity = data.transactions;
+ break;
- if( !_.isEqual($scope.gasSpending, data.gasSpending) && data.gasSpending.length >= MAX_BINS )
- $scope.gasSpending = data.gasSpending;
+ case "client-ping":
+ socket.emit('client-pong', {
+ serverTime: data.serverTime,
+ clientTime: _.now()
+ });
- if( !_.isEqual($scope.miners, data.miners) ) {
- $scope.miners = data.miners;
- getMinersNames();
- }
+ break;
+ }
- break;
+ // $scope.$apply();
+ }
- case "inactive":
- var index = findIndex({id: data.id});
+ function findIndex(search) {
+ return _.findIndex($scope.nodes, search);
+ }
- if( index >= 0 )
- {
- if( !_.isUndefined(data.stats) )
- $scope.nodes[index].stats = data.stats;
+ function getMinersNames() {
+ if ($scope.miners.length > 0) {
+ _.forIn($scope.miners, function (value, key) {
+ if (value.name !== false)
+ return;
- // toastr['error']("Node "+ $scope.nodes[index].info.name +" went away!", "Node connection was lost!");
+ if (value.miner === "0x0000000000000000000000000000000000000000")
+ return;
- updateActiveNodes();
- }
+ var name = _.result(_.find(_.pluck($scope.nodes, 'info'), 'coinbase', value.miner), 'name');
- break;
+ if (!_.isUndefined(name))
+ $scope.miners[key].name = name;
+ });
+ }
+ }
- case "latency":
- if( !_.isUndefined(data.id) && !_.isUndefined(data.latency) )
- {
- var index = findIndex({id: data.id});
+ function addNewNode(data) {
+ var index = findIndex({ id: data.id });
- if( index >= 0 )
- {
- var node = $scope.nodes[index];
+ if (_.isUndefined(data.history)) {
+ data.history = new Array(40);
+ _.fill(data.history, -1);
+ }
- if( !_.isUndefined(node) && !_.isUndefined(node.stats) && !_.isUndefined(node.stats.latency) && node.stats.latency !== data.latency )
- {
- node.stats.latency = data.latency;
- latencyFilter(node);
- }
- }
- }
+ if (index < 0) {
+ if (!_.isUndefined(data.stats) && _.isUndefined(data.stats.hashrate)) {
+ data.stats.hashrate = 0;
+ }
- break;
+ data.pinned = false;
- case "client-ping":
- socket.emit('client-pong', {
- serverTime: data.serverTime,
- clientTime: _.now()
- });
+ $scope.nodes.push(data);
- break;
- }
+ return true;
+ }
- // $scope.$apply();
- }
+ data.pinned = (!_.isUndefined($scope.nodes[index].pinned) ? $scope.nodes[index].pinned : false);
- function findIndex(search)
- {
- return _.findIndex($scope.nodes, search);
- }
-
- function getMinersNames()
- {
- if( $scope.miners.length > 0 )
- {
- _.forIn($scope.miners, function (value, key)
- {
- if(value.name !== false)
- return;
-
- if(value.miner === "0x0000000000000000000000000000000000000000")
- return;
-
- var name = _.result(_.find(_.pluck($scope.nodes, 'info'), 'coinbase', value.miner), 'name');
-
- if( !_.isUndefined(name) )
- $scope.miners[key].name = name;
- });
- }
- }
-
- function addNewNode(data)
- {
- var index = findIndex({id: data.id});
-
- if( _.isUndefined(data.history) )
- {
- data.history = new Array(40);
- _.fill(data.history, -1);
- }
-
- if( index < 0 )
- {
- if( !_.isUndefined(data.stats) && _.isUndefined(data.stats.hashrate) )
- {
- data.stats.hashrate = 0;
- }
-
- data.pinned = false;
-
- $scope.nodes.push(data);
-
- return true;
- }
-
- data.pinned = ( !_.isUndefined($scope.nodes[index].pinned) ? $scope.nodes[index].pinned : false);
-
- if( !_.isUndefined($scope.nodes[index].history) )
- {
- data.history = $scope.nodes[index].history;
- }
-
- $scope.nodes[index] = data;
-
- updateActiveNodes();
-
- return false;
- }
-
- function updateActiveNodes()
- {
- updateBestBlock();
-
- $scope.nodesTotal = $scope.nodes.length;
-
- $scope.nodesActive = _.filter($scope.nodes, function (node) {
- // forkFilter(node);
- return node.stats.active == true;
- }).length;
-
- $scope.upTimeTotal = _.reduce($scope.nodes, function (total, node) {
- return total + node.stats.uptime;
- }, 0) / $scope.nodes.length;
-
- $scope.map = _.map($scope.nodes, function (node) {
- var fill = $filter('bubbleClass')(node.stats, $scope.bestBlock);
-
- if(node.geo != null)
- return {
- radius: 3,
- latitude: node.geo.ll[0],
- longitude: node.geo.ll[1],
- nodeName: node.info.name,
- fillClass: "text-" + fill,
- fillKey: fill,
- };
- else
- return {
- radius: 0,
- latitude: 0,
- longitude: 0
- };
- });
- }
-
- function updateBestBlock()
- {
- if( $scope.nodes.length )
- {
- var chains = {};
- var maxScore = 0;
-
- // _($scope.nodes)
- // .map(function (item)
- // {
- // maxScore += (item.trusted ? 50 : 1);
-
- // if( _.isUndefined(chains[item.stats.block.number]) )
- // chains[item.stats.block.number] = [];
-
- // if( _.isUndefined(chains[item.stats.block.number][item.stats.block.fork]) )
- // chains[item.stats.block.number][item.stats.block.fork] = {
- // fork: item.stats.block.fork,
- // count: 0,
- // trusted: 0,
- // score: 0
- // };
-
- // if(item.stats.block.trusted)
- // chains[item.stats.block.number][item.stats.block.fork].trusted++;
- // else
- // chains[item.stats.block.number][item.stats.block.fork].count++;
-
- // chains[item.stats.block.number][item.stats.block.fork].score = chains[item.stats.block.number][item.stats.block.fork].trusted * 50 + chains[item.stats.block.number][item.stats.block.fork].count;
- // })
- // .value();
-
- // $scope.maxScore = maxScore;
- // $scope.chains = _.reduce(chains, function (result, item, key)
- // {
- // result[key] = _.max(item, 'score');
- // return result;
- // }, {});
-
- var bestBlock = _.max($scope.nodes, function (node)
- {
- // if( $scope.chains[node.stats.block.number].fork === node.stats.block.fork && $scope.chains[node.stats.block.number].score / $scope.maxScore >= 0.5 )
- // {
- return parseInt(node.stats.block.number);
- // }
-
- // return 0;
- }).stats.block.number;
-
- if( bestBlock !== $scope.bestBlock )
- {
- $scope.bestBlock = bestBlock;
- $scope.bestStats = _.max($scope.nodes, function (node) {
- return parseInt(node.stats.block.number);
- }).stats;
-
- $scope.lastBlock = $scope.bestStats.block.arrived;
- $scope.lastDifficulty = $scope.bestStats.block.difficulty;
- }
- }
- }
-
- // function forkFilter(node)
- // {
- // if( _.isUndefined(node.readable) )
- // node.readable = {};
-
- // node.readable.forkClass = 'hidden';
- // node.readable.forkMessage = '';
-
- // return true;
-
- // if( $scope.chains[node.stats.block.number].fork === node.stats.block.fork && $scope.chains[node.stats.block.number].score / $scope.maxScore >= 0.5 )
- // {
- // node.readable.forkClass = 'hidden';
- // node.readable.forkMessage = '';
-
- // return true;
- // }
-
- // if( $scope.chains[node.stats.block.number].fork !== node.stats.block.fork )
- // {
- // node.readable.forkClass = 'text-danger';
- // node.readable.forkMessage = 'Wrong chain.
This chain is a fork.';
-
- // return false;
- // }
-
- // if( $scope.chains[node.stats.block.number].score / $scope.maxScore < 0.5)
- // {
- // node.readable.forkClass = 'text-warning';
- // node.readable.forkMessage = 'May not be main chain.
Waiting for more confirmations.';
-
- // return false;
- // }
- // }
-
- function latencyFilter(node)
- {
- if( _.isUndefined(node.readable) )
- node.readable = {};
-
- if( _.isUndefined(node.stats) ) {
- node.readable.latencyClass = 'text-danger';
- node.readable.latency = 'offline';
- }
-
- if (node.stats.active === false)
- {
- node.readable.latencyClass = 'text-danger';
- node.readable.latency = 'offline';
- }
- else
- {
- if (node.stats.latency <= 100)
- node.readable.latencyClass = 'text-success';
-
- if (node.stats.latency > 100 && node.stats.latency <= 1000)
- node.readable.latencyClass = 'text-warning';
-
- if (node.stats.latency > 1000)
- node.readable.latencyClass = 'text-danger';
-
- node.readable.latency = node.stats.latency + ' ms';
- }
- }
-
- // very simple xss filter
- function xssFilter(obj){
- if(_.isArray(obj)) {
- return _.map(obj, xssFilter);
-
- } else if(_.isObject(obj)) {
- return _.mapValues(obj, xssFilter);
-
- } else if(_.isString(obj)) {
- return obj.replace(/\< *\/* *script *>*/gi,'').replace(/javascript/gi,'');
- } else
- return obj;
- }
+ if (!_.isUndefined($scope.nodes[index].history)) {
+ data.history = $scope.nodes[index].history;
+ }
+
+ $scope.nodes[index] = data;
+
+ updateActiveNodes();
+
+ return false;
+ }
+
+ function updateActiveNodes() {
+ updateBestBlock();
+
+ $scope.nodesTotal = $scope.nodes.length;
+
+ $scope.nodesActive = _.filter($scope.nodes, function (node) {
+ // forkFilter(node);
+ return node.stats.active == true;
+ }).length;
+
+ $scope.upTimeTotal = _.reduce($scope.nodes, function (total, node) {
+ return total + node.stats.uptime;
+ }, 0) / $scope.nodes.length;
+
+ $scope.map = _.map($scope.nodes, function (node) {
+ var fill = $filter('bubbleClass')(node.stats, $scope.bestBlock);
+
+ if (node.geo != null)
+ return {
+ radius: 3,
+ latitude: node.geo.ll[0],
+ longitude: node.geo.ll[1],
+ nodeName: node.info.name,
+ fillClass: "text-" + fill,
+ fillKey: fill,
+ };
+ else
+ return {
+ radius: 0,
+ latitude: 0,
+ longitude: 0
+ };
+ });
+ }
+
+ function updateBestBlock() {
+ if ($scope.nodes.length) {
+ var bestBlock = _.max($scope.nodes, function (node) {
+ return parseInt(node.stats.block.number);
+ }).stats.block.number;
+
+ if (bestBlock !== $scope.bestBlock) {
+ $scope.bestBlock = bestBlock;
+ $scope.bestStats = _.max($scope.nodes, function (node) {
+ return parseInt(node.stats.block.number);
+ }).stats;
+
+ $scope.lastBlock = $scope.bestStats.block.arrived;
+ $scope.lastDifficulty = $scope.bestStats.block.difficulty;
+ }
+ }
+ }
+
+ function latencyFilter(node) {
+ if (_.isUndefined(node.readable))
+ node.readable = {};
+
+ if (_.isUndefined(node.stats)) {
+ node.readable.latencyClass = 'text-danger';
+ node.readable.latency = 'offline';
+ }
+
+ if (node.stats.active === false) {
+ node.readable.latencyClass = 'text-danger';
+ node.readable.latency = 'offline';
+ } else {
+ if (node.stats.latency <= 100)
+ node.readable.latencyClass = 'text-success';
+
+ if (node.stats.latency > 100 && node.stats.latency <= 1000)
+ node.readable.latencyClass = 'text-warning';
+
+ if (node.stats.latency > 1000)
+ node.readable.latencyClass = 'text-danger';
+
+ node.readable.latency = node.stats.latency + ' ms';
+ }
+ }
+
+ // very simple xss filter
+ function xssFilter(obj) {
+ if (_.isArray(obj)) {
+ return _.map(obj, xssFilter);
+
+ } else if (_.isObject(obj)) {
+ return _.mapValues(obj, xssFilter);
+
+ } else if (_.isString(obj)) {
+ return obj.replace(/\< *\/* *script *>*/gi, '').replace(/javascript/gi, '');
+ } else
+ return obj;
+ }
});
\ No newline at end of file
diff --git a/src/js/filters.js b/src/js/filters.js
index fdbced8d..9ed5feda 100644
--- a/src/js/filters.js
+++ b/src/js/filters.js
@@ -179,7 +179,22 @@ angular.module('netStatsApp.filters', [])
if(hash.substr(0,2) === '0x')
hash = hash.substr(2,64);
- return hash.substr(0, 8) + '...' + hash.substr(56, 8);
+ return hash.substr(0, 8) + '..' + hash.substr(56, 8);
+ }
+})
+.filter('nameFilter', function() {
+ return function(name) {
+ if(typeof name === 'undefined')
+ return "?";
+ return name.substr(0, 30) + '..';
+ }
+})
+.filter('addressFilter', function() {
+ return function(address) {
+ if(typeof address === 'undefined')
+ return "?";
+
+ return address.substr(0, 10) + '..';
}
})
.filter('timeClass', function() {
@@ -392,6 +407,11 @@ angular.module('netStatsApp.filters', [])
return blockTimeClass(time);
}
})
+.filter('blocksInEpochClass', function() {
+ return function(blocks, epochSize) {
+ return blockTimeClass(Math.round(40*(1-blocks/epochSize)));
+ }
+})
.filter('upTimeFilter', function() {
return function(uptime) {
return Math.round(uptime) + '%';
diff --git a/src/views/index.jade b/src/views/index.jade
index eb9f4904..ffbfad34 100644
--- a/src/views/index.jade
+++ b/src/views/index.jade
@@ -30,13 +30,21 @@ block content
span.big-details {{ avgBlockTime | avgTimeFilter }}
div.clearfix
div.col-xs-2.stat-holder
- div.big-info.blockremain.text-info
+ div.big-info.blockremain(class="{{ bestStats.block.blockRemain | blocksInEpochClass : bestStats.block.epochSize}}")
div.pull-left.icon-full-width
i.icon-block
div.big-details-holder
span.small-title blocks until epoch
span.big-details {{ bestStats.block.blockRemain | number }}
div.clearfix
+ div.col-xs-2.stat-holder
+ div.big-info.blockremain.text-success
+ div.pull-left.icon-full-width
+ i.icon-mining
+ div.big-details-holder
+ span.small-title elected validators
+ span.big-details {{ validators.elected.length | number }}
+ div.clearfix
div.clearfix
div.row(ng-cloak)
@@ -58,10 +66,15 @@ block content
span.small-title gas limit
span.small-value {{ bestStats.block.gasLimit }} gas
div.col-xs-2.stat-holder.box
- div.gasprice.text-info
+ div.gasprice.text-warning
i.icon-block
span.small-title epoch size
span.small-value {{ bestStats.block.epochSize }}
+ div.col-xs-2.stat-holder.box
+ div.gasprice.text-warning
+ i.icon-check
+ span.small-title registered validators
+ span.small-value {{ validators.registered.length }}
div.row(ng-cloak)
div.col-xs-2.stat-holder
@@ -89,9 +102,9 @@ block content
sparkchart.big-details.spark-difficulty(data="{{lastGasLimit.join(',')}}")
div.clearfix
div.col-xs-2.stat-holder.xpull-right
- div.big-info.chart.xdouble-chart.text-danger
- //- i.icon-hashrate
- span.small-title recent validators
+ div.big-info.chart.xdouble-chart.text-success
+ i.icon-mining
+ span.small-title recent block proposers
div.blocks-holder(ng-repeat='miner in miners track by miner.miner', data-toggle="tooltip", data-placement="right", data-original-title="{{miner.blocks}}")
div.block-count(class="{{miner.blocks | minerBlocksClass : 'text-'}}") {{miner.blocks}}
div.small-title-miner {{miner.miner | minerNameFilter : miner.name}}
@@ -107,12 +120,16 @@ block content
i.icon-check-o(data-toggle="tooltip", data-placement="top", title="Pin nodes to display first", ng-click="orderTable(['-stats.block.number', 'stats.block.propagation'], false)")
th.th-nodename
i.icon-node(data-toggle="tooltip", data-placement="top", title="Node name", ng-click="orderTable(['info.name'], false)")
- th.th-nodetype
- i.icon-laptop(data-toggle="tooltip", data-placement="top", title="Node type", ng-click="orderTable(['info.node'], false)")
th.th-latency
i.icon-clock(data-toggle="tooltip", data-placement="top", title="Node latency", ng-click="orderTable(['stats.latency'], false)")
+ th.th-nodeaddress
+ i.icon-node(data-toggle="tooltip", data-placement="top", title="Node address", ng-click="orderTable(['address'], false)")
+ th.th-nodetype
+ i.icon-group(data-toggle="tooltip", data-placement="top", title="Validator Group", ng-click="orderTable(['validatorData.affiliation'], false)")
+ th
+ i.icon-check(data-toggle="tooltip", data-placement="top", title="Validating", ng-click="orderTable(['-stats.hashrate'], false)")
th
- i.icon-check(data-toggle="tooltip", data-placement="top", title="Is validating", ng-click="orderTable(['-stats.hashrate'], false)")
+ i.icon-mining(data-toggle="tooltip", data-placement="top", title="Elected", ng-click="orderTable(['-stats.hashrate'], false)")
th
i.icon-group(data-toggle="tooltip", data-placement="top", title="Peers", ng-click="orderTable(['-stats.peers'], false)")
th
@@ -136,21 +153,19 @@ block content
td.td-nodecheck
i(ng-click="pinNode(node.id)", class="{{ node.pinned | nodePinClass }}", data-toggle="tooltip", data-placement="right", data-original-title="Click to {{ node.pinned ? 'un' : '' }}pin")
td.nodeInfo(rel="{{node.id}}")
- span.small(data-toggle="tooltip", data-placement="top", data-html="true", data-original-title="{{node | geoTooltip}}") {{node.info.name}}
- //- span.small ({{node.info.ip}})
- a.small(href="https://github.com/ethereum/wiki/wiki/Network-Status#updating", target="_blank", data-toggle="tooltip", data-placement="top", data-html="true", data-original-title="Netstats client needs update.
Click this icon for instructions.", class="{{ node.info | nodeClientClass : currentApiVersion }}")
- i.icon-warning-o
- td
- div.small(ng-bind-html="node.info.node | nodeVersion")
+ span.small(data-toggle="tooltip", data-placement="top", data-html="true", data-original-title="{{node | geoTooltip}}") {{node.info.name | nameFilter}}
td(class="{{ node.readable.latencyClass }}")
span.small {{ node.readable.latency }}
+ td.nodeInfo(rel="{{node.address}}")
+ span.small {{node.address | addressFilter }}
+ td.nodeInfo(rel="{{node.validatorData.affiliation}}")
+ span.small {{ node.validatorData.affiliation || "no affiliation" | nameFilter}}
td(class="{{ node.stats.mining | hashrateClass : node.stats.active }}", ng-bind-html="node.stats.hashrate | stakingFilter : node.stats.mining")
+ td(class="{{ node.stats.elected | hashrateClass : node.stats.elected }}", ng-bind-html="node.stats.elected | stakingFilter : node.stats.elected")
td(class="{{ node.stats.peers | peerClass : node.stats.active }}", style="padding-left: 11px;") {{node.stats.peers}}
td(style="padding-left: 15px;") {{node.stats.pending}}
td(class="{{ node.stats | blockClass : bestBlock }}")
span(class="{{ node.readable.forkMessage ? node.readable.forkClass : '' }}") {{'#'}}{{ node.stats.block.number | number }}
- //- a.small(data-toggle="tooltip", data-placement="top", data-html="true", data-original-title="{{ node.readable.forkMessage }}", class="{{ node.readable.forkClass }}")
- i.icon-warning-o
td(class="{{ node.stats | blockClass : bestBlock }}")
span.small {{node.stats.block.hash | hashFilter}}
td(style="padding-left: 14px;") {{node.stats.block.transactions.length || 0}}