Merge with last version of 'zone117x/node-cryptonote-pool'

pull/3/head
fancoder 2014-07-18 17:38:30 +00:00
commit 96ae55de70
23 changed files with 2034 additions and 983 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
node_modules/
.idea/
config.json
config.json
logs/

View File

@ -232,6 +232,7 @@ Explanation for each field:
"enabled": true,
"interval": 600, //how often to run in seconds
"maxAddresses": 50, //split up payments if sending to more than this many addresses
"mixin": 3, //number of transactions yours is indistinguishable from
"transferFee": 5000000000, //fee to pay for each transaction
"minPayment": 100000000000, //miner balance required before sending payment
"denomination": 100000000000 //truncate to this precision and store remainder
@ -247,8 +248,9 @@ Explanation for each field:
/* Block depth required for a block to unlocked/mature. Found in daemon source as
the variable CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW */
"depth": 60,
"poolFee": 2, //2% pool fee
"devDonation": 0.1 //0.1% donation to send to pool dev - only works with Monero
"poolFee": 1.8, //1.8% pool fee (2% total fee total including donations)
"devDonation": 0.1, //0.1% donation to send to pool dev - only works with Monero
"coreDevDonation": 0.1 //0.1% donation to send to core devs - only works with Monero
},
/* AJAX API used for front-end website. */
@ -256,7 +258,10 @@ Explanation for each field:
"enabled": true,
"hashrateWindow": 600, //how many second worth of shares used to estimate hash rate
"updateInterval": 3, //gather stats and broadcast every this many seconds
"port": 8117
"port": 8117,
"blocks": 30, //amount of blocks to send at a time
"payments": 30, //amount of payments to send at a time
"password": "test" //password required for admin stats
},
/* Coin daemon connection details. */
@ -322,11 +327,11 @@ node init.js -module=api
#### 5) Host the front-end
Simply host the contents of the `website` directory on file server capable of serving simple static files.
Simply host the contents of the `website_example` directory on file server capable of serving simple static files.
In the `website` directory copy `config_example.js` to `config.js` then edit the variables in
the file to use your pool's specific configuration. Variable explanations:
Edit the variables in the `website_example/config.js` file to use your pool's specific configuration.
Variable explanations:
```javascript
@ -351,22 +356,22 @@ var cryptonatorWidget = ["XMR-BTC", "XMR-USD", "XMR-EUR", "XMR-GBP"];
/* Download link to cryptonote-easy-miner for Windows users. */
var easyminerDownload = "https://github.com/zone117x/cryptonote-easy-miner/releases/";
/* Download link to simplewallet for your configured coin. */
var simplewalletDownload = "http://bit.ly/monero-starter-pack";
/* Used for front-end block links. For other coins it can be changed, for example with
Bytecoin you can use "https://minergate.com/blockchain/bcn/block/". */
var blockchainExplorer = "http://monerochain.info/block/";
/* Used by front-end transaction links. Change for other coins. */
var transactionExplorer = "http://monerochain.info/tx/";
```
#### 6) Customize your website
The following files are included so that you can customize your pool website without having to make significant changes
to `index.html` thus reducing the difficulty of merging updates to `index.html` with your own changes:
* `additional.css` for creating your own pool style
* `addtional.js` for changing the functionality of your pool website
* `info.html` for display news/updates/information on your site
to `index.html` or other front-end files thus reducing the difficulty of merging updates with your own changes:
* `custom.css` for creating your own pool style
* `custom.js` for changing the functionality of your pool website
Then simply serve the files via nginx, Apache, Google Drive, or anything that can host static content.

View File

@ -35,6 +35,12 @@
"port": 7777,
"difficulty": 10000,
"desc": "High end hardware"
},
{
"port": 8888,
"difficulty": 10000,
"desc": "Hidden port",
"hidden": true
}
],
"varDiff": {
@ -64,6 +70,7 @@
"enabled": true,
"interval": 600,
"maxAddresses": 50,
"mixin": 3,
"transferFee": 5000000000,
"minPayment": 100000000000,
"denomination": 100000000000
@ -74,7 +81,8 @@
"interval": 30,
"depth": 60,
"poolFee": 2,
"devDonation": 0.1
"devDonation": 0.1,
"coreDevDonation": 0.1
},
"api": {
@ -82,6 +90,8 @@
"hashrateWindow": 600,
"updateInterval": 5,
"port": 8117,
"blocks": 30,
"payments": 30,
"password": "your_password"
},

19
init.js
View File

@ -5,24 +5,7 @@ var os = require('os');
var redis = require('redis');
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.0.6";
require('./lib/configReader.js');
require('./lib/logger.js');

View File

