Merge pull request #109 from hellcatz/patch-2

Major Updates 2

```major: added support for komodo, zcash, zclassic, (zdash not tested)
coins: added requireShielding boolean
coins: updated zcash_testnet founders addresses
configs: added komodo_example.conf
api.js: added payments json api call
stats.js: improved getTotalSharesByAdress to support multiple coins.
stats.js: report more collected stats
stats.js: report payment stats
stats.js: optimizations to historical data saving
paymentProcessor.js: added requireShielding support
paymentProcessor.js: added support to pay directly from komodo pool address without shielding first
paymentProcessor.js: lower tx fee reserve for komodo to 0.00005 KMD
paymentProcessor.js: tx fee reserve for all coins is 0.0004
paymentProcessor.js: improved multi-coin support
paymentProcessor.js: added minConfShield and minConfPayout variables
paymentProcessor.js: updated coin network stat caching
paymentProcessor.js: improved operation id handling when shielding coins
paymentProcessor.js: updated coin network stat caching
paymentProcessor.js: updated payment stat collection in redis
miner_stats.html: removed ZEC references```
This commit is contained in:
Procrastinator 2017-04-09 22:37:47 -04:00 committed by GitHub
commit 05968ef296
13 changed files with 336 additions and 184 deletions

View File

@ -1,5 +1,6 @@
{
"name": "komodo",
"symbol": "kmd",
"algorithm": "equihash"
"algorithm": "equihash",
"txfee": 0.00005
}

View File

@ -2,7 +2,7 @@
"name": "zcash",
"symbol": "zec",
"algorithm": "equihash",
"requireShielding": true,
"payFoundersReward": true,
"percentFoundersReward": 20,
"maxFoundersRewardBlockHeight": 849999,
@ -56,5 +56,6 @@
"t3PSn5TbMMAEw7Eu36DYctFezRzpX1hzf3M",
"t3R3Y5vnBLrEn8L6wFjPjBLnxSUQsKnmFpv",
"t3Pcm737EsVkGTbhsu2NekKtJeG92mvYyoN"
]
],
"txfee": 0.0004
}

View File

