diff --git a/POOLS.md b/POOLS.md index 7700bac9..3e06c1fd 100644 --- a/POOLS.md +++ b/POOLS.md @@ -95,7 +95,6 @@ Small Time Miners are running various stratum only pools for different coins. ### nutnut - | Pool URL | Coin | Avg. Hashrate | Avg. Active Workers | Notes | | -------- | ---- | ------------: | ------------------: | ----- | | http://ftc.nut2pools.com | Feathercoin | 45-50Mhs | 25 workers | New style, PPLNS | @@ -104,7 +103,6 @@ Small Time Miners are running various stratum only pools for different coins. ### ahmedbodi - | Pool URL | Coin | Avg. Hashrate | Avg. Active Workers | Notes | | -------- | ---- | ------------: | ------------------: | ----- | | http://gme.crypto-expert.com | Gamecoin | 1.5Mhs | 3 workers | Custom Template, Prop | @@ -116,14 +114,12 @@ Small Time Miners are running various stratum only pools for different coins. ### Neozonz - | Pool URL | Coin | Avg. Hashrate | Avg. Active Workers | Notes | | -------- | ---- | ------------: | ------------------: | ----- | | https://www.mine-litecoin.com | Litecoin | 10Mhs | 75 workers | Custom Template, Prop | ### CaptainAK - | Pool URL | Coin | Avg. Hashrate | Avg. Active Workers | Notes | | ----------------------- | ---- | ------------: | ------------------: | ----- | | http://btb.pnwminer.com | Bitbar | 5-10 Mhs | 5 workers | PPLNS | @@ -158,9 +154,19 @@ Small Time Miners are running various stratum only pools for different coins. | -------------------------------- | ----- | ------------: | ------------------: | ----- | | http://spotspool.chriskoeber.com | Spots | 37 Mhs | 55 workers | Prop | -### Coinium + +### [Poolerino.com](http://poolerino.com/ "Poolerino.com") ([@Fredyy90](https://github.com/Fredyy90/ "Fredyy90 on GitHub")) + +[Poolerino.com](http://poolerino.com/ "Poolerino.com") is going to build up a network of first-class, high performance pools on highend enterprise server hardware + +| Pool URL | Coin | Avg. Hashrate | Avg. Active Workers | Notes | +| -------- | ---- | ------------: | ------------------: | ----- | +| http://doge.poolerino.com | Doge | 300 MH/s | 600 | PROP + VARDIFF + 0% Fee | + +### Coinium (http://www.coinium.org/ "Coinium.org") ([@raistlinthewiz](https://github.com/raistlinthewþz/ "raistlinthewiz on GitHub")) | Pool URL | Coin | Avg. Hashrate | Avg. Active Workers | Notes | | -------------------------------- | ----- | ------------: | ------------------: | ----- | | http://doge.coinium.org | DOGE | 30 Mhs | 60 workers | PPLNS | -| http://emd.coinium.org | EMD | 1 Mhs | 3 workers | PPLNS | +| http://emd.coinium.org | EMD | 10 Mhs | 10 workers | PPLNS | +| http://earth.coinium.org | EAC | 10 Mhs | 30 workers | diff --git a/README.md b/README.md index d61073dd..e05b8c66 100644 --- a/README.md +++ b/README.md @@ -70,13 +70,12 @@ Features The following feature have been implemented so far: * Fully re-written GUI with [Smarty][2] templates + * Full file based template support + * **NEW** SQL based templates * Mobile WebUI -* Scrypt, **NEW** SHA256, VARDIFF Support +* Scrypt, SHA256, VARDIFF Support * Reward Systems - * Propotional - * PPS - * PPLNS -* Statistics are cached in Memcache by Cronjob for quick data access + * Propotional, PPS and PPLNS * New Theme * Live Dashboard * AJAX Support @@ -87,12 +86,9 @@ The following feature have been implemented so far: * Worker activity * Worker hashrates * Pool statistics -* Minimal Block statistics -* Pool donations -* Pool fees -* Block Bonus Payouts -* Manual payout -* Auto payout +* Block statistics +* Pool donations, fees and block bonuses +* Manual and auto payout * Transaction list * Admin Panel * Cron Monitoring Overview @@ -101,22 +97,24 @@ The following feature have been implemented so far: * User Transactions * News Posts * Pool Settings + * Templates + * Pool Workers + * User Reports + * Template Overwrite * Notification system * IDLE Workers * New blocks found in pool * Auto Payout * Manual Payout * User-to-user Invitation System -* Support for various Scrypt based coins via config - * MNC - * LTC - * ... +* Support for various coins via config + * All scrypt coins + * All sha256d coins Installation ============ -Please take a look at the [Quick Start Guide](https://github.com/TheSerapher/php-mpos/wiki/Quick-Start-Guide). This will give you -an idea how to setup `MPOS`. +Please take a look at the [Quick Start Guide](https://github.com/TheSerapher/php-mpos/wiki/Quick-Start-Guide). This will give you an idea how to setup `MPOS`. Customization ============= diff --git a/cronjobs/archive_cleanup.php b/cronjobs/archive_cleanup.php index c65d8006..687d41c7 100755 --- a/cronjobs/archive_cleanup.php +++ b/cronjobs/archive_cleanup.php @@ -26,9 +26,12 @@ chdir(dirname(__FILE__)); require_once('shared.inc.php'); // If we don't keep archives, delete some now to release disk space -if (!$share->purgeArchive()) { +$affected_rows = $share->purgeArchive(); +if ($affected_rows === false) { $log->logError("Failed to delete archived shares, not critical but should be checked: " . $share->getCronError()); $monitoring->endCronjob($cron_name, 'E0008', 1, true); +} else { + $log->logDebug("Deleted $affected_rows archived shares this run"); } // Cron cleanup and monitoring diff --git a/public/include/classes/notification.class.php b/public/include/classes/notification.class.php index ddf59e9c..b11f990c 100644 --- a/public/include/classes/notification.class.php +++ b/public/include/classes/notification.class.php @@ -155,7 +155,7 @@ class Notification extends Mail { if ($stmt->close() && $this->sendMail('notifications/' . $strType, $aMailData) && $this->addNotification($account_id, $strType, $aMailData)) { return true; } else { - $this->setErrorMessage('SendMail call failed: ' . $this->mail->getError()); + $this->setErrorMessage('SendMail call failed: ' . $this->getError()); return false; } } else { diff --git a/public/include/classes/share.class.php b/public/include/classes/share.class.php index 0b6beb89..337d1b77 100644 --- a/public/include/classes/share.class.php +++ b/public/include/classes/share.class.php @@ -172,50 +172,31 @@ class Share Extends Base { **/ public function purgeArchive() { // Fallbacks if unset - if (!isset($this->config['purge']['shares'])) $this->config['purge']['shares'] = 25000; - if (!isset($this->config['purge']['sleep'])) $this->config['purge']['sleep'] = 1; + if (!isset($this->config['archive']['purge'])) $this->config['archive']['purge'] = 5; - // TODO: This could need some cleanup work somtime but works - if ($this->config['payout_system'] == 'pplns') { - // Fetch our last block so we can go back configured rounds - $aLastBlock = $this->block->getLast(); - // Fetch the block we need to find the share_id - $aBlock = $this->block->getBlock($aLastBlock['height'] - $this->config['archive']['maxrounds']); - - // We need to find a hard limit id so we don't run into an infinite loop, skip process if we can't find a limit - $stmt = $this->mysqli->prepare("SELECT MAX(id) AS id FROM $this->tableArchive WHERE block_id < ? AND time < DATE_SUB(now(), INTERVAL ? MINUTE)"); - if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $aBlock['id'], $this->config['archive']['maxage']) && $stmt->execute() && $result = $stmt->get_result()) - if ( ! $max_id = $result->fetch_object()->id ) return true; - // Now that we know our block, remove those shares - $affected = 1; - while ($affected > 0) { - // Sleep first to allow any IO to cleanup - sleep($this->config['purge']['sleep']); - $stmt = $this->mysqli->prepare("DELETE FROM $this->tableArchive WHERE block_id < ? AND time < DATE_SUB(now(), INTERVAL ? MINUTE) AND id <= ? LIMIT " . $this->config['purge']['shares']); - if ($this->checkStmt($stmt) && $stmt->bind_param('iii', $aBlock['id'], $this->config['archive']['maxage'], $max_id) && $stmt->execute()) { - $affected = $stmt->affected_rows; - } else { - return $this->sqlError(); - } - } + $stmt = $this->mysqli->prepare("SELECT CEIL(COUNT(id) / 100 * ?) AS count FROM $this->tableArchive"); + if ($this->checkStmt($stmt) && $stmt->bind_param('i', $this->config['archive']['purge']) && $stmt->execute() && $result = $stmt->get_result()) { + $limit = $result->fetch_object()->count; } else { - // We need to find a hard limit id so we don't run into an infinite loop, skip process if we can't find a limit - $stmt = $this->mysqli->prepare("SELECT MAX(id) AS id FROM $this->tableArchive WHERE time < DATE_SUB(now(), INTERVAL ? MINUTE)"); - if ($this->checkStmt($stmt) && $stmt->bind_param('i', $this->config['archive']['maxage']) && $stmt->execute() && $result = $stmt->get_result()) - if ( ! $max_id = $result->fetch_object()->id ) return true; - $affected = 1; - while ($affected > 0) { - // Sleep first to allow any IO to cleanup - sleep($this->config['purge']['sleep']); - $stmt = $this->mysqli->prepare("DELETE FROM $this->tableArchive WHERE time < DATE_SUB(now(), INTERVAL ? MINUTE) AND id <= ? LIMIT " . $this->config['purge']['shares']); - if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $this->config['archive']['maxage'], $max_id) && $stmt->execute()) { - $affected = $stmt->affected_rows; - } else { - return $this->sqlError(); - } - } + return $this->sqlError(); } - return true; + $stmt->close(); + $stmt = $this->mysqli->prepare(" + DELETE FROM $this->tableArchive WHERE time < ( + SELECT MIN(time) FROM ( + SELECT MIN(time) AS time + FROM $this->tableArchive + WHERE block_id = ( + SELECT MIN(id) AS minid FROM ( + SELECT id FROM " . $this->block->getTableName() . " ORDER BY height DESC LIMIT ? + ) AS minheight + ) UNION SELECT DATE_SUB(now(), INTERVAL ? MINUTE) AS time + ) AS mintime + ) LIMIT $limit + "); + if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $this->config['archive']['maxrounds'], $this->config['archive']['maxage']) && $stmt->execute()) + return $stmt->affected_rows; + return $this->sqlError(); } /** diff --git a/public/include/classes/statistics.class.php b/public/include/classes/statistics.class.php index 601b5863..e832c742 100644 --- a/public/include/classes/statistics.class.php +++ b/public/include/classes/statistics.class.php @@ -812,11 +812,11 @@ class Statistics extends Base { $dNetworkHashrate = $this->bitcoin->getnetworkhashps(); $dDifficulty = $this->bitcoin->getdifficulty(); } else { - $dNetworkHashrate = 0; + $dNetworkHashrate = 1; $dDifficulty = 1; } - return pow(2, 32) * $dDifficulty / $dNetworkHashrate; + return $this->memcache->setCache(__FUNCTION__, pow(2, 32) * $dDifficulty / $dNetworkHashrate); } /** @@ -832,7 +832,23 @@ class Statistics extends Base { $dDifficulty = 1; } - return round($dDifficulty * $this->config['cointarget'] / $this->getNetworkExpectedTimePerBlock(), 8); + return $this->memcache->setCache(__FUNCTION__, round($dDifficulty * $this->config['cointarget'] / $this->getNetworkExpectedTimePerBlock(), 8)); + } + + /** + * Get Number of blocks until next difficulty change + * @return blocks int blocks until difficulty change + **/ + public function getBlocksUntilDiffChange(){ + if ($data = $this->memcache->get(__FUNCTION__)) return $data; + + if ($this->bitcoin->can_connect() === true) { + $iBlockcount = $this->bitcoin->getblockcount(); + } else { + $iBlockcount = 1; + } + + return $this->memcache->setCache(__FUNCTION__, $this->config['coindiffchangetarget'] - ($iBlockcount % $this->config['coindiffchangetarget'])); } /** diff --git a/public/include/config/admin_settings.inc.php b/public/include/config/admin_settings.inc.php index 9858dd76..67f96020 100644 --- a/public/include/config/admin_settings.inc.php +++ b/public/include/config/admin_settings.inc.php @@ -106,7 +106,7 @@ $aSettings['statistics'][] = array( ); $aSettings['statistics'][] = array( 'display' => 'Ajax Data Interval', 'type' => 'select', - 'options' => array('60' => '1', '300' => '5', '600' => '10'), + 'options' => array('60' => '1', '180' => '3', '300' => '5', '600' => '10'), 'default' => 300, 'name' => 'statistics_ajax_data_interval', 'value' => $setting->getValue('statistics_ajax_data_interval'), 'tooltip' => 'Time in minutes, interval for hashrate and sharerate calculations. Higher intervals allow for better accuracy at a higer server load.' diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index 780ec62d..8d59ba48 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -178,6 +178,19 @@ $config['currency'] = 'LTC'; **/ $config['cointarget'] = '150'; +/** + * Diff change every X Blocks + * + * Explanation + * Amount of Blocks until Difficulty change + * + * Fastcoin: 300 Blocks + * Litecoin: 2016 Blocks + * Bitcoin: 2016 Blocks + * + **/ +$config['coindiffchangetarget'] = 2016; + /** * Default transaction fee to apply to user transactions * diff --git a/public/include/database.inc.php b/public/include/database.inc.php index 7c5b5aaf..ee77cb83 100644 --- a/public/include/database.inc.php +++ b/public/include/database.inc.php @@ -5,7 +5,7 @@ if (!defined('SECURITY')) die('Hacking attempt'); // Instantiate class, we are using mysqlng -$mysqli = new mysqli($config['db']['host'], $config['db']['user'], $config['db']['pass'], $config['db']['name']); +$mysqli = new mysqli($config['db']['host'], $config['db']['user'], $config['db']['pass'], $config['db']['name'], $config['db']['port']); /* check connection */ if (mysqli_connect_errno()) { diff --git a/public/include/pages/api/getdashboarddata.inc.php b/public/include/pages/api/getdashboarddata.inc.php index ee5cdc70..f36a210e 100644 --- a/public/include/pages/api/getdashboarddata.inc.php +++ b/public/include/pages/api/getdashboarddata.inc.php @@ -91,10 +91,11 @@ if ($iEstShares > 0 && $aRoundShares['valid'] > 0) { $dExpectedTimePerBlock = $statistics->getNetworkExpectedTimePerBlock(); $dEstNextDifficulty = $statistics->getExpectedNextDifficulty(); +$iBlocksUntilDiffChange = $statistics->getBlocksUntilDiffChange(); // Output JSON format $data = array( - 'raw' => array( 'personal' => array( 'hashrate' => $dPersonalHashrate ), 'pool' => array( 'hashrate' => $dPoolHashrate ), 'network' => array( 'hashrate' => $dNetworkHashrate / 1000, 'esttimeperblock' => $dExpectedTimePerBlock, 'nextdifficulty' => $dEstNextDifficulty ) ), + 'raw' => array( 'personal' => array( 'hashrate' => $dPersonalHashrate ), 'pool' => array( 'hashrate' => $dPoolHashrate ), 'network' => array( 'hashrate' => $dNetworkHashrate / 1000, 'esttimeperblock' => $dExpectedTimePerBlock, 'nextdifficulty' => $dEstNextDifficulty, 'blocksuntildiffchange' => $iBlocksUntilDiffChange ) ), 'personal' => array ( 'hashrate' => $dPersonalHashrateAdjusted, 'sharerate' => $dPersonalSharerate, 'sharedifficulty' => $dPersonalShareDifficulty, 'shares' => array('valid' => $aUserRoundShares['valid'], 'invalid' => $aUserRoundShares['invalid'], 'invalid_percent' => $dUserInvalidPercent, 'unpaid' => $dUnpaidShares ), @@ -111,7 +112,7 @@ $data = array( 'target_bits' => $config['difficulty'] ), 'system' => array( 'load' => sys_getloadavg() ), - 'network' => array( 'hashrate' => $dNetworkHashrateAdjusted, 'difficulty' => $dDifficulty, 'block' => $iBlock, 'esttimeperblock' => round($dExpectedTimePerBlock ,2), 'nextdifficulty' => $dEstNextDifficulty ), + 'network' => array( 'hashrate' => $dNetworkHashrateAdjusted, 'difficulty' => $dDifficulty, 'block' => $iBlock, 'esttimeperblock' => round($dExpectedTimePerBlock ,2), 'nextdifficulty' => $dEstNextDifficulty, 'blocksuntildiffchange' => $iBlocksUntilDiffChange ), ); echo $api->get_json($data); diff --git a/public/include/pages/dashboard.inc.php b/public/include/pages/dashboard.inc.php index 86ccc331..f835f6e7 100644 --- a/public/include/pages/dashboard.inc.php +++ b/public/include/pages/dashboard.inc.php @@ -37,12 +37,13 @@ if ($user->isAuthenticated()) { $dExpectedTimePerBlock = $statistics->getNetworkExpectedTimePerBlock(); $dEstNextDifficulty = $statistics->getExpectedNextDifficulty(); + $iBlocksUntilDiffChange = $statistics->getBlocksUntilDiffChange(); // Make it available in Smarty $smarty->assign('DISABLED_DASHBOARD', $setting->getValue('disable_dashboard')); $smarty->assign('DISABLED_DASHBOARD_API', $setting->getValue('disable_dashboard_api')); $smarty->assign('ESTIMATES', array('shares' => $iEstShares, 'percent' => $dEstPercent)); - $smarty->assign('NETWORK', array('difficulty' => $dDifficulty, 'block' => $iBlock, 'EstNextDifficulty' => $dEstNextDifficulty, 'EstTimePerBlock' => $dExpectedTimePerBlock)); + $smarty->assign('NETWORK', array('difficulty' => $dDifficulty, 'block' => $iBlock, 'EstNextDifficulty' => $dEstNextDifficulty, 'EstTimePerBlock' => $dExpectedTimePerBlock, 'BlocksUntilDiffChange' => $iBlocksUntilDiffChange)); $smarty->assign('INTERVAL', $interval / 60); $smarty->assign('CONTENT', 'default.tpl'); } diff --git a/public/include/pages/register/register.inc.php b/public/include/pages/register/register.inc.php index 3c63963b..95066311 100644 --- a/public/include/pages/register/register.inc.php +++ b/public/include/pages/register/register.inc.php @@ -8,8 +8,8 @@ if ($setting->getValue('recaptcha_enabled')) { $rsp = recaptcha_check_answer ( $setting->getValue('recaptcha_private_key'), $_SERVER["REMOTE_ADDR"], - $_POST["recaptcha_challenge_field"], - $_POST["recaptcha_response_field"] + ( (isset($_POST["recaptcha_challenge_field"])) ? $_POST["recaptcha_challenge_field"] : null ), + ( (isset($_POST["recaptcha_response_field"])) ? $_POST["recaptcha_response_field"] : null ) ); } @@ -19,7 +19,7 @@ if ($setting->getValue('disable_invitations') && $setting->getValue('lock_regist $_SESSION['POPUP'][] = array('CONTENT' => 'Only invited users are allowed to register.', 'TYPE' => 'errormsg'); } else { // Check if recaptcha is enabled, process form data if valid - if($setting->getValue('recaptcha_enabled') && $_POST["recaptcha_response_field"] && $_POST["recaptcha_response_field"]!=''){ + if($setting->getValue('recaptcha_enabled') && isset($_POST["recaptcha_response_field"]) && $_POST["recaptcha_response_field"]!=''){ if ($rsp->is_valid) { $smarty->assign("RECAPTCHA", recaptcha_get_html($setting->getValue('recaptcha_public_key'))); isset($_POST['token']) ? $token = $_POST['token'] : $token = ''; diff --git a/public/include/pages/statistics/pool.inc.php b/public/include/pages/statistics/pool.inc.php index 1e3f65f3..5dd26d29 100644 --- a/public/include/pages/statistics/pool.inc.php +++ b/public/include/pages/statistics/pool.inc.php @@ -54,6 +54,7 @@ if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { $dExpectedTimePerBlock = $statistics->getNetworkExpectedTimePerBlock(); $dEstNextDifficulty = $statistics->getExpectedNextDifficulty(); + $iBlocksUntilDiffChange = $statistics->getBlocksUntilDiffChange(); // Propagate content our template $smarty->assign("ESTTIME", $iEstTime); @@ -64,7 +65,7 @@ if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { $smarty->assign("CONTRIBHASHES", $aContributorsHashes); $smarty->assign("CURRENTBLOCK", $iBlock); $smarty->assign("CURRENTBLOCKHASH", @$sBlockHash); - $smarty->assign('NETWORK', array('difficulty' => $dDifficulty, 'block' => $iBlock, 'EstNextDifficulty' => $dEstNextDifficulty, 'EstTimePerBlock' => $dExpectedTimePerBlock)); + $smarty->assign('NETWORK', array('difficulty' => $dDifficulty, 'block' => $iBlock, 'EstNextDifficulty' => $dEstNextDifficulty, 'EstTimePerBlock' => $dExpectedTimePerBlock, 'BlocksUntilDiffChange' => $iBlocksUntilDiffChange)); $smarty->assign('ESTIMATES', array('shares' => $iEstShares, 'percent' => $dEstPercent)); if (count($aBlockData) > 0) { $smarty->assign("LASTBLOCK", $aBlockData['height']); diff --git a/public/templates/mpos/admin/news/default.tpl b/public/templates/mpos/admin/news/default.tpl index fdfbc4ab..447c4474 100644 --- a/public/templates/mpos/admin/news/default.tpl +++ b/public/templates/mpos/admin/news/default.tpl @@ -1,5 +1,6 @@