@ -4,7 +4,6 @@ var url = require("url");
var zlib = require('zlib');
var async = require('async');
var redis = require('redis');
var apiInterfaces = require('./apiInterfaces.js')(config.daemon, config.wallet);
@ -13,13 +12,16 @@ require('./exceptionWriter.js')(logSystem);
var redisCommands = [
['zremrangebyscore', config.coin + ':hashrate', '-inf', ''],
['zrangebyscore', config.coin + ':hashrate', '', '+inf'],
['hgetall', config.coin + ':stats'],
['smembers', config.coin + ':blocksPending'],
['smembers', config.coin + ':blocksUnlocked'],
['smembers', config.coin + ':blocksOrphaned'],
['zrange', config.coin + ':hashrate', 0, -1],
['hgetall', config.coin + ':stats'],
['zrange', config.coin + ':blocks:candidates', 0, -1, 'WITHSCORES'],
['zrevrange', config.coin + ':blocks:matured', 0, config.api.blocks - 1, 'WITHSCORES'],
['hgetall', config.coin + ':shares:roundCurrent'],
['hgetall', config.coin + ':stats']
['hgetall', config.coin + ':stats'],
['zcard', config.coin + ':blocks:matured'],
['zrevrange', config.coin + ':payments:all', 0, config.api.payments - 1, 'WITHSCORES'],
['zcard', config.coin + ':payments:all'],
['keys', config.coin + ':payments:*']
];
var currentStats = "";
@ -40,7 +42,6 @@ function collectStats(){
var windowTime = (((Date.now() / 1000) - config.api.hashrateWindow) | 0).toString();
redisCommands[0][3] = '(' + windowTime;
redisCommands[1][2] = windowTime;
async.parallel({
pool: function(callback){
@ -56,11 +57,11 @@ function collectStats(){
var data = {
stats: replies[2],
blocks: {
pending: replies[3],
unlocked: replies[4],
orphaned: replies[5]
}
blocks: replies[3].concat(replies[4]),
totalBlocks: parseInt(replies[7]) + (replies[3].length / 2),
payments: replies[8],
totalPayments: parseInt(replies[9]),
totalMinersPaid: replies[10].length - 1
};
var hashrates = replies[1];
@ -86,14 +87,14 @@ function collectStats(){
data.roundHashes = 0;
if (replies[6]){
for (var miner in replies[6]){
data.roundHashes += parseInt(replies[6][miner]);
if (replies[5]){
for (var miner in replies[5]){
data.roundHashes += parseInt(replies[5][miner]);
}
}
if (replies[7]) {
data.lastBlockFound = replies[7].lastBlockFound;
if (replies[6]) {
data.lastBlockFound = replies[6].lastBlockFound;
}
callback(null, data);
@ -119,14 +120,18 @@ function collectStats(){
},
config: function(callback){
callback(null, {
ports: config.poolServer.ports,
ports: getPublicPorts(config.poolServer.ports),
hashrateWindow: config.api.hashrateWindow,
fee: config.blockUnlocker.poolFee,
coin: config.coin,
symbol: config.symbol,
depth: config.blockUnlocker.depth,
version: config.version,
donation: config.blockUnlocker.devDonation
donation: config.blockUnlocker.devDonation,
coreDonation: config.blockUnlocker.coreDevDonation,
doDonations: doDonations,
version: version,
minPaymentThreshold: config.payments.minPayment,
denominationUnit: config.payments.denomination
});
}
}, function(error, results){
@ -150,6 +155,12 @@ function collectStats(){
}
function getPublicPorts(ports){
return ports.filter(function(port) {
return !port.hidden;
});
}
function getReadableHashRateString(hashrate){
var i = 0;
var byteUnits = [' H', ' KH', ' MH', ' GH', ' TH', ' PH' ];
@ -172,16 +183,23 @@ function broadcastLiveStats(){
var redisCommands = [];
for (var address in addressConnections){
redisCommands.push(['hgetall', config.coin + ':workers:' + address]);
redisCommands.push(['zrevrange', config.coin + ':payments:' + address, 0, config.api.payments - 1, 'WITHSCORES']);
}
redisClient.multi(redisCommands).exec(function(error, replies){
var addresses = Object.keys(addressConnections);
for (var i = 0; i < addresses.length; i++){
var offset = i * 2;
var address = addresses[i];
var stats = replies[i];
var stats = replies[offset];
var res = addressConnections[address];
res.end(stats ? formatMinerStats(stats, address) : '{"error": "not found"');
if (!stats){
res.end(JSON.stringify({error: "not found"}));
return;
}
stats.hashrate = minerStats[address];
res.end(JSON.stringify({stats: stats, payments: replies[offset + 1]}));
}
});
}
@ -209,21 +227,84 @@ function handleMinerStats(urlParts, response){
});
}
else{
redisClient.hgetall(config.coin + ':workers:' + address, function(error, stats){
if (!stats){
redisClient.multi([
['hgetall', config.coin + ':workers:' + address],
['zrevrange', config.coin + ':payments:' + address, 0, config.api.payments - 1, 'WITHSCORES']
]).exec(function(error, replies){
if (error || !replies[0]){
response.end(JSON.stringify({error: 'not found'}));
return;
}
response.end(formatMinerStats(stats, address));
var stats = replies[0];
stats.hashrate = minerStats[address];
response.end(JSON.stringify({stats: stats, payments: replies[1]}));
});
}
}
function formatMinerStats(redisData, address){
redisData.hashrate = minerStats[address];
redisData.symbol = config.symbol;
return JSON.stringify({stats: redisData});
function handleGetPayments(urlParts, response){
var paymentKey = ':payments:all';
if (urlParts.query.address)
paymentKey = ':payments:' + urlParts.query.address;
redisClient.zrevrangebyscore(
config.coin + paymentKey,
'(' + urlParts.query.time,
'-inf',
'WITHSCORES',
'LIMIT',
0,
config.api.payments,
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);
}
)
}
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);
});
}
@ -252,21 +333,24 @@ function handleAdminStats(response){
async.waterfall([
//Get worker keys
//Get worker keys & unlocked blocks
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) {
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);
return;
}
callback(null, result);
callback(null, replies[0], replies[1]);
});
},
//Get worker balances
function(keys, callback){
var redisCommands = keys.map(function(k){
function(workerKeys, blocks, callback){
var redisCommands = workerKeys.map(function(k){
return ['hmget', k, 'balance', 'paid'];
});
redisClient.multi(redisCommands).exec(function(error, replies){
@ -276,19 +360,42 @@ function handleAdminStats(response){
return;
}
var stats = {
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);
callback(null, replies, blocks);
});
}], 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){
response.end(JSON.stringify({error: 'error collecting stats'}));
return;
@ -346,6 +453,12 @@ var server = http.createServer(function(request, response){
case '/stats_address':
handleMinerStats(urlParts, response);
break;
case '/get_payments':
handleGetPayments(urlParts, response);
break;
case '/get_blocks':
handleGetBlocks(urlParts, response);
break;
case '/admin_stats':
if (!authorize(request, response))
return;

View File

@ -24,36 +24,37 @@ apiInterfaces.batchRpcDaemon(batchArray, function(error, response){
*/
var devDonationAddress = '45Jmf8PnJKziGyrLouJMeBFw2yVyX1QB52sKEQ4S1VSU2NVsaVGPNu4bWKkaHaeZ6tWCepP6iceZk8XhTLzDaEVa72QrtVh';
var doDonations = config.blockUnlocker.devDonation > 0 && devDonationAddress[0] === config.poolServer.poolAddress[0];
function runInterval(){
async.waterfall([
//Get all pending blocks in redis
//Get all block candidates in redis
function(callback){
redisClient.smembers(config.coin + ':blocksPending', function(error, result){
redisClient.zrange(config.coin + ':blocks:candidates', 0, -1, 'WITHSCORES', function(error, results){
if (error){
log('error', logSystem, 'Error trying to get pending blocks from redis %j', [error]);
callback(true);
return;
}
if (result.length === 0){
log('info', logSystem, 'No pending blocks in redis');
if (results.length === 0){
log('info', logSystem, 'No blocks candidates in redis');
callback(true);
return;
}
var blocks = result.map(function(item){
var parts = item.split(':');
return {
height: parseInt(parts[0]),
difficulty: parseInt(parts[1]),
hash: parts[2],
serialized: item
};
});
var blocks = [];
for (var i = 0; i < results.length; i += 2){
var parts = results[i].split(':');
blocks.push({
serialized: results[i],
height: parseInt(results[i + 1]),
hash: parts[0],
time: parts[1],
difficulty: parts[2],
shares: parts[3]
});
}
callback(null, blocks);
});
},
@ -75,7 +76,7 @@ function runInterval(){
return;
}
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.reward = blockHeader.reward;
mapCback(block.unlocked);
@ -83,7 +84,7 @@ function runInterval(){
}, function(unlockedBlocks){
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);
return;
}
@ -110,9 +111,6 @@ function runInterval(){
for (var i = 0; i < replies.length; i++){
var workerShares = replies[i];
blocks[i].workerShares = workerShares;
blocks[i].totalShares = Object.keys(workerShares).reduce(function(p, c){
return p + parseInt(workerShares[c])
}, 0);
}
callback(null, blocks);
});
@ -121,19 +119,29 @@ function runInterval(){
//Handle orphaned blocks
function(blocks, callback){
var orphanCommands = [];
blocks.forEach(function(block){
if (!block.orphan) return;
var workerShares = block.workerShares;
if (!block.orphaned) return;
orphanCommands.push(['del', config.coin + ':shares:round' + block.height]);
orphanCommands.push(['srem', config.coin + ':blocksPending', block.serialized]);
orphanCommands.push(['sadd', config.coin + ':blocksOrphaned', block.serialized + ':' + block.totalShares]);
orphanCommands.push(['zrem', config.coin + ':blocks:candidates', block.serialized]);
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){
orphanCommands.push(['hincrby', config.coin + ':shares:roundCurrent',
worker, workerShares[worker]]);
});
if (block.workerShares) {
var workerShares = block.workerShares;
Object.keys(workerShares).forEach(function (worker) {
orphanCommands.push(['hincrby', config.coin + ':shares:roundCurrent', worker, workerShares[worker]]);
});
}
});
if (orphanCommands.length > 0){
redisClient.multi(orphanCommands).exec(function(error, replies){
if (error){
@ -155,30 +163,43 @@ function runInterval(){
var payments = {};
var totalBlocksUnlocked = 0;
blocks.forEach(function(block){
if (block.orphan) return;
if (block.orphaned) return;
totalBlocksUnlocked++;
unlockedBlocksCommands.push(['del', config.coin + ':shares:round' + block.height]);
unlockedBlocksCommands.push(['srem', config.coin + ':blocksPending', block.serialized]);
unlockedBlocksCommands.push(['sadd', config.coin + ':blocksUnlocked', block.serialized + ':' + block.totalShares]);
unlockedBlocksCommands.push(['zrem', config.coin + ':blocks:candidates', block.serialized]);
unlockedBlocksCommands.push(['zadd', config.coin + ':blocks:matured', block.height, [
block.hash,
block.time,
block.difficulty,
block.shares,
block.orphaned,
block.reward
].join(':')]);
var feePercent = config.blockUnlocker.poolFee / 100;
if (doDonations) {
feePercent += config.blockUnlocker.devDonation / 100;
feePercent += config.blockUnlocker.coreDevDonation / 100;
var devDonation = block.reward * (config.blockUnlocker.devDonation / 100);
payments[devDonationAddress] = devDonation;
var coreDevDonation = block.reward * (config.blockUnlocker.coreDevDonation / 100);
payments[coreDevDonationAddress] = coreDevDonation;
}
var reward = block.reward - (block.reward * feePercent);
var totalShares = block.totalShares;
Object.keys(block.workerShares).forEach(function(worker){
var percent = block.workerShares[worker] / totalShares;
var workerReward = reward * percent;
payments[worker] = (payments[worker] || 0) + workerReward;
});
if (block.workerShares) {
var totalShares = parseInt(block.shares);
Object.keys(block.workerShares).forEach(function (worker) {
var percent = block.workerShares[worker] / totalShares;
var workerReward = reward * percent;
payments[worker] = (payments[worker] || 0) + workerReward;
});
}
});
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]);
callback(null);
});
}
], function(error, result){
setTimeout(runInterval, config.blockUnlocker.interval * 1000);
})

View File

@ -0,0 +1,25 @@
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;
}
global.version = "v0.99.3.1";
global.devDonationAddress = '45Jmf8PnJKziGyrLouJMeBFw2yVyX1QB52sKEQ4S1VSU2NVsaVGPNu4bWKkaHaeZ6tWCepP6iceZk8XhTLzDaEVa72QrtVh';
global.coreDevDonationAddress = '46BeWrHpwXmHDpDEUmZBWZfoQpdc6HaERCNmx1pEYL2rAcuwufPN9rXHHtyUA4QVy66qeFQkn6sfK8aHYjA3jk3o1Bv16em';
global.doDonations = devDonationAddress[0] === config.poolServer.poolAddress[0] && (
config.blockUnlocker.devDonation > 0 || config.blockUnlocker.coreDevDonation > 0
);

View File

@ -77,10 +77,11 @@ function runInterval(){
for (var i = 0; i < transferCommandsLength; i++){
transferCommands.push({
redis: [],
amount : 0,
rpc: {
destinations: [],
fee: config.payments.transferFee,
mixin: 0,
mixin: config.payments.mixin,
unlock_time: 0
}
});
@ -94,6 +95,7 @@ function runInterval(){
transferCommands[commandIndex].rpc.destinations.push({amount: amount, address: worker});
transferCommands[commandIndex].redis.push(['hincrby', config.coin + ':workers:' + worker, 'balance', -amount]);
transferCommands[commandIndex].redis.push(['hincrby', config.coin + ':workers:' + worker, 'paid', amount]);
transferCommands[commandIndex].amount += amount;
addresses++;
if (addresses >= config.payments.maxAddresses){
@ -102,6 +104,8 @@ function runInterval(){
}
}
var timeOffset = 0;
async.filter(transferCommands, function(transferCmd, cback){
apiInterfaces.rpcWallet('transfer', transferCmd.rpc, function(error, result){
if (error){
@ -110,6 +114,31 @@ function runInterval(){
cback(false);
return;
}
var now = (timeOffset++) + Date.now() / 1000 | 0;
var txHash = result.tx_hash.replace('<', '').replace('>', '');
transferCmd.redis.push(['zadd', config.coin + ':payments:all', now, [
txHash,
transferCmd.amount,
transferCmd.rpc.fee,
transferCmd.rpc.mixin,
Object.keys(transferCmd.rpc.destinations).length
].join(':')]);
for (var i = 0; i < transferCmd.rpc.destinations.length; i++){
var destination = transferCmd.rpc.destinations[i];
transferCmd.redis.push(['zadd', config.coin + ':payments:' + destination.address, now, [
txHash,
destination.amount,
transferCmd.rpc.fee,
transferCmd.rpc.mixin
].join(':')]);
}
log('info', logSystem, 'Payments sent via wallet daemon %j', [result]);
redisClient.multi(transferCmd.redis).exec(function(error, replies){
if (error){

View File

@ -154,7 +154,7 @@ function processBlockTemplate(template){
if (currentBlockTemplate)
validBlockTemplates.push(currentBlockTemplate);
if (validBlockTemplates.length > 10)
if (validBlockTemplates.length > 3)
validBlockTemplates.shift();
currentBlockTemplate = new BlockTemplate(template);
@ -335,7 +335,7 @@ Miner.prototype = {
function recordShareData(miner, job, shareDiff, blockCandidate, hashHex, shareType){
function recordShareData(miner, job, shareDiff, blockCandidate, hashHex, shareType, blockTemplate){
var dateNow = Date.now();
var dateNowSeconds = dateNow / 1000 | 0;
@ -348,15 +348,33 @@ function recordShareData(miner, job, shareDiff, blockCandidate, hashHex, shareTy
];
if (blockCandidate){
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(['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){
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,
blockTemplate.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]);
@ -385,7 +403,7 @@ function processShare(miner, job, blockTemplate, nonce, resultHash){
if (hash.toString('hex') !== resultHash) {
log('error', logSystem, 'Bad hash from miner %s@%s', [miner.login, miner.ip]);
log('warn', logSystem, 'Bad hash from miner %s@%s', [miner.login, miner.ip]);
return false;
}
@ -394,22 +412,22 @@ function processShare(miner, job, blockTemplate, nonce, resultHash){
var hashNum = bignum.fromBuffer(new Buffer(hashArray));
var hashDiff = diff1.div(hashNum);
var blockFastHash;
if (hashDiff.ge(blockTemplate.difficulty)){
blockFastHash = cnUtil.get_block_id(shareBuffer).toString('hex');
apiInterfaces.rpcDaemon('submitblock', [shareBuffer.toString('hex')], function(error, result){
if (error){
log('error', logSystem, 'Error submitting block at height %d - %j', [job.height, error]);
log('error', logSystem, 'Error submitting block at height %d from %s@%s, share type: "%s" - %j', [job.height, miner.login, miner.ip, shareType, error]);
recordShareData(miner, job, hashDiff.toString(), false, null, shareType);
}
else{
var blockFastHash = cnUtil.get_block_id(shareBuffer).toString('hex');
log('info', logSystem,
'Block %s found at height %d by miner %s@%s - submit result: %j',
[blockFastHash.substr(0, 6), job.height, miner.login, miner.ip, result]
);
recordShareData(miner, job, hashDiff.toString(), true, blockFastHash, shareType);
recordShareData(miner, job, hashDiff.toString(), true, blockFastHash, shareType, blockTemplate);
jobRefresh();
}
});

View File

@ -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();
});

View File

@ -1,54 +0,0 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.0/jquery.timeago.min.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
<script src="config.js"></script>
<script>
var coinDecimals = coinUnits.toString().length - 1;
function getReadableCoins(coins){
return (parseInt(coins || 0) / coinUnits).toFixed(coinDecimals);
}
$(function(){
var password = prompt('Admin password:');
$.get(api + '/admin_stats', {password: password}, function(data){
renderData(data);
});
});
function renderData(data){
$('#totalOwed').text(getReadableCoins(data.totalOwed));
$('#totalPaid').text(getReadableCoins(data.totalPaid));
}
</script>
</head>
<body>
<div>
<span>Total Owed: </span><span id="totalOwed"></span>
</div>
<div>
<span>Total Paid: </span><span id="totalPaid"></span>
</div>
</body>
</html>

View File

@ -1,782 +0,0 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
<title>Cryptonote Mining Pool</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.0/jquery.timeago.min.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
<link href='http://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>
<script src="config.js"></script>
<script src="additional.js"></script>
<link href="additional.css" rel="stylesheet">
<style>
#coinName{
text-transform: capitalize;
}
body {
padding-top: 90px;
padding-bottom: 70px;
}
.container{
font-size: 1.2em;
}
.page{
display: none;
}
.stats {
margin-bottom: 40px;
}
.stats:last-child{
width: auto;
}
.stats > h3 > i{
font-size: 0.80em;
width: 21px;
}
.stats > div{
padding: 5px 0;
}
.stats > div > .fa {
width: 25px;
}
.stats > div > span:first-of-type{
font-weight: bold;
}
.marketFooter{
font-size: 10px;
opacity: 0.6;
}
#networkLastReward{
text-transform: uppercase;
}
#donationPercent{
font-size: 0.8em;
margin-left: 5px;
}
#lastHash{
font-family: monospace;
font-size: 0.8em;
}
#yourStatsInput{
width: 820px;
max-width: 100%;
display: inline-block;
vertical-align: bottom;
font-family: monospace;
}
#yourAddressDisplay > span {
font-family: monospace;
}
.yourStats{
display: none;
}
#yourAddressDisplay{
display: inline-block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
font-family: monospace;
font-size: 0.8em;
}
#addressError{
color: red;
}
#stats_updated{
opacity: 0;
float: right;
margin-left: 30px;
color: #e8e8e8;
line-height: 47px;
font-size: 0.9em;
}
.blocksStatHolder > h4{
display: inline-block;
margin-right: 10px;
}
.blocksStatHolder > span{
display: inline-block;
border-radius: 5px;
padding: 1px 9px;
border: 1px solid #e5e5e5;
margin: 2px;
}
#blocks_rows > tr > td{
vertical-align: middle;
font-family: 'Inconsolata', monospace;
font-size: 0.9em;
}
.luckGood{
color: darkgreen;
}
.luckBad{
color: darkred;
}
#page_getting_started > .stats:first-of-type{
margin-bottom: 15px;
}
#page_getting_started .stats {
margin-bottom: 10px;
}
.getting_started_windows{
line-height: 40px;
}
#miningPorts > .stats{
width: auto !important;
margin-right: 70px;
margin-left: 15px;
display: inline-block;
}
#miningPorts > .stats:last-child{
margin-right: 0;
}
#getting_started_list > li{
margin-bottom: 20px;
}
#cpuminer_code{
display: inline-block;
white-space: normal;
}
footer{
position: fixed;
bottom: 0;
width: 100%;
background-color: #f5f5f5;
}
footer > div{
margin: 10px auto;
text-align: center;
}
</style>
</head>
<body>
<script>
$(function(){
$('#siteInfo').load('info.html');
/*$.get('info.html', function(html){
$('#siteInfo').html(html);
}, 'html');*/
$.get(api + '/stats', function(data){
renderStats(data);
updateMarkets();
});
function fetchLiveStats() {
$.ajax({
url: api + '/live_stats',
dataType: 'json',
cache: 'false'
}).done(function(data){
renderStats(data);
}).fail(function(){
console.log('live stats long polling connection failed');
}).always(function () {
fetchLiveStats();
});
}
fetchLiveStats();
window.onhashchange = function(){
pageRouter();
};
pageRouter();
$('#networkLastBlockFound,#poolLastBlockFound,#yourLastShare,#marketLastUpdated').timeago();
var $miningPorts = $('#miningPorts');
var miningPortTemplate = $miningPorts.html();
$miningPorts.empty();
var lookupBtnHtml = $('#lookUp').html();
var addressEventSource;
var addressTimeout;
$('#lookUp').click(function(){
var address = $('#yourStatsInput').val().trim();
if (!address){
$('#yourStatsInput').focus();
return;
}
$('#addressError').hide();
$('.yourStats').hide();
$(this).html('<i class="fa fa-refresh fa-spin"></i> Searching...');
if (addressEventSource) addressEventSource.abort();
if (addressTimeout) clearTimeout(addressTimeout);
function fetchAddressStats(longpoll){
addressEventSource = $.ajax({
url: api + '/stats_address',
data: {
address: address,
longpoll: longpoll
},
dataType: 'json',
cache: 'false',
success: function(data){
$('#lookUp').html(lookupBtnHtml);
if (!data.stats){
$('.yourStats').hide();
$('#addressError').text(data.error).show();
if (addressTimeout) clearTimeout(addressTimeout);
addressTimeout = setTimeout(function(){
fetchAddressStats(false);
}, 2000);
return;
}
$('#addressError').hide();
$('#yourAddressDisplay').text(address);
if (data.stats.lastShare)
$('#yourLastShare').timeago('update', new Date(parseInt(data.stats.lastShare) * 1000).toISOString());
else
$('#yourLastShare').text('Never');
$('#yourHashrateHolder').text((data.stats.hashrate || '0 H') + '/sec');
$('#yourHashes').text(data.stats.hashes || '0');
$('#yourPaid').text(getReadableCoins(data.stats.paid) + ' ' + data.stats.symbol);
$('#yourPendingBalance').text(getReadableCoins(data.stats.balance) + ' ' + data.stats.symbol);
$('.yourStats').show();
docCookies.setItem('mining_address', address, Infinity);
fetchAddressStats(true);
},
error: function(e){
if (e.statusText === 'abort') return;
$('#lookUp').html(lookupBtnHtml);
$('#addressError').text('Connection error').show();
if (addressTimeout) clearTimeout(addressTimeout);
addressTimeout = setTimeout(function(){
fetchAddressStats(false);
}, 2000);
}
});
}
fetchAddressStats(false);
});
var lastPortsJson = '';
var stats_update = $('#stats_updated')[0];
var cryptonatorMarkets;
function updateMarkets(){
var completedFetches = 0;
var marketsData = [];
for (var i = 0; i < cryptonatorMarkets.length; i++){
(function(i){
$.get('https://www.cryptonator.com/api/ticker/' + cryptonatorMarkets[i], function(data){
marketsData[i] = data;
completedFetches++;
if (completedFetches !== cryptonatorMarkets.length) return;
var $marketHeader = $('#marketHeader');
$('.marketTicker').remove();
for (var f = marketsData.length - 1; f >= 0 ; f--){
var price = parseFloat(marketsData[f].ticker.price);
if (price > 1) price = Math.round(price * 100) / 100;
else price = marketsData[f].ticker.price;
$marketHeader.after('<div class="marketTicker">' + marketsData[f].ticker.base + ': <span>' + price + ' ' + marketsData[f].ticker.target + '</span></div>');
}
$('#marketLastUpdated').timeago('update', new Date(marketsData[0].timestamp * 1000).toISOString());
}, 'json');
})(i);
}
}
setInterval(updateMarkets, 300000); //poll market data every 5 minutes
function renderStats(stats){
$('#coinName').text(stats.config.coin);
$('#networkHashrate').text(getReadableHashRateString(stats.network.difficulty / 60) + '/sec');
$('#networkLastBlockFound').timeago('update', new Date(stats.network.timestamp * 1000).toISOString());
$('#networkDifficulty').text(stats.network.difficulty);
$('#blockchainHeight').text(stats.network.height);
$('#networkLastReward').text((stats.network.reward / coinUnits).toFixed(4) + ' ' + stats.config.symbol);
$('#lastHash').attr('href', blockchainExplorer + stats.network.hash).text(stats.network.hash.substr(0, 13) + '...');
$('#poolHashrate').text(getReadableHashRateString(stats.pool.hashrate) + '/sec');
cryptonatorMarkets = cryptonatorWidget;
if (stats.pool.lastBlockFound){
var d = new Date(parseInt(stats.pool.lastBlockFound)).toISOString();
$('#poolLastBlockFound').timeago('update', d);
}
else{
$('#poolLastBlockFound').removeAttr('title').data('ts', '').text('Never');
}
$('#poolRoundHashes').text(stats.pool.roundHashes);
$('#poolMiners').text(stats.pool.miners);
$('#poolFee').text(stats.config.fee + '%');
if (stats.config.donation && stats.config.donation > 0)
$('#donationPercent').text(' + ' + stats.config.donation + '% development funding');
else
$('#donationPercent').text('');
$('#blockSolvedTime').text(getReadableTime(stats.network.difficulty / stats.pool.hashrate));
$('#miningPoolHost').text(poolHost);
var portsJson = JSON.stringify(stats.config.ports);
if (lastPortsJson !== portsJson) {
lastPortsJson = portsJson;
var $miningPortChildren = [];
for (var i = 0; i < stats.config.ports.length; i++) {
var portData = stats.config.ports[i];
var $portChild = $(miningPortTemplate);
$portChild.find('.miningPort').text(portData.port);
$portChild.find('.miningPortDiff').text(portData.difficulty);
$portChild.find('.miningPortDesc').text(portData.desc);
$miningPortChildren.push($portChild);
}
$miningPorts.empty().append($miningPortChildren);
}
$('#easyminer_link').attr('href', easyminerDownload);
$('#simplewallet_download').attr('href', simplewalletDownload);
$('#cpuminer_code').text('minerd -a cryptonight -o stratum+tcp://'
+ poolHost + ':'
+ (stats.config.ports[0] || {}).port
+ ' -u address -p x');
var kiwiIrc = 'https://kiwiirc.com/client/' + irc;
if ($('#kiwi_irc').attr('src') !== kiwiIrc){
$('#kiwi_irc').attr('src', kiwiIrc);
}
$('#emailLink').attr('href', 'mailto:' + email).text(email);
$('#blocksMaturityCount').text(stats.config.depth);
renderBlocks(stats.pool.blocks, stats.config.depth, stats.network.height, blockchainExplorer);
$('#blocks_rows').find('tr[class=""]').each(function(){
var height = parseInt(this.children[0].innerHTML);
this.children[1].innerHTML = getMaturity(stats.config.depth, stats.network.height, height);
});
$('#poolVersion').text(stats.config.version);
stats_update.style.transition = 'opacity 100ms ease-out';
stats_update.style.opacity = 1;
setTimeout(function(){
stats_update.style.transition = 'opacity 7000ms linear';
stats_update.style.opacity = 0;
}, 500);
}
function getMaturity(depth, chainHeight, blockHeight){
var toGo = depth - (chainHeight - blockHeight);
if (toGo < 1) return '';
return '' + toGo + ' to go';
}
var lastBlocksJson = '';
function renderBlocks(blocksResults, depth, chainHeight, explorer){
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];
});
var blockStatusClasses = {
'pending': '',
'unlocked': 'success',
'orphaned': 'danger'
};
var formatDate = function(time){
if (!time) return '';
return new Date(parseInt(time) * 1000).toLocaleString();
};
var formatLuck = function(percent){
if (!percent) return '';
return '<span class="' + (percent < 100 ? 'luckBad' : 'luckGood') + '">' + percent + '%</span>';
};
var rows = '';
var totalLuck = 0;
var totalLuckBlocks = 0;
for (var i = 0; i < blocks.length; i++){
var block = blocks[i];
var blockLuck = null;
if (block[5]){
blockLuck = Math.round(parseInt(block[2]) / parseInt(block[5]) * 100);
totalLuck += blockLuck;
totalLuckBlocks++;
}
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);
$('#averageBlockLuck').html(formatLuck(Math.round(totalLuck / totalLuckBlocks)));
$('#blocksCountPending').text(Object.keys(blocksResults['pending']).length);
$('#blocksCountUnlocked').text(Object.keys(blocksResults['unlocked']).length);
$('#blocksCountOrphaned').text(Object.keys(blocksResults['orphaned']).length);
}
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);
},
keys: /* optional method: you can safely remove it! */ function () {
var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
for (var nIdx = 0; nIdx < aKeys.length; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
return aKeys;
}
};
var coinDecimals = coinUnits.toString().length - 1;
function getReadableCoins(coins){
return (parseInt(coins || 0) / coinUnits).toFixed(coinDecimals);
}
function getReadableTime(seconds){
var units = [
[60, 'second'],
[60, 'minute'],
[24, 'hour'],
[12, 'day'],
[7, 'week'],
[30, 'month'],
[12, 'year']
];
function formatAmounts(amount, unit){
var rounded = Math.round(amount);
return '' + rounded + ' ' + unit + (rounded > 1 ? 's' : '');
}
var amount = seconds;
for (var i = 0; i < units.length; i++){
if (amount < units[i][0])
return formatAmounts(amount, units[i][1]);
amount = amount / units[i][0];
}
return formatAmounts(amount, units[units.length - 1][1]);
}
function getReadableHashRateString(hashrate){
var i = 0;
var byteUnits = [' H', ' KH', ' MH', ' GH', ' TH', ' PH' ];
while (hashrate > 1024){
hashrate = hashrate / 1024;
i++;
}
return hashrate.toFixed(2) + byteUnits[i];
}
function pageRouter(){
$('.page').hide();
$('.hot_link').parent().removeClass('active');
var $link = $('a.hot_link[href="' + (window.location.hash || '#') + '"]');
$link.parent().addClass('active');
var page = $link.data('page');
$('#' + page).show();
}
var address = docCookies.getItem('mining_address');
if (address){
$('#yourStatsInput').val(address);
$('#lookUp').click();
}
$('#yourStatsInput').keyup(function(e){
if(e.keyCode === 13)
$('#lookUp').click();
});
});
</script>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand"><span id="coinName"></span> Mining Pool</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a class="hot_link" data-page="page_home" href="#"><i class="fa fa-home"></i> Home</a></li>
<li><a class="hot_link" data-page="page_pool_blocks" href="#pool_blocks"><i class="fa fa-cubes"></i> Pool Blocks</a></li>
<li><a class="hot_link" data-page="page_getting_started" href="#getting_started"><i class="fa fa-rocket"></i> Getting Started</a></li>
<li><a class="hot_link" data-page="page_community" href="#community"><i class="fa fa-comments"></i> Community</a></li>
</ul>
<div id="stats_updated">Stats Updated &nbsp;<i class="fa fa-bolt"></i></div>
</div>
</div>
</div>
<div class="container">
<div class="page" id="page_home">
<div id="siteInfo"></div>
<div class="row">
<div class="col-md-4 stats">
<h3>Network</h3>
<div><i class="fa fa-tachometer"></i> Hash Rate: <span id="networkHashrate"></span></div>
<div><i class="fa fa-clock-o"></i> Block Found: <span id="networkLastBlockFound"></span></div>
<div><i class="fa fa-unlock-alt"></i> Difficulty: <span id="networkDifficulty"></span></div>
<div><i class="fa fa-bars"></i> Blockchain Height: <span id="blockchainHeight"></span></div>
<div><i class="fa fa-money"></i> Last Reward: <span id="networkLastReward"></span></div>
<div><i class="fa fa-paw"></i> Last Hash: <a id="lastHash" target="_blank"></a></div>
</div>
<div class="col-md-4 stats">
<h3>Pool</h3>
<div><i class="fa fa-tachometer"></i> Hash Rate: <span id="poolHashrate"></span></div>
<div><i class="fa fa-clock-o"></i> Block Found: <span id="poolLastBlockFound"></span></div>
<div><i class="fa fa-cloud-upload"></i> Round Hashes: <span id="poolRoundHashes"></span></div>
<div><i class="fa fa-users"></i> Connected Miners: <span id="poolMiners"></span></div>
<div><i class="fa fa-money"></i> Fee: <span id="poolFee"></span><span id="donationPercent"></span></div>
<div><i class="fa fa-history"></i> Block Found Every: <span id="blockSolvedTime"></span> (est.)</div>
</div>
<div class="col-md-4 stats">
<h3 id="marketHeader">Market</h3>
<div class="marketFooter">Updated: <span id="marketLastUpdated"></span></div>
<div class="marketFooter">Powered by <a href="https://www.cryptonator.com/">Cryptonator</a></div>
</div>
</div>
<div class="stats">
<h3>Your Stats</h3>
<label for="yourStatsInput"><i class="fa fa-key"></i> Address</label><br>
<input class="form-control" id="yourStatsInput" type="text">
<button class="btn btn-default" id="lookUp"><i class="fa fa-search"></i> Lookup</button>
<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-bank"></i> Pending Balance: <span id="yourPendingBalance"></span></div>
<div class="yourStats"><i class="fa fa-money"></i> Total Paid: <span id="yourPaid"></span></div>
<div class="yourStats"><i class="fa fa-clock-o"></i> Last Share Submitted: <span id="yourLastShare"></span></div>
<div class="yourStats"><i class="fa fa-tachometer"></i> Hash Rate: <span id="yourHashrateHolder"></span></div>
<div class="yourStats"><i class="fa fa-cloud-upload"></i> Total Hashes Submitted: <span id="yourHashes"></span></div>
</div>
</div>
<div class="page" id="page_pool_blocks">
<div class="blocksStatHolder">
<h4>Block Candidates</h4>
<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><span id="averageBlockLuck"></span> Luck Average</span>
</div>
<hr>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th><i class="fa fa-bars"></i> Height</th>
<th title="How many more blocks network must mine before this block is matured"><i class="fa fa-link"></i> Maturity</th>
<th><i class="fa fa-unlock-alt"></i> Difficulty</th>
<th><i class="fa fa-paw"></i> Block Hash</th>
<th><i class="fa fa-clock-o"></i> Time Found</th>
<th><i class="fa fa-star-half-o"></i> Luck</th>
</tr>
</thead>
<tbody id="blocks_rows">
</tbody>
</table>
</div>
</div>
<div class="page" id="page_getting_started">
<h1>Getting Started</h1>
<hr>
<div class="stats">
<h4>Mining Host</h4>
<div><i class="fa fa-cloud"></i> Pool Address: <span id="miningPoolHost"></span></div>
</div>
<h4>Mining Ports</h4>
<div id="miningPorts" class="row">
<div class="stats">
<div><i class="fa fa-tachometer"></i> Port: <span class="miningPort"></span></div>
<div><i class="fa fa-unlock-alt"></i> Starting Difficulty: <span class="miningPortDiff"></span></div>
<div><i class="fa fa-question"></i> Description: <span class="miningPortDesc"></span></div>
</div>
</div>
<hr>
<h4>For <i class="fa fa-windows"></i> Windows users</h4>
<br>
<p class="getting_started_windows">
You can <a class="btn btn-default btn-sm" target="_blank" id="easyminer_link"><i class="fa fa-download"></i> Download</a> and run <a target="_blank" href="https://github.com/zone117x/cryptonote-easy-miner">cryptonote-easy-miner</a> <i class="fa fa-github"></i> which will automatically generate your wallet address
and manage multiple instances of simpleminer to take advantage of all your CPU Cores.
</p>
<hr>
<h4>Or download the the binaries for your system</h4>
<br>
<ol id="getting_started_list">
<li><a id="simplewallet_download" target="_blank" class="btn btn-default btn-sm"><i class="fa fa-download"></i> Download</a> simplewallet for your system</li>
<li>Run simplewallet to generate your public address</li>
<li><a target="_blank" class="btn btn-default btn-sm" href="https://github.com/LucasJones/cpuminer-multi/releases/"><i class="fa fa-download"></i> Download</a> CPUMiner-multi</li>
<li>Choose a port and point your miner to our pool with your address, for example:
<br><br>
<code id="cpuminer_code"></code>
</li>
</ol>
</div>
<div class="page" id="page_community">
<h3>Chat Room</h3>
<iframe id="kiwi_irc" style="border:0; width:100%; height:500px;"></iframe>
<h3>Contact</h3>
<p>Email pool support at <a id="emailLink" href=""></a></p>
</div>
</div>
<footer>
<div class="text-muted">
Powered by <a target="_blank" href="//github.com/zone117x/node-cryptonote-pool"><i class="fa fa-github"></i> node-cryptonote-pool</a>
<span id="poolVersion"></span>
created by Matthew Little & open sourced under the <a href="http://www.gnu.org/licenses/gpl-2.0.html">GPL</a>
</div>
</footer>
</body>
</html>

View File

@ -1,10 +0,0 @@
<!-- Insert your pool's unique HTML here -->
<!-- Example:
<ul>
<li>Update July 24th, we have decreased pool fees to 1%, enjoy!</li>
</lu>
-->

View File

@ -0,0 +1,155 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.0/jquery.timeago.min.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<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>
$(function(){
getStats();
});
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){
$('#totalOwed').text(getReadableCoins(data.totalOwed));
$('#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>
</head>
<body>
<div class="container">
<h3>Admin Center <i id="loading" class="fa fa-circle-o-notch fa-spin"></i></h3>
<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>
</body>
</html>

View File

@ -12,6 +12,6 @@ var cryptonatorWidget = ["XMR-BTC", "XMR-USD", "XMR-EUR", "XMR-GBP"];
var easyminerDownload = "https://github.com/zone117x/cryptonote-easy-miner/releases/";
var simplewalletDownload = "http://bit.ly/monero-starter-pack";
var blockchainExplorer = "http://monerochain.info/block/";
var blockchainExplorer = "http://monerochain.info/block/";
var transactionExplorer = "http://monerochain.info/tx/";

View File

@ -0,0 +1,359 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
<title>Cryptonote Mining Pool</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.0/jquery.timeago.min.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
<link href='http://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>
<script src="config.js"></script>
<script src="custom.js"></script>
<link href="custom.css" rel="stylesheet">
<style>
#coinName{
text-transform: capitalize;
}
body {
padding-top: 90px;
padding-bottom: 80px;
overflow-y: scroll;
}
.container{
font-size: 1.2em;
}
#loading{
font-size: 2em;
}
.stats {
margin-bottom: 10px;
margin-top: 5px;
}
.stats:last-child{
width: auto;
}
.stats > h3 > i{
font-size: 0.80em;
width: 21px;
}
.stats > div{
padding: 5px 0;
}
.stats > div > .fa {
width: 25px;
}
.stats > div > span:first-of-type{
font-weight: bold;
}
#stats_updated{
opacity: 0;
float: right;
margin-left: 30px;
color: #e8e8e8;
line-height: 47px;
font-size: 0.9em;
}
footer{
position: fixed;
bottom: 0;
width: 100%;
background-color: #f5f5f5;
}
footer > div{
margin: 10px auto;
text-align: center;
}
</style>
</head>
<body>
<script>
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);
}
};
$.fn.update = function(txt){
var el = this[0];
if (el.textContent !== txt)
el.textContent = txt;
return this;
};
function updateTextClasses(className, text){
var els = document.getElementsByClassName(className);
for (var i = 0; i < els.length; i++){
var el = els[i];
if (el.textContent !== text)
el.textContent = text;
}
}
function updateText(elementId, text){
var el = document.getElementById(elementId);
if (el.textContent !== text){
el.textContent = text;
}
return el;
}
var currentPage;
var lastStats;
function getReadableCoins(coins, digits, withoutSymbol){
var amount = (parseInt(coins || 0) / coinUnits).toFixed(digits || coinUnits.toString().length - 1);
return amount + (withoutSymbol ? '' : (' ' + lastStats.config.symbol));
}
function formatDate(time){
if (!time) return '';
return new Date(parseInt(time) * 1000).toLocaleString();
}
function formatPaymentLink(hash){
return '<a target="_blank" href="' + transactionExplorer + hash + '">' + hash + '</a>';
}
function getPaymentRowElement(payment, jsonString){
var row = document.createElement('tr');
row.setAttribute('data-json', jsonString);
row.setAttribute('data-time', payment.time);
row.setAttribute('id', 'paymentRow' + payment.time);
row.innerHTML = getPaymentCells(payment);
return row;
}
function parsePayment(time, serializedPayment){
var parts = serializedPayment.split(':');
return {
time: parseInt(time),
hash: parts[0],
amount: parts[1],
fee: parts[2],
mixin: parts[3],
recipients: parts[4]
};
}
function renderPayments(paymentsResults){
var $paymentsRows = $('#payments_rows');
for (var i = 0; i < paymentsResults.length; i += 2){
var payment = parsePayment(paymentsResults[i + 1], paymentsResults[i]);
var paymentJson = JSON.stringify(payment);
var existingRow = document.getElementById('paymentRow' + payment.time);
if (existingRow && existingRow.getAttribute('data-json') !== paymentJson){
$(existingRow).replaceWith(getPaymentRowElement(payment, paymentJson));
}
else if (!existingRow){
var paymentElement = getPaymentRowElement(payment, paymentJson);
var inserted = false;
var rows = $paymentsRows.children().get();
for (var f = 0; f < rows.length; f++) {
var pTime = parseInt(rows[f].getAttribute('data-time'));
if (pTime < payment.time){
inserted = true;
$(rows[f]).before(paymentElement);
break;
}
}
if (!inserted)
$paymentsRows.append(paymentElement);
}
}
}
function pulseLiveUpdate(){
var stats_update = document.getElementById('stats_updated');
stats_update.style.transition = 'opacity 100ms ease-out';
stats_update.style.opacity = 1;
setTimeout(function(){
stats_update.style.transition = 'opacity 7000ms linear';
stats_update.style.opacity = 0;
}, 500);
}
window.onhashchange = function(){
routePage();
};
function fetchLiveStats() {
$.ajax({
url: api + '/live_stats',
dataType: 'json',
cache: 'false'
}).done(function(data){
pulseLiveUpdate();
lastStats = data;
updateIndex();
currentPage.update();
}).always(function () {
fetchLiveStats();
});
}
var xhrPageLoading;
function routePage(loadedCallback) {
if (currentPage) currentPage.destroy();
$('#page').html('');
$('#loading').show();
if (xhrPageLoading)
xhrPageLoading.abort();
$('.hot_link').parent().removeClass('active');
var $link = $('a.hot_link[href="' + (window.location.hash || '#') + '"]');
$link.parent().addClass('active');
var page = $link.data('page');
xhrPageLoading = $.ajax({
url: 'pages/' + page,
cache: false,
success: function (data) {
$('#loading').hide();
$('#page').show().html(data);
currentPage.update();
if (loadedCallback) loadedCallback();
}
});
}
function updateIndex(){
updateText('coinName', lastStats.config.coin);
updateText('poolVersion', lastStats.config.version);
}
$(function(){
$.get(api + '/stats', function(data){
lastStats = data;
updateIndex();
routePage(fetchLiveStats);
});
});
</script>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand"><span id="coinName"></span> Mining Pool</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a class="hot_link" data-page="home.html" href="#">
<i class="fa fa-home"></i> Home
</a></li>
<li><a class="hot_link" data-page="getting_started.html" href="#getting_started">
<i class="fa fa-rocket"></i> Getting Started
</a></li>
<li><a class="hot_link" data-page="pool_blocks.html" href="#pool_blocks">
<i class="fa fa-cubes"></i> Pool Blocks
</a></li>
<li><a class="hot_link" data-page="payments.html" href="#payments">
<i class="fa fa-paper-plane-o"></i> Payments
</a></li>
<li><a class="hot_link" data-page="support.html" href="#support">
<i class="fa fa-comments"></i> Support
</a></li>
</ul>
<div id="stats_updated">Stats Updated &nbsp;<i class="fa fa-bolt"></i></div>
</div>
</div>
</div>
<div class="container">
<div id="page"></div>
<p id="loading" class="text-center"><i class="fa fa-circle-o-notch fa-spin"></i></p>
</div>
<footer>
<div class="text-muted">
Powered by <a target="_blank" href="//github.com/zone117x/node-cryptonote-pool"><i class="fa fa-github"></i> node-cryptonote-pool</a>
<span id="poolVersion"></span>
created by Matthew Little & open sourced under the <a href="http://www.gnu.org/licenses/gpl-2.0.html">GPL</a>
</div>
</footer>
</body>
</html>

View File

@ -0,0 +1,206 @@
<style>
hr{
border-top-color: #d9d9d9 !important;
margin-top: 40px !important;
margin-bottom: 40px !important;
}
#miningPorts > .stats{
width: auto !important;
margin-right: 70px;
margin-left: 15px;
display: inline-block;
}
#miningPorts > .stats:last-child{
margin-right: 0;
}
.getting_started_windows{
line-height: 40px;
}
#getting_started_list > li{
margin-bottom: 20px;
}
#mining_apps > tr:first-child > td{
padding-top: 35px;
}
#mining_apps > tr > td{
border-top: none;
}
#mining_apps > tr:nth-child(even) > td{
border-bottom: 1px solid #e5e5e5;
padding-bottom: 20px;
}
#mining_apps > tr:nth-child(odd) > td{
padding-top: 20px;
}
#mining_apps{
margin-top: 10px;
}
.miningAppTitle{
font-weight: bold;
}
.exampleAddress{
font-style: italic;
}
#cpuminer_code{
display: inline-block;
white-space: normal;
}
</style>
<h3>Connection Details</h3>
<div class="stats">
<div><i class="fa fa-cloud"></i> Mining Pool Address: <span id="miningPoolHost"></span></div>
</div>
<h4>Mining Ports</h4>
<div id="miningPorts" class="row">
<div class="stats">
<div><i class="fa fa-tachometer"></i> Port: <span class="miningPort"></span></div>
<div><i class="fa fa-unlock-alt"></i> Starting Difficulty: <span class="miningPortDiff"></span></div>
<div><i class="fa fa-question"></i> Description: <span class="miningPortDesc"></span></div>
</div>
</div>
<hr>
<h3>For <i class="fa fa-windows"></i> Windows users new to mining</h3>
<p class="getting_started_windows">
You can <a class="btn btn-default btn-sm" target="_blank" id="easyminer_link"><i class="fa fa-download"></i> Download</a>
and run <a target="_blank" href="https://github.com/zone117x/cryptonote-easy-miner">cryptonote-easy-miner</a> <i class="fa fa-github"></i>
which will automatically generate your wallet address and run CPUMiner with the proper parameters.
</p>
<hr>
<h3>Wallet & Daemon Software</h3>
<p>
<ul>
<li><a target="_blank" href="http://monero.cc/getting-started/">Getting started with Monero</a></li>
<li>Monero information and news on its <a href="https://bitcointalk.org/index.php?topic=583449.0">BitcoinTalk announcement thread</a></li>
</ul>
</p>
<hr>
<h3>Mining Apps</h3>
<div class="yourStats table-responsive">
<table class="table">
<thead>
<tr>
<th><i class="fa fa-book"></i> App Name</th>
<th><i class="fa fa-car"></i> Architecture</th>
<th><i class="fa fa-download"></i> Downloads</th>
<th><i class="fa fa-comments"></i> Discussion</th>
<th><i class="fa fa-file-code-o"></i> Source Code</th>
</tr>
</thead>
<tbody id="mining_apps">
<tr>
<td class="miningAppTitle">CPUMiner (forked by LucasJones & Wolf)</td>
<td>CPU</td>
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=632724">BitcoinTalk</a></td>
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=632724">BitcoinTalk</a></td>
<td><a target="_blank" href="https://github.com/wolf9466/cpuminer-multi">Github</a></td>
</tr>
<tr>
<td colspan="5">
<span>Example:</span>
<code>minerd -a cryptonight -o stratum+tcp://<span class="exampleHost"></span>:<span class="examplePort"></span> -u <span class="exampleAddress">YOUR_WALLET_ADDRESS</span> -p x</code>
</td>
</tr>
<tr>
<td class="miningAppTitle">YAM Miner (by yvg1900)</td>
<td>CPU</td>
<td><a target="_blank" href="https://mega.co.nz/#F!UlkU0RyR!E8n4CFkqVu0WoOnsJnQkSg">MEGA</a></td>
<td><a target="_blank" href="https://twitter.com/yvg1900">Twitter</a></td>
<td>Proprietary <i class="fa fa-frown-o"></i></td>
</tr>
<tr>
<td colspan="5">
<span>Example:</span>
<code>yam -c x -M stratum+tcp://<span class="exampleAddress">YOUR_WALLET_ADDRESS</span>:x@<span class="exampleHost"></span>:<span class="examplePort"></span>/xmr</code>
</td>
</tr>
<tr>
<td class="miningAppTitle">Claymore CPU Miner</td>
<td>CPU</td>
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=647251.0">BitcoinTalk</a></td>
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=647251.0">BitcoinTalk</a></td>
<td>Proprietary <i class="fa fa-frown-o"></i></td>
</tr>
<tr>
<td colspan="5">
<span>Example:</span>
<code>NsCpuCNMiner64 -o stratum+tcp://<span class="exampleHost"></span>:<span class="examplePort"></span> -u <span class="exampleAddress">YOUR_WALLET_ADDRESS</span> -p x</code>
</td>
</tr>
<tr>
<td class="miningAppTitle">Claymore GPU Miner</td>
<td>OpenCL (AMD)</td>
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=638915.0">BitcoinTalk</a></td>
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=638915.0">Discussion</a></td>
<td>Proprietary <i class="fa fa-frown-o"></i></td>
</tr>
<tr>
<td colspan="5">
<span>Example:</span>
<code>NsGpuCNMiner -o stratum+tcp://<span class="exampleHost"></span>:<span class="examplePort"></span> -u <span class="exampleAddress">YOUR_WALLET_ADDRESS</span> -p x</code>
</td>
</tr>
<tr>
<td class="miningAppTitle">ccminer (forked by tsiv)</td>
<td>CUDA (Nvidia)</td>
<td><a target="_blank" href="https://github.com/tsiv/ccminer-cryptonight/releases">Github</a></td>
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=656841.msg7487737#msg7487737">BitcoinTalk</a></td>
<td><a target="_blank" href="https://github.com/tsiv/ccminer-cryptonight">Github</a></td>
</tr>
<tr>
<td colspan="5">
<span>Example:</span>
<code>ccminer -o stratum+tcp://<span class="exampleHost"></span>:<span class="examplePort"></span> -u <span class="exampleAddress">YOUR_WALLET_ADDRESS</span> -p x</code>
</td>
</tr>
</tbody>
</table>
</div>
<script>
currentPage = {
destroy: function(){
},
update: function(){
var portsJson = JSON.stringify(lastStats.config.ports);
if (lastPortsJson !== portsJson) {
lastPortsJson = portsJson;
var $miningPortChildren = [];
for (var i = 0; i < lastStats.config.ports.length; i++) {
var portData = lastStats.config.ports[i];
var $portChild = $(miningPortTemplate);
$portChild.find('.miningPort').text(portData.port);
$portChild.find('.miningPortDiff').text(portData.difficulty);
$portChild.find('.miningPortDesc').text(portData.desc);
$miningPortChildren.push($portChild);
}
$miningPorts.empty().append($miningPortChildren);
}
updateTextClasses('exampleHost', poolHost);
updateTextClasses('examplePort', lastStats.config.ports[0].port.toString());
}
};
document.getElementById('easyminer_link').setAttribute('href', easyminerDownload);
document.getElementById('miningPoolHost').textContent = poolHost;
var lastPortsJson = '';
var $miningPorts = $('#miningPorts');
var miningPortTemplate = $miningPorts.html();
$miningPorts.empty();
</script>