@ -2,23 +2,24 @@
"name": "zcash_testnet",
"symbol": "taz",
"algorithm": "equihash",
"requireShielding": true,
"payFoundersReward": true,
"percentFoundersReward": 20,
"maxFoundersRewardBlockHeight": 849999,
"foundersRewardAddressChangeInterval": 17709.3125,
"vFoundersRewardAddress": [
"t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi", "t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543", "t2NGQjYMQhFndDHguvUw4wZdNdsssA6K7x2", "t2ENg7hHVqqs9JwU5cgjvSbxnT2a9USNfhy",
"t2BkYdVCHzvTJJUTx4yZB8qeegD8QsPx8bo", "t2J8q1xH1EuigJ52MfExyyjYtN3VgvshKDf", "t2Crq9mydTm37kZokC68HzT6yez3t2FBnFj", "t2EaMPUiQ1kthqcP5UEkF42CAFKJqXCkXC9",
"t2F9dtQc63JDDyrhnfpzvVYTJcr57MkqA12", "t2LPirmnfYSZc481GgZBa6xUGcoovfytBnC", "t26xfxoSw2UV9Pe5o3C8V4YybQD4SESfxtp", "t2D3k4fNdErd66YxtvXEdft9xuLoKD7CcVo",
"t2DWYBkxKNivdmsMiivNJzutaQGqmoRjRnL", "t2C3kFF9iQRxfc4B9zgbWo4dQLLqzqjpuGQ", "t2MnT5tzu9HSKcppRyUNwoTp8MUueuSGNaB", "t2AREsWdoW1F8EQYsScsjkgqobmgrkKeUkK",
"t2Vf4wKcJ3ZFtLj4jezUUKkwYR92BLHn5UT", "t2K3fdViH6R5tRuXLphKyoYXyZhyWGghDNY", "t2VEn3KiKyHSGyzd3nDw6ESWtaCQHwuv9WC", "t2F8XouqdNMq6zzEvxQXHV1TjwZRHwRg8gC",
"t2BS7Mrbaef3fA4xrmkvDisFVXVrRBnZ6Qj", "t2FuSwoLCdBVPwdZuYoHrEzxAb9qy4qjbnL", "t2SX3U8NtrT6gz5Db1AtQCSGjrpptr8JC6h", "t2V51gZNSoJ5kRL74bf9YTtbZuv8Fcqx2FH",
"t2FyTsLjjdm4jeVwir4xzj7FAkUidbr1b4R", "t2EYbGLekmpqHyn8UBF6kqpahrYm7D6N1Le", "t2NQTrStZHtJECNFT3dUBLYA9AErxPCmkka", "t2GSWZZJzoesYxfPTWXkFn5UaxjiYxGBU2a",
"t2RpffkzyLRevGM3w9aWdqMX6bd8uuAK3vn", "t2JzjoQqnuXtTGSN7k7yk5keURBGvYofh1d", "t2AEefc72ieTnsXKmgK2bZNckiwvZe3oPNL", "t2NNs3ZGZFsNj2wvmVd8BSwSfvETgiLrD8J",
"t2ECCQPVcxUCSSQopdNquguEPE14HsVfcUn", "t2JabDUkG8TaqVKYfqDJ3rqkVdHKp6hwXvG", "t2FGzW5Zdc8Cy98ZKmRygsVGi6oKcmYir9n", "t2DUD8a21FtEFn42oVLp5NGbogY13uyjy9t",
"t2UjVSd3zheHPgAkuX8WQW2CiC9xHQ8EvWp", "t2TBUAhELyHUn8i6SXYsXz5Lmy7kDzA1uT5", "t2Tz3uCyhP6eizUWDc3bGH7XUC9GQsEyQNc", "t2NysJSZtLwMLWEJ6MH3BsxRh6h27mNcsSy",
"t2KXJVVyyrjVxxSeazbY9ksGyft4qsXUNm9", "t2J9YYtH31cveiLZzjaE4AcuwVho6qjTNzp", "t2QgvW4sP9zaGpPMH1GRzy7cpydmuRfB4AZ", "t2NDTJP9MosKpyFPHJmfjc5pGCvAU58XGa4",
"t29pHDBWq7qN4EjwSEHg8wEqYe9pkmVrtRP", "t2Ez9KM8VJLuArcxuEkNRAkhNvidKkzXcjJ", "t2D5y7J5fpXajLbGrMBQkFg2mFN8fo3n8cX", "t2UV2wr1PTaUiybpkV3FdSdGxUJeZdZztyt"
]
"vFoundersRewardAddress": [
"t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi", "t2N9PH9Wk9xjqYg9iin1Ua3aekJqfAtE543", "t2NGQjYMQhFndDHguvUw4wZdNdsssA6K7x2", "t2ENg7hHVqqs9JwU5cgjvSbxnT2a9USNfhy",
"t2BkYdVCHzvTJJUTx4yZB8qeegD8QsPx8bo", "t2J8q1xH1EuigJ52MfExyyjYtN3VgvshKDf", "t2Crq9mydTm37kZokC68HzT6yez3t2FBnFj", "t2EaMPUiQ1kthqcP5UEkF42CAFKJqXCkXC9",
"t2F9dtQc63JDDyrhnfpzvVYTJcr57MkqA12", "t2LPirmnfYSZc481GgZBa6xUGcoovfytBnC", "t26xfxoSw2UV9Pe5o3C8V4YybQD4SESfxtp", "t2D3k4fNdErd66YxtvXEdft9xuLoKD7CcVo",
"t2DWYBkxKNivdmsMiivNJzutaQGqmoRjRnL", "t2C3kFF9iQRxfc4B9zgbWo4dQLLqzqjpuGQ", "t2MnT5tzu9HSKcppRyUNwoTp8MUueuSGNaB", "t2AREsWdoW1F8EQYsScsjkgqobmgrkKeUkK",
"t2Vf4wKcJ3ZFtLj4jezUUKkwYR92BLHn5UT", "t2K3fdViH6R5tRuXLphKyoYXyZhyWGghDNY", "t2VEn3KiKyHSGyzd3nDw6ESWtaCQHwuv9WC", "t2F8XouqdNMq6zzEvxQXHV1TjwZRHwRg8gC",
"t2BS7Mrbaef3fA4xrmkvDisFVXVrRBnZ6Qj", "t2FuSwoLCdBVPwdZuYoHrEzxAb9qy4qjbnL", "t2SX3U8NtrT6gz5Db1AtQCSGjrpptr8JC6h", "t2V51gZNSoJ5kRL74bf9YTtbZuv8Fcqx2FH",
"t2FyTsLjjdm4jeVwir4xzj7FAkUidbr1b4R", "t2EYbGLekmpqHyn8UBF6kqpahrYm7D6N1Le", "t2NQTrStZHtJECNFT3dUBLYA9AErxPCmkka", "t2GSWZZJzoesYxfPTWXkFn5UaxjiYxGBU2a",
"t2RpffkzyLRevGM3w9aWdqMX6bd8uuAK3vn", "t2JzjoQqnuXtTGSN7k7yk5keURBGvYofh1d", "t2AEefc72ieTnsXKmgK2bZNckiwvZe3oPNL", "t2NNs3ZGZFsNj2wvmVd8BSwSfvETgiLrD8J",
"t2ECCQPVcxUCSSQopdNquguEPE14HsVfcUn", "t2JabDUkG8TaqVKYfqDJ3rqkVdHKp6hwXvG", "t2FGzW5Zdc8Cy98ZKmRygsVGi6oKcmYir9n", "t2DUD8a21FtEFn42oVLp5NGbogY13uyjy9t",
"t2UjVSd3zheHPgAkuX8WQW2CiC9xHQ8EvWp", "t2TBUAhELyHUn8i6SXYsXz5Lmy7kDzA1uT5", "t2Tz3uCyhP6eizUWDc3bGH7XUC9GQsEyQNc", "t2NysJSZtLwMLWEJ6MH3BsxRh6h27mNcsSy",
"t2KXJVVyyrjVxxSeazbY9ksGyft4qsXUNm9", "t2J9YYtH31cveiLZzjaE4AcuwVho6qjTNzp", "t2QgvW4sP9zaGpPMH1GRzy7cpydmuRfB4AZ", "t2NDTJP9MosKpyFPHJmfjc5pGCvAU58XGa4",
"t29pHDBWq7qN4EjwSEHg8wEqYe9pkmVrtRP", "t2Ez9KM8VJLuArcxuEkNRAkhNvidKkzXcjJ", "t2D5y7J5fpXajLbGrMBQkFg2mFN8fo3n8cX", "t2UV2wr1PTaUiybpkV3FdSdGxUJeZdZztyt"
],
"txfee": 0.0004
}