News Posts

+
diff --git a/public/templates/mpos/dashboard/js_api.tpl b/public/templates/mpos/dashboard/js_api.tpl index b78b5bc4..dc3508dc 100644 --- a/public/templates/mpos/dashboard/js_api.tpl +++ b/public/templates/mpos/dashboard/js_api.tpl @@ -140,7 +140,7 @@ $(document).ready(function(){ $('#b-pvalid').html(data.getdashboarddata.data.pool.shares.valid); $('#b-pivalid').html(data.getdashboarddata.data.pool.shares.invalid + " (" + data.getdashboarddata.data.pool.shares.invalid_percent + "%)" ); $('#b-diff').html(data.getdashboarddata.data.network.difficulty); - $('#b-nextdiff').html(data.getdashboarddata.data.network.nextdifficulty); + $('#b-nextdiff').html(data.getdashboarddata.data.network.nextdifficulty + " (Change in " + data.getdashboarddata.data.network.blocksuntildiffchange + " Blocks)"); $('#b-esttimeperblock').html(data.getdashboarddata.data.network.esttimeperblock + " seconds"); // <- this needs some nicer format $('#b-nblock').html(data.getdashboarddata.data.network.block); $('#b-target').html(data.getdashboarddata.data.pool.shares.estimated + " (done: " + data.getdashboarddata.data.pool.shares.progress + "%)" ); diff --git a/public/templates/mpos/dashboard/network_info.tpl b/public/templates/mpos/dashboard/network_info.tpl index c470794b..d8c7642b 100644 --- a/public/templates/mpos/dashboard/network_info.tpl +++ b/public/templates/mpos/dashboard/network_info.tpl @@ -7,7 +7,7 @@ Est Next Difficulty - {$NETWORK.EstNextDifficulty} + {$NETWORK.EstNextDifficulty} (Change in {$NETWORK.BlocksUntilDiffChange} Blocks) Est. Avg. Time per Block diff --git a/public/templates/mpos/statistics/pool/general_stats.tpl b/public/templates/mpos/statistics/pool/general_stats.tpl index 100d8b84..a23040f0 100644 --- a/public/templates/mpos/statistics/pool/general_stats.tpl +++ b/public/templates/mpos/statistics/pool/general_stats.tpl @@ -26,14 +26,14 @@ Est. Next Difficulty {if ! $GLOBAL.website.chaininfo.disabled} - {$NETWORK.EstNextDifficulty} + {$NETWORK.EstNextDifficulty} (Change in {$NETWORK.BlocksUntilDiffChange} Blocks) {else} - {$NETWORK.EstNextDifficulty} + {$NETWORK.EstNextDifficulty} (Change in {$NETWORK.EstNextDifficulty} Blocks) {/if} Est. Avg. Time per Round (Network) - {$NETWORK.EstTimePerBlock|seconds_to_words} + {$NETWORK.EstTimePerBlock|seconds_to_words} Est. Avg. Time per Round (Pool)