View File

@ -0,0 +1,467 @@
<style>
.marketFooter{
font-size: 10px;
opacity: 0.6;
}
#networkLastReward{
text-transform: uppercase;
}
#lastHash{
font-family: 'Inconsolata', monospace;
font-size: 0.8em;
}
#poolDonations{
font-size: 0.75em;
}
#miningProfitCalc{
margin: 35px 0;
}
#calcHashDropdown{
border-radius: 0;
border-left: 0;
border-right: 0;
}
#calcHashHolder{
width: 590px;
max-width: 100%;
}
#calcHashRate{
z-index: inherit;
font-family: 'Inconsolata', monospace;
}
#calcHashAmount{
font-family: 'Inconsolata', monospace;
}
#calcHashResultsHolder{
min-width: 145px;
max-width: 145px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#yourStatsInput{
z-index: inherit;
font-family: 'Inconsolata', monospace;
}
#yourAddressDisplay > span {
font-family: 'Inconsolata', monospace;
}
#lookUp > span:nth-child(2){
display: none;
}
.yourStats{
display: none;
}
#yourAddressDisplay{
display: inline-block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
font-family: 'Inconsolata', monospace;
font-size: 0.9em;
}
#addressError{
color: red;
}
#payments_rows > tr > td{
vertical-align: middle;
font-family: 'Inconsolata', monospace;
font-size: 0.95em;
text-align: center;
}
#payments_rows > tr > td:nth-child(2){
text-align: left;
}
</style>
<div id="siteInfo">
<!-- Description or information about this pool -->
</div>
<div class="row">
<div class="col-md-4 stats">
<h3>Network</h3>
<div><i class="fa fa-tachometer"></i> Hash Rate: <span id="networkHashrate"></span></div>
<div><i class="fa fa-clock-o"></i> Block Found: <span id="networkLastBlockFound"></span></div>
<div><i class="fa fa-unlock-alt"></i> Difficulty: <span id="networkDifficulty"></span></div>
<div><i class="fa fa-bars"></i> Blockchain Height: <span id="blockchainHeight"></span></div>
<div><i class="fa fa-money"></i> Last Reward: <span id="networkLastReward"></span></div>
<div><i class="fa fa-paw"></i> Last Hash: <a id="lastHash" target="_blank"></a></div>
</div>
<div class="col-md-4 stats">
<h3>Our Pool</h3>
<div><i class="fa fa-tachometer"></i> Hash Rate: <span id="poolHashrate"></span></div>
<div><i class="fa fa-clock-o"></i> Block Found: <span id="poolLastBlockFound"></span></div>
<div><i class="fa fa-users"></i> Connected Miners: <span id="poolMiners"></span></div>
<div><i class="fa fa-gift"></i> Donations: <span id="poolDonations"></span></div>
<div><i class="fa fa-money"></i> Total Pool Fee: <span id="poolFee"></span></div>
<div><i class="fa fa-history"></i> Block Found Every: <span id="blockSolvedTime"></span> (est.)</div>
</div>
<div class="col-md-4 stats">
<h3 id="marketHeader">Market</h3>
<div class="marketFooter">Updated: <span id="marketLastUpdated"></span></div>
<div class="marketFooter">Powered by <a href="https://www.cryptonator.com/">Cryptonator</a></div>
</div>
</div>
<hr>
<div id="miningProfitCalc">
<h3>Estimate Mining Profits</h3>
<div id="calcHashHolder">
<div class="input-group">
<input type="number" class="form-control" id="calcHashRate" placeholder="Enter Your Hash Rate">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" id="calcHashDropdown">
<span id="calcHashUnit" data-mul="1">KH/s</span> <span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu" id="calcHashUnits">
<li><a href="#" data-mul="0">H/s</a></li>
<li><a href="#" data-mul="1">KH/s</a></li>
<li><a href="#" data-mul="2">MH/s</a></li>
</ul>
</div>
<span class="input-group-addon">=</span>
<span class="input-group-addon" id="calcHashResultsHolder"><span id="calcHashAmount"></span> <span id="calcHashSymbol"></span>/day</span>
</div>
</div>
</div>
<hr>
<div class="stats">
<h3>Your Stats & Payment History</h3>
<div class="input-group">
<input class="form-control" id="yourStatsInput" type="text" placeholder="Enter Your Address">
<span class="input-group-btn"><button class="btn btn-default" type="button" id="lookUp">
<span><i class="fa fa-search"></i> Lookup</span>
<span><i class="fa fa-refresh fa-spin"></i> Searching...</span>
</button></span>
</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-bank"></i> Pending Balance: <span id="yourPendingBalance"></span></div>
<div class="yourStats"><i class="fa fa-money"></i> Total Paid: <span id="yourPaid"></span></div>
<div class="yourStats"><i class="fa fa-clock-o"></i> Last Share Submitted: <span id="yourLastShare"></span></div>
<div class="yourStats"><i class="fa fa-tachometer"></i> Hash Rate: <span id="yourHashrateHolder"></span></div>
<div class="yourStats"><i class="fa fa-cloud-upload"></i> Total Hashes Submitted: <span id="yourHashes"></span></div>
<br class="yourStats">
<h4 class="yourStats">Payments</h4>
<div class="yourStats table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th><i class="fa fa-clock-o"></i> Time Sent</th>
<th><i class="fa fa-paw"></i> Transaction Hash</th>
<th><i class="fa fa-money"></i> Amount</th>
<th><i class="fa fa-sitemap"></i> Mixin</th>
</tr>
</thead>
<tbody id="payments_rows">
</tbody>
</table>
</div>
<p class="yourStats text-center">
<button type="button" class="btn btn-default" id="loadMorePayments">Load More</button>
</p>
</div>
<script>
currentPage = {
destroy: function(){
$('#networkLastBlockFound,#poolLastBlockFound,#yourLastShare,#marketLastUpdated').timeago('dispose');
if (xhrAddressPoll) xhrAddressPoll.abort();
if (addressTimeout) clearTimeout(addressTimeout);
clearInterval(intervalMarketPolling);
for (var marketPoll in xhrMarketGets){
xhrMarketGets[marketPoll].abort();
}
if (xhrGetPayments) xhrGetPayments.abort();
},
update: function(){
$('#networkLastBlockFound').timeago('update', new Date(lastStats.network.timestamp * 1000).toISOString());
updateText('networkHashrate', getReadableHashRateString(lastStats.network.difficulty / 60) + '/sec');
updateText('networkDifficulty', lastStats.network.difficulty.toString());
updateText('blockchainHeight', lastStats.network.height.toString());
updateText('networkLastReward', getReadableCoins(lastStats.network.reward, 4));
updateText('lastHash', lastStats.network.hash.substr(0, 13) + '...').setAttribute('href',
blockchainExplorer + lastStats.network.hash
);
updateText('poolHashrate', getReadableHashRateString(lastStats.pool.hashrate) + '/sec');
if (lastStats.pool.lastBlockFound){
var d = new Date(parseInt(lastStats.pool.lastBlockFound)).toISOString();
$('#poolLastBlockFound').timeago('update', d);
}
else
$('#poolLastBlockFound').removeAttr('title').data('ts', '').update('Never');
//updateText('poolRoundHashes', lastStats.pool.roundHashes.toString());
updateText('poolMiners', lastStats.pool.miners.toString());
var totalFee = lastStats.config.fee;
if (lastStats.config.doDonations){
totalFee += lastStats.config.donation;
totalFee += lastStats.config.coreDonation;
var feeText = [];
if (lastStats.config.donation > 0) feeText.push(lastStats.config.donation + '% to pool dev');
if (lastStats.config.coreDonation > 0) feeText.push(lastStats.config.coreDonation + '% to core devs');
updateText('poolDonations', feeText.join(', '));
}
else{
updateText('poolDonations', '');
}
updateText('poolFee', totalFee + '%');
updateText('blockSolvedTime', getReadableTime(lastStats.network.difficulty / lastStats.pool.hashrate));
updateText('calcHashSymbol', lastStats.config.symbol);
calcEstimateProfit();
}
};
$('#networkLastBlockFound,#poolLastBlockFound,#yourLastShare,#marketLastUpdated').timeago();
function getReadableTime(seconds){
var units = [ [60, 'second'], [60, 'minute'], [24, 'hour'],
[7, 'day'], [4, 'week'], [12, 'month'], [1, 'year'] ];
function formatAmounts(amount, unit){
var rounded = Math.round(amount);
return '' + rounded + ' ' + unit + (rounded > 1 ? 's' : '');
}
var amount = seconds;
for (var i = 0; i < units.length; i++){
if (amount < units[i][0])
return formatAmounts(amount, units[i][1]);
amount = amount / units[i][0];
}
return formatAmounts(amount, units[units.length - 1][1]);
}
function getReadableHashRateString(hashrate){
var i = 0;
var byteUnits = [' H', ' KH', ' MH', ' GH', ' TH', ' PH' ];
while (hashrate > 1024){
hashrate = hashrate / 1024;
i++;
}
return hashrate.toFixed(2) + byteUnits[i];
}
/* Market data polling */
var intervalMarketPolling = setInterval(updateMarkets, 300000); //poll market data every 5 minutes
var xhrMarketGets = {};
updateMarkets();
function updateMarkets(){
var completedFetches = 0;
var marketsData = [];
for (var i = 0; i < cryptonatorWidget.length; i++){
(function(i){
xhrMarketGets[cryptonatorWidget[i]] = $.get('https://www.cryptonator.com/api/ticker/' + cryptonatorWidget[i], function(data){
marketsData[i] = data;
completedFetches++;
if (completedFetches !== cryptonatorWidget.length) return;
var $marketHeader = $('#marketHeader');
$('.marketTicker').remove();
for (var f = marketsData.length - 1; f >= 0 ; f--){
var price = parseFloat(marketsData[f].ticker.price);
if (price > 1) price = Math.round(price * 100) / 100;
else price = marketsData[f].ticker.price;
$marketHeader.after('<div class="marketTicker">' + marketsData[f].ticker.base + ': <span>' + price + ' ' + marketsData[f].ticker.target + '</span></div>');
}
$('#marketLastUpdated').timeago('update', new Date(marketsData[0].timestamp * 1000).toISOString());
}, 'json');
})(i);
}
}
/* Hash Profitability Calculator */
$('#calcHashRate').keyup(calcEstimateProfit).change(calcEstimateProfit);
$('#calcHashUnits > li > a').click(function(e){
e.preventDefault();
$('#calcHashUnit').text($(this).text()).data('mul', $(this).data('mul'));
calcEstimateProfit();
});
function calcEstimateProfit(){
try {
var rateUnit = Math.pow(1024,parseInt($('#calcHashUnit').data('mul')));
var inp2 = parseFloat($('#calcHashRate').val()) * rateUnit;
var resl = 16 / ((lastStats.network.difficulty / inp2) / 86400);
if (!isNaN(resl)) {
updateText('calcHashAmount', (Math.round(resl * 100) / 100).toString());
return;
}
}
catch(e){ }
updateText('calcHashAmount', '');
}
/* Stats by mining address lookup */
function getPaymentCells(payment){
return '<td>' + formatDate(payment.time) + '</td>' +
'<td>' + formatPaymentLink(payment.hash) + '</td>' +
'<td>' + getReadableCoins(payment.amount, 4, true) + '</td>' +
'<td>' + payment.mixin + '</td>';
}
var xhrAddressPoll;
var addressTimeout;
$('#lookUp').click(function(){
var address = $('#yourStatsInput').val().trim();
if (!address){
$('#yourStatsInput').focus();
return;
}
$('#addressError').hide();
$('.yourStats').hide();
$('#payments_rows').empty();
$('#lookUp > span:first-child').hide();
$('#lookUp > span:last-child').show();
if (xhrAddressPoll) xhrAddressPoll.abort();
if (addressTimeout) clearTimeout(addressTimeout);
function fetchAddressStats(longpoll){
xhrAddressPoll = $.ajax({
url: api + '/stats_address',
data: {
address: address,
longpoll: longpoll
},
dataType: 'json',
cache: 'false',
success: function(data){
$('#lookUp > span:last-child').hide();
$('#lookUp > span:first-child').show();
if (!data.stats){
$('.yourStats').hide();
$('#addressError').text(data.error).show();
if (addressTimeout) clearTimeout(addressTimeout);
addressTimeout = setTimeout(function(){
fetchAddressStats(false);
}, 2000);
return;
}
$('#addressError').hide();
updateText('yourAddressDisplay', address);
if (data.stats.lastShare)
$('#yourLastShare').timeago('update', new Date(parseInt(data.stats.lastShare) * 1000).toISOString());
else
updateText('yourLastShare', 'Never');
updateText('yourHashrateHolder', (data.stats.hashrate || '0 H') + '/sec');
updateText('yourHashes', (data.stats.hashes || 0).toString());
updateText('yourPaid', getReadableCoins(data.stats.paid));
updateText('yourPendingBalance', getReadableCoins(data.stats.balance));
renderPayments(data.payments);
$('.yourStats').show();
docCookies.setItem('mining_address', address, Infinity);
fetchAddressStats(true);
},
error: function(e){
if (e.statusText === 'abort') return;
$('#lookUp').html(lookupBtnHtml);
$('#addressError').text('Connection error').show();
if (addressTimeout) clearTimeout(addressTimeout);
addressTimeout = setTimeout(function(){
fetchAddressStats(false);
}, 2000);
}
});
}
fetchAddressStats(false);
});
var address = docCookies.getItem('mining_address');
if (address){
$('#yourStatsInput').val(address);
$('#lookUp').click();
}
$('#yourStatsInput').keyup(function(e){
if(e.keyCode === 13)
$('#lookUp').click();
});
var xhrGetPayments;
$('#loadMorePayments').click(function(){
if (xhrGetPayments) xhrGetPayments.abort();
xhrGetPayments = $.ajax({
url: api + '/get_payments',
data: {
time: $('#payments_rows').children().last().data('time'),
address: address
},
dataType: 'json',
cache: 'false',
success: function(data){
renderPayments(data);
}
});
});
</script>

