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());
var block = this._blockchain.add(stats.block, id);
var block = this._blockchain.add(stats.block, id, node.trusted);
if (!block)
{
@ -61,7 +61,7 @@ Collection.prototype.addBlock = function(id, stats, callback)
{
this._blockchain.clean(this.getBestBlockFromItems());
var block = this._blockchain.add(stats, id);
var block = this._blockchain.add(stats, id, node.trusted);
if (!block)
{
@ -121,7 +121,7 @@ Collection.prototype.addHistory = function(id, blocks, callback)
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();
@ -262,9 +262,9 @@ Collection.prototype.getHistory = function()
Collection.prototype.getBestBlockFromItems = function()
{
return _.result(_.max(this._items, function(item) {
return item.stats.block.number;
}), 'stats.block.number', 0);
return Math.max(this._blockchain.bestBlockNumber(), _.result(_.max(this._items, function(item) {
return ( !item.trusted ? 0 : item.stats.block.number );
}), 'stats.block.number', 0));
}
Collection.prototype.canNodeUpdate = function(id)

View File

@ -1,12 +1,13 @@
var _ = require('lodash');
var d3 = require('d3');
var MAX_HISTORY = 1000;
var MAX_HISTORY = 2000;
var MAX_PEER_PROPAGATION = 40;
var MIN_PROPAGATION_RANGE = 0;
var MAX_PROPAGATION_RANGE = 10000;
var MAX_UNCLES = 1000;
var MAX_UNCLES_PER_BIN = 25;
var MAX_BINS = 40;
@ -14,86 +15,110 @@ var History = function History(data)
{
this._items = [];
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;
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 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 )
{
block.arrived = historyBlock.block.arrived;
block.received = now;
block.propagation = now - historyBlock.block.received;
// 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
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
});
changed = true;
}
else
{
block.arrived = historyBlock.block.arrived;
// 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;
if(historyBlock.hash !== block.hash || historyBlock.totalDifficulty !== block.totalDifficulty || historyBlock.transactions.length !== block.transactions.length)
forkIndex = historyBlock.forks.push(block) - 1;
historyBlock.forks[forkIndex].fork = forkIndex;
}
}
if( trusted && !compareBlocks(historyBlock.block, historyBlock.forks[forkIndex]) )
{
historyBlock.hash = block.hash;
historyBlock.parentHash = block.parentHash;
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;
// 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 )
@ -111,13 +136,16 @@ History.prototype.add = function(block, id)
var item = {
height: block.number,
block: block,
forks: [block],
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({
node: id,
trusted: trusted,
fork: 0,
received: now,
propagation: block.propagation
});
@ -137,6 +165,43 @@ History.prototype.add = function(block, id)
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)
{
this._items.unshift(block);
@ -158,7 +223,7 @@ History.prototype.clean = function(max)
console.log("History items before:", this._items.length);
this._items = _(this._items).filter(function(item) {
return item.height <= max;
return (item.height <= max && item.block.trusted === false);
}).value();
console.log("History items after:", this._items.length);
@ -202,6 +267,22 @@ History.prototype.bestBlockNumber = function()
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)
{
var propagation = new Array( MAX_PEER_PROPAGATION );
@ -290,6 +371,11 @@ History.prototype.getUncleCount = function()
{
var uncles = _( this._items )
.sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_UNCLES)
.map(function (item)
{
return item.block.uncles.length;
@ -313,6 +399,10 @@ History.prototype.getBlockTimes = function()
{
var blockTimes = _( this._items )
.sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_BINS)
.reverse()
.map(function (item)
@ -328,6 +418,10 @@ History.prototype.getDifficulty = function()
{
var difficultyHistory = _( this._items )
.sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_BINS)
.reverse()
.map(function (item)
@ -343,6 +437,10 @@ History.prototype.getTransactionsCount = function()
{
var txCount = _( this._items )
.sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_BINS)
.reverse()
.map(function (item)
@ -358,6 +456,10 @@ History.prototype.getGasSpending = function()
{
var gasSpending = _( this._items )
.sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_BINS)
.reverse()
.map(function (item)
@ -376,6 +478,10 @@ History.prototype.getAvgHashrate = function()
var blocktimeHistory = _( this._items )
.sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, 64)
.map(function (item)
{
@ -392,6 +498,10 @@ History.prototype.getMinersCount = function()
{
var miners = _( this._items )
.sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_BINS)
.map(function (item)
{
@ -423,6 +533,10 @@ History.prototype.getCharts = function()
{
var chartHistory = _( this._items )
.sortByOrder( 'height', false )
.filter(function (item)
{
return item.block.trusted;
})
.slice(0, MAX_BINS)
.reverse()
.map(function (item)

View File

@ -1,5 +1,6 @@
var geoip = require('geoip-lite');
var _ = require('lodash');
var trusted = require('./utils/config');
var MAX_HISTORY = 40;
var MAX_INACTIVE_TIME = 1000*60*60*4;
@ -7,6 +8,7 @@ var MAX_INACTIVE_TIME = 1000*60*60*4;
var Node = function(data)
{
this.id = null;
this.trusted = false;
this.info = {};
this.geo = {}
this.stats = {
@ -79,6 +81,11 @@ Node.prototype.setInfo = function(data, callback)
if( !_.isUndefined(data.ip) )
{
if( trusted.indexOf(data.ip) >= 0 )
{
this.trusted = true;
}
this.setGeo(data.ip);
}
@ -360,7 +367,7 @@ Node.prototype.getBlockNumber = function()
Node.prototype.canUpdate = function()
{
return this.info.canUpdateHistory || false;
return (this.info.canUpdateHistory && this.trusted) || false;
}
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.nodesActive = _.filter($scope.nodes, function (node) {
forkFilter(node);
return node.stats.active == true;
}).length;
@ -491,8 +492,49 @@ netStatsApp.controller('StatsCtrl', function($scope, $filter, $localStorage, soc
{
if( $scope.nodes.length )
{
var bestBlock = _.max($scope.nodes, function (node) {
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 )
@ -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)
{
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.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 }}") {{'#'}}{{ 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 }}")
span.small {{node.stats.block.hash | hashFilter}}
td(class="{{ node.stats | blockClass : bestBlock }}")