diff --git a/coins/komodo.json b/coins/komodo.json index 5816e5e..f9a4ee1 100644 --- a/coins/komodo.json +++ b/coins/komodo.json @@ -1,5 +1,6 @@ { "name": "komodo", "symbol": "kmd", - "algorithm": "equihash" + "algorithm": "equihash", + "txfee": 0.00005 } diff --git a/coins/zcash.json b/coins/zcash.json index 4573970..c65158e 100644 --- a/coins/zcash.json +++ b/coins/zcash.json @@ -56,5 +56,6 @@ "t3PSn5TbMMAEw7Eu36DYctFezRzpX1hzf3M", "t3R3Y5vnBLrEn8L6wFjPjBLnxSUQsKnmFpv", "t3Pcm737EsVkGTbhsu2NekKtJeG92mvYyoN" - ] + ], + "txfee": 0.0004 } diff --git a/coins/zcash_testnet.json b/coins/zcash_testnet.json index 3c087dc..0c0c6ec 100644 --- a/coins/zcash_testnet.json +++ b/coins/zcash_testnet.json @@ -9,16 +9,17 @@ "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", + "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 } diff --git a/coins/zclassic.json b/coins/zclassic.json index 443025d..d6cfde2 100644 --- a/coins/zclassic.json +++ b/coins/zclassic.json @@ -3,5 +3,6 @@ "symbol": "zcl", "algorithm": "equihash", "requireShielding": true, - "peerMagic": "24e92764" + "peerMagic": "24e92764", + "txfee": 0.0004 } diff --git a/coins/zdash.json b/coins/zdash.json index 638818d..f08eaf6 100644 --- a/coins/zdash.json +++ b/coins/zdash.json @@ -2,5 +2,6 @@ "name": "zdash", "symbol": "zdash", "algorithm": "equihash", - "requireShielding": true + "requireShielding": true, + "txfee": 0.0004 } diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 0ebdaa8..4372e21 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -50,12 +50,13 @@ function SetupForPool(logger, poolOptions, setupFinished){ var logSystem = 'Payments'; var logComponent = coin; var opidCount = 0; - + var minConfShield = 3; var minConfPayout = 10; - + var requireShielding = poolOptions.coin.requireShielding === true; - + var fee = poolOptions.coin.txfee; + logger.special(logSystem, logComponent, logComponent + ' requireShielding: ' + requireShielding); var daemon = new Stratum.daemon.interface([processingConfig.daemon], function(severity, message){ @@ -221,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) { @@ -247,7 +248,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ } ); } - + // send z_address balance to t_address function sendZToT (callback, zBalance) { if (callback === true) @@ -260,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) { @@ -285,21 +286,21 @@ function SetupForPool(logger, poolOptions, setupFinished){ } ); } - - + + function cacheNetworkStats () { var params = null; daemon.cmd('getmininginfo', params, function (result) { var finalRedisCommands = []; var coin = logComponent; - + if (result.error) { logger.error(logSystem, logComponent, 'Error with RPC call `getmininginfo`' + JSON.stringify(result.error)); return; } else { - if (result[0].response.blocks !== null) { + 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]); @@ -307,7 +308,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ logger.error(logSystem, logComponent, "Error parse RPC call reponse.blocks tp `getmininginfo`." + JSON.stringify(result[0].response)); } } - + daemon.cmd('getnetworkinfo', params, function (result) { if (result.error) { @@ -335,7 +336,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ } ); } - + // 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 @@ -356,7 +357,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ // update network stats using coin daemon cacheNetworkStats(); }, interval); - + // check operation statuses every x seconds var opid_interval = poolOptions.walletInterval * 1000; // shielding not required for some equihash coins @@ -438,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'], @@ -470,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. */ @@ -482,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 ' @@ -494,35 +495,35 @@ function SetupForPool(logger, poolOptions, setupFinished){ callback(true); return; } - + // update confirmations in redis for pending blocks 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 @@ -568,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); @@ -627,18 +628,12 @@ function SetupForPool(logger, poolOptions, setupFinished){ + round.txHash); return; } - - // TODO, estimate transaction fees, make dynamic - var fee = 0.00005; // komodo - if (logComponent != "komodo") { - fee = 0.0004; // all other coins - } - + round.category = generationTx.category; if (round.category === 'generate') { round.reward = balanceRound(generationTx.amount - fee) || balanceRound(generationTx.value - fee); // TODO: Adjust fees to be dynamic } - + }); var canDeleteShares = function(r){ @@ -653,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) { @@ -667,28 +662,24 @@ function SetupForPool(logger, poolOptions, setupFinished){ } }); - // TODO, estimate transaction fees, make dynamic - var fee = 500; // komodo - if (logComponent != "komodo") { - fee = 4000; // all other coins - } - + fee = fee * 1e8; + // calculate what the pool owes its miners var totalOwed = parseInt(0); for (var i = 0; i < rounds.length; i++) { totalOwed = totalOwed + Math.round(rounds[i].reward * magnitude) - fee; // TODO: make tx fees dynamic } - + 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); + return callback(true); } 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); @@ -730,7 +721,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ + round.height + ' blockHash: ' + round.blockHash); return; } - + switch (round.category){ case 'kicked': case 'orphan': @@ -813,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); @@ -841,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) { @@ -866,24 +857,24 @@ 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); @@ -899,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.');