View File

@ -0,0 +1,93 @@
<style>
.paymentsStatHolder > span{
display: inline-block;
border-radius: 5px;
padding: 1px 9px;
border: 1px solid #e5e5e5;
margin: 2px;
}
.paymentsStatHolder > span > span{
font-weight: bold;
}
#payments_rows > tr > td{
vertical-align: middle;
font-family: 'Inconsolata', monospace;
font-size: 0.95em;
text-align: center;
}
</style>
<div class="paymentsStatHolder">
<span class="bg-primary">Total Payments: <span id="paymentsTotal"></span></span>
<span class="bg-info">Total Miners Paid: <span id="paymentsTotalPaid"></span></span>
<span class="bg-info">Minimum Payment Threshold: <span id="paymentsMinimum"></span></span>
<span class="bg-info">Denomination Unit: <span id="paymentsDenomination"></span></span>
</div>
<hr>
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th><i class="fa fa-clock-o"></i> Time Sent</th>
<th><i class="fa fa-paw"></i> Transaction Hash</th>
<th><i class="fa fa-money"></i> Amount</th>
<th><i class="fa fa-tag"></i> Fee</th>
<th><i class="fa fa-sitemap"></i> Mixin</th>
<th><i class="fa fa-group"></i> Payees</th>
</tr>
</thead>
<tbody id="payments_rows">
</tbody>
</table>
</div>
<p class="text-center">
<button type="button" class="btn btn-default" id="loadMorePayments">Load More</button>
</p>
<script>
currentPage = {
destroy: function(){
if (xhrGetPayments) xhrGetPayments.abort();
},
update: function(){
updateText('paymentsTotal', lastStats.pool.totalPayments.toString());
updateText('paymentsTotalPaid', lastStats.pool.totalMinersPaid.toString());
updateText('paymentsMinimum', getReadableCoins(lastStats.config.minPaymentThreshold, 3));
updateText('paymentsDenomination', getReadableCoins(lastStats.config.denominationUnit, 3));
renderPayments(lastStats.pool.payments);
}
};
var xhrGetPayments;
$('#loadMorePayments').click(function(){
if (xhrGetPayments) xhrGetPayments.abort();
xhrGetPayments = $.ajax({
url: api + '/get_payments',
data: {
time: $('#payments_rows').children().last().data('time')
},
dataType: 'json',
cache: 'false',
success: function(data){
renderPayments(data);
}
});
});
function getPaymentCells(payment){
return '<td>' + formatDate(payment.time) + '</td>' +
'<td>' + formatPaymentLink(payment.hash) + '</td>' +
'<td>' + getReadableCoins(payment.amount, 4, true) + '</td>' +
'<td>' + getReadableCoins(payment.fee, 4, true) + '</td>' +
'<td>' + payment.mixin + '</td>' +
'<td>' + payment.recipients + '</td>';
}
</script>

View File

@ -0,0 +1,199 @@
<style>
.blocksStatHolder > span{
display: inline-block;
border-radius: 5px;
padding: 1px 9px;
border: 1px solid #e5e5e5;
margin: 2px;
}
.blocksStatHolder > span > span{
font-weight: bold;
}
#blocks_rows > tr > td{
vertical-align: middle;
font-family: 'Inconsolata', monospace;
font-size: 0.95em;
text-align: center;
}
.luckGood{
color: darkgreen;
}
.luckBad{
color: darkred;
}
</style>
<div class="blocksStatHolder">
<span class="bg-primary">Total Blocks Mined: <span id="blocksTotal"></span></span>
<span class="bg-info">Maturity Depth Requirement: <span id="blocksMaturityCount"></span></span>
</div>
<hr>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th><i class="fa fa-bars"></i> Height</th>
<th title="How many more blocks network must mine before this block is matured"><i class="fa fa-link"></i> Maturity</th>
<th><i class="fa fa-unlock-alt"></i> Difficulty</th>
<th><i class="fa fa-paw"></i> Block Hash</th>
<th><i class="fa fa-clock-o"></i> Time Found</th>
<th><i class="fa fa-star-half-o"></i> Luck</th>
</tr>
</thead>
<tbody id="blocks_rows">
</tbody>
</table>
</div>
<p class="text-center">
<button type="button" class="btn btn-default" id="loadMoreBlocks">Load More</button>
</p>
<script>
currentPage = {
destroy: function(){
if (xhrGetBlocks) xhrGetBlocks.abort();
},
update: function(){
updateText('blocksTotal', lastStats.pool.totalBlocks.toString());
updateText('blocksMaturityCount', lastStats.config.depth.toString());
renderBlocks(lastStats.pool.blocks);
}
};
var xhrGetBlocks;
$('#loadMoreBlocks').click(function(){
if (xhrGetBlocks) xhrGetBlocks.abort();
xhrGetBlocks = $.ajax({
url: api + '/get_blocks',
data: {
height: $('#blocks_rows').children().last().data('height')
},
dataType: 'json',
cache: 'false',
success: function(data){
renderBlocks(data);
}
});
});
function parseBlock(height, serializedBlock){
var parts = serializedBlock.split(':');
var block = {
height: parseInt(height),
hash: parts[0],
time: parts[1],
difficulty: parseInt(parts[2]),
shares: parseInt(parts[3]),
orphaned: parts[4],
reward: parts[5]
};
var toGo = lastStats.config.depth - (lastStats.network.height - block.height);
block.maturity = toGo < 1 ? '' : (toGo + ' to go');
switch (block.orphaned){
case '0':
block.status = 'unlocked';
break;
case '1':
block.status = 'orphaned';
break;
default:
block.status = 'pending';
break;
}
return block;
}
function getBlockRowElement(block, jsonString){
function formatLuck(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 formatBlockLink(hash){
return '<a target="_blank" href="' + blockchainExplorer + hash + '">' + hash + '</a>';
}
var blockStatusClasses = {
'pending': '',
'unlocked': 'success',
'orphaned': 'danger'
};
var row = document.createElement('tr');
row.setAttribute('data-json', jsonString);
row.setAttribute('data-height', block.height);
row.setAttribute('id', 'blockRow' + block.height);
row.setAttribute('title', block.status);
row.className = blockStatusClasses[block.status];
var columns =
'<td>' + block.height + '</td>' +
'<td>' + block.maturity + '</td>' +
'<td>' + block.difficulty + '</td>' +
'<td>' + formatBlockLink(block.hash) + '</td>' +
'<td>' + formatDate(block.time) + '</td>' +
'<td>' + formatLuck(block.difficulty, block.shares) + '</td>';
row.innerHTML = columns;
return row;
}
function renderBlocks(blocksResults){
var $blocksRows = $('#blocks_rows');
for (var i = 0; i < blocksResults.length; i += 2){
var block = parseBlock(blocksResults[i + 1], blocksResults[i]);
var blockJson = JSON.stringify(block);
var existingRow = document.getElementById('blockRow' + block.height);
if (existingRow && existingRow.getAttribute('data-json') !== blockJson){
$(existingRow).replaceWith(getBlockRowElement(block, blockJson));
}
else if (!existingRow){
var blockElement = getBlockRowElement(block, blockJson);
var inserted = false;
var rows = $blocksRows.children().get();
for (var f = 0; f < rows.length; f++) {
var bHeight = parseInt(rows[f].getAttribute('data-height'));
if (bHeight < block.height){
inserted = true;
$(rows[f]).before(blockElement);
break;
}
}
if (!inserted)
$blocksRows.append(blockElement);
}
}
}
</script>

View File

@ -0,0 +1,24 @@
<h3>Contact</h3>
<p>Email pool support at <a id="emailLink" href=""></a></p>
<h3>Chat Room</h3>
<iframe id="kiwi_irc" style="border:0; width:100%; height:500px;"></iframe>
<script>
currentPage = {
destroy: function(){
},
update: function(){
}
};
document.getElementById('kiwi_irc').setAttribute('src', 'https://kiwiirc.com/client/' + irc);
var emailEl = document.getElementById('emailLink');
emailEl.setAttribute('href', 'mailto:' + email);
emailEl.textContent = email;
</script>