diff --git a/cronjobs/findblock.php b/cronjobs/findblock.php index 114d117b..50d03a61 100755 --- a/cronjobs/findblock.php +++ b/cronjobs/findblock.php @@ -51,17 +51,17 @@ if (empty($aTransactions['transactions'])) { // Let us add those blocks as unaccounted foreach ($aTransactions['transactions'] as $iIndex => $aData) { if ( $aData['category'] == 'generate' || $aData['category'] == 'immature' ) { - $aBlockInfo = $bitcoin->query('getblock', $aData['blockhash']); + $aBlockRPCInfo = $bitcoin->query('getblock', $aData['blockhash']); $config['reward_type'] == 'block' ? $aData['amount'] = $aData['amount'] : $aData['amount'] = $config['reward']; - $aData['height'] = $aBlockInfo['height']; - $aData['difficulty'] = $aBlockInfo['difficulty']; + $aData['height'] = $aBlockRPCInfo['height']; + $aData['difficulty'] = $aBlockRPCInfo['difficulty']; $log->logInfo(substr($aData['blockhash'], 0, 15) . "...\t" . $aData['height'] . "\t" . $aData['amount'] . "\t" . $aData['confirmations'] . "\t\t" . $aData['difficulty'] . "\t" . strftime("%Y-%m-%d %H:%M:%S", $aData['time'])); - if ( ! empty($aBlockInfo['flags']) && preg_match('/proof-of-stake/', $aBlockInfo['flags']) ) { + if ( ! empty($aBlockRPCInfo['flags']) && preg_match('/proof-of-stake/', $aBlockRPCInfo['flags']) ) { $log->logInfo("Block above with height " . $aData['height'] . " not added to database, proof-of-stake block!"); continue; } @@ -78,38 +78,65 @@ if (empty($aAllBlocks)) { $log->logDebug('No new blocks without share_id found in database'); } else { // Loop through our unaccounted blocks - $log->logInfo("Block ID\t\tHeight\tAmount\tShare ID\tShares\tFinder\tType"); + $log->logInfo("Block ID\tHeight\t\tAmount\tShare ID\tShares\tFinder\t\tType"); foreach ($aAllBlocks as $iIndex => $aBlock) { if (empty($aBlock['share_id'])) { - // Fetch this blocks upstream ID - $aBlockInfo = $bitcoin->query('getblock', $aBlock['blockhash']); - if ($share->setUpstream($aBlockInfo, $block->getLastUpstreamId())) { - $iCurrentUpstreamId = $share->getUpstreamId(); - $iAccountId = $user->getUserId($share->getUpstreamFinder()); - } else { - $log->logFatal('Unable to fetch blocks upstream share, aborted:' . $share->getError()); - $monitoring->setStatus($cron_name . "_active", "yesno", 0); - $monitoring->setStatus($cron_name . "_message", "message", "Unable to fetch blocks " . $aBlock['height'] . " upstream share: " . $share->getError()); - $monitoring->setStatus($cron_name . "_status", "okerror", 1); - exit; - } - // Fetch share information - if (!$iPreviousShareId = $block->getLastShareId()) { + $iPreviousShareId = $block->getLastShareId(); + if ( !$iPreviousShareId && $block->getBlockCount() > 1) { $iPreviousShareId = 0; - $log->logInfo('Unable to find highest share ID found so far, if this is your first block, this is normal.'); + // $log->logError('Unable to find highest share ID found so far, assuming share ID 0 as previous found upstream share.'); } - $iRoundShares = $share->getRoundShares($iPreviousShareId, $iCurrentUpstreamId); - // Store new information - if (!$block->setShareId($aBlock['id'], $iCurrentUpstreamId)) - $log->logError('Failed to update share ID in database for block ' . $aBlock['height']); - if (!$block->setFinder($aBlock['id'], $iAccountId)) - $log->logError('Failed to update finder account ID in database for block ' . $aBlock['height']); - if (!$block->setShares($aBlock['id'], $iRoundShares)) - $log->logError('Failed to update share count in database for block ' . $aBlock['height']); - if ($config['block_bonus'] > 0 && !$transaction->addTransaction($iAccountId, $config['block_bonus'], 'Bonus', $aBlock['id'])) { - $log->logError('Failed to create Bonus transaction in database for user ' . $user->getUserName($iAccountId) . ' for block ' . $aBlock['height']); + // Fetch this blocks upstream ID + $aBlockRPCInfo = $bitcoin->query('getblock', $aBlock['blockhash']); + if ($share->findUpstreamShare($aBlockRPCInfo, $iPreviousShareId)) { + $iCurrentUpstreamId = $share->getUpstreamShareId(); + // Out of order share detection + if ($iCurrentUpstreamId < $iPreviousShareId) { + // Fetch our offending block + $aBlockError = $block->getBlockByShareId($iPreviousShareId); + $log->logError('E0001: The block with height ' . $aBlock['height'] . ' found share ' . $iCurrentUpstreamId . ' which is < than ' . $iPreviousShareId . ' of block ' . $aBlockError['height'] . '.'); + if ( !$aShareError = $share->getShareById($aBlockError['share_id']) || !$aShareCurrent = $share->getShareById($iCurrentUpstreamId)) { + // We were not able to fetch all shares that were causing this detection to trigger, bail out + $log->logFatal('E0002: Failed to fetch both offending shares ' . $iCurrentUpstreamId . ' and ' . $iPreviousShareId . '. Block height: ' . $aBlock['height']); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "E0002: Upstream shares not found"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); + } + // Shares seem to be out of order, so lets change them + if ( !$share->updateShareById($iCurrentUpstreamId, $aShareError) || !$share->updateShareById($iPreviousShareId, $aShareCurrent)) { + // We couldn't update one of the shares! That might mean they have been deleted already + $log->logFatal('E0003: Failed to change shares order!'); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "E0003: Failed share update"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); + } + // Reset our offending block so the next run re-checks the shares + if (!$block->setShareId($aBlockError['id'], NULL) && !$block->setFinder($aBlockError['id'], NULL) || !$block->setShares($aBlockError['id'], NULL)) { + $log->logFatal('E0004: Failed to reset previous block: ' . $aBlockError['height']); + $log->logError('Failed to reset block in database: ' . $aBlockError['height']); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "E0004: Failed to reset block"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); + } + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Out of Order Share detected, autofixed"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(0); + } else { + $iRoundShares = $share->getRoundShares($iPreviousShareId, $iCurrentUpstreamId); + $iAccountId = $user->getUserId($share->getUpstreamFinder()); + } + } else { + $log->logFatal('E0005: Unable to fetch blocks upstream share, aborted:' . $share->getError()); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Unable to fetch blocks " . $aBlock['height'] . " upstream share: " . $share->getError()); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); } $log->logInfo( @@ -122,6 +149,17 @@ if (empty($aAllBlocks)) { . $share->share_type ); + // Store new information + if (!$block->setShareId($aBlock['id'], $iCurrentUpstreamId)) + $log->logError('Failed to update share ID in database for block ' . $aBlock['height']); + if (!$block->setFinder($aBlock['id'], $iAccountId)) + $log->logError('Failed to update finder account ID in database for block ' . $aBlock['height']); + if (!$block->setShares($aBlock['id'], $iRoundShares)) + $log->logError('Failed to update share count in database for block ' . $aBlock['height']); + if ($config['block_bonus'] > 0 && !$transaction->addTransaction($iAccountId, $config['block_bonus'], 'Bonus', $aBlock['id'])) { + $log->logError('Failed to create Bonus transaction in database for user ' . $user->getUserName($iAccountId) . ' for block ' . $aBlock['height']); + } + if ($setting->getValue('disable_notifications') != 1) { // Notify users $aAccounts = $notification->getNotificationAccountIdByType('new_block'); diff --git a/cronjobs/pplns_payout.php b/cronjobs/pplns_payout.php index 15e8d6b4..1fe17879 100755 --- a/cronjobs/pplns_payout.php +++ b/cronjobs/pplns_payout.php @@ -68,7 +68,7 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $config['reward_type'] == 'block' ? $dReward = $aBlock['amount'] : $dReward = $config['reward']; $aRoundAccountShares = $share->getSharesForAccounts($iPreviousShareId, $aBlock['share_id']); - $log->logInfo('Shares: ' . $iRoundShares . "\t" . 'Height: ' . $aBlock['height'] . ' Amount: ' . $aBlock['amount'] . "\t" . 'Found by ID: ' . $aBlock['account_id']); + $log->logInfo('Target: ' . $pplns_target . '; Shares: ' . $iRoundShares . '; Height: ' . $aBlock['height'] . '; Amount: ' . $aBlock['amount'] . '; Found by ID: ' . $aBlock['account_id']); if ($iRoundShares >= $pplns_target) { $log->logDebug("Matching or exceeding PPLNS target of $pplns_target with $iRoundShares"); diff --git a/cronjobs/pps_payout.php b/cronjobs/pps_payout.php index 7c3515fb..ca0a8c3c 100755 --- a/cronjobs/pps_payout.php +++ b/cronjobs/pps_payout.php @@ -54,19 +54,17 @@ if ($config['pps']['reward']['type'] == 'blockavg' && $block->getBlockCount() > if ($config['pps']['reward']['type'] == 'block') { if ($aLastBlock = $block->getLast()) { $pps_reward = $aLastBlock['amount']; - $log->logInfo("PPS reward using last block, amount: " . $pps_reward . "\tdifficulty: " . $dDifficulty); } else { $pps_reward = $config['pps']['reward']['default']; - $log->logInfo("PPS reward using default, amount: " . $pps_reward . "\tdifficulty: " . $dDifficulty); } } else { $pps_reward = $config['pps']['reward']['default']; - $log->logInfo("PPS reward fixed default, amount: " . $pps_reward . "\tdifficulty: " . $dDifficulty); } } // Per-share value to be paid out to users -$pps_value = round($pps_reward / (pow(2,32) * $dDifficulty) * pow(2, $config['pps_target']), 12); +$pps_value = round($pps_reward / (pow(2, $config['target_bits']) * $dDifficulty), 12); + // Find our last share accounted and last inserted share for PPS calculations $iPreviousShareId = $setting->getValue('pps_last_share_id'); @@ -75,11 +73,16 @@ $iLastShareId = $share->getLastInsertedShareId(); // Check for all new shares, we start one higher as our last accounted share to avoid duplicates $aAccountShares = $share->getSharesForAccounts($iPreviousShareId + 1, $iLastShareId); -$log->logInfo("ID\tUsername\tInvalid\tValid\t\tPPS Value\t\tPayout\t\tDonation\tFee"); +if (!empty($aAccountShares)) { + // Info for this payout + $log->logInfo("PPS reward type: " . $config['pps']['reward']['type'] . ", amount: " . $pps_reward . "\tdifficulty: " . $dDifficulty . "\tPPS value: " . $pps_value); + $log->logInfo("ID\tUsername\tInvalid\tValid\t\tPPS Value\t\tPayout\t\tDonation\tFee"); +} foreach ($aAccountShares as $aData) { - // Take our valid shares and multiply by per share value - $aData['payout'] = round($aData['valid'] * $pps_value, 8); + // MPOS uses a base difficulty setting to avoid showing weightened shares + // Since we need weightened shares here, we go back to the proper value for payouts + $aData['payout'] = round($aData['valid'] * pow(2, ($config['difficulty'] - 16)) * $pps_value, 8); // Defaults $aData['fee' ] = 0; @@ -94,7 +97,7 @@ foreach ($aAccountShares as $aData) { $log->logInfo($aData['id'] . "\t" . $aData['username'] . "\t" . $aData['invalid'] . "\t" . - $aData['valid'] . "\t*\t" . + $aData['valid'] * pow(2, ($config['difficulty'] - 16)) . "\t*\t" . number_format($pps_value, 12) . "\t=\t" . number_format($aData['payout'], 8) . "\t" . number_format($aData['donation'], 8) . "\t" . diff --git a/public/include/autoloader.inc.php b/public/include/autoloader.inc.php index 8950f103..b393387e 100644 --- a/public/include/autoloader.inc.php +++ b/public/include/autoloader.inc.php @@ -1,5 +1,11 @@ table; + } public function setDebug($debug) { $this->debug = $debug; } @@ -50,6 +54,13 @@ class Base { return $this->sError; } + protected function getAllAssoc($value, $field='id', $type='i') { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE $field = ? LIMIT 1"); + if ($this->checkStmt($stmt) && $stmt->bind_param($type, $value) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_assoc(); + return false; + } /** * Get a single row from the table * @param value string Value to search for diff --git a/public/include/classes/block.class.php b/public/include/classes/block.class.php index 027d06c5..bdb30cb1 100644 --- a/public/include/classes/block.class.php +++ b/public/include/classes/block.class.php @@ -56,6 +56,30 @@ class Block { return false; } + /** + * Get a specific block, by share_id + * @param share_id int Blocks share_id + * @return data array Block information from DB + **/ + public function getBlockByShareId($share_id) { + $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE share_id = ? LIMIT 1"); + if ($this->checkStmt($stmt) && $stmt->bind_param('i', $share_id) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_assoc(); + return false; + } + + /** + * Get a specific block, by id + * @param share_id int Blocks share_id + * @return data array Block information from DB + **/ + public function getBlockById($id) { + $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE id = ? LIMIT 1"); + if ($this->checkStmt($stmt) && $stmt->bind_param('i', $id) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_assoc(); + return false; + } + /** * Get our last, highest share ID inserted for a block * @param none diff --git a/public/include/classes/roundstats.class.php b/public/include/classes/roundstats.class.php index 45dbb7ea..efe2e161 100644 --- a/public/include/classes/roundstats.class.php +++ b/public/include/classes/roundstats.class.php @@ -225,6 +225,97 @@ class RoundStats { return false; } + /** + * Get ALL last blocks from height for admin panel + **/ + public function getAllReportBlocksFoundHeight($iHeight=0, $limit=10) { + $stmt = $this->mysqli->prepare(" + SELECT + height, shares + FROM $this->tableBlocks + WHERE height <= ? + ORDER BY height DESC LIMIT ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $iHeight, $limit) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_all(MYSQLI_ASSOC); + return false; + } + + /** + * Get USER last blocks from height for admin panel + **/ + public function getUserReportBlocksFoundHeight($iHeight=0, $limit=10, $iUser) { + $stmt = $this->mysqli->prepare(" + SELECT + b.height, b.shares + FROM $this->tableBlocks AS b + LEFT JOIN $this->tableStats AS s ON s.block_id = b.id + LEFT JOIN $this->tableUsers AS a ON a.id = s.account_id + WHERE b.height <= ? AND a.id = ? + ORDER BY height DESC LIMIT ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param('iii', $iHeight, $iUser, $limit) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_all(MYSQLI_ASSOC); + return false; + } + + /** + * Get shares for block height for user admin panel + **/ + public function getRoundStatsForUser($iHeight=0, $iUser) { + $stmt = $this->mysqli->prepare(" + SELECT + s.valid, + s.invalid, + s.pplns_valid, + s.pplns_invalid + FROM $this->tableStats AS s + LEFT JOIN $this->tableBlocks AS b ON s.block_id = b.id + LEFT JOIN $this->tableUsers AS a ON a.id = s.account_id + WHERE b.height = ? AND a.id = ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $iHeight, $iUser) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_assoc(); + return false; + } + + /** + * Get credit transactions for round block height for admin panel + **/ + public function getUserRoundTransHeight($iHeight=0, $iUser) { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare(" + SELECT + IFNULL(t.amount, 0) AS amount + FROM $this->tableTrans AS t + LEFT JOIN $this->tableBlocks AS b ON t.block_id = b.id + LEFT JOIN $this->tableUsers AS a ON t.account_id = a.id + WHERE b.height = ? AND t.type = 'Credit' AND t.account_id = ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $iHeight, $iUser) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_object()->amount; + $this->debug->append('Unable to fetch transactions'); + return false; + } + + /** + * Get all users for admin panel + **/ + public function getAllUsers($filter='%') { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare(" + SELECT + a.id AS id, + a.username AS username + FROM $this->tableUsers AS a + WHERE a.username LIKE ? + GROUP BY username + ORDER BY username"); + if ($this->checkStmt($stmt) && $stmt->bind_param('s', $filter) && $stmt->execute() && $result = $stmt->get_result()) { + while ($row = $result->fetch_assoc()) { + $aData[$row['id']] = $row['username']; + } + return $aData; + } + return false; + } + private function checkStmt($bState) { if ($bState ===! true) { $this->debug->append("Failed to prepare statement: " . $this->mysqli->error); diff --git a/public/include/classes/share.class.php b/public/include/classes/share.class.php index 705c41e1..7b31e1a0 100644 --- a/public/include/classes/share.class.php +++ b/public/include/classes/share.class.php @@ -4,9 +4,8 @@ if (!defined('SECURITY')) die('Hacking attempt'); -class Share { - private $sError = ''; - private $table = 'shares'; +class Share Extends Base { + protected $table = 'shares'; private $tableArchive = 'shares_archive'; private $oUpstream; private $iLastUpstreamId; @@ -22,14 +21,6 @@ class Share { $this->debug->append("Instantiated Share class", 2); } - // get and set methods - private function setErrorMessage($msg) { - $this->sError = $msg; - } - public function getError() { - return $this->sError; - } - /** * Fetch archive tables name for this class * @param none @@ -38,13 +29,43 @@ class Share { public function getArchiveTableName() { return $this->tableArchive; } + /** - * Fetch normal table name for this class - * @param none - * @return data string Table name + * Fetch a single share by ID + * @param id int Share ID + * @return array Share data **/ - public function getTableName() { - return $this->table; + public function getShareById($id) { + return $this->getAllAssoc($id); + } + + /** + * Update an entire shares data + **/ + public function updateShareById($id, $data) { + $this->debug->append("STA " . __METHOD__, 4); + $sql = "UPDATE $this->table SET"; + $start = true; + // Remove ID column + unset($data['id']); + foreach ($data as $column => $value) { + $start == true ? $sql .= " $column = ? " : $sql .= ", $column = ?"; + $start = false; + switch($column) { + case 'difficulty': + $this->addParam('d', $value); + break; + default: + $this->addParam('s', $value); + break; + } + } + $sql .= " WHERE id = ? LIMIT 1"; + $this->addParam('i', $id); + $stmt = $this->mysqli->prepare($sql); + if ($this->checkStmt($stmt) && call_user_func_array( array($stmt, 'bind_param'), $this->getParam()) && $stmt->execute()) + return true; + return false; } /** @@ -229,7 +250,7 @@ class Share { public function getUpstreamFinder() { return @$this->oUpstream->account; } - public function getUpstreamId() { + public function getUpstreamShareId() { return @$this->oUpstream->id; } /** @@ -240,7 +261,7 @@ class Share { * @param last int Skips all shares up to last to find new share * @return bool **/ - public function setUpstream($aBlock, $last=0) { + public function findUpstreamShare($aBlock, $last=0) { // Many use stratum, so we create our stratum check first $version = pack("I*", sprintf('%08d', $aBlock['version'])); $previousblockhash = pack("H*", swapEndian($aBlock['previousblockhash'])); @@ -343,10 +364,12 @@ class Share { * Fetch the lowest needed share ID from archive **/ function getMinArchiveShareId($iCount) { + // We don't use baseline here to be more accurate + $iCount = $iCount * pow(2, ($this->config['difficulty'] - 16)); $stmt = $this->mysqli->prepare(" SELECT MIN(b.share_id) AS share_id FROM ( - SELECT share_id, @total := @total + (IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty) / POW(2, (" . $this->config['difficulty'] . " - 16))) AS total + SELECT share_id, @total := @total + IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty) AS total FROM $this->tableArchive, (SELECT @total := 0) AS a WHERE our_result = 'Y' AND @total < ? @@ -356,20 +379,13 @@ class Share { "); if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $iCount, $iCount) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_object()->share_id; + $this->setErrorMessage("Failed fetching additional shares from archive: " . $this->mysqli->error); return false; } - - /** - * Helper function - **/ - private function checkStmt($bState) { - if ($bState ===! true) { - $this->debug->append("Failed to prepare statement: " . $this->mysqli->error); - $this->setErrorMessage('Internal application Error'); - return false; - } - return true; - } } $share = new Share($debug, $mysqli, $user, $block, $config); +$share->setMysql($mysqli); +$share->setConfig($config); +$share->setUser($user); +$share->setBlock($block); diff --git a/public/include/classes/statistics.class.php b/public/include/classes/statistics.class.php index 38f5360a..650fa288 100644 --- a/public/include/classes/statistics.class.php +++ b/public/include/classes/statistics.class.php @@ -65,7 +65,7 @@ class Statistics { b.*, a.username AS finder, a.is_anonymous AS is_anonymous, - ROUND((difficulty * 65535) / POW(2, (" . $this->config['difficulty'] . " -16)), 0) AS estshares + ROUND((difficulty * POW(2, 32 - " . $this->config['target_bits'] . ")) / POW(2, (" . $this->config['difficulty'] . " -16)), 0) AS estshares FROM " . $this->block->getTableName() . " AS b LEFT JOIN " . $this->user->getTableName() . " AS a ON b.account_id = a.id @@ -90,7 +90,7 @@ class Statistics { b.*, a.username AS finder, a.is_anonymous AS is_anonymous, - ROUND((difficulty * 65535) / POW(2, (" . $this->config['difficulty'] . " -16)), 0) AS estshares + ROUND((difficulty * POW(2, 32 - " . $this->config['target_bits'] . ")) / POW(2, (" . $this->config['difficulty'] . " -16)), 0) AS estshares FROM " . $this->block->getTableName() . " AS b LEFT JOIN " . $this->user->getTableName() . " AS a ON b.account_id = a.id @@ -170,11 +170,11 @@ class Statistics { SELECT ( ( - SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * 65536 / ? / 1000), 0) AS hashrate + SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * POW(2, " . $this->config['target_bits'] . ") / ? / 1000), 0) AS hashrate FROM " . $this->share->getTableName() . " WHERE time > DATE_SUB(now(), INTERVAL ? SECOND) ) + ( - SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * 65536 / ? / 1000), 0) AS hashrate + SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * POW(2, " . $this->config['target_bits'] . ") / ? / 1000), 0) AS hashrate FROM " . $this->share->getArchiveTableName() . " WHERE time > DATE_SUB(now(), INTERVAL ? SECOND) ) @@ -370,14 +370,14 @@ class Statistics { $stmt = $this->mysqli->prepare(" SELECT ( - SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * 65536 / ? / 1000), 0) AS hashrate + SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * POW(2, " . $this->config['target_bits'] . ") / ? / 1000), 0) AS hashrate FROM " . $this->share->getTableName() . " AS s, " . $this->user->getTableName() . " AS u WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) AND s.time > DATE_SUB(now(), INTERVAL ? SECOND) AND u.id = ? ) + ( - SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * 65536 / ? / 1000), 0) AS hashrate + SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * POW(2, " . $this->config['target_bits'] . ") / ? / 1000), 0) AS hashrate FROM " . $this->share->getArchiveTableName() . " AS s, " . $this->user->getTableName() . " AS u WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) @@ -392,7 +392,29 @@ class Statistics { return false; } + public function getUserUnpaidPPSShares($account_id, $last_paid_pps_id) { + $this->debug->append("STA " . __METHOD__, 4); + if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data; + $stmt = $this->mysqli->prepare(" + SELECT + ROUND(IFNULL(SUM(IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)), 0) / POW(2, (" . $this->config['difficulty'] . " - 16)), 0) AS total + FROM " . $this->share->getTableName() . " AS s + JOIN " . $this->user->getTableName() . " AS a + ON a.username = SUBSTRING_INDEX( s.username, '.', 1 ) + AND a.id = ? + AND s.id > ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $account_id, $last_paid_pps_id) && $stmt->execute() && $result = $stmt->get_result() ) + return $this->memcache->setCache(__FUNCTION__ . $account_id, $result->fetch_object()->total); + $this->debug->append("Failed fetching average share dificulty: " . $this->mysqli->error, 3); + return 0; + } + /** + * Get average share difficulty across all workers for user + * @param account_id int Account ID + * @param interval int Data interval in seconds + * @return double Share difficulty or 0 + **/ public function getUserShareDifficulty($account_id, $interval=600) { $this->debug->append("STA " . __METHOD__, 4); if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data; @@ -454,7 +476,7 @@ class Statistics { $this->debug->append("STA " . __METHOD__, 4); if ($data = $this->memcache->get(__FUNCTION__ . $worker_id)) return $data; $stmt = $this->mysqli->prepare(" - SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * 65536 / 600 / 1000), 0) AS hashrate + SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * POW(2, " . $this->config['target_bits'] . ") / 600 / 1000), 0) AS hashrate FROM " . $this->share->getTableName() . " AS s, " . $this->user->getTableName() . " AS u WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) @@ -524,7 +546,7 @@ class Statistics { a.username AS account, a.donate_percent AS donate_percent, a.is_anonymous AS is_anonymous, - IFNULL(ROUND(SUM(t1.difficulty) * 65536 / 600 / 1000, 2), 0) AS hashrate + IFNULL(ROUND(SUM(t1.difficulty) * POW(2, " . $this->config['target_bits'] . ") / 600 / 1000, 2), 0) AS hashrate FROM ( SELECT IFNULL(IF(difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0) AS difficulty, username FROM " . $this->share->getTableName() . " WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) AND our_result = 'Y' @@ -553,7 +575,7 @@ class Statistics { if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data; $stmt = $this->mysqli->prepare(" SELECT - IFNULL(ROUND(SUM(IF(s.difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)) * 65536/3600/1000), 0) AS hashrate, + IFNULL(ROUND(SUM(IF(s.difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)) * POW(2, " . $this->config['target_bits'] . ") / 3600 / 1000), 0) AS hashrate, HOUR(s.time) AS hour FROM " . $this->share->getTableName() . " AS s, accounts AS a WHERE time < NOW() - INTERVAL 1 HOUR @@ -563,7 +585,7 @@ class Statistics { GROUP BY HOUR(time) UNION ALL SELECT - IFNULL(ROUND(SUM(IF(s.difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)) * 65536/3600/1000), 0) AS hashrate, + IFNULL(ROUND(SUM(IF(s.difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)) * POW(2, " . $this->config['target_bits'] . ") / 3600 / 1000), 0) AS hashrate, HOUR(s.time) AS hour FROM " . $this->share->getArchiveTableName() . " AS s, accounts AS a WHERE time < NOW() - INTERVAL 1 HOUR @@ -594,7 +616,7 @@ class Statistics { if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__)) return $data; $stmt = $this->mysqli->prepare(" SELECT - IFNULL(ROUND(SUM(IF(s.difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)) * 65536/3600/1000), 0) AS hashrate, + IFNULL(ROUND(SUM(IF(s.difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)) * POW(2, " . $this->config['target_bits'] . ") / 3600 / 1000), 0) AS hashrate, HOUR(s.time) AS hour FROM " . $this->share->getTableName() . " AS s WHERE time < NOW() - INTERVAL 1 HOUR @@ -602,7 +624,7 @@ class Statistics { GROUP BY HOUR(time) UNION ALL SELECT - IFNULL(ROUND(SUM(IF(s.difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)) * 65536/3600/1000), 0) AS hashrate, + IFNULL(ROUND(SUM(IF(s.difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)) * POW(2, " . $this->config['target_bits'] . ") / 3600 / 1000), 0) AS hashrate, HOUR(s.time) AS hour FROM " . $this->share->getArchiveTableName() . " AS s WHERE time < NOW() - INTERVAL 1 HOUR @@ -645,18 +667,16 @@ class Statistics { } } else { // Hack so we can use this method for PPS estimates too + // value1 = shares/s + // value2 = avg share difficulty if (@$value1 > 0 && @$value2 > 0) { - // Default: No fees applied so multiply by 1 - $fee = 1; - if ($this->config['fees'] > 0) - $bNoFees == 0 ? $fee = round(((float)$this->config['fees'] / 100), 8) : $fee = 1; + $hour = 60 * 60; $pps = $value1 * $value2 * $ppsvalue; - $hour = 3600; - $aEstimates['hours1'] = $pps * $hour * $fee; + $aEstimates['hours1'] = $pps * $hour; $aEstimates['hours24'] = $pps * 24 * $hour; $aEstimates['days7'] = $pps * 24 * 7 * $hour; - $aEstimates['days14'] = $pps * 14 * 24 * 7 * $hour; - $aEstimates['days30'] = $pps * 30 * 24 * 7 * $hour; + $aEstimates['days14'] = $pps * 14 * 24 * $hour; + $aEstimates['days30'] = $pps * 30 * 24 * $hour; } else { $aEstimates['hours1'] = 0; $aEstimates['hours24'] = 0; diff --git a/public/include/classes/transaction.class.php b/public/include/classes/transaction.class.php index 557ce3e3..6d7beb4b 100644 --- a/public/include/classes/transaction.class.php +++ b/public/include/classes/transaction.class.php @@ -5,7 +5,8 @@ if (!defined('SECURITY')) die('Hacking attempt'); class Transaction extends Base { - private $sError = '', $table = 'transactions'; + private $sError = ''; + protected $table = 'transactions'; public $num_rows = 0, $insert_id = 0; /** diff --git a/public/include/classes/worker.class.php b/public/include/classes/worker.class.php index 42af02de..2960bfe7 100644 --- a/public/include/classes/worker.class.php +++ b/public/include/classes/worker.class.php @@ -176,6 +176,40 @@ class Worker { return false; } + /** + * Fetch all workers for admin panel + * @param limit int + * @return mixed array Workers and their settings or false + **/ + public function getAllWorkers($iLimit=0) { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare(" + SELECT id, username, password, monitor, difficulty, + ( + SELECT + IFNULL(IF(our_result='Y', ROUND(SUM(IF(difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * 65536 / 600 / 1000), 0), 0) AS hashrate + FROM " . $this->share->getTableName() . " + WHERE + username = w.username + AND time > DATE_SUB(now(), INTERVAL 10 MINUTE) + ) + ( + SELECT + IFNULL(IF(our_result='Y', ROUND(SUM(IF(difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * 65536 / 600 / 1000), 0), 0) AS hashrate + FROM " . $this->share->getArchiveTableName() . " + WHERE + username = w.username + AND time > DATE_SUB(now(), INTERVAL 10 MINUTE) + ) AS hashrate + FROM $this->table AS w + ORDER BY hashrate DESC LIMIT ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param('i', $iLimit) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_all(MYSQLI_ASSOC); + // Catchall + $this->setErrorMessage('Failed to fetch workers'); + $this->debug->append('Fetching workers failed: ' . $this->mysqli->error); + return false; + } + /** * Get all currently active workers in the past 10 minutes * @param none diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index b9cfda0d..1609b8b8 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -20,6 +20,17 @@ define('DEBUG', 0); // SALT used to hash passwords define('SALT', 'PLEASEMAKEMESOMETHINGRANDOM'); +/** + * Underlying coin algorithm that you are mining on. Set this to whatever your coin needs: + * + * Options: + * sha256d : SHA coins like Bitcoin + * scrypt : Scrypt based coins like Litecoin + * Default: + * scrypt : Scrypt is default + **/ +$config['algorithm'] = 'scrypt'; + /** * Database configuration * @@ -308,10 +319,6 @@ $config['pps']['reward']['default'] = 50; $config['pps']['reward']['type'] = 'blockavg'; $config['pps']['blockavg']['blockcount'] = 10; -// pps base payout target, default 16 = difficulty 1 shares for vardiff -// (1/(65536 * difficulty) * reward) = (reward / (pow(2,32) * difficulty) * pow(2, 16)) -$config['pps_target'] = 16; // do not change unless you know what it does - /** * Memcache configuration * diff --git a/public/include/pages/admin/poolworkers.inc.php b/public/include/pages/admin/poolworkers.inc.php new file mode 100644 index 00000000..54ea4e3c --- /dev/null +++ b/public/include/pages/admin/poolworkers.inc.php @@ -0,0 +1,18 @@ +isAuthenticated() || !$user->isAdmin($_SESSION['USERDATA']['id'])) { + header("HTTP/1.1 404 Page not found"); + die("404 Page not found"); +} + + $iActiveWorkers = $worker->getCountAllActiveWorkers(); + $aWorkers = $worker->getAllWorkers($iActiveWorkers); + + $smarty->assign('WORKERS', $aWorkers); + +$smarty->assign('CONTENT', 'default.tpl'); + +?> diff --git a/public/include/pages/admin/reports.inc.php b/public/include/pages/admin/reports.inc.php new file mode 100644 index 00000000..1ce870f4 --- /dev/null +++ b/public/include/pages/admin/reports.inc.php @@ -0,0 +1,86 @@ +isAuthenticated() || !$user->isAdmin($_SESSION['USERDATA']['id'])) { + header("HTTP/1.1 404 Page not found"); + die("404 Page not found"); +} + +if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { + $debug->append('No cached version available, fetching from backend', 3); + + $aUserList = $roundstats->getAllUsers('%'); + + $iHeight = 0; + $iUserId = 0; + $filter = 0; + $userName = 'None'; + if (@$_REQUEST['id']) { + $iUserId = $_REQUEST['id']; + $userName = $user->getUserName($_REQUEST['id']); + } + + $setting->getValue('statistics_block_count') ? $iLimit = $setting->getValue('statistics_block_count') : $iLimit = 20; + if (@$_REQUEST['limit']) { + $iLimit = $_REQUEST['limit']; + if ( $iLimit > 1000 ) + $iLimit = 1000; + } + + if (@$_REQUEST['next'] && !empty($_REQUEST['height'])) { + $iHeight = @$roundstats->getNextBlockForStats($_REQUEST['height'], $iLimit); + if (!$iHeight) { + $aBlock = $block->getLast(); + $iHeight = $aBlock['height']; + } + } else if (@$_REQUEST['prev'] && !empty($_REQUEST['height'])) { + $iHeight = $_REQUEST['height']; + } else if (!empty($_REQUEST['height']) && is_numeric($_REQUEST['height'])) { + $iHeight = $_REQUEST['height']; + } else { + $aBlock = $block->getLast(); + $iHeight = $aBlock['height']; + } + + if (@$_REQUEST['search']) { + $iHeight = $roundstats->searchForBlockHeight($_REQUEST['search']); + } + + if (@$_REQUEST['filter']) { + $filter = $_REQUEST['filter']; + } + + $aBlocksData = array(); + if ( $iUserId ) { + if ($filter) { + $aBlocksData = $roundstats->getAllReportBlocksFoundHeight($iHeight, $iLimit); + } else { + $aBlocksData = $roundstats->getUserReportBlocksFoundHeight($iHeight, $iLimit, $iUserId); + } + foreach($aBlocksData as $key => $aData) { + $aBlocksData[$key]['pplns_shares'] = @$roundstats->getPPLNSRoundShares($aData['height']); + $aBlocksData[$key]['user'] = @$roundstats->getRoundStatsForUser($aData['height'], $iUserId); + $aBlocksData[$key]['user_credit'] = @$roundstats->getUserRoundTransHeight($aData['height'], $iUserId); + } + } + + $smarty->assign('REPORTDATA', $aBlocksData); + $smarty->assign("USERLIST", $aUserList); + $smarty->assign("USERNAME", $userName); + $smarty->assign("USERID", $iUserId); + $smarty->assign("BLOCKLIMIT", $iLimit); + $smarty->assign("HEIGHT", $iHeight); + $smarty->assign("FILTER", $filter); +} else { + $debug->append('Using cached page', 3); +} + +if ($user->isAuthenticated(false)) { + $smarty->assign("CONTENT", "default.tpl"); +} else { + $smarty->assign("CONTENT", "empty"); +} +?> diff --git a/public/include/pages/api/getdashboarddata.inc.php b/public/include/pages/api/getdashboarddata.inc.php index 4d15763d..f4e48fbd 100644 --- a/public/include/pages/api/getdashboarddata.inc.php +++ b/public/include/pages/api/getdashboarddata.inc.php @@ -41,7 +41,9 @@ $aRoundShares = $statistics->getRoundShares(); if ($config['payout_system'] != 'pps') { $aEstimates = $statistics->getUserEstimates($aRoundShares, $aUserRoundShares, $user->getUserDonatePercent($user_id), $user->getUserNoFee($user_id)); + $dUnpaidShares = 0; } else { + $dUnpaidShares = $statistics->getUserUnpaidPPSShares($user_id, $setting->getValue('pps_last_share_id')); if ($config['pps']['reward']['type'] == 'blockavg' && $block->getBlockCount() > 0) { $pps_reward = round($block->getAvgBlockReward($config['pps']['blockavg']['blockcount'])); } else { @@ -56,7 +58,7 @@ if ($config['payout_system'] != 'pps') { } } - $ppsvalue = round($pps_reward / (pow(2,32) * $dDifficulty) * pow(2, $config['pps_target']), 12); + $ppsvalue = round($pps_reward / (pow(2,32) * $dDifficulty) * pow(2, $config['target_bits']), 12); $aEstimates = $statistics->getUserEstimates($dPersonalSharerate, $dPersonalShareDifficulty, $user->getUserDonatePercent($user_id), $user->getUserNoFee($user_id), $ppsvalue); } @@ -89,7 +91,7 @@ $data = array( 'raw' => array( 'personal' => array( 'hashrate' => $dPersonalHashrate ), 'pool' => array( 'hashrate' => $dPoolHashrate ), 'network' => array( 'hashrate' => $dNetworkHashrate / 1000 ) ), 'personal' => array ( 'hashrate' => $dPersonalHashrateAdjusted, 'sharerate' => $dPersonalSharerate, 'sharedifficulty' => $dPersonalShareDifficulty, - 'shares' => array('valid' => $aUserRoundShares['valid'], 'invalid' => $aUserRoundShares['invalid'], 'invalid_percent' => $dUserInvalidPercent), + 'shares' => array('valid' => $aUserRoundShares['valid'], 'invalid' => $aUserRoundShares['invalid'], 'invalid_percent' => $dUserInvalidPercent, 'unpaid' => $dUnpaidShares ), 'balance' => $transaction->getBalance($user_id), 'estimates' => $aEstimates, 'workers' => $aWorkers ), 'pool' => array( 'workers' => $worker->getCountAllActiveWorkers(), 'hashrate' => $dPoolHashrateAdjusted, diff --git a/public/include/smarty_globals.inc.php b/public/include/smarty_globals.inc.php index 9bb4ac60..5f2c691e 100644 --- a/public/include/smarty_globals.inc.php +++ b/public/include/smarty_globals.inc.php @@ -140,7 +140,9 @@ if (@$_SESSION['USERDATA']['id']) { } } - $aGlobal['ppsvalue'] = number_format(round($pps_reward / (pow(2,32) * $dDifficulty) * pow(2, $config['pps_target']), 12) ,12); + $aGlobal['userdata']['pps']['unpaidshares'] = $statistics->getUserUnpaidPPSShares($_SESSION['USERDATA']['id'], $setting->getValue('pps_last_share_id')); + $aGlobal['ppsvalue'] = number_format(round($pps_reward / (pow(2, $config['target_bits']) * $dDifficulty), 12) ,12); + $aGlobal['poolppsvalue'] = $aGlobal['ppsvalue'] * pow(2, $config['difficulty'] - 16); $aGlobal['userdata']['sharedifficulty'] = $statistics->getUserShareDifficulty($_SESSION['USERDATA']['id']); $aGlobal['userdata']['estimates'] = $statistics->getUserEstimates($aGlobal['userdata']['sharerate'], $aGlobal['userdata']['sharedifficulty'], $aGlobal['userdata']['donate_percent'], $aGlobal['userdata']['no_fees'], $aGlobal['ppsvalue']); break; diff --git a/public/templates/mmcFE/admin/poolworkers/default.tpl b/public/templates/mmcFE/admin/poolworkers/default.tpl new file mode 100644 index 00000000..f7afe308 --- /dev/null +++ b/public/templates/mmcFE/admin/poolworkers/default.tpl @@ -0,0 +1,34 @@ +{include file="global/block_header.tpl" BLOCK_HEADER="{count($WORKERS)} Current Active Pool Workers"} +
+ + + + + + + {if $GLOBAL.config.disable_notifications != 1}{/if} + + + + + {nocache} + {section worker $WORKERS} + + + + + + {if $GLOBAL.config.disable_notifications != 1} + + {/if} + + + + {/section} + {/nocache} + +
Worker NamePasswordActiveMonitorKhash/sDifficulty
{$WORKERS[worker].username|escape}{$WORKERS[worker].password|escape} + + {$WORKERS[worker].hashrate|number_format|default:"0"}{if $WORKERS[worker].hashrate > 0}{$WORKERS[worker].difficulty|number_format:"2"|default:"0"}{else}0{/if}
+
+{include file="global/block_footer.tpl"} diff --git a/public/templates/mmcFE/admin/reports/default.tpl b/public/templates/mmcFE/admin/reports/default.tpl new file mode 100644 index 00000000..53ca1869 --- /dev/null +++ b/public/templates/mmcFE/admin/reports/default.tpl @@ -0,0 +1,2 @@ +{include file="admin/reports/earnings_control.tpl"} +{include file="admin/reports/earnings_report.tpl"} diff --git a/public/templates/mmcFE/admin/reports/earnings_control.tpl b/public/templates/mmcFE/admin/reports/earnings_control.tpl new file mode 100644 index 00000000..d6c7d0a5 --- /dev/null +++ b/public/templates/mmcFE/admin/reports/earnings_control.tpl @@ -0,0 +1,50 @@ +{include file="global/block_header.tpl" ALIGN="left" BLOCK_STYLE="width: 100%" BLOCK_HEADER="Earnings Information" STYLE="padding-left:5px;padding-right:5px;"} +
+ + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + +
Select UserBlock LimitStarting Block HeightShow Empty Rounds
+ {html_options name="id" options=$USERLIST selected=$USERID|default:"0"} + + + + + + + +
+ +
+
+{include file="global/block_footer.tpl"} diff --git a/public/templates/mmcFE/admin/reports/earnings_report.tpl b/public/templates/mmcFE/admin/reports/earnings_report.tpl new file mode 100644 index 00000000..a724eb13 --- /dev/null +++ b/public/templates/mmcFE/admin/reports/earnings_report.tpl @@ -0,0 +1,85 @@ +{include file="global/block_header.tpl" ALIGN="left" BLOCK_STYLE="width: 100%" BLOCK_HEADER="Earnings Report Last {$BLOCKLIMIT} Blocks For User: {$USERNAME}" STYLE="padding-left:5px;padding-right:5px;"} + + + + + + + + + + + + + + + + + + + +{assign var=percentage value=0} +{assign var=percentage1 value=0} +{assign var=percentage2 value=0} +{assign var=totalvalid value=0} +{assign var=totalinvalid value=0} +{assign var=totalshares value=0} +{assign var=usertotalshares value=0} +{assign var=totalpercentage value=0} +{assign var=pplnsshares value=0} +{assign var=userpplnsshares value=0} +{assign var=pplnsvalid value=0} +{assign var=pplnsinvalid value=0} +{assign var=amount value=0} +{section txs $REPORTDATA} + {assign var="totalshares" value=$totalshares+$REPORTDATA[txs].shares} + {assign var=totalvalid value=$totalvalid+$REPORTDATA[txs]['user'].valid} + {assign var=totalinvalid value=$totalinvalid+$REPORTDATA[txs]['user'].invalid} + {assign var="pplnsshares" value=$pplnsshares+$REPORTDATA[txs].pplns_shares} + {assign var=pplnsvalid value=$pplnsvalid+$REPORTDATA[txs]['user'].pplns_valid} + {assign var=pplnsinvalid value=$pplnsinvalid+$REPORTDATA[txs]['user'].pplns_invalid} + {assign var=amount value=$amount+$REPORTDATA[txs].user_credit} + {if $REPORTDATA[txs]['user'].pplns_valid > 0} + {assign var="userpplnsshares" value=$userpplnsshares+$REPORTDATA[txs].pplns_shares} + {/if} + {if $REPORTDATA[txs]['user'].valid > 0} + {assign var="usertotalshares" value=$usertotalshares+$REPORTDATA[txs].shares} + {/if} + + + + + + + + + + + + + + + {assign var=percentage1 value=0} + +{/section} + + + + + + + + + + + + + + + {assign var=percentage2 value=0} + + +
BlockRound SharesRound ValidInvalidInvalid %Round %PPLNS SharesPPLNS ValidInvalidInvalid %PPLNS %VarianceAmount
{$REPORTDATA[txs].height|default:"0"}{$REPORTDATA[txs].shares|default:"0"}{$REPORTDATA[txs]['user'].valid|number_format|default:"0"}{$REPORTDATA[txs]['user'].invalid|number_format|default:"0"}{if $REPORTDATA[txs]['user'].invalid > 0 }{($REPORTDATA[txs]['user'].invalid / $REPORTDATA[txs]['user'].valid * 100)|number_format:"2"|default:"0"}{else}0.00{/if}{if $REPORTDATA[txs]['user'].valid > 0 }{(( 100 / $REPORTDATA[txs].shares) * $REPORTDATA[txs]['user'].valid)|number_format:"2"}{else}0.00{/if}{$REPORTDATA[txs].pplns_shares|number_format|default:"0"}{$REPORTDATA[txs]['user'].pplns_valid|number_format|default:"0"}{$REPORTDATA[txs]['user'].pplns_invalid|number_format|default:"0"}{if $REPORTDATA[txs]['user'].pplns_invalid > 0 && $REPORTDATA[txs]['user'].pplns_valid > 0 }{($REPORTDATA[txs]['user'].pplns_invalid / $REPORTDATA[txs]['user'].pplns_valid * 100)|number_format:"2"|default:"0"}{else}0.00{/if}{if $REPORTDATA[txs].shares > 0 && $REPORTDATA[txs]['user'].pplns_valid > 0}{(( 100 / $REPORTDATA[txs].pplns_shares) * $REPORTDATA[txs]['user'].pplns_valid)|number_format:"2"|default:"0"}{else}0.00{/if}{if $REPORTDATA[txs]['user'].valid > 0 && $REPORTDATA[txs]['user'].pplns_valid > 0}{math assign="percentage1" equation=(100 / ((( 100 / $REPORTDATA[txs].shares) * $REPORTDATA[txs]['user'].valid) / (( 100 / $REPORTDATA[txs].pplns_shares) * $REPORTDATA[txs]['user'].pplns_valid)))}{else if $REPORTDATA[txs]['user'].pplns_valid == 0}{assign var=percentage1 value=0}{else}{assign var=percentage1 value=100}{/if} + {$percentage1|number_format:"2"|default:"0"}{$REPORTDATA[txs].user_credit|default:"0"|number_format:"8"}
Totals{$totalshares|number_format}{$totalvalid|number_format}{$totalinvalid|number_format}{if $totalinvalid > 0 && $totalvalid > 0 }{($totalinvalid / $totalvalid * 100)|number_format:"2"|default:"0"}{else}0.00{/if}{if $usertotalshares > 0 && $totalvalid > 0}{(( 100 / $usertotalshares) * $totalvalid)|number_format:"2"|default:"0"}{else}0.00{/if}{$pplnsshares|number_format}{$pplnsvalid|number_format}{$pplnsinvalid|number_format}{if $pplnsinvalid > 0 && $pplnsvalid > 0 }{($pplnsinvalid / $pplnsvalid * 100)|number_format:"2"|default:"0"}{else}0.00{/if}{if $userpplnsshares > 0 && $pplnsvalid > 0}{(( 100 / $userpplnsshares) * $pplnsvalid)|number_format:"2"|default:"0"}{else}0.00{/if}{if $totalvalid > 0 && $pplnsvalid > 0}{math assign="percentage2" equation=(100 / ((( 100 / $usertotalshares) * $totalvalid) / (( 100 / $userpplnsshares) * $pplnsvalid)))}{else if $pplnsvalid == 0}{assign var=percentage2 value=0}{else}{assign var=percentage2 value=100}{/if} + {$percentage2|number_format:"2"|default:"0"}{$amount|default:"0"|number_format:"8"}
+{include file="global/block_footer.tpl"} diff --git a/public/templates/mmcFE/global/navigation.tpl b/public/templates/mmcFE/global/navigation.tpl index c7f762e9..8d9e2fa6 100644 --- a/public/templates/mmcFE/global/navigation.tpl +++ b/public/templates/mmcFE/global/navigation.tpl @@ -21,6 +21,8 @@
  • Transactions
  • Settings
  • News
  • +
  • Reports
  • +
  • Pool Workers
  • {/if} diff --git a/public/templates/mpos/about/pool/default.tpl b/public/templates/mpos/about/pool/default.tpl index 25ff710c..030f2a74 100644 --- a/public/templates/mpos/about/pool/default.tpl +++ b/public/templates/mpos/about/pool/default.tpl @@ -1,5 +1,5 @@
    -

    ThePool Collective

    +

    {$GLOBAL.website.name}

    diff --git a/public/templates/mpos/admin/poolworkers/default.tpl b/public/templates/mpos/admin/poolworkers/default.tpl new file mode 100644 index 00000000..5d721421 --- /dev/null +++ b/public/templates/mpos/admin/poolworkers/default.tpl @@ -0,0 +1,31 @@ +
    +

    {count($WORKERS)} Current Active Pool Workers

    +
    + + + + + + {if $GLOBAL.config.disable_notifications != 1}{/if} + + + + + {nocache} + {section worker $WORKERS} + + + + + + {if $GLOBAL.config.disable_notifications != 1} + + {/if} + + + + {/section} + {/nocache} + +
    Worker NamePasswordActiveMonitorKhash/sDifficulty
    {$WORKERS[worker].username|escape}{$WORKERS[worker].password|escape}{$WORKERS[worker].hashrate|number_format|default:"0"}{if $WORKERS[worker].hashrate > 0}{$WORKERS[worker].difficulty|number_format:"2"|default:"0"}{else}0{/if}
    +
    diff --git a/public/templates/mpos/admin/reports/default.tpl b/public/templates/mpos/admin/reports/default.tpl new file mode 100644 index 00000000..53ca1869 --- /dev/null +++ b/public/templates/mpos/admin/reports/default.tpl @@ -0,0 +1,2 @@ +{include file="admin/reports/earnings_control.tpl"} +{include file="admin/reports/earnings_report.tpl"} diff --git a/public/templates/mpos/admin/reports/earnings_control.tpl b/public/templates/mpos/admin/reports/earnings_control.tpl new file mode 100644 index 00000000..bbdf7321 --- /dev/null +++ b/public/templates/mpos/admin/reports/earnings_control.tpl @@ -0,0 +1,55 @@ +
    + + +
    +

    Earnings Information

    + + + + + + +
    + + + +
    + + + + + + + + + +
    +
    + + {html_options name="id" options=$USERLIST selected=$USERID|default:"0"} +
    +
    +
    + + +
    +
    +
    + + +
    +
    SHOW EMPTY ROUNDS

    + + + +
    + +
    +
    diff --git a/public/templates/mpos/admin/reports/earnings_report.tpl b/public/templates/mpos/admin/reports/earnings_report.tpl new file mode 100644 index 00000000..292642bf --- /dev/null +++ b/public/templates/mpos/admin/reports/earnings_report.tpl @@ -0,0 +1,88 @@ +
    +

    Earnings Report Last {$BLOCKLIMIT} Blocks For User: {$USERNAME}

    + + + + + + + + + + + + + + + + + + + +{assign var=percentage value=0} +{assign var=percentage1 value=0} +{assign var=percentage2 value=0} +{assign var=totalvalid value=0} +{assign var=totalinvalid value=0} +{assign var=totalshares value=0} +{assign var=usertotalshares value=0} +{assign var=totalpercentage value=0} +{assign var=pplnsshares value=0} +{assign var=userpplnsshares value=0} +{assign var=pplnsvalid value=0} +{assign var=pplnsinvalid value=0} +{assign var=amount value=0} +{section txs $REPORTDATA} + {assign var="totalshares" value=$totalshares+$REPORTDATA[txs].shares} + {assign var=totalvalid value=$totalvalid+$REPORTDATA[txs]['user'].valid} + {assign var=totalinvalid value=$totalinvalid+$REPORTDATA[txs]['user'].invalid} + {assign var="pplnsshares" value=$pplnsshares+$REPORTDATA[txs].pplns_shares} + {assign var=pplnsvalid value=$pplnsvalid+$REPORTDATA[txs]['user'].pplns_valid} + {assign var=pplnsinvalid value=$pplnsinvalid+$REPORTDATA[txs]['user'].pplns_invalid} + {assign var=amount value=$amount+$REPORTDATA[txs].user_credit} + {if $REPORTDATA[txs]['user'].pplns_valid > 0} + {assign var="userpplnsshares" value=$userpplnsshares+$REPORTDATA[txs].pplns_shares} + {/if} + {if $REPORTDATA[txs]['user'].valid > 0} + {assign var="usertotalshares" value=$usertotalshares+$REPORTDATA[txs].shares} + {/if} + + + + + + + + + + + + + + + {assign var=percentage1 value=0} + +{/section} + + + + + + + + + + + + + + + {assign var=percentage2 value=0} + + +
    BlockRound SharesRound ValidInvalidInvalid %Round %PPLNS SharesPPLNS ValidInvalidInvalid %PPLNS %VarianceAmount
    {$REPORTDATA[txs].height|default:"0"}{$REPORTDATA[txs].shares|default:"0"}{$REPORTDATA[txs]['user'].valid|number_format|default:"0"}{$REPORTDATA[txs]['user'].invalid|number_format|default:"0"}{if $REPORTDATA[txs]['user'].invalid > 0 }{($REPORTDATA[txs]['user'].invalid / $REPORTDATA[txs]['user'].valid * 100)|number_format:"2"|default:"0"}{else}0.00{/if}{if $REPORTDATA[txs]['user'].valid > 0 }{(( 100 / $REPORTDATA[txs].shares) * $REPORTDATA[txs]['user'].valid)|number_format:"2"}{else}0.00{/if}{$REPORTDATA[txs].pplns_shares|number_format|default:"0"}{$REPORTDATA[txs]['user'].pplns_valid|number_format|default:"0"}{$REPORTDATA[txs]['user'].pplns_invalid|number_format|default:"0"}{if $REPORTDATA[txs]['user'].pplns_invalid > 0 && $REPORTDATA[txs]['user'].pplns_valid > 0 }{($REPORTDATA[txs]['user'].pplns_invalid / $REPORTDATA[txs]['user'].pplns_valid * 100)|number_format:"2"|default:"0"}{else}0.00{/if}{if $REPORTDATA[txs].shares > 0 && $REPORTDATA[txs]['user'].pplns_valid > 0}{(( 100 / $REPORTDATA[txs].pplns_shares) * $REPORTDATA[txs]['user'].pplns_valid)|number_format:"2"|default:"0"}{else}0.00{/if}{if $REPORTDATA[txs]['user'].valid > 0 && $REPORTDATA[txs]['user'].pplns_valid > 0}{math assign="percentage1" equation=(100 / ((( 100 / $REPORTDATA[txs].shares) * $REPORTDATA[txs]['user'].valid) / (( 100 / $REPORTDATA[txs].pplns_shares) * $REPORTDATA[txs]['user'].pplns_valid)))}{else if $REPORTDATA[txs]['user'].pplns_valid == 0}{assign var=percentage1 value=0}{else}{assign var=percentage1 value=100}{/if} + {$percentage1|number_format:"2"|default:"0"}{$REPORTDATA[txs].user_credit|default:"0"|number_format:"8"}
    Totals{$totalshares|number_format}{$totalvalid|number_format}{$totalinvalid|number_format}{if $totalinvalid > 0 && $totalvalid > 0 }{($totalinvalid / $totalvalid * 100)|number_format:"2"|default:"0"}{else}0.00{/if}{if $usertotalshares > 0 && $totalvalid > 0}{(( 100 / $usertotalshares) * $totalvalid)|number_format:"2"|default:"0"}{else}0.00{/if}{$pplnsshares|number_format}{$pplnsvalid|number_format}{$pplnsinvalid|number_format}{if $pplnsinvalid > 0 && $pplnsvalid > 0 }{($pplnsinvalid / $pplnsvalid * 100)|number_format:"2"|default:"0"}{else}0.00{/if}{if $userpplnsshares > 0 && $pplnsvalid > 0}{(( 100 / $userpplnsshares) * $pplnsvalid)|number_format:"2"|default:"0"}{else}0.00{/if}{if $totalvalid > 0 && $pplnsvalid > 0}{math assign="percentage2" equation=(100 / ((( 100 / $usertotalshares) * $totalvalid) / (( 100 / $userpplnsshares) * $pplnsvalid)))}{else if $pplnsvalid == 0}{assign var=percentage2 value=0}{else}{assign var=percentage2 value=100}{/if} + {$percentage2|number_format:"2"|default:"0"}{$amount|default:"0"|number_format:"8"}
    + +
    diff --git a/public/templates/mpos/dashboard/js.tpl b/public/templates/mpos/dashboard/js.tpl index 8c4822c2..1bf84344 100644 --- a/public/templates/mpos/dashboard/js.tpl +++ b/public/templates/mpos/dashboard/js.tpl @@ -157,6 +157,7 @@ $(document).ready(function(){ $('#b-fee').html((parseFloat(data.getdashboarddata.data.personal.estimates.fee).toFixed(4))); $('#b-donation').html((parseFloat(data.getdashboarddata.data.personal.estimates.donation).toFixed(4))); {/literal}{else}{literal} + $('#b-ppsunpaid').html((parseFloat(data.getdashboarddata.data.personal.shares.unpaid).toFixed(0))); $('#b-ppsdiff').html((parseFloat(data.getdashboarddata.data.personal.sharedifficulty).toFixed(2))); $('#b-est1').html((parseFloat(data.getdashboarddata.data.personal.estimates.hours1).toFixed(8))); $('#b-est24hours').html((parseFloat(data.getdashboarddata.data.personal.estimates.hours24).toFixed(8))); diff --git a/public/templates/mpos/dashboard/system_stats.tpl b/public/templates/mpos/dashboard/system_stats.tpl index 04a97692..46b12f12 100644 --- a/public/templates/mpos/dashboard/system_stats.tpl +++ b/public/templates/mpos/dashboard/system_stats.tpl @@ -10,8 +10,16 @@ {elseif $GLOBAL.config.payout_system == 'pps'} - PPS Value - {$GLOBAL.ppsvalue} + Unpaid Shares + {$GLOBAL.userdata.pps.unpaidshares} + + + Baseline PPS Rate + {$GLOBAL.ppsvalue} {$GLOBAL.config.currency} + + + Pools PPS Rate + {$GLOBAL.poolppsvalue} {$GLOBAL.config.currency} PPS Difficulty diff --git a/public/templates/mpos/global/navigation.tpl b/public/templates/mpos/global/navigation.tpl index af922939..6f2552ed 100644 --- a/public/templates/mpos/global/navigation.tpl +++ b/public/templates/mpos/global/navigation.tpl @@ -22,6 +22,8 @@
  • Transactions
  • Settings
  • News
  • +
  • Reports
  • +
  • Pool Workers
  • {/if} {if $smarty.session.AUTHENTICATED|default} diff --git a/public/templates/mpos/statistics/blocks/default.tpl b/public/templates/mpos/statistics/blocks/default.tpl index e3a770a1..c2587a12 100644 --- a/public/templates/mpos/statistics/blocks/default.tpl +++ b/public/templates/mpos/statistics/blocks/default.tpl @@ -103,14 +103,13 @@ {$BLOCKSFOUND[block].difficulty|number_format:"2"} {$BLOCKSFOUND[block].amount|number_format:"2"} -{math assign="estshares" equation="(pow(2,32 - targetdiff) * blockdiff)" targetdiff=$GLOBAL.config.targetdiff blockdiff=$BLOCKSFOUND[block].difficulty} -{assign var="totalexpectedshares" value=$totalexpectedshares+$estshares} - {$estshares|number_format} +{assign var="totalexpectedshares" value=$totalexpectedshares+$BLOCKSFOUND[block].estshares} + {$BLOCKSFOUND[block].estshares|number_format} {if $GLOBAL.config.payout_system == 'pplns'}{$BLOCKSFOUND[block].pplns_shares|number_format}{/if} {$BLOCKSFOUND[block].shares|number_format} -{math assign="percentage" equation="shares / estshares * 100" shares=$BLOCKSFOUND[block].shares estshares=$estshares} +{math assign="percentage" equation="shares / estshares * 100" shares=$BLOCKSFOUND[block].shares estshares=$BLOCKSFOUND[block].estshares} {assign var="totalpercentage" value=$totalpercentage+$percentage} {$percentage|number_format:"2"}