[REDIS DATA BREAKING CHANGES] More efficient block storage format for redis. Limited number of blocks sent to front-end. Expanded admin page. More realistic 'luck' value. Run redisBlocksUpgrade.js script to convert block data to new format.
parent
b3f0af041b
commit
b4f49e953a
|
@ -256,7 +256,9 @@ Explanation for each field:
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"hashrateWindow": 600, //how many second worth of shares used to estimate hash rate
|
"hashrateWindow": 600, //how many second worth of shares used to estimate hash rate
|
||||||
"updateInterval": 3, //gather stats and broadcast every this many seconds
|
"updateInterval": 3, //gather stats and broadcast every this many seconds
|
||||||
"port": 8117
|
"port": 8117,
|
||||||
|
"blocks": 30, //amount of blocks to send at a time
|
||||||
|
"password": "test" //password required for admin stats
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Coin daemon connection details. */
|
/* Coin daemon connection details. */
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
"hashrateWindow": 600,
|
"hashrateWindow": 600,
|
||||||
"updateInterval": 5,
|
"updateInterval": 5,
|
||||||
"port": 8117,
|
"port": 8117,
|
||||||
|
"blocks": 30,
|
||||||
"password": "your_password"
|
"password": "your_password"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
19
init.js
19
init.js
|
@ -5,24 +5,7 @@ var os = require('os');
|
||||||
var redis = require('redis');
|
var redis = require('redis');
|
||||||
|
|
||||||
|
|
||||||
var configFile = (function(){
|
require('./lib/configReader.js');
|
||||||
for (var i = 0; i < process.argv.length; i++){
|
|
||||||
if (process.argv[i].indexOf('-config=') === 0)
|
|
||||||
return process.argv[i].split('=')[1];
|
|
||||||
}
|
|
||||||
return 'config.json';
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
global.config = JSON.parse(fs.readFileSync(configFile));
|
|
||||||
}
|
|
||||||
catch(e){
|
|
||||||
console.error('Failed to read config file ' + configFile + '\n\n' + e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.version = "v0.99.0.6";
|
|
||||||
|
|
||||||
require('./lib/logger.js');
|
require('./lib/logger.js');
|
||||||
|
|
||||||
|
|
116
lib/api.js
116
lib/api.js
|
@ -13,13 +13,13 @@ require('./exceptionWriter.js')(logSystem);
|
||||||
|
|
||||||
var redisCommands = [
|
var redisCommands = [
|
||||||
['zremrangebyscore', config.coin + ':hashrate', '-inf', ''],
|
['zremrangebyscore', config.coin + ':hashrate', '-inf', ''],
|
||||||
['zrangebyscore', config.coin + ':hashrate', '', '+inf'],
|
['zrange', config.coin + ':hashrate', 0, -1],
|
||||||
['hgetall', config.coin + ':stats'],
|
['hgetall', config.coin + ':stats'],
|
||||||
['smembers', config.coin + ':blocksPending'],
|
['zrange', config.coin + ':blocks:candidates', 0, -1, 'WITHSCORES'],
|
||||||
['smembers', config.coin + ':blocksUnlocked'],
|
['zrevrange', config.coin + ':blocks:matured', 0, config.api.blocks, 'WITHSCORES'],
|
||||||
['smembers', config.coin + ':blocksOrphaned'],
|
|
||||||
['hgetall', config.coin + ':shares:roundCurrent'],
|
['hgetall', config.coin + ':shares:roundCurrent'],
|
||||||
['hgetall', config.coin + ':stats']
|
['hgetall', config.coin + ':stats'],
|
||||||
|
['zcard', config.coin + ':blocks:matured']
|
||||||
];
|
];
|
||||||
|
|
||||||
var currentStats = "";
|
var currentStats = "";
|
||||||
|
@ -56,11 +56,8 @@ function collectStats(){
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
stats: replies[2],
|
stats: replies[2],
|
||||||
blocks: {
|
blocks: replies[3].concat(replies[4]),
|
||||||
pending: replies[3],
|
totalBlocks: parseInt(replies[7]) + replies[3].length
|
||||||
unlocked: replies[4],
|
|
||||||
orphaned: replies[5]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var hashrates = replies[1];
|
var hashrates = replies[1];
|
||||||
|
@ -86,14 +83,14 @@ function collectStats(){
|
||||||
|
|
||||||
data.roundHashes = 0;
|
data.roundHashes = 0;
|
||||||
|
|
||||||
if (replies[6]){
|
if (replies[5]){
|
||||||
for (var miner in replies[6]){
|
for (var miner in replies[5]){
|
||||||
data.roundHashes += parseInt(replies[6][miner]);
|
data.roundHashes += parseInt(replies[5][miner]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replies[7]) {
|
if (replies[6]) {
|
||||||
data.lastBlockFound = replies[7].lastBlockFound;
|
data.lastBlockFound = replies[6].lastBlockFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(null, data);
|
callback(null, data);
|
||||||
|
@ -227,6 +224,28 @@ function formatMinerStats(redisData, address){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handleGetBlocks(urlParts, response){
|
||||||
|
redisClient.zrevrangebyscore(config.coin + ':blocks:matured', '(' + urlParts.query.height, '-inf', 'WITHSCORES', 'LIMIT', 0, config.api.blocks, function(err, result){
|
||||||
|
|
||||||
|
var reply;
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
reply = JSON.stringify({error: 'query failed'});
|
||||||
|
else
|
||||||
|
reply = JSON.stringify(result);
|
||||||
|
|
||||||
|
response.writeHead("200", {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': reply.length
|
||||||
|
});
|
||||||
|
response.end(reply);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
collectStats();
|
collectStats();
|
||||||
|
|
||||||
function authorize(request, response){
|
function authorize(request, response){
|
||||||
|
@ -252,21 +271,24 @@ function handleAdminStats(response){
|
||||||
|
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
|
|
||||||
//Get worker keys
|
//Get worker keys & unlocked blocks
|
||||||
function(callback){
|
function(callback){
|
||||||
redisClient.keys(config.coin + ':workers:*', function(error, result) {
|
redisClient.multi([
|
||||||
|
['keys', config.coin + ':workers:*'],
|
||||||
|
['zrange', config.coin + ':blocks:matured', 0, -1]
|
||||||
|
]).exec(function(error, replies) {
|
||||||
if (error) {
|
if (error) {
|
||||||
log('error', logSystem, 'Error trying to get worker balances from redis %j', [error]);
|
log('error', logSystem, 'Error trying to get admin data from redis %j', [error]);
|
||||||
callback(true);
|
callback(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
callback(null, result);
|
callback(null, replies[0], replies[1]);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
//Get worker balances
|
//Get worker balances
|
||||||
function(keys, callback){
|
function(workerKeys, blocks, callback){
|
||||||
var redisCommands = keys.map(function(k){
|
var redisCommands = workerKeys.map(function(k){
|
||||||
return ['hmget', k, 'balance', 'paid'];
|
return ['hmget', k, 'balance', 'paid'];
|
||||||
});
|
});
|
||||||
redisClient.multi(redisCommands).exec(function(error, replies){
|
redisClient.multi(redisCommands).exec(function(error, replies){
|
||||||
|
@ -276,19 +298,42 @@ function handleAdminStats(response){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var stats = {
|
callback(null, replies, blocks);
|
||||||
totalOwed: 0,
|
|
||||||
totalPaid: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
for (var i = 0; i < replies.length; i++){
|
|
||||||
stats.totalOwed += parseInt(replies[i][0]) || 0;
|
|
||||||
stats.totalPaid += parseInt(replies[i][1]) || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, stats);
|
|
||||||
});
|
});
|
||||||
}], function(error, stats){
|
},
|
||||||
|
function(workerData, blocks, callback){
|
||||||
|
var stats = {
|
||||||
|
totalOwed: 0,
|
||||||
|
totalPaid: 0,
|
||||||
|
totalRevenue: 0,
|
||||||
|
totalDiff: 0,
|
||||||
|
totalShares: 0,
|
||||||
|
blocksOrphaned: 0,
|
||||||
|
blocksUnlocked: 0,
|
||||||
|
totalWorkers: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < workerData.length; i++){
|
||||||
|
stats.totalOwed += parseInt(workerData[i][0]) || 0;
|
||||||
|
stats.totalPaid += parseInt(workerData[i][1]) || 0;
|
||||||
|
stats.totalWorkers++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < blocks.length; i++){
|
||||||
|
var block = blocks[i].split(':');
|
||||||
|
if (block[5]) {
|
||||||
|
stats.blocksUnlocked++;
|
||||||
|
stats.totalDiff += parseInt(block[2]);
|
||||||
|
stats.totalShares += parseInt(block[3]);
|
||||||
|
stats.totalRevenue += parseInt(block[5]);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
stats.blocksOrphaned++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback(null, stats);
|
||||||
|
}
|
||||||
|
], function(error, stats){
|
||||||
if (error){
|
if (error){
|
||||||
response.end(JSON.stringify({error: 'error collecting stats'}));
|
response.end(JSON.stringify({error: 'error collecting stats'}));
|
||||||
return;
|
return;
|
||||||
|
@ -346,6 +391,9 @@ var server = http.createServer(function(request, response){
|
||||||
case '/stats_address':
|
case '/stats_address':
|
||||||
handleMinerStats(urlParts, response);
|
handleMinerStats(urlParts, response);
|
||||||
break;
|
break;
|
||||||
|
case '/get_blocks':
|
||||||
|
handleGetBlocks(urlParts, response);
|
||||||
|
break;
|
||||||
case '/admin_stats':
|
case '/admin_stats':
|
||||||
if (!authorize(request, response))
|
if (!authorize(request, response))
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -32,28 +32,34 @@ var doDonations = config.blockUnlocker.devDonation > 0 && devDonationAddress[0]
|
||||||
function runInterval(){
|
function runInterval(){
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
|
|
||||||
//Get all pending blocks in redis
|
//Get all block candidates in redis
|
||||||
function(callback){
|
function(callback){
|
||||||
redisClient.smembers(config.coin + ':blocksPending', function(error, result){
|
redisClient.zrange(config.coin + ':blocks:candidates', 0, -1, 'WITHSCORES', function(error, results){
|
||||||
if (error){
|
if (error){
|
||||||
log('error', logSystem, 'Error trying to get pending blocks from redis %j', [error]);
|
log('error', logSystem, 'Error trying to get pending blocks from redis %j', [error]);
|
||||||
callback(true);
|
callback(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (result.length === 0){
|
if (results.length === 0){
|
||||||
log('info', logSystem, 'No pending blocks in redis');
|
log('info', logSystem, 'No blocks candidates in redis');
|
||||||
callback(true);
|
callback(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var blocks = result.map(function(item){
|
|
||||||
var parts = item.split(':');
|
var blocks = [];
|
||||||
return {
|
|
||||||
height: parseInt(parts[0]),
|
for (var i = 0; i < results.length; i += 2){
|
||||||
difficulty: parseInt(parts[1]),
|
var parts = results[i].split(':');
|
||||||
hash: parts[2],
|
blocks.push({
|
||||||
serialized: item
|
serialized: results[i],
|
||||||
};
|
height: parseInt(results[i + 1]),
|
||||||
});
|
hash: parts[0],
|
||||||
|
time: parts[1],
|
||||||
|
difficulty: parts[2],
|
||||||
|
shares: parts[3]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
callback(null, blocks);
|
callback(null, blocks);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -75,7 +81,7 @@ function runInterval(){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var blockHeader = result.block_header;
|
var blockHeader = result.block_header;
|
||||||
block.orphan = (blockHeader.hash !== block.hash);
|
block.orphaned = blockHeader.hash === block.hash ? 0 : 1;
|
||||||
block.unlocked = blockHeader.depth >= config.blockUnlocker.depth;
|
block.unlocked = blockHeader.depth >= config.blockUnlocker.depth;
|
||||||
block.reward = blockHeader.reward;
|
block.reward = blockHeader.reward;
|
||||||
mapCback(block.unlocked);
|
mapCback(block.unlocked);
|
||||||
|
@ -83,7 +89,7 @@ function runInterval(){
|
||||||
}, function(unlockedBlocks){
|
}, function(unlockedBlocks){
|
||||||
|
|
||||||
if (unlockedBlocks.length === 0){
|
if (unlockedBlocks.length === 0){
|
||||||
log('info', logSystem, 'No pending blocks are unlocked or orphaned yet (%d pending)', [blocks.length]);
|
log('info', logSystem, 'No pending blocks are unlocked yet (%d pending)', [blocks.length]);
|
||||||
callback(true);
|
callback(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -110,9 +116,6 @@ function runInterval(){
|
||||||
for (var i = 0; i < replies.length; i++){
|
for (var i = 0; i < replies.length; i++){
|
||||||
var workerShares = replies[i];
|
var workerShares = replies[i];
|
||||||
blocks[i].workerShares = workerShares;
|
blocks[i].workerShares = workerShares;
|
||||||
blocks[i].totalShares = Object.keys(workerShares).reduce(function(p, c){
|
|
||||||
return p + parseInt(workerShares[c])
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
callback(null, blocks);
|
callback(null, blocks);
|
||||||
});
|
});
|
||||||
|
@ -121,19 +124,29 @@ function runInterval(){
|
||||||
//Handle orphaned blocks
|
//Handle orphaned blocks
|
||||||
function(blocks, callback){
|
function(blocks, callback){
|
||||||
var orphanCommands = [];
|
var orphanCommands = [];
|
||||||
|
|
||||||
blocks.forEach(function(block){
|
blocks.forEach(function(block){
|
||||||
if (!block.orphan) return;
|
if (!block.orphaned) return;
|
||||||
var workerShares = block.workerShares;
|
|
||||||
orphanCommands.push(['del', config.coin + ':shares:round' + block.height]);
|
orphanCommands.push(['del', config.coin + ':shares:round' + block.height]);
|
||||||
|
|
||||||
orphanCommands.push(['srem', config.coin + ':blocksPending', block.serialized]);
|
orphanCommands.push(['zrem', config.coin + ':blocks:candidates', block.serialized]);
|
||||||
orphanCommands.push(['sadd', config.coin + ':blocksOrphaned', block.serialized + ':' + block.totalShares]);
|
orphanCommands.push(['zadd', config.coin + ':blocks:matured', block.height, [
|
||||||
|
block.hash,
|
||||||
|
block.time,
|
||||||
|
block.difficulty,
|
||||||
|
block.shares,
|
||||||
|
block.orphaned
|
||||||
|
].join(':')]);
|
||||||
|
|
||||||
Object.keys(workerShares).forEach(function(worker){
|
if (block.workerShares) {
|
||||||
orphanCommands.push(['hincrby', config.coin + ':shares:roundCurrent',
|
var workerShares = block.workerShares;
|
||||||
worker, workerShares[worker]]);
|
Object.keys(workerShares).forEach(function (worker) {
|
||||||
});
|
orphanCommands.push(['hincrby', config.coin + ':shares:roundCurrent', worker, workerShares[worker]]);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (orphanCommands.length > 0){
|
if (orphanCommands.length > 0){
|
||||||
redisClient.multi(orphanCommands).exec(function(error, replies){
|
redisClient.multi(orphanCommands).exec(function(error, replies){
|
||||||
if (error){
|
if (error){
|
||||||
|
@ -155,13 +168,19 @@ function runInterval(){
|
||||||
var payments = {};
|
var payments = {};
|
||||||
var totalBlocksUnlocked = 0;
|
var totalBlocksUnlocked = 0;
|
||||||
blocks.forEach(function(block){
|
blocks.forEach(function(block){
|
||||||
if (block.orphan) return;
|
if (block.orphaned) return;
|
||||||
totalBlocksUnlocked++;
|
totalBlocksUnlocked++;
|
||||||
|
|
||||||
unlockedBlocksCommands.push(['del', config.coin + ':shares:round' + block.height]);
|
unlockedBlocksCommands.push(['del', config.coin + ':shares:round' + block.height]);
|
||||||
|
unlockedBlocksCommands.push(['zrem', config.coin + ':blocks:candidates', block.serialized]);
|
||||||
unlockedBlocksCommands.push(['srem', config.coin + ':blocksPending', block.serialized]);
|
unlockedBlocksCommands.push(['zadd', config.coin + ':blocks:matured', block.height, [
|
||||||
unlockedBlocksCommands.push(['sadd', config.coin + ':blocksUnlocked', block.serialized + ':' + block.totalShares]);
|
block.hash,
|
||||||
|
block.time,
|
||||||
|
block.difficulty,
|
||||||
|
block.shares,
|
||||||
|
block.orphaned,
|
||||||
|
block.reward
|
||||||
|
].join(':')]);
|
||||||
|
|
||||||
var feePercent = config.blockUnlocker.poolFee / 100;
|
var feePercent = config.blockUnlocker.poolFee / 100;
|
||||||
|
|
||||||
|
@ -173,12 +192,14 @@ function runInterval(){
|
||||||
|
|
||||||
var reward = block.reward - (block.reward * feePercent);
|
var reward = block.reward - (block.reward * feePercent);
|
||||||
|
|
||||||
var totalShares = block.totalShares;
|
if (block.workerShares) {
|
||||||
Object.keys(block.workerShares).forEach(function(worker){
|
var totalShares = parseInt(block.shares);
|
||||||
var percent = block.workerShares[worker] / totalShares;
|
Object.keys(block.workerShares).forEach(function (worker) {
|
||||||
var workerReward = reward * percent;
|
var percent = block.workerShares[worker] / totalShares;
|
||||||
payments[worker] = (payments[worker] || 0) + workerReward;
|
var workerReward = reward * percent;
|
||||||
});
|
payments[worker] = (payments[worker] || 0) + workerReward;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (var worker in payments) {
|
for (var worker in payments) {
|
||||||
|
@ -205,9 +226,7 @@ function runInterval(){
|
||||||
log('info', logSystem, 'Unlocked %d blocks and update balances for %d workers', [totalBlocksUnlocked, Object.keys(payments).length]);
|
log('info', logSystem, 'Unlocked %d blocks and update balances for %d workers', [totalBlocksUnlocked, Object.keys(payments).length]);
|
||||||
callback(null);
|
callback(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
], function(error, result){
|
], function(error, result){
|
||||||
setTimeout(runInterval, config.blockUnlocker.interval * 1000);
|
setTimeout(runInterval, config.blockUnlocker.interval * 1000);
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var configFile = (function(){
|
||||||
|
for (var i = 0; i < process.argv.length; i++){
|
||||||
|
if (process.argv[i].indexOf('-config=') === 0)
|
||||||
|
return process.argv[i].split('=')[1];
|
||||||
|
}
|
||||||
|
return 'config.json';
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
global.config = JSON.parse(fs.readFileSync(configFile));
|
||||||
|
}
|
||||||
|
catch(e){
|
||||||
|
console.error('Failed to read config file ' + configFile + '\n\n' + e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.version = "v0.99.1";
|
29
lib/pool.js
29
lib/pool.js
|
@ -350,15 +350,34 @@ function recordShareData(miner, job, shareDiff, blockCandidate, hashHex, shareTy
|
||||||
];
|
];
|
||||||
|
|
||||||
if (blockCandidate){
|
if (blockCandidate){
|
||||||
redisCommands.push(['sadd', config.coin + ':blocksPending', [job.height, currentBlockTemplate.difficulty, hashHex, Date.now() / 1000 | 0].join(':')]);
|
//redisCommands.push(['sadd', config.coin + ':blocksPending', [job.height, currentBlockTemplate.difficulty, hashHex, Date.now() / 1000 | 0].join(':')]);
|
||||||
redisCommands.push(['rename', config.coin + ':shares:roundCurrent', config.coin + ':shares:round' + job.height]);
|
|
||||||
redisCommands.push(['hset', config.coin + ':stats', 'lastBlockFound', Date.now()]);
|
redisCommands.push(['hset', config.coin + ':stats', 'lastBlockFound', Date.now()]);
|
||||||
|
redisCommands.push(['rename', config.coin + ':shares:roundCurrent', config.coin + ':shares:round' + job.height]);
|
||||||
|
redisCommands.push(['hgetall', config.coin + ':shares:round' + job.height]);
|
||||||
}
|
}
|
||||||
|
|
||||||
redisClient.multi(redisCommands).exec(function(err, replies){
|
redisClient.multi(redisCommands).exec(function(err, replies){
|
||||||
if (err){
|
if (err){
|
||||||
log('error', logSystem, 'Failed to insert share data into redis %j', [err]);
|
log('error', logSystem, 'Failed to insert share data into redis %j \n %j', [err, redisCommands]);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (blockCandidate){
|
||||||
|
var workerShares = replies[replies.length - 1];
|
||||||
|
var totalShares = Object.keys(workerShares).reduce(function(p, c){
|
||||||
|
return p + parseInt(workerShares[c])
|
||||||
|
}, 0);
|
||||||
|
redisClient.zadd(config.coin + ':blocks:candidates', job.height, [
|
||||||
|
hashHex,
|
||||||
|
Date.now() / 1000 | 0,
|
||||||
|
currentBlockTemplate.difficulty,
|
||||||
|
totalShares
|
||||||
|
].join(':'), function(err, result){
|
||||||
|
if (err){
|
||||||
|
log('error', logSystem, 'Failed inserting block candidate %s \n %j', [hashHex, err]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
log('info', logSystem, 'Accepted %s share at difficulty %d/%d from %s@%s', [shareType, job.difficulty, shareDiff, miner.login, miner.ip]);
|
log('info', logSystem, 'Accepted %s share at difficulty %d/%d from %s@%s', [shareType, job.difficulty, shareDiff, miner.login, miner.ip]);
|
||||||
|
@ -396,10 +415,9 @@ function processShare(miner, job, blockTemplate, nonce, resultHash){
|
||||||
var hashNum = bignum.fromBuffer(new Buffer(hashArray));
|
var hashNum = bignum.fromBuffer(new Buffer(hashArray));
|
||||||
var hashDiff = diff1.div(hashNum);
|
var hashDiff = diff1.div(hashNum);
|
||||||
|
|
||||||
var blockFastHash;
|
|
||||||
|
|
||||||
if (hashDiff.ge(blockTemplate.difficulty)){
|
if (hashDiff.ge(blockTemplate.difficulty)){
|
||||||
blockFastHash = cryptoNightFast(convertedBlob || cnUtil.convert_blob(shareBuffer)).toString('hex');
|
|
||||||
|
|
||||||
apiInterfaces.rpcDaemon('submitblock', [shareBuffer.toString('hex')], function(error, result){
|
apiInterfaces.rpcDaemon('submitblock', [shareBuffer.toString('hex')], function(error, result){
|
||||||
if (error){
|
if (error){
|
||||||
|
@ -407,6 +425,7 @@ function processShare(miner, job, blockTemplate, nonce, resultHash){
|
||||||
recordShareData(miner, job, hashDiff.toString(), false, null, shareType);
|
recordShareData(miner, job, hashDiff.toString(), false, null, shareType);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
var blockFastHash = cryptoNightFast(convertedBlob || cnUtil.convert_blob(shareBuffer)).toString('hex');
|
||||||
log('info', logSystem,
|
log('info', logSystem,
|
||||||
'Block %s found at height %d by miner %s@%s - submit result: %j',
|
'Block %s found at height %d by miner %s@%s - submit result: %j',
|
||||||
[blockFastHash.substr(0, 6), job.height, miner.login, miner.ip, result]
|
[blockFastHash.substr(0, 6), job.height, miner.login, miner.ip, result]
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
This script converts the block data in redis from the old format (v0.99.0.6 and earlier) to the new format
|
||||||
|
used in v0.99.1+
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
var async = require('async');
|
||||||
|
|
||||||
|
var redis = require('redis');
|
||||||
|
|
||||||
|
require('./lib/configReader.js');
|
||||||
|
|
||||||
|
var apiInterfaces = require('./lib/apiInterfaces.js')(config.daemon, config.wallet);
|
||||||
|
|
||||||
|
|
||||||
|
function log(severity, system, text, data){
|
||||||
|
|
||||||
|
var formattedMessage = text;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
data.unshift(text);
|
||||||
|
formattedMessage = util.format.apply(null, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(severity + ': ' + formattedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var logSystem = 'reward script';
|
||||||
|
|
||||||
|
var redisClient = redis.createClient(config.redis.port, config.redis.host);
|
||||||
|
|
||||||
|
function getTotalShares(height, callback){
|
||||||
|
|
||||||
|
redisClient.hgetall(config.coin + ':shares:round' + height, function(err, workerShares){
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalShares = Object.keys(workerShares).reduce(function(p, c){
|
||||||
|
return p + parseInt(workerShares[c])
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
callback(null, totalShares);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
function(callback){
|
||||||
|
redisClient.smembers(config.coin + ':blocksUnlocked', function(error, result){
|
||||||
|
if (error){
|
||||||
|
log('error', logSystem, 'Error trying to get unlocke blocks from redis %j', [error]);
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.length === 0){
|
||||||
|
log('info', logSystem, 'No unlocked blocks in redis');
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var blocks = result.map(function(item){
|
||||||
|
var parts = item.split(':');
|
||||||
|
return {
|
||||||
|
height: parseInt(parts[0]),
|
||||||
|
difficulty: parts[1],
|
||||||
|
hash: parts[2],
|
||||||
|
time: parts[3],
|
||||||
|
shares: parts[4],
|
||||||
|
orphaned: 0
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
async.map(blocks, function(block, mapCback){
|
||||||
|
apiInterfaces.rpcDaemon('getblockheaderbyheight', {height: block.height}, function(error, result){
|
||||||
|
if (error){
|
||||||
|
log('error', logSystem, 'Error with getblockheaderbyheight RPC request for block %s - %j', [block.serialized, error]);
|
||||||
|
mapCback(null, block);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!result.block_header){
|
||||||
|
log('error', logSystem, 'Error with getblockheaderbyheight, no details returned for %s - %j', [block.serialized, result]);
|
||||||
|
mapCback(null, block);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var blockHeader = result.block_header;
|
||||||
|
block.reward = blockHeader.reward;
|
||||||
|
mapCback(null, block);
|
||||||
|
});
|
||||||
|
}, function(err, blocks){
|
||||||
|
|
||||||
|
if (blocks.length === 0){
|
||||||
|
log('info', logSystem, 'No unlocked blocks');
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var zaddCommands = [config.coin + ':blocks:matured'];
|
||||||
|
|
||||||
|
for (var i = 0; i < blocks.length; i++){
|
||||||
|
var block = blocks[i];
|
||||||
|
zaddCommands.push(block.height);
|
||||||
|
zaddCommands.push([
|
||||||
|
block.hash,
|
||||||
|
block.time,
|
||||||
|
block.difficulty,
|
||||||
|
block.shares,
|
||||||
|
block.orphaned,
|
||||||
|
block.reward
|
||||||
|
].join(':'));
|
||||||
|
}
|
||||||
|
|
||||||
|
redisClient.zadd(zaddCommands, function(err, result){
|
||||||
|
if (err){
|
||||||
|
console.log('failed zadd ' + JSON.stringify(err));
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('successfully converted unlocked blocks to matured blocks');
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(callback){
|
||||||
|
redisClient.smembers(config.coin + ':blocksPending', function(error, result) {
|
||||||
|
if (error) {
|
||||||
|
log('error', logSystem, 'Error trying to get pending blocks from redis %j', [error]);
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.length === 0) {
|
||||||
|
log('info', logSystem, 'No pending blocks in redis');
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async.map(result, function(item, mapCback){
|
||||||
|
var parts = item.split(':');
|
||||||
|
var block = {
|
||||||
|
height: parseInt(parts[0]),
|
||||||
|
difficulty: parts[1],
|
||||||
|
hash: parts[2],
|
||||||
|
time: parts[3],
|
||||||
|
serialized: item
|
||||||
|
};
|
||||||
|
getTotalShares(block.height, function(err, shares){
|
||||||
|
block.shares = shares;
|
||||||
|
mapCback(null, block);
|
||||||
|
});
|
||||||
|
}, function(err, blocks){
|
||||||
|
|
||||||
|
var zaddCommands = [config.coin + ':blocks:candidates'];
|
||||||
|
|
||||||
|
for (var i = 0; i < blocks.length; i++){
|
||||||
|
var block = blocks[i];
|
||||||
|
zaddCommands.push(block.height);
|
||||||
|
zaddCommands.push([
|
||||||
|
block.hash,
|
||||||
|
block.time,
|
||||||
|
block.difficulty,
|
||||||
|
block.shares
|
||||||
|
].join(':'));
|
||||||
|
}
|
||||||
|
|
||||||
|
redisClient.zadd(zaddCommands, function(err, result){
|
||||||
|
if (err){
|
||||||
|
console.log('failed zadd ' + JSON.stringify(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('successfully converted pending blocks to block candidates');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
], function(){
|
||||||
|
process.exit();
|
||||||
|
});
|
|
@ -13,29 +13,112 @@
|
||||||
|
|
||||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
<link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#statsHolder{
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.luckGood{
|
||||||
|
color: darkgreen;
|
||||||
|
}
|
||||||
|
.luckBad{
|
||||||
|
color: darkred;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script src="config.js"></script>
|
<script src="config.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var coinDecimals = coinUnits.toString().length - 1;
|
|
||||||
|
|
||||||
function getReadableCoins(coins){
|
|
||||||
return (parseInt(coins || 0) / coinUnits).toFixed(coinDecimals);
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function(){
|
$(function(){
|
||||||
|
getStats();
|
||||||
var password = prompt('Admin password:');
|
|
||||||
|
|
||||||
$.get(api + '/admin_stats', {password: password}, function(data){
|
|
||||||
renderData(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var docCookies = {
|
||||||
|
getItem: function (sKey) {
|
||||||
|
return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
|
||||||
|
},
|
||||||
|
setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
|
||||||
|
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
|
||||||
|
var sExpires = "";
|
||||||
|
if (vEnd) {
|
||||||
|
switch (vEnd.constructor) {
|
||||||
|
case Number:
|
||||||
|
sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
|
||||||
|
break;
|
||||||
|
case String:
|
||||||
|
sExpires = "; expires=" + vEnd;
|
||||||
|
break;
|
||||||
|
case Date:
|
||||||
|
sExpires = "; expires=" + vEnd.toUTCString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
removeItem: function (sKey, sPath, sDomain) {
|
||||||
|
if (!sKey || !this.hasItem(sKey)) { return false; }
|
||||||
|
document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : "");
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
hasItem: function (sKey) {
|
||||||
|
return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function getReadableCoins(coins){
|
||||||
|
return (parseInt(coins || 0) / coinUnits).toFixed(coinUnits.toString().length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStats(promptPassword){
|
||||||
|
|
||||||
|
var password = docCookies.getItem('password');
|
||||||
|
|
||||||
|
|
||||||
|
if (!password || promptPassword)
|
||||||
|
password = prompt('Enter admin password');
|
||||||
|
|
||||||
|
|
||||||
|
$('#loading').show();
|
||||||
|
$.ajax({
|
||||||
|
url: api + '/admin_stats',
|
||||||
|
data: {password: password},
|
||||||
|
success: function(data){
|
||||||
|
docCookies.setItem('password', password, Infinity);
|
||||||
|
$('#loading').hide();
|
||||||
|
renderData(data);
|
||||||
|
},
|
||||||
|
error: function(e){
|
||||||
|
docCookies.removeItem('password');
|
||||||
|
getStats(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatLuck = function(difficulty, shares){
|
||||||
|
|
||||||
|
if (difficulty > shares){
|
||||||
|
var percent = 100 - Math.round(shares / difficulty * 100);
|
||||||
|
return '<span class="luckGood">' + percent + '%</span>';
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
var percent = (100 - Math.round(difficulty / shares * 100)) * -1;
|
||||||
|
return '<span class="luckBad">' + percent + '%</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
function renderData(data){
|
function renderData(data){
|
||||||
$('#totalOwed').text(getReadableCoins(data.totalOwed));
|
$('#totalOwed').text(getReadableCoins(data.totalOwed));
|
||||||
$('#totalPaid').text(getReadableCoins(data.totalPaid));
|
$('#totalPaid').text(getReadableCoins(data.totalPaid));
|
||||||
|
$('#totalMined').text(getReadableCoins(data.totalRevenue));
|
||||||
|
$('#profit').text(getReadableCoins(data.totalRevenue - data.totalOwed - data.totalPaid));
|
||||||
|
$('#averageLuck').html(formatLuck(data.totalDiff, data.totalShares));
|
||||||
|
$('#orphanPercent').text((data.blocksOrphaned / data.blocksUnlocked * 100).toFixed(2));
|
||||||
|
$('#registeredAddresses').text(data.totalWorkers);
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -43,11 +126,29 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div>
|
<div class="container">
|
||||||
<span>Total Owed: </span><span id="totalOwed"></span>
|
|
||||||
</div>
|
<h3>Admin Center <i id="loading" class="fa fa-circle-o-notch fa-spin"></i></h3>
|
||||||
<div>
|
|
||||||
<span>Total Paid: </span><span id="totalPaid"></span>
|
<hr>
|
||||||
|
|
||||||
|
<h4>Stats</h4>
|
||||||
|
<dl class="dl-horizontal" id="statsHolder">
|
||||||
|
<dt>Total Owed</dt><dd id="totalOwed">...</dd>
|
||||||
|
<dt>Total Paid</dt><dd id="totalPaid">...</dd>
|
||||||
|
<dt>Total Mined</dt><dd id="totalMined">...</dd>
|
||||||
|
<dt>Profit (before tx fees)</dt><dd id="profit">...</dd>
|
||||||
|
<dt>Average Luck</dt><dd id="averageLuck">...</dd>
|
||||||
|
<dt>Orphan Percent</dt><dd id="orphanPercent">...</dd>
|
||||||
|
<dt>Registered Addresses</dt><dd id="registeredAddresses">...</dd>
|
||||||
|
</dl>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h4>Miner Lookup</h4>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -68,18 +68,15 @@
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
#lastHash{
|
#lastHash{
|
||||||
font-family: monospace;
|
font-family: 'Inconsolata', monospace;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
#yourStatsInput{
|
#yourStatsInput{
|
||||||
width: 820px;
|
z-index: inherit;
|
||||||
max-width: 100%;
|
font-family: 'Inconsolata', monospace;
|
||||||
display: inline-block;
|
|
||||||
vertical-align: bottom;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
}
|
||||||
#yourAddressDisplay > span {
|
#yourAddressDisplay > span {
|
||||||
font-family: monospace;
|
font-family: 'Inconsolata', monospace;
|
||||||
}
|
}
|
||||||
.yourStats{
|
.yourStats{
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -90,8 +87,8 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-family: monospace;
|
font-family: 'Inconsolata', monospace;
|
||||||
font-size: 0.8em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
#addressError{
|
#addressError{
|
||||||
color: red;
|
color: red;
|
||||||
|
@ -121,7 +118,7 @@
|
||||||
#blocks_rows > tr > td{
|
#blocks_rows > tr > td{
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-family: 'Inconsolata', monospace;
|
font-family: 'Inconsolata', monospace;
|
||||||
font-size: 0.9em;
|
font-size: 0.95em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.luckGood{
|
.luckGood{
|
||||||
|
@ -183,18 +180,12 @@
|
||||||
$(function(){
|
$(function(){
|
||||||
|
|
||||||
$('#siteInfo').load('info.html');
|
$('#siteInfo').load('info.html');
|
||||||
/*$.get('info.html', function(html){
|
|
||||||
$('#siteInfo').html(html);
|
|
||||||
}, 'html');*/
|
|
||||||
|
|
||||||
$.get(api + '/stats', function(data){
|
$.get(api + '/stats', function(data){
|
||||||
renderStats(data);
|
renderStats(data);
|
||||||
updateMarkets();
|
updateMarkets();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function fetchLiveStats() {
|
function fetchLiveStats() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: api + '/live_stats',
|
url: api + '/live_stats',
|
||||||
|
@ -338,10 +329,16 @@
|
||||||
|
|
||||||
setInterval(updateMarkets, 300000); //poll market data every 5 minutes
|
setInterval(updateMarkets, 300000); //poll market data every 5 minutes
|
||||||
|
|
||||||
|
var lastStats;
|
||||||
|
|
||||||
function renderStats(stats){
|
function renderStats(stats){
|
||||||
|
|
||||||
|
lastStats = stats;
|
||||||
|
|
||||||
$('#coinName').text(stats.config.coin);
|
$('#coinName').text(stats.config.coin);
|
||||||
|
|
||||||
|
$('#blocksTotal').text(stats.pool.totalBlocks);
|
||||||
|
|
||||||
$('#networkHashrate').text(getReadableHashRateString(stats.network.difficulty / 60) + '/sec');
|
$('#networkHashrate').text(getReadableHashRateString(stats.network.difficulty / 60) + '/sec');
|
||||||
|
|
||||||
$('#networkLastBlockFound').timeago('update', new Date(stats.network.timestamp * 1000).toISOString());
|
$('#networkLastBlockFound').timeago('update', new Date(stats.network.timestamp * 1000).toISOString());
|
||||||
|
@ -408,7 +405,11 @@
|
||||||
|
|
||||||
$('#blocksMaturityCount').text(stats.config.depth);
|
$('#blocksMaturityCount').text(stats.config.depth);
|
||||||
|
|
||||||
renderBlocks(stats.pool.blocks, stats.config.depth, stats.network.height, blockchainExplorer);
|
var blocksJson = JSON.stringify(stats.pool.blocks);
|
||||||
|
if (lastBlocksJson !== blocksJson) {
|
||||||
|
lastBlocksJson = blocksJson;
|
||||||
|
renderBlocks(stats.pool.blocks);
|
||||||
|
}
|
||||||
|
|
||||||
$('#blocks_rows').find('tr[class=""]').each(function(){
|
$('#blocks_rows').find('tr[class=""]').each(function(){
|
||||||
var height = parseInt(this.children[0].innerHTML);
|
var height = parseInt(this.children[0].innerHTML);
|
||||||
|
@ -434,27 +435,13 @@
|
||||||
|
|
||||||
var lastBlocksJson = '';
|
var lastBlocksJson = '';
|
||||||
|
|
||||||
function renderBlocks(blocksResults, depth, chainHeight, explorer){
|
var $blockRows = $('#blocks_rows');
|
||||||
|
|
||||||
var blocksJson = JSON.stringify(blocksResults);
|
|
||||||
if (lastBlocksJson === blocksJson) return;
|
|
||||||
lastBlocksJson = blocksJson;
|
|
||||||
|
|
||||||
var blocks = [];
|
|
||||||
for (var status in blocksResults){
|
|
||||||
var blockArray = blocksResults[status];
|
|
||||||
for (var i = 0; i < blockArray.length; i++){
|
|
||||||
var blockData = blockArray[i].split(':');
|
|
||||||
blockData[0] = parseInt(blockData[0]);
|
|
||||||
blockData.unshift(status);
|
|
||||||
blocks.push(blockData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
blocks.sort(function(a, b){
|
|
||||||
return b[1] - a[1];
|
|
||||||
});
|
|
||||||
|
|
||||||
|
function renderBlocks(blocksResults){
|
||||||
|
|
||||||
|
var depth = lastStats.config.depth;
|
||||||
|
var chainHeight = lastStats.network.height;
|
||||||
|
var explorer = blockchainExplorer;
|
||||||
|
|
||||||
var blockStatusClasses = {
|
var blockStatusClasses = {
|
||||||
'pending': '',
|
'pending': '',
|
||||||
|
@ -467,39 +454,78 @@
|
||||||
return new Date(parseInt(time) * 1000).toLocaleString();
|
return new Date(parseInt(time) * 1000).toLocaleString();
|
||||||
};
|
};
|
||||||
|
|
||||||
var formatLuck = function(percent){
|
var formatLuck = function(difficulty, shares){
|
||||||
if (!percent) return '';
|
|
||||||
return '<span class="' + (percent < 100 ? 'luckBad' : 'luckGood') + '">' + percent + '%</span>';
|
if (difficulty > shares){
|
||||||
|
var percent = 100 - Math.round(shares / difficulty * 100);
|
||||||
|
return '<span class="luckGood"> ' + percent + '%</span>';
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
var percent = (100 - Math.round(difficulty / shares * 100)) * -1;
|
||||||
|
return '<span class="luckBad">' + percent + '%</span>';
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var rows = '';
|
var pendingRows = '';
|
||||||
|
|
||||||
var totalLuck = 0;
|
for (var i = 0; i < blocksResults.length; i += 2){
|
||||||
var totalLuckBlocks = 0;
|
var parts = blocksResults[i].split(':');
|
||||||
|
var block = {
|
||||||
|
height: parseInt(blocksResults[i + 1]),
|
||||||
|
hash: parts[0],
|
||||||
|
time: parts[1],
|
||||||
|
difficulty: parseInt(parts[2]),
|
||||||
|
shares: parseInt(parts[3]),
|
||||||
|
orphaned: parts[4],
|
||||||
|
reward: parts[5]
|
||||||
|
};
|
||||||
|
|
||||||
for (var i = 0; i < blocks.length; i++){
|
switch (block.orphaned){
|
||||||
var block = blocks[i];
|
case '0':
|
||||||
var blockLuck = null;
|
block.status = 'unlocked';
|
||||||
if (block[5]){
|
break;
|
||||||
blockLuck = Math.round(parseInt(block[2]) / parseInt(block[5]) * 100);
|
case '1':
|
||||||
totalLuck += blockLuck;
|
block.status = 'orphaned';
|
||||||
totalLuckBlocks++;
|
break;
|
||||||
|
default:
|
||||||
|
block.status = 'pending';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var row = '<tr data-height="' + block.height + '" id="blockMatured' + block.height + '" title="' + block.status
|
||||||
|
+ '" class="' + blockStatusClasses[block.status] + '">' +
|
||||||
|
'<td>' + block.height + '</td>' +
|
||||||
|
'<td>' + (block.status === 'pending' ? getMaturity(depth, chainHeight, block.height) : '') + '</td>' +
|
||||||
|
'<td>' + block.difficulty + '</td>' +
|
||||||
|
'<td><a target="_blank" href="' + explorer + block.hash + '">' + block.hash + '</a></td>' +
|
||||||
|
'<td>' + formatDate(block.time) + '</td>' +
|
||||||
|
'<td>' + formatLuck(block.difficulty, block.shares) + '</td>' +
|
||||||
|
'</tr>';
|
||||||
|
|
||||||
|
if (block.status === 'pending'){
|
||||||
|
pendingRows += row;
|
||||||
|
}
|
||||||
|
else if (!$blockRows.find('#blockMatured' + block.height).length){
|
||||||
|
var inserted = false;
|
||||||
|
$blockRows.children().each(function(){
|
||||||
|
var bHeight = parseInt(this.getAttribute('data-height'));
|
||||||
|
if (bHeight < block.height){
|
||||||
|
$(this).after(row);
|
||||||
|
inserted = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!inserted){
|
||||||
|
$blockRows.append(row);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rows += ('<tr title="' + block[0] + '" class="' + blockStatusClasses[block[0]] + '">' +
|
|
||||||
'<td>' + block[1] + '</td>' +
|
|
||||||
'<td>' + (block[0] === 'pending' ? getMaturity(depth, chainHeight, blocks[1]) : '') + '</td>' +
|
|
||||||
'<td>' + block[2] + '</td>' +
|
|
||||||
'<td><a target="_blank" href="' + explorer + block[3] + '">' + block[3] + '</a></td>' +
|
|
||||||
'<td>' + formatDate(block[4]) + '</td>' +
|
|
||||||
'<td>' + formatLuck(blockLuck) + '</td>' +
|
|
||||||
'</tr>');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#blocks_rows').empty().append(rows);
|
if (pendingRows) {
|
||||||
$('#averageBlockLuck').html(formatLuck(Math.round(totalLuck / totalLuckBlocks)));
|
$blockRows.children('[title="pending"]').remove();
|
||||||
$('#blocksCountPending').text(Object.keys(blocksResults['pending']).length);
|
$blockRows.prepend(pendingRows);
|
||||||
$('#blocksCountUnlocked').text(Object.keys(blocksResults['unlocked']).length);
|
}
|
||||||
$('#blocksCountOrphaned').text(Object.keys(blocksResults['orphaned']).length);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -607,11 +633,27 @@
|
||||||
$('#lookUp').click();
|
$('#lookUp').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var getBlocksAjax;
|
||||||
|
|
||||||
|
$('#loadMoreBlocks').click(function(){
|
||||||
|
if (getBlocksAjax)
|
||||||
|
getBlocksAjax.abort();
|
||||||
|
getBlocksAjax = $.ajax({
|
||||||
|
url: api + '/get_blocks',
|
||||||
|
data: {
|
||||||
|
height: $blockRows.children().last().data('height')
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
cache: 'false',
|
||||||
|
success: function(data){
|
||||||
|
renderBlocks(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
||||||
|
@ -674,9 +716,13 @@
|
||||||
|
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<h3>Your Stats</h3>
|
<h3>Your Stats</h3>
|
||||||
<label for="yourStatsInput"><i class="fa fa-key"></i> Address</label><br>
|
|
||||||
<input class="form-control" id="yourStatsInput" type="text">
|
<div class="input-group">
|
||||||
<button class="btn btn-default" id="lookUp"><i class="fa fa-search"></i> Lookup</button>
|
<label class="input-group-addon" for="yourStatsInput"><i class="fa fa-key"></i> Address</label>
|
||||||
|
<input class="form-control" id="yourStatsInput" type="text">
|
||||||
|
<span class="input-group-btn"><button class="btn btn-default" type="button" id="lookUp"><i class="fa fa-search"></i> Lookup</button></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="addressError"></div>
|
<div id="addressError"></div>
|
||||||
<div class="yourStats"><i class="fa fa-key"></i> Address: <span id="yourAddressDisplay"></span></div>
|
<div class="yourStats"><i class="fa fa-key"></i> Address: <span id="yourAddressDisplay"></span></div>
|
||||||
<div class="yourStats"><i class="fa fa-bank"></i> Pending Balance: <span id="yourPendingBalance"></span></div>
|
<div class="yourStats"><i class="fa fa-bank"></i> Pending Balance: <span id="yourPendingBalance"></span></div>
|
||||||
|
@ -690,12 +736,8 @@
|
||||||
|
|
||||||
<div class="page" id="page_pool_blocks">
|
<div class="page" id="page_pool_blocks">
|
||||||
<div class="blocksStatHolder">
|
<div class="blocksStatHolder">
|
||||||
<h4>Block Candidates</h4>
|
<span class="bg-primary"><span id="blocksTotal"></span> Total Blocks Mined</span>
|
||||||
<span><span id="blocksCountPending"></span> Maturing</span>
|
|
||||||
<span class="bg-success"><span id="blocksCountUnlocked"></span> Unlocked</span>
|
|
||||||
<span class="bg-danger"><span id="blocksCountOrphaned"></span> Orphaned</span>
|
|
||||||
<span class="bg-info">Maturity requires <span id="blocksMaturityCount"></span> blocks</span>
|
<span class="bg-info">Maturity requires <span id="blocksMaturityCount"></span> blocks</span>
|
||||||
<span><span id="averageBlockLuck"></span> Luck Average</span>
|
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
|
@ -714,6 +756,11 @@
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<p class="text-center">
|
||||||
|
<button type="button" class="btn btn-default" id="loadMoreBlocks">Load More</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue