added fork check

This commit is contained in:
cubedro 2015-06-09 19:28:29 +03:00
parent 4b5bdf3e3e
commit f67529a75e
9 changed files with 270 additions and 60 deletions

2
dist/index.html vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -30,7 +30,7 @@ Collection.prototype.update = function(id, stats, callback)
{ {
this._blockchain.clean(this.getBestBlockFromItems()); this._blockchain.clean(this.getBestBlockFromItems());
var block = this._blockchain.add(stats.block, id); var block = this._blockchain.add(stats.block, id, node.trusted);
if (!block) if (!block)
{ {
@ -61,7 +61,7 @@ Collection.prototype.addBlock = function(id, stats, callback)
{ {
this._blockchain.clean(this.getBestBlockFromItems()); this._blockchain.clean(this.getBestBlockFromItems());
var block = this._blockchain.add(stats, id); var block = this._blockchain.add(stats, id, node.trusted);
if (!block) if (!block)
{ {
@ -121,7 +121,7 @@ Collection.prototype.addHistory = function(id, blocks, callback)
for (var i = 0; i <= blocks.length - 1; i++) for (var i = 0; i <= blocks.length - 1; i++)
{ {
this._blockchain.add(blocks[i], id); this._blockchain.add(blocks[i], id, node.trusted, true);
}; };
this.getCharts(); this.getCharts();
@ -262,9 +262,9 @@ Collection.prototype.getHistory = function()
Collection.prototype.getBestBlockFromItems = function() Collection.prototype.getBestBlockFromItems = function()
{ {
return _.result(_.max(this._items, function(item) { return Math.max(this._blockchain.bestBlockNumber(), _.result(_.max(this._items, function(item) {
return item.stats.block.number; return ( !item.trusted ? 0 : item.stats.block.number );
}), 'stats.block.number', 0); }), 'stats.block.number', 0));
} }
Collection.prototype.canNodeUpdate = function(id) Collection.prototype.canNodeUpdate = function(id)

View File

@ -1,12 +1,13 @@
var _ = require('lodash'); var _ = require('lodash');
var d3 = require('d3'); var d3 = require('d3');
var MAX_HISTORY = 1000; var MAX_HISTORY = 2000;
var MAX_PEER_PROPAGATION = 40; var MAX_PEER_PROPAGATION = 40;
var MIN_PROPAGATION_RANGE = 0; var MIN_PROPAGATION_RANGE = 0;
var MAX_PROPAGATION_RANGE = 10000; var MAX_PROPAGATION_RANGE = 10000;
var MAX_UNCLES = 1000;
var MAX_UNCLES_PER_BIN = 25; var MAX_UNCLES_PER_BIN = 25;
var MAX_BINS = 40; var MAX_BINS = 40;
@ -14,86 +15,110 @@ var History = function History(data)
{ {
this._items = []; this._items = [];
this._callback = null; this._callback = null;
var item = {
height: 0,
block: {
number: 0,
hash: '0x?',
arrived: 0,
received: 0,
propagation: 0,
difficulty: 0,
gasUsed: 0,
transactions: [],
uncles: []
},
propagTimes: []
};
} }
History.prototype.add = function(block, id) History.prototype.add = function(block, id, trusted, addingHistory)
{ {
var changed = false; var changed = false;
if( !_.isUndefined(block) && !_.isUndefined(block.number) && !_.isUndefined(block.uncles) && !_.isUndefined(block.transactions) && !_.isUndefined(block.difficulty) && block.number > 0 ) if( !_.isUndefined(block) && !_.isUndefined(block.number) && !_.isUndefined(block.uncles) && !_.isUndefined(block.transactions) && !_.isUndefined(block.difficulty) && block.number > 0 )
{ {
var historyBlock = this.search(block.number); var historyBlock = this.search(block.number);
var forkIndex = -1;
var now = _.now(); var now = _.now();
block.trusted = trusted;
block.arrived = now; block.arrived = now;
block.received = now; block.received = now;
block.propagation = 0; block.propagation = 0;
block.fork = 0;
if( historyBlock ) 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 } ); var propIndex = _.findIndex( historyBlock.propagTimes, { node: id } );
// Check if node already check a fork with this height
forkIndex = compareForks(historyBlock, block);
if( propIndex === -1 ) if( propIndex === -1 )
{ {
block.arrived = historyBlock.block.arrived; // Node didn't submit this block before
block.received = now; if( forkIndex >= 0 && !_.isUndefined(historyBlock.forks[forkIndex]) )
block.propagation = now - historyBlock.block.received; {
// 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
forkIndex = historyBlock.forks.push(block) - 1;
historyBlock.forks[forkIndex].fork = forkIndex;
}
// Push propagation time
historyBlock.propagTimes.push({ historyBlock.propagTimes.push({
node: id, node: id,
trusted: trusted,
fork: forkIndex,
received: now, received: now,
propagation: block.propagation propagation: block.propagation
}); });
changed = true;
} }
else else
{ {
block.arrived = historyBlock.block.arrived; // Node submited the block before
block.received = historyBlock.propagTimes[propIndex].received; if( forkIndex >= 0 && !_.isUndefined(historyBlock.forks[forkIndex]) )
block.propagation = historyBlock.propagTimes[propIndex].propagation;
if(historyBlock.hash !== block.hash || historyBlock.totalDifficulty !== block.totalDifficulty || historyBlock.transactions.length !== block.transactions.length)
{ {
historyBlock.hash = block.hash; // Matching fork found => update data
historyBlock.parentHash = block.parentHash; block.arrived = historyBlock.forks[forkIndex].arrived;
historyBlock.nonce = block.nonce;
historyBlock.sha3Uncles = block.sha3Uncles;
historyBlock.transactionsRoot = block.transactionsRoot;
historyBlock.stateRoot = block.stateRoot;
historyBlock.miner = block.miner;
historyBlock.difficulty = block.difficulty;
historyBlock.totalDifficulty = block.totalDifficulty;
historyBlock.size = block.size;
historyBlock.extraData = block.extraData;
historyBlock.gasLimit = block.gasLimit;
historyBlock.gasUsed = block.gasUsed;
historyBlock.timestamp = block.timestamp;
historyBlock.transactions = block.transactions;
historyBlock.uncles = block.uncles;
changed = true; 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;
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 else
{ {
// Couldn't find block with this height
// Getting previous max block
var prevBlock = this.prevMaxBlock(block.number); var prevBlock = this.prevMaxBlock(block.number);
if( prevBlock ) if( prevBlock )
@ -111,13 +136,16 @@ History.prototype.add = function(block, id)
var item = { var item = {
height: block.number, height: block.number,
block: block, block: block,
forks: [block],
propagTimes: [] propagTimes: []
} }
if( this._items.length === 0 || block.number >= (this.bestBlockNumber() - MAX_HISTORY + 1) ) if( this._items.length === 0 || (this._items.length === MAX_HISTORY && block.number > this.worstBlockNumber() && !addingHistory) || (this._items.length < MAX_HISTORY && block.number < this.bestBlockNumber()) )
{ {
item.propagTimes.push({ item.propagTimes.push({
node: id, node: id,
trusted: trusted,
fork: 0,
received: now, received: now,
propagation: block.propagation propagation: block.propagation
}); });
@ -137,6 +165,43 @@ History.prototype.add = function(block, id)
return false; return false;
} }
function compareBlocks(block1, block2)
{
if( block1.hash !== block2.hash ||
block1.parentHash !== block2.parentHash ||
block1.nonce !== block2.nonce ||
block1.sha3Uncles !== block2.sha3Uncles ||
block1.transactionsRoot !== block2.transactionsRoot ||
block1.stateRoot !== block2.stateRoot ||
block1.miner !== block2.miner ||
block1.difficulty !== block2.difficulty ||
block1.totalDifficulty !== block2.totalDifficulty ||
block1.size !== block2.size ||
block1.extraData !== block2.extraData ||
block1.gasLimit !== block2.gasLimit ||
block1.gasUsed !== block2.gasUsed ||
block1.transactions.length !== block2.transactions.length ||
block1.uncles.length !== block2.uncles.length)
return false;
return true;
}
function compareForks(historyBlock, block2)
{
if( _.isUndefined(historyBlock) )
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;
return -1;
}
History.prototype._save = function(block) History.prototype._save = function(block)
{ {
this._items.unshift(block); this._items.unshift(block);
@ -158,7 +223,7 @@ History.prototype.clean = function(max)
console.log("History items before:", this._items.length); console.log("History items before:", this._items.length);
this._items = _(this._items).filter(function(item) { this._items = _(this._items).filter(function(item) {
return item.height <= max; return (item.height <= max && item.block.trusted === false);
}).value(); }).value();
console.log("History items after:", this._items.length); console.log("History items after:", this._items.length);
@ -202,6 +267,22 @@ History.prototype.bestBlockNumber = function()
return 0; return 0;
} }
History.prototype.worstBlock = function()
{
return _.min(this._items, 'height');
}
History.prototype.worstBlockNumber = function(trusted)
{
var worst = this.worstBlock();
if( !_.isUndefined(worst.height) )
return worst.height;
return 0;
}
History.prototype.getNodePropagation = function(id) History.prototype.getNodePropagation = function(id)
{ {
var propagation = new Array( MAX_PEER_PROPAGATION ); var propagation = new Array( MAX_PEER_PROPAGATION );
@ -290,6 +371,11 @@ History.prototype.getUncleCount = function()
{ {
var uncles = _( this._items ) var uncles = _( this._items )
.sortByOrder( 'height', false ) .sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_UNCLES)
.map(function (item) .map(function (item)
{ {
return item.block.uncles.length; return item.block.uncles.length;
@ -313,6 +399,10 @@ History.prototype.getBlockTimes = function()
{ {
var blockTimes = _( this._items ) var blockTimes = _( this._items )
.sortByOrder( 'height', false ) .sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_BINS) .slice(0, MAX_BINS)
.reverse() .reverse()
.map(function (item) .map(function (item)
@ -328,6 +418,10 @@ History.prototype.getDifficulty = function()
{ {
var difficultyHistory = _( this._items ) var difficultyHistory = _( this._items )
.sortByOrder( 'height', false ) .sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_BINS) .slice(0, MAX_BINS)
.reverse() .reverse()
.map(function (item) .map(function (item)
@ -343,6 +437,10 @@ History.prototype.getTransactionsCount = function()
{ {
var txCount = _( this._items ) var txCount = _( this._items )
.sortByOrder( 'height', false ) .sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_BINS) .slice(0, MAX_BINS)
.reverse() .reverse()
.map(function (item) .map(function (item)
@ -358,6 +456,10 @@ History.prototype.getGasSpending = function()
{ {
var gasSpending = _( this._items ) var gasSpending = _( this._items )
.sortByOrder( 'height', false ) .sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_BINS) .slice(0, MAX_BINS)
.reverse() .reverse()
.map(function (item) .map(function (item)
@ -376,6 +478,10 @@ History.prototype.getAvgHashrate = function()
var blocktimeHistory = _( this._items ) var blocktimeHistory = _( this._items )
.sortByOrder( 'height', false ) .sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, 64) .slice(0, 64)
.map(function (item) .map(function (item)
{ {
@ -392,6 +498,10 @@ History.prototype.getMinersCount = function()
{ {
var miners = _( this._items ) var miners = _( this._items )
.sortByOrder( 'height', false ) .sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_BINS) .slice(0, MAX_BINS)
.map(function (item) .map(function (item)
{ {
@ -423,6 +533,10 @@ History.prototype.getCharts = function()
{ {
var chartHistory = _( this._items ) var chartHistory = _( this._items )
.sortByOrder( 'height', false ) .sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_BINS) .slice(0, MAX_BINS)
.reverse() .reverse()
.map(function (item) .map(function (item)

View File

@ -1,5 +1,6 @@
var geoip = require('geoip-lite'); var geoip = require('geoip-lite');
var _ = require('lodash'); var _ = require('lodash');
var trusted = require('./utils/config');
var MAX_HISTORY = 40; var MAX_HISTORY = 40;
var MAX_INACTIVE_TIME = 1000*60*60*4; var MAX_INACTIVE_TIME = 1000*60*60*4;
@ -7,6 +8,7 @@ var MAX_INACTIVE_TIME = 1000*60*60*4;
var Node = function(data) var Node = function(data)
{ {
this.id = null; this.id = null;
this.trusted = false;
this.info = {}; this.info = {};
this.geo = {} this.geo = {}
this.stats = { this.stats = {
@ -79,6 +81,11 @@ Node.prototype.setInfo = function(data, callback)
if( !_.isUndefined(data.ip) ) if( !_.isUndefined(data.ip) )
{ {
if( trusted.indexOf(data.ip) >= 0 )
{
this.trusted = true;
}
this.setGeo(data.ip); this.setGeo(data.ip);
} }
@ -360,7 +367,7 @@ Node.prototype.getBlockNumber = function()
Node.prototype.canUpdate = function() Node.prototype.canUpdate = function()
{ {
return this.info.canUpdateHistory || false; return (this.info.canUpdateHistory && this.trusted) || false;
} }
Node.prototype.isInactiveAndOld = function() Node.prototype.isInactiveAndOld = function()

14
lib/utils/config.js Normal file
View File

@ -0,0 +1,14 @@
var trusted = [
'54.94.239.50',
'52.16.188.185',
'52.4.40.229',
'52.4.131.128',
'52.0.243.36',
'52.4.180.23',
'52.5.60.7',
'52.5.26.21',
'52.5.25.137',
'::ffff:127.0.0.1',
];
module.exports = trusted;

View File

@ -459,6 +459,7 @@ netStatsApp.controller('StatsCtrl', function($scope, $filter, $localStorage, soc
$scope.nodesTotal = $scope.nodes.length; $scope.nodesTotal = $scope.nodes.length;
$scope.nodesActive = _.filter($scope.nodes, function (node) { $scope.nodesActive = _.filter($scope.nodes, function (node) {
forkFilter(node);
return node.stats.active == true; return node.stats.active == true;
}).length; }).length;
@ -491,8 +492,49 @@ netStatsApp.controller('StatsCtrl', function($scope, $filter, $localStorage, soc
{ {
if( $scope.nodes.length ) if( $scope.nodes.length )
{ {
var bestBlock = _.max($scope.nodes, function (node) { var chains = {};
return parseInt(node.stats.block.number); 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; }).stats.block.number;
if( bestBlock !== $scope.bestBlock ) if( bestBlock !== $scope.bestBlock )
@ -508,6 +550,36 @@ netStatsApp.controller('StatsCtrl', function($scope, $filter, $localStorage, soc
} }
} }
function forkFilter(node)
{
if( _.isUndefined(node.readable) )
node.readable = {};
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.<br/>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.<br/>Waiting for more confirmations.';
return false;
}
}
function latencyFilter(node) function latencyFilter(node)
{ {
if( _.isUndefined(node.readable) ) if( _.isUndefined(node.readable) )

View File

@ -199,7 +199,10 @@ block content
td(class="{{ node.stats.mining | hashrateClass : node.stats.active }}", ng-bind-html="node.stats.hashrate | hashrateFilter : node.stats.mining") td(class="{{ node.stats.mining | hashrateClass : node.stats.active }}", ng-bind-html="node.stats.hashrate | hashrateFilter : node.stats.mining")
td(class="{{ node.stats.peers | peerClass : node.stats.active }}", style="padding-left: 11px;") {{node.stats.peers}} 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(style="padding-left: 15px;") {{node.stats.pending}}
td(class="{{ node.stats | blockClass : bestBlock }}") {{'#'}}{{ node.stats.block.number | number }} 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 }}") td(class="{{ node.stats | blockClass : bestBlock }}")
span.small {{node.stats.block.hash | hashFilter}} span.small {{node.stats.block.hash | hashFilter}}
td(class="{{ node.stats | blockClass : bestBlock }}") td(class="{{ node.stats | blockClass : bestBlock }}")