View File

@ -2,5 +2,7 @@
"name": "zclassic",
"symbol": "zcl",
"algorithm": "equihash",
"peerMagic": "24e92764"
"requireShielding": true,
"peerMagic": "24e92764",
"txfee": 0.0004
}

View File

@ -1,5 +1,7 @@
{
"name": "zdash",
"symbol": "zdash",
"algorithm": "equihash"
"algorithm": "equihash",
"requireShielding": true,
"txfee": 0.0004
}

View File

@ -10,7 +10,7 @@
},
"defaultPoolConfigs": {
"blockRefreshInterval": 30,
"blockRefreshInterval": 500,
"jobRebroadcastTimeout": 55,
"connectionTimeout": 600,
"emitInvalidBlockHashes": false,
@ -36,7 +36,7 @@
"stratumHost": "cryppit.com",
"stats": {
"updateInterval": 30,
"historicalRetention": 43200,
"historicalRetention": 14400,
"hashrateWindow": 300
},
"adminCenter": {

View File

@ -19,6 +19,13 @@ module.exports = function(logger, portalConfig, poolConfigs){
return;
case 'pool_stats':
res.end(JSON.stringify(portalStats.statPoolHistory));
return;
case 'payments':
var poolBlocks = [];
for(var pool in portalStats.stats.pools) {
poolBlocks.push({name: pool, pending: portalStats.stats.pools[pool].pending, payments: portalStats.stats.pools[pool].payments});
}
res.end(JSON.stringify(poolBlocks));
return;
case 'worker_stats':
if (req.url.indexOf("?")>0) {
@ -56,7 +63,6 @@ module.exports = function(logger, portalConfig, poolConfigs){
//console.log(portalStats.statHistory[h].time);
}
}
// note, h is the last record from above loop, which is latest
for(var pool in portalStats.stats.pools) {
for(var w in portalStats.stats.pools[pool].workers){
if (w.startsWith(address)) {

View File

@ -51,6 +51,14 @@ function SetupForPool(logger, poolOptions, setupFinished){
var logComponent = coin;
var opidCount = 0;
var minConfShield = 3;
var minConfPayout = 10;
var requireShielding = poolOptions.coin.requireShielding === true;
var fee = parseFloat(poolOptions.coin.txfee) || parseFloat(0.0004);
logger.special(logSystem, logComponent, logComponent + ' requireShielding: ' + requireShielding);
var daemon = new Stratum.daemon.interface([processingConfig.daemon], function(severity, message){
logger[severity](logSystem, logComponent, message);
});
@ -214,14 +222,14 @@ function SetupForPool(logger, poolOptions, setupFinished){
return;
if ((tBalance - 10000) < 0)
return;
// do not allow more than a single z_sendmany operation at a time
if (opidCount > 0) {
logger.warning(logSystem, logComponent, 'sendTToZ is waiting, too many z_sendmany operations already in progress.');
return;
}
var amount = balanceRound((tBalance - 10000) / magnitude);
var amount = balanceRound((tBalance - 10000) / magnitude);
var params = [poolOptions.address, [{'address': poolOptions.zAddress, 'amount': amount}]];
daemon.cmd('z_sendmany', params,
function (result) {
@ -240,7 +248,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
}
);
}
// send z_address balance to t_address
function sendZToT (callback, zBalance) {
if (callback === true)
@ -253,12 +261,12 @@ function SetupForPool(logger, poolOptions, setupFinished){
logger.warning(logSystem, logComponent, 'sendZToT is waiting, too many z_sendmany operations already in progress.');
return;
}
var amount = balanceRound((zBalance - 10000) / magnitude);
// no more than 100 ZEC at a time
if (amount > 100.0)
amount = 100.0;
var params = [poolOptions.zAddress, [{'address': poolOptions.tAddress, 'amount': amount}]];
daemon.cmd('z_sendmany', params,
function (result) {
@ -278,119 +286,130 @@ function SetupForPool(logger, poolOptions, setupFinished){
}
);
}
function cacheZCashNetworkStats () {
function cacheNetworkStats () {
var params = null;
daemon.cmd('getmininginfo', params,
function (result) {
var finalRedisCommands = [];
var coin = logComponent;
if (result.error) {
logger.error(logSystem, logComponent, 'Error getting stats from zcashd'
logger.error(logSystem, logComponent, 'Error with RPC call `getmininginfo`'
+ JSON.stringify(result.error));
return;
} else {
logger.special(logSystem, logComponent, "Updating "+logComponent+" network stats...");
var coin = logComponent;
var finalRedisCommands = [];
finalRedisCommands.push(['hset', coin + ':stats', 'networkBlocks', result[0].response.blocks]);
finalRedisCommands.push(['hset', coin + ':stats', 'networkDiff', result[0].response.difficulty]);
finalRedisCommands.push(['hset', coin + ':stats', 'networkSols', result[0].response.networksolps]);
redisClient.multi(finalRedisCommands).exec(function(error, results){
if (error){
logger.error(logSystem, logComponent, 'Could not update zcash stats to redis ' + JSON.stringify(error));
return;
}
});
if (result[0].response.blocks !== null) {
finalRedisCommands.push(['hset', coin + ':stats', 'networkBlocks', result[0].response.blocks]);
finalRedisCommands.push(['hset', coin + ':stats', 'networkDiff', result[0].response.difficulty]);
finalRedisCommands.push(['hset', coin + ':stats', 'networkSols', result[0].response.networksolps]);
} else {
logger.error(logSystem, logComponent, "Error parse RPC call reponse.blocks tp `getmininginfo`." + JSON.stringify(result[0].response));
}
}
daemon.cmd('getinfo', params,
daemon.cmd('getnetworkinfo', params,
function (result) {
if (result.error) {
logger.error(logSystem, logComponent, 'Error getting stats from zcashd'
logger.error(logSystem, logComponent, 'Error with RPC call `getnetworkinfo`'
+ JSON.stringify(result.error));
return;
} else {
var coin = logComponent;
var finalRedisCommands = [];
finalRedisCommands.push(['hset', coin + ':stats', 'networkConnections', result[0].response.connections]);
redisClient.multi(finalRedisCommands).exec(function(error, results){
if (error){
logger.error(logSystem, logComponent, 'Could not update zcash stats to redis ' + JSON.stringify(error));
return;
}
});
if (result[0].response !== null) {
finalRedisCommands.push(['hset', coin + ':stats', 'networkConnections', result[0].response.connections]);
finalRedisCommands.push(['hset', coin + ':stats', 'networkVersion', result[0].response.version]);
finalRedisCommands.push(['hset', coin + ':stats', 'networkSubVersion', result[0].response.subversion]);
finalRedisCommands.push(['hset', coin + ':stats', 'networkProtocolVersion', result[0].response.protocolversion]);
} else {
logger.error(logSystem, logComponent, "Error parse RPC call response to `getnetworkinfo`." + JSON.stringify(result[0].response));
}
}
redisClient.multi(finalRedisCommands).exec(function(error, results){
if (error){
logger.error(logSystem, logComponent, 'Error update coin stats to redis ' + JSON.stringify(error));
return;
}
});
}
);
);
}
);
}
// run coinbase coin transfers every x minutes
var intervalState = 0; // do not send ZtoT and TtoZ and same time, this results in operation failed!
var interval = poolOptions.walletInterval * 60 * 1000; // run every x minutes
setInterval(function() {
intervalState++;
switch (intervalState){
case 1:
listUnspent(poolOptions.address, null, 1, false, sendTToZ);
break;
default:
listUnspentZ(poolOptions.zAddress, 1, false, sendZToT);
//listUnspent(null, poolOptions.address, 1, true, function (){});
intervalState = 0;
break;
// shielding not required for some equihash coins
if (requireShielding === true) {
intervalState++;
switch (intervalState) {
case 1:
listUnspent(poolOptions.address, null, minConfShield, false, sendTToZ);
break;
default:
listUnspentZ(poolOptions.zAddress, minConfShield, false, sendZToT);
intervalState = 0;
break;
}
}
// update zcash stats
cacheZCashNetworkStats();
// update network stats using coin daemon
cacheNetworkStats();
}, interval);
// check operation statuses every x seconds
var opid_interval = poolOptions.walletInterval * 1000;
setInterval(function(){
var checkOpIdSuccessAndGetResult = function(ops) {
ops.forEach(function(op, i){
if (op.status == "success" || op.status == "failed") {
daemon.cmd('z_getoperationresult', [[op.id]], function (result) {
if (result.error) {
logger.warning(logSystem, logComponent, 'Unable to get payment operation id result ' + JSON.stringify(result));
}
if (result.response) {
if (opidCount > 0) {
opidCount = 0;
// shielding not required for some equihash coins
if (requireShielding === true) {
setInterval(function(){
var checkOpIdSuccessAndGetResult = function(ops) {
ops.forEach(function(op, i){
if (op.status == "success" || op.status == "failed") {
daemon.cmd('z_getoperationresult', [[op.id]], function (result) {
if (result.error) {
logger.warning(logSystem, logComponent, 'Unable to get payment operation id result ' + JSON.stringify(result));
}
if (op.status == "failed") {
if (op.error) {
logger.error(logSystem, logComponent, "Payment operation failed " + op.id + " " + op.error.code +", " + op.error.message);
} else {
logger.error(logSystem, logComponent, "Payment operation failed " + op.id);
if (result.response) {
if (opidCount > 0) {
opidCount = 0;
}
if (op.status == "failed") {
if (op.error) {
logger.error(logSystem, logComponent, "Payment operation failed " + op.id + " " + op.error.code +", " + op.error.message);
} else {
logger.error(logSystem, logComponent, "Payment operation failed " + op.id);
}
} else {
logger.special(logSystem, logComponent, 'Payment operation success ' + op.id + ' txid: ' + op.result.txid);
}
} else {
logger.special(logSystem, logComponent, 'Payment operation success ' + op.id + ' txid: ' + op.result.txid);
}
}, true, true);
} else if (op.status == "executing") {
if (opidCount == 0) {
opidCount++;
logger.special(logSystem, logComponent, 'Payment operation in progress ' + op.id );
}
}, true, true);
} else if (op.status == "executing") {
if (opidCount == 0) {
opidCount++;
logger.special(logSystem, logComponent, 'Payment operation in progress ' + op.id );
}
}
});
};
daemon.cmd('z_getoperationstatus', null, function (result) {
if (result.error) {
logger.warning(logSystem, logComponent, 'Unable to get operation ids for clearing.');
}
if (result.response) {
checkOpIdSuccessAndGetResult(result.response);
}
}, true, true);
}, opid_interval);
});
};
daemon.cmd('z_getoperationstatus', null, function (result) {
if (result.error) {
logger.warning(logSystem, logComponent, 'Unable to get operation ids for clearing.');
}
if (result.response) {
checkOpIdSuccessAndGetResult(result.response);
}
}, true, true);
}, opid_interval);
}
var satoshisToCoins = function(satoshis){
return parseFloat((satoshis / magnitude).toFixed(coinPrecision));
};
var coinsToSatoshies = function(coins){
return coins * magnitude;
return Math.round(coins * magnitude);
};
function balanceRound(number) {
@ -420,7 +439,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
/* Call redis to get an array of rounds - which are coinbase transactions and block heights from submitted
blocks. */
function(callback){
function(callback){
startRedisTimer();
redisClient.multi([
['hgetall', coin + ':balances'],
@ -452,7 +471,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
callback(null, workers, rounds);
});
},
/* Does a batch rpc call to daemon with all the transaction hashes to see if they are confirmed yet.
It also adds the block reward amount to the round object - which the daemon gives also gives us. */
@ -464,11 +483,11 @@ function SetupForPool(logger, poolOptions, setupFinished){
});
// guarantee a response for batchRPCcommand2
batchRPCcommand2.push(['getblockcount']);
startRPCTimer();
daemon.batchCmd(batchRPCcommand2, function(error, blockDetails){
endRPCTimer();
// error getting block info by hash?
if (error || !blockDetails){
logger.error(logSystem, logComponent, 'Check finished - daemon rpc error with batch getblock '
@ -476,35 +495,35 @@ function SetupForPool(logger, poolOptions, setupFinished){
callback(true);
return;
}
// update confirmations in redis for pending blocks
var confirmsUpdate = blockDetails.map(function(b){
var confirmsUpdate = blockDetails.map(function(b) {
if (b.result != null && b.result.confirmations > 0) {
if (b.result.confirmations > 100) {
return ['hdel', logComponent + ':blocksPendingConfirms', b.result.hash];
return ['hdel', logComponent + ':blocksPendingConfirms', b.result.hash];
}
return ['hset', logComponent + ':blocksPendingConfirms', b.result.hash, b.result.confirmations];
}
return null;
});
// filter nulls, last item is always null...
confirmsUpdate = confirmsUpdate.filter(function(val) { return val !== null; });
// guarantee at least one redis update
if (confirmsUpdate.length < 1)
confirmsUpdate.push(['hset', logComponent + ':blocksPendingConfirms', 0, 0]);
startRedisTimer();
redisClient.multi(confirmsUpdate).exec(function(error, updated){
endRedisTimer();
if (error){
logger.error(logSystem, logComponent, 'failed to update pending block confirmations'
+ JSON.stringify(error));
callback(true);
return;
}
// check for invalid blocks by block hash
blockDetails.forEach(function(block, i) {
// this is just the response from getblockcount
@ -550,35 +569,35 @@ function SetupForPool(logger, poolOptions, setupFinished){
}
}
});
// now check block transaction ids
var batchRPCcommand = rounds.map(function(r){
return ['gettransaction', [r.txHash]];
});
});
// guarantee a response for batchRPCcommand
batchRPCcommand.push(['getaccount', [poolOptions.address]]);
startRPCTimer();
daemon.batchCmd(batchRPCcommand, function(error, txDetails){
endRPCTimer();
if (error || !txDetails){
logger.error(logSystem, logComponent, 'Check finished - daemon rpc error with batch gettransactions '
+ JSON.stringify(error));
callback(true);
return;
}
var addressAccount = "";
// check for transaction errors and generated coins
txDetails.forEach(function(tx, i){
if (i === txDetails.length - 1){
addressAccount = tx.result;
return;
}
var round = rounds[i];
if (tx.error && tx.error.code === -5){
logger.warning(logSystem, logComponent, 'Daemon reports invalid transaction: ' + round.txHash);
@ -612,9 +631,9 @@ function SetupForPool(logger, poolOptions, setupFinished){
round.category = generationTx.category;
if (round.category === 'generate') {
round.reward = generationTx.amount - 0.0004 || generationTx.value - 0.0004; // TODO: Adjust fees to be dynamic
round.reward = balanceRound(generationTx.amount - fee) || balanceRound(generationTx.value - fee); // TODO: Adjust fees to be dynamic
}
});
var canDeleteShares = function(r){
@ -629,7 +648,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
}
return true;
};
//Filter out all rounds that are immature (not confirmed or orphaned yet)
rounds = rounds.filter(function(r){
switch (r.category) {
@ -643,18 +662,28 @@ function SetupForPool(logger, poolOptions, setupFinished){
}
});
// check if we have enough tAddress funds to send payments
var totalOwed = 0;
var feeSatoshi = fee * magnitude;
// calculate what the pool owes its miners
var totalOwed = parseInt(0);
for (var i = 0; i < rounds.length; i++) {
totalOwed = totalOwed + (rounds[i].reward * magnitude) - 4000; // TODO: make tx fees dynamic
totalOwed = totalOwed + Math.round(rounds[i].reward * magnitude) - feeSatoshi; // TODO: make tx fees dynamic
}
listUnspent(null, poolOptions.address, 1, false, function (error, tBalance){
if (tBalance < totalOwed) {
logger.error(logSystem, logComponent, (tBalance / magnitude).toFixed(8) + ' is not enough payment funds to process ' + (totalOwed / magnitude).toFixed(8) + ' of payments. (Possibly due to pending txs)');
var notAddr = null;
if (requireShielding === true) {
notAddr = poolOptions.address;
}
// check if we have enough tAddress funds to brgin payment processing
listUnspent(null, notAddr, minConfPayout, false, function (error, tBalance){
if (error) {
logger.error(logSystem, logComponent, 'Error checking pool balance before payouts. (Unable to begin payment process)');
return callback(true);
}
else {
} else if (tBalance < totalOwed) {
logger.error(logSystem, logComponent, 'Insufficient pool funds to being payment process; '+(tBalance / magnitude).toFixed(8) + ' < ' + (totalOwed / magnitude).toFixed(8)+'. (Possibly due to pending txs) ');
return callback(true);
} else {
// zcash daemon does not support account feature
addressAccount = "";
callback(null, workers, rounds, addressAccount);
@ -692,7 +721,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
+ round.height + ' blockHash: ' + round.blockHash);
return;
}
switch (round.category){
case 'kicked':
case 'orphan':
@ -775,7 +804,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
worker.sent = 0;
}
}
// if no payouts...continue to next set of callbacks
if (Object.keys(addressAmounts).length === 0){
callback(null, workers, rounds);
@ -803,7 +832,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
var higherPercent = withholdPercent + 0.01;
logger.warning(logSystem, logComponent, 'Not enough funds to cover the tx fees for sending out payments, decreasing rewards by '
+ (higherPercent * 100) + '% and retrying');
trySend(higherPercent);
}
else if (result.error && result.error.code === -5) {
@ -828,30 +857,30 @@ function SetupForPool(logger, poolOptions, setupFinished){
return;
}
else {
// make sure sendmany gives us back a txid
var txid = null;
if (result.response) {
txid = result.response;
}
if (txid != null) {
// it worked, congrats on your pools payout ;)
logger.special(logSystem, logComponent, 'Sent ' + (totalSent / magnitude).toFixed(8)
+ ' to ' + Object.keys(addressAmounts).length + ' miners; txid: '+txid);
if (withholdPercent > 0) {
logger.warning(logSystem, logComponent, 'Had to withhold ' + (withholdPercent * 100)
+ '% of reward from miners to cover transaction fees. '
+ 'Fund pool wallet with coins to prevent this from happening');
}
// save payments data to redis
var paymentBlocks = rounds.map(function(r){
return parseInt(r.height);
});
var paymentsUpdate = [];
var paymentsData = [{txid:txid, paid:balanceRound(totalSent / magnitude), shares:totalShares, miners:Object.keys(addressAmounts).length}, {blocks: paymentBlocks}, addressAmounts];
var paymentsData = {time:Date.now(), txid:txid, shares:totalShares, paid:balanceRound(totalSent / magnitude), miners:Object.keys(addressAmounts).length, blocks: paymentBlocks, amounts: addressAmounts};
paymentsUpdate.push(['zadd', logComponent + ':payments', Date.now(), JSON.stringify(paymentsData)]);
startRedisTimer();
redisClient.multi(paymentsUpdate).exec(function(error, payments){
@ -861,11 +890,11 @@ function SetupForPool(logger, poolOptions, setupFinished){
}
callback(null, workers, rounds);
});
} else {
clearInterval(paymentInterval);
logger.error(logSystem, logComponent, 'Error RPC sendmany did not return txid '
+ JSON.stringify(result) + 'Disabling payment processing to prevent possible double-payouts.');
@ -882,11 +911,12 @@ function SetupForPool(logger, poolOptions, setupFinished){
},
function(workers, rounds, callback){
var totalPaid = 0;
var totalPaid = parseFloat(0);
var balanceUpdateCommands = [];
var workerPayoutsCommand = [];
// update worker paid/balance stats
for (var w in workers) {
var worker = workers[w];
if (worker.balanceChange !== 0){
@ -916,6 +946,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
}
};
// handle the round
rounds.forEach(function(r){
switch(r.category){
case 'kicked':
@ -994,5 +1025,4 @@ function SetupForPool(logger, poolOptions, setupFinished){
else return address;
};
}

View File

@ -2,7 +2,6 @@ var zlib = require('zlib');
var redis = require('redis');
var async = require('async');
//var fancyTimestamp = require('fancy-timestamp');
var os = require('os');
@ -176,11 +175,18 @@ module.exports = function(logger, portalConfig, poolConfigs){
var client = redisClients[0].client,
coins = redisClients[0].coins,
shares = [];
var totalShares = 0;
var pindex = parseInt(0);
var totalShares = parseFloat(0);
async.each(_this.stats.pools, function(pool, pcb) {
pindex++;
var coin = String(_this.stats.pools[pool.name].name);
client.hscan(coin + ':shares:roundCurrent', 0, "match", a+"*", function(error, result) {
var workerName = "";
if (error) {
pcb(error);
return;
}
var workerName="";
var shares = 0;
for (var i in result[1]) {
if (Math.abs(i % 2) != 1) {
@ -189,15 +195,20 @@ module.exports = function(logger, portalConfig, poolConfigs){
shares += parseFloat(result[1][i]);
}
}
totalShares = shares;
pcb();
if (shares>0) {
totalShares = shares;
}
pcb();
});
}, function(err) {
if (err) {
cback(0);
return;
}
cback(totalShares);
if (err) {
cback(0);
return;
}
if (totalShares > 0 || (pindex >= Object.keys(_this.stats.pools).length)) {
cback(totalShares);
return;
}
});
};
@ -285,7 +296,7 @@ module.exports = function(logger, portalConfig, poolConfigs){
['smembers', ':blocksConfirmed'],
['hgetall', ':shares:roundCurrent'],
['hgetall', ':blocksPendingConfirms'],
['hgetall', ':payments']
['zrange', ':payments', -100, -1]
];
var commandsPerCoin = redisCommandTemplates.length;
@ -320,23 +331,39 @@ module.exports = function(logger, portalConfig, poolConfigs){
networkSols: replies[i + 2] ? (replies[i + 2].networkSols || 0) : 0,
networkSolsString: getReadableNetworkHashRateString(replies[i + 2] ? (replies[i + 2].networkSols || 0) : 0),
networkDiff: replies[i + 2] ? (replies[i + 2].networkDiff || 0) : 0,
networkConnections: replies[i + 2] ? (replies[i + 2].networkConnections || 0) : 0
networkConnections: replies[i + 2] ? (replies[i + 2].networkConnections || 0) : 0,
networkVersion: replies[i + 2] ? (replies[i + 2].networkSubVersion || 0) : 0,
networkProtocolVersion: replies[i + 2] ? (replies[i + 2].networkProtocolVersion || 0) : 0
},
/* block stat counts */
blocks: {
pending: replies[i + 3],
confirmed: replies[i + 4],
orphaned: replies[i + 5]
},
/* show all pending blocks */
pending: {
blocks: replies[i + 6].sort(sortBlocks),
confirms: replies[i + 9]
},
/* show last 5 found blocks */
confirmed: {
blocks: replies[i + 7].sort(sortBlocks)
blocks: replies[i + 7].sort(sortBlocks).slice(0,5)
},
payments: replies[i + 10],
payments: [],
currentRoundShares: replies[i + 8]
};
for(var j = replies[i + 10].length; j > 0; j--){
var jsonObj;
try {
jsonObj = JSON.parse(replies[i + 10][j-1]);
} catch(e) {
jsonObj = null;
}
if (jsonObj !== null) {
coinStats.payments.push(jsonObj);
}
}
/*
for (var b in coinStats.confirmed.blocks) {
var parms = coinStats.confirmed.blocks[b].split(':');
@ -460,7 +487,8 @@ module.exports = function(logger, portalConfig, poolConfigs){
var shareMultiplier = Math.pow(2, 32) / algos[coinStats.algorithm].multiplier;
coinStats.hashrate = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow;
coinStats.hashrateString = _this.getReadableHashRateString(coinStats.hashrate);
var _blocktime = 250;
var _blocktime = 160;
var _networkHashRate = parseFloat(coinStats.poolStats.networkSols) * 1.2;
var _myHashRate = (coinStats.hashrate / 1000000) * 2;
coinStats.luckDays = ((_networkHashRate / _myHashRate * _blocktime) / (24 * 60 * 60)).toFixed(3);
@ -481,7 +509,7 @@ module.exports = function(logger, portalConfig, poolConfigs){
portalStats.algos[algo].hashrate += coinStats.hashrate;
portalStats.algos[algo].workers += Object.keys(coinStats.workers).length;
var _shareTotal = 0;
var _shareTotal = parseFloat(0);
for (var worker in coinStats.currentRoundShares) {
var miner = worker.split(".")[0];
if (miner in coinStats.miners) {
@ -492,10 +520,9 @@ module.exports = function(logger, portalConfig, poolConfigs){
}
_shareTotal += parseFloat(coinStats.currentRoundShares[worker]);
}
coinStats.shareCount = Math.round(_shareTotal * 100) / 100;
coinStats.shareCount = _shareTotal;
for (var worker in coinStats.workers) {
var _blocktime = 250;
var _workerRate = shareMultiplier * coinStats.workers[worker].shares / portalConfig.website.stats.hashrateWindow;
var _wHashRate = (_workerRate / 1000000) * 2;
coinStats.workers[worker].luckDays = ((_networkHashRate / _wHashRate * _blocktime) / (24 * 60 * 60)).toFixed(3);
@ -504,7 +531,6 @@ module.exports = function(logger, portalConfig, poolConfigs){
coinStats.workers[worker].hashrateString = _this.getReadableHashRateString(_workerRate);
}
for (var miner in coinStats.miners) {
var _blocktime = 250;
var _workerRate = shareMultiplier * coinStats.miners[miner].shares / portalConfig.website.stats.hashrateWindow;
var _wHashRate = (_workerRate / 1000000) * 2;
coinStats.miners[miner].luckDays = ((_networkHashRate / _wHashRate * _blocktime) / (24 * 60 * 60)).toFixed(3);
@ -524,13 +550,20 @@ module.exports = function(logger, portalConfig, poolConfigs){
var algoStats = portalStats.algos[algo];
algoStats.hashrateString = _this.getReadableHashRateString(algoStats.hashrate);
});
// TODO, create stats object and copy elements from portalStats we want to display...
var showStats = portalStats;
_this.stats = portalStats;
_this.statsString = JSON.stringify(portalStats);
_this.statHistory.push(portalStats);
// save historical hashrate, not entire stats!
var saveStats = JSON.parse(JSON.stringify(portalStats));
Object.keys(saveStats.pools).forEach(function(pool){
delete saveStats.pools[pool].pending;
delete saveStats.pools[pool].confirmed;
delete saveStats.pools[pool].currentRoundShares;
delete saveStats.pools[pool].payments;
delete saveStats.pools[pool].miners;
});
_this.statsString = JSON.stringify(saveStats);
_this.statHistory.push(saveStats);
addStatPoolHistory(portalStats);

View File

@ -230,7 +230,6 @@ module.exports = function(logger){
var payout = function(req, res, next){
var address = req.params.address || null;
if (address != null){
portalStats.getPayout(address, function(data){
res.write(data.toString());
@ -241,26 +240,20 @@ module.exports = function(logger){
next();
};
var shares = function(req, res, next){
portalStats.getCoins(function(){
processTemplates();
res.end(indexesProcessed['user_shares']);
});
};
var usershares = function(req, res, next){
var coin = req.params.coin || null;
if(coin != null){
portalStats.getCoinTotals(coin, null, function(){
processTemplates();
res.end(indexesProcessed['user_shares']);
});
}
else

View File

@ -0,0 +1,82 @@
{
"enabled":false,
"coin": "komodo.json",
"address": "",
"_comment_address": "pools komodo address; ex, RSXGTHQSqwcMw1vowKfEE7sQ8fAmv1tmso",
"zAddress": "",
"_comment_zAddress": "shielding not required in komodo, not used",
"tAddress": "",
"_comment_tAddress": "set to same as pools komodo address; ex, RSXGTHQSqwcMw1vowKfEE7sQ8fAmv1tmso",
"walletInterval": 1,
"_comment_walletInterval": "Used to cache komodo coin stats, shielding not performed.",
"rewardRecipients": {
"": 1.0
},
"tlsOptions": {
"enabled": false,
"serverKey":"",
"serverCert":"",
"ca":""
},
"paymentProcessing": {
"enabled": true,
"paymentInterval": 57,
"_comment_paymentInterval": "Interval in seconds to check and perform payments.",
"minimumPayment": 0.1,
"daemon": {
"host": "127.0.0.1",
"port": 8232,
"user": "username",
"password": "password"
}
},
"ports": {
"3857": {
"tls":false,
"diff": 0.05,
"varDiff": {
"minDiff": 0.04,
"maxDiff": 16,
"targetTime": 15,
"retargetTime": 60,
"variancePercent": 30
}
}
},
"daemons": [
{
"host": "127.0.0.1",
"port": 8232,
"user": "username",
"password": "password"
}
],
"p2p": {
"enabled": false,
"host": "127.0.0.1",
"port": 19333,
"disableTransactions": true
},
"mposMode": {
"enabled": false,
"host": "127.0.0.1",
"port": 3306,
"user": "me",
"password": "mypass",
"database": "kmd",
"checkPassword": true,
"autoCreateWorker": false
}
}

View File

@ -4,6 +4,7 @@
<ul>
<li><a href="/api/stats">/stats</a> global pool stats</li>
<li><a href="/api/pool_stats">/pool_stats</a> - historical stats</li>
<li><a href="/api/payments">/payments</a> - payment history</li>
<li><a href="/api/worker_stats?taddr">/worker_stats?taddr</a> - historical time per pool json </li>
<li><a href="/api/live_stats">/live_stats</a> - live stats </li>

View File

@ -88,8 +88,8 @@
<div class="chartHolder"><svg id="workerHashrate" /></div>
<div>
<div style="float:right; padding-top: 9px; padding-right: 18px;"><i class="fa fa-cog"></i> Shares: <span id="statsTotalShares">...</span></div>
<div style="float:left; padding-top: 9px; padding-left: 18px; padding-right: 18px;"><i class="fa fa-money"></i> Bal: <span id="statsTotalBal">...</span> ZEC </div>
<div style="padding-top: 9px; padding-left: 18px;"><i class="fa fa-money"></i> Paid: <span id="statsTotalPaid">...</span> ZEC </div>
<div style="float:left; padding-top: 9px; padding-left: 18px; padding-right: 18px;"><i class="fa fa-money"></i> Bal: <span id="statsTotalBal">...</span> </div>
<div style="padding-top: 9px; padding-left: 18px;"><i class="fa fa-money"></i> Paid: <span id="statsTotalPaid">...</span> </div>
</div>
</div>
</div>