diff --git a/.gitignore b/.gitignore index b4976dfc..6c1d5b6f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,10 @@ /include/config/global.inc.scrypt.php /include/config/global.inc.sha.php +# Test files +/scripts/test.php +/cronjobs/test.php + # IDE Settings /.idea/* .buildpath diff --git a/cronjobs/findblock.php b/cronjobs/findblock.php index 42e347a0..3516fe2c 100755 --- a/cronjobs/findblock.php +++ b/cronjobs/findblock.php @@ -59,7 +59,7 @@ if (empty($aTransactions['transactions'])) { $config['reward_type'] == 'block' ? $aData['amount'] = $aData['amount'] : $aData['amount'] = $config['reward']; $aData['height'] = $aBlockRPCInfo['height']; $aTxDetails = $bitcoin->gettransaction($aBlockRPCInfo['tx'][0]); - if (!isset($aBlockRPCInfo['confirmations'])) { + if (isset($aBlockRPCInfo['confirmations'])) { $aData['confirmations'] = $aBlockRPCInfo['confirmations']; } else if (isset($aTxDetails['confirmations'])) { $aData['confirmations'] = $aTxDetails['confirmations']; diff --git a/cronjobs/statistics.php b/cronjobs/statistics.php index 6530fab7..eb564c1a 100755 --- a/cronjobs/statistics.php +++ b/cronjobs/statistics.php @@ -27,7 +27,7 @@ require_once('shared.inc.php'); // Header $log->logInfo('Running statistical queries, errors may just mean no shares were available'); -$strLogMask = "| %-26.26s | %8.8s | %-6.6s |"; +$strLogMask = "| %-33.33s | %8.8s | %-6.6s |"; $log->logInfo(sprintf($strLogMask, 'Method', 'Runtime', 'Status')); // Per user share statistics based on all shares submitted @@ -37,9 +37,15 @@ $log->logInfo(sprintf($strLogMask, 'getAllUserShares', number_format(microtime(t // Get all user hashrate statistics for caching $start = microtime(true); -$statistics->getAllUserMiningStats() ? $status = 'OK' : $status = 'ERROR'; -$log->logInfo(sprintf($strLogMask, 'getAllUserMiningStats', number_format(microtime(true) - $start, 3), $status)); +$statistics->fetchAllUserMiningStats() ? $status = 'OK' : $status = 'ERROR'; +$log->logInfo(sprintf($strLogMask, 'fetchAllUserMiningStats', number_format(microtime(true) - $start, 3), $status)); +// Store our statistical data into our `statistics_users` table +$start = microtime(true); +$statistics->storeAllUserMiningStatsSnapshot($statistics->getAllUserMiningStats()) ? $status = 'OK' : $status = 'ERROR'; +$log->logInfo(sprintf($strLogMask, 'storeAllUserMiningStatsSnapshot', number_format(microtime(true) - $start, 3), $status)); + +// Get stats for pool overview $start = microtime(true); $statistics->getTopContributors('hashes') ? $status = 'OK' : $status = 'ERROR'; $log->logInfo(sprintf($strLogMask, 'getTopContributors(hashes)', number_format(microtime(true) - $start, 3), $status)); diff --git a/cronjobs/tables_cleanup.php b/cronjobs/tables_cleanup.php index 04386133..42189f2f 100755 --- a/cronjobs/tables_cleanup.php +++ b/cronjobs/tables_cleanup.php @@ -65,7 +65,7 @@ $status = 'OK'; $message = ''; $affected = $share->purgeArchive(); if ($affected === false) { - $message = 'Failed to delete notifications: ' . $oToken->getCronError(); + $message = 'Failed to delete shares: ' . $share->getCronError(); $status = 'ERROR'; $monitoring->endCronjob($cron_name, 'E0008', 0, false, false); } else { @@ -73,6 +73,19 @@ if ($affected === false) { } $log->logInfo(sprintf($strLogMask, 'purgeArchive', $affected, number_format(microtime(true) - $start, 3), $status, $message)); +// Clenaup shares archive +$start = microtime(true); +$status = 'OK'; +$message = ''; +$affected = $statistics->purgeUserStats($setting->getValue('statistics_graphing_days', 1)); +if ($affected === false) { + $message = 'Failed to delete entries: ' . $statistics->getCronError(); + $status = 'ERROR'; + $monitoring->endCronjob($cron_name, 'E0008', 0, false, false); +} else { + $affected == 0 ? $message = 'No entries deleted' : $message = 'Deleted old entries'; +} +$log->logInfo(sprintf($strLogMask, 'purgeUserStats', $affected, number_format(microtime(true) - $start, 3), $status, $message)); // Cron cleanup and monitoring require_once('cron_end.inc.php'); diff --git a/include/autoloader.inc.php b/include/autoloader.inc.php index 4d14e17e..eb44f86f 100644 --- a/include/autoloader.inc.php +++ b/include/autoloader.inc.php @@ -16,6 +16,7 @@ require_once(INCLUDE_DIR . '/config/error_codes.inc.php'); // We need to load these first require_once(CLASS_DIR . '/base.class.php'); require_once(CLASS_DIR . '/coins/coin_base.class.php'); +require_once(CLASS_DIR . '/coin_address.class.php'); require_once(CLASS_DIR . '/setting.class.php'); require_once(INCLUDE_DIR . '/version.inc.php'); if (PHP_OS == 'WINNT') require_once(CLASS_DIR . '/memcached.class.php'); diff --git a/include/classes/base.class.php b/include/classes/base.class.php index 1bb27e3a..d9f950ad 100644 --- a/include/classes/base.class.php +++ b/include/classes/base.class.php @@ -22,6 +22,9 @@ class Base { public function setCoin($coin) { $this->coin = $coin; } + public function setCoinAddress($coin_address) { + $this->coin_address = $coin_address; + } public function setLog($log) { $this->log = $log; } diff --git a/include/classes/coin_address.class.php b/include/classes/coin_address.class.php new file mode 100644 index 00000000..1e9bbb08 --- /dev/null +++ b/include/classes/coin_address.class.php @@ -0,0 +1,107 @@ +config['currency']; + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare(" + SELECT coin_address + FROM " . $this->getTableName() . " + WHERE account_id = ? AND currency = ? + "); + if ( $this->checkStmt($stmt) && $stmt->bind_param('is', $userID, $currency) && $stmt->execute() && $result = $stmt->get_result()) { + if ($result->num_rows == 1) { + return $result->fetch_object()->coin_address; + } + } + $this->debug->append("Unable to fetch users coin address for " . $currency); + return $this->sqlError(); + } + + /** + * Check if a coin address is already set + * @param address string Coin Address to check for + * @return bool true or false + **/ + public function existsCoinAddress($address) { + $this->debug->append("STA " . __METHOD__, 4); + return $this->getSingle($address, 'coin_address', 'coin_address', 's') === $address; + } + + /** + * Add a new coin address record for a user + * @param userID int Account ID + * @param address string Coin Address + * @param currency string Currency short handle, defaults to config option + * @return bool true or false + **/ + public function add($userID, $address, $currency=NULL) { + if ($currency === NULL) $currency = $this->config['currency']; + if ($address != $this->getCoinAddress($userID) && $this->existsCoinAddress($address)) { + $this->setErrorMessage('Unable to update coin address, address already exists'); + return false; + } + $stmt = $this->mysqli->prepare("INSERT INTO " . $this->getTableName() . " (account_id, currency, coin_address) VALUES (?, ?, ?)"); + if ( $this->checkStmt($stmt) && $stmt->bind_param('iss', $userID, $currency, $address) && $stmt->execute()) { + return true; + } + return $this->sqlError(); + } + + /** + * Remove a coin address record for a user + * @param userID int Account ID + * @param currency string Currency short handle, defaults to config option + * @return bool true or false + **/ + public function remove ($userID, $currency=NULL) { + if ($currency === NULL) $currency = $this->config['currency']; + $stmt = $this->mysqli->prepare("DELETE FROM " . $this->getTableName() . " WHERE account_id = ? AND currency = ?"); + if ( $this->checkStmt($stmt) && $stmt->bind_param('is', $userID, $currency) && $stmt->execute()) { + return true; + } + return $this->sqlError(); + } + + /** + * Update a coin address record for a user and a currency + * @param userID int Account ID + * @param address string Coin Address + * @param currency string Currency short handle, defaults to config option + * @return bool true or false + **/ + public function update($userID, $address, $currency=NULL) { + if ($currency === NULL) $currency = $this->config['currency']; + if ($address != $this->getCoinAddress($userID) && $this->existsCoinAddress($address)) { + $this->setErrorMessage('Unable to update coin address, address already exists'); + return false; + } + if ($this->getCoinAddress($userID) != NULL) { + $stmt = $this->mysqli->prepare("UPDATE " . $this->getTableName() . " SET coin_address = ? WHERE account_id = ? AND currency = ?"); + if ( $this->checkStmt($stmt) && $stmt->bind_param('sis', $address, $userID, $currency) && $stmt->execute()) { + return true; + } + } else { + $stmt = $this->mysqli->prepare("INSERT INTO " . $this->getTableName() . " (coin_address, account_id, currency) VALUES (?, ?, ?)"); + if ( $this->checkStmt($stmt) && $stmt->bind_param('sis', $address, $userID, $currency) && $stmt->execute()) { + return true; + } + } + return $this->sqlError(); + } +} + +$coin_address = new CoinAddress(); +$coin_address->setDebug($debug); +$coin_address->setConfig($config); +$coin_address->setMysql($mysqli); +$coin_address->setErrorCodes($aErrorCodes); diff --git a/include/classes/coins/coin_base.class.php b/include/classes/coins/coin_base.class.php index 7eeb3ee1..b9242822 100644 --- a/include/classes/coins/coin_base.class.php +++ b/include/classes/coins/coin_base.class.php @@ -12,6 +12,9 @@ class CoinBase extends Base { // Our coins target bits protected $target_bits = NULL; + // Our coins share difficulty precision + protected $share_difficulty_precision = 0; + /** * Read our target bits **/ @@ -19,6 +22,13 @@ class CoinBase extends Base { return $this->target_bits; } + /** + * Read our share difficulty precision + **/ + public function getShareDifficultyPrecision() { + return $this->share_difficulty_precision; + } + /** * Calculate the PPS value for this coin * WARNING: Get this wrong and you will over- or underpay your miners! diff --git a/include/classes/coins/coin_x11.class.php b/include/classes/coins/coin_x11.class.php index 6e356be3..60c14f63 100644 --- a/include/classes/coins/coin_x11.class.php +++ b/include/classes/coins/coin_x11.class.php @@ -8,6 +8,7 @@ $defflip = (!cfip()) ? exit(header('HTTP/1.1 401 Unauthorized')) : 1; **/ class Coin extends CoinBase { protected $target_bits = 24; + protected $share_difficulty_precision = 4; } ?> diff --git a/include/classes/statistics.class.php b/include/classes/statistics.class.php index 191bb00d..4a9c36f3 100644 --- a/include/classes/statistics.class.php +++ b/include/classes/statistics.class.php @@ -9,6 +9,7 @@ $defflip = (!cfip()) ? exit(header('HTTP/1.1 401 Unauthorized')) : 1; **/ class Statistics extends Base { protected $table = 'statistics_shares'; + protected $table_user_stats = 'statistics_users'; private $getcache = true; // Disable fetching values from cache @@ -18,6 +19,12 @@ class Statistics extends Base { public function getGetCache() { return $this->getcache; } + public function getAllUserMiningStats() { + return $this->allUserMiningStats; + } + public function getUserStatsTableName() { + return $this->table_user_stats; + } /** * Get our first block found @@ -45,37 +52,37 @@ class Statistics extends Base { IFNULL(SUM(IF(confirmations > 0, 1, 0)), 0) AS TotalValid, IFNULL(SUM(IF(confirmations = -1, 1, 0)), 0) AS TotalOrphan, IFNULL(SUM(IF(confirmations > 0, difficulty, 0)), 0) AS TotalDifficulty, - IFNULL(ROUND(SUM(IF(confirmations > -1, shares, 0))), 0) AS TotalShares, + IFNULL(SUM(IF(confirmations > -1, shares, 0)), 0) AS TotalShares, IFNULL(SUM(IF(confirmations > -1, amount, 0)), 0) AS TotalAmount, IFNULL(SUM(IF(FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 3600 SECOND), 1, 0)), 0) AS 1HourTotal, IFNULL(SUM(IF(confirmations > 0 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 3600 SECOND), 1, 0)), 0) AS 1HourValid, IFNULL(SUM(IF(confirmations = -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 3600 SECOND), 1, 0)), 0) AS 1HourOrphan, IFNULL(SUM(IF(confirmations > 0 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 3600 SECOND), difficulty, 0)), 0) AS 1HourDifficulty, - IFNULL(ROUND(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 3600 SECOND), shares, 0))), 0) AS 1HourShares, + IFNULL(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 3600 SECOND), shares, 0)), 0) AS 1HourShares, IFNULL(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 3600 SECOND), amount, 0)), 0) AS 1HourAmount, IFNULL(SUM(IF(FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 86400 SECOND), 1, 0)), 0) AS 24HourTotal, IFNULL(SUM(IF(confirmations > 0 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 86400 SECOND), 1, 0)), 0) AS 24HourValid, IFNULL(SUM(IF(confirmations = -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 86400 SECOND), 1, 0)), 0) AS 24HourOrphan, IFNULL(SUM(IF(confirmations > 0 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 86400 SECOND), difficulty, 0)), 0) AS 24HourDifficulty, - IFNULL(ROUND(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 86400 SECOND), shares, 0))), 0) AS 24HourShares, + IFNULL(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 86400 SECOND), shares, 0)), 0) AS 24HourShares, IFNULL(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 86400 SECOND), amount, 0)), 0) AS 24HourAmount, IFNULL(SUM(IF(FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 604800 SECOND), 1, 0)), 0) AS 7DaysTotal, IFNULL(SUM(IF(confirmations > 0 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 604800 SECOND), 1, 0)), 0) AS 7DaysValid, IFNULL(SUM(IF(confirmations = -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 604800 SECOND), 1, 0)), 0) AS 7DaysOrphan, IFNULL(SUM(IF(confirmations > 0 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 604800 SECOND), difficulty, 0)), 0) AS 7DaysDifficulty, - IFNULL(ROUND(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 604800 SECOND), shares, 0))), 0) AS 7DaysShares, + IFNULL(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 604800 SECOND), shares, 0)), 0) AS 7DaysShares, IFNULL(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 604800 SECOND), amount, 0)), 0) AS 7DaysAmount, IFNULL(SUM(IF(FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 2419200 SECOND), 1, 0)), 0) AS 4WeeksTotal, IFNULL(SUM(IF(confirmations > 0 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 2419200 SECOND), 1, 0)), 0) AS 4WeeksValid, IFNULL(SUM(IF(confirmations = -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 2419200 SECOND), 1, 0)), 0) AS 4WeeksOrphan, IFNULL(SUM(IF(confirmations > 0 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 2419200 SECOND), difficulty, 0)), 0) AS 4WeeksDifficulty, - IFNULL(ROUND(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 2419200 SECOND), shares, 0))), 0) AS 4WeeksShares, + IFNULL(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 2419200 SECOND), shares, 0)), 0) AS 4WeeksShares, IFNULL(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 2419200 SECOND), amount, 0)), 0) AS 4WeeksAmount, IFNULL(SUM(IF(FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 29030400 SECOND), 1, 0)), 0) AS 12MonthTotal, IFNULL(SUM(IF(confirmations > 0 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 29030400 SECOND), 1, 0)), 0) AS 12MonthValid, IFNULL(SUM(IF(confirmations = -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 29030400 SECOND), 1, 0)), 0) AS 12MonthOrphan, IFNULL(SUM(IF(confirmations > 0 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 29030400 SECOND), difficulty, 0)), 0) AS 12MonthDifficulty, - IFNULL(ROUND(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 29030400 SECOND), shares, 0))), 0) AS 12MonthShares, + IFNULL(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 29030400 SECOND), shares, 0)), 0) AS 12MonthShares, IFNULL(SUM(IF(confirmations > -1 AND FROM_UNIXTIME(time) >= DATE_SUB(now(), INTERVAL 29030400 SECOND), amount, 0)), 0) AS 12MonthAmount FROM " . $this->block->getTableName()); if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) { @@ -127,7 +134,7 @@ class Statistics extends Base { b.*, a.username AS finder, a.is_anonymous AS is_anonymous, - ROUND(difficulty * POW(2, 32 - " . $this->coin->getTargetBits() . "), 0) AS estshares + ROUND(difficulty * POW(2, 32 - " . $this->coin->getTargetBits() . "), 4) AS estshares FROM " . $this->block->getTableName() . " AS b LEFT JOIN " . $this->user->getTableName() . " AS a ON b.account_id = a.id @@ -163,7 +170,7 @@ class Statistics extends Base { return $this->memcache->setCache(__FUNCTION__ . $limit, $result->fetch_all(MYSQLI_ASSOC), 5); return $this->sqlError(); } - + /** * Get SUM of blocks found and generated Coins for each worker * @param limit int Last limit blocks @@ -185,7 +192,7 @@ class Statistics extends Base { return $this->memcache->setCache(__FUNCTION__ . $account_id . $limit, $result->fetch_all(MYSQLI_ASSOC), 5); return $this->sqlError(); } - + /** * Currently the only function writing to the database * Stored per block user statistics of valid and invalid shares @@ -223,12 +230,12 @@ class Statistics extends Base { SELECT ( ( - SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty))), 0) AS shares + SELECT IFNULL(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)), 0) AS shares FROM " . $this->share->getTableName() . " WHERE time > DATE_SUB(now(), INTERVAL ? SECOND) AND our_result = 'Y' ) + ( - SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty))), 0) AS shares + SELECT IFNULL(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)), 0) AS shares FROM " . $this->share->getArchiveTableName() . " WHERE time > DATE_SUB(now(), INTERVAL ? SECOND) AND our_result = 'Y' @@ -254,12 +261,12 @@ class Statistics extends Base { SELECT ( ( - SELECT ROUND(COUNT(id) / ?, 2) AS sharerate + SELECT ROUND(SUM(difficulty) / ?, 2) AS sharerate FROM " . $this->share->getTableName() . " WHERE time > DATE_SUB(now(), INTERVAL ? SECOND) AND our_result = 'Y' ) + ( - SELECT ROUND(COUNT(id) / ?, 2) AS sharerate + SELECT ROUND(SUM(difficulty) / ?, 2) AS sharerate FROM " . $this->share->getArchiveTableName() . " WHERE time > DATE_SUB(now(), INTERVAL ? SECOND) AND our_result = 'Y' @@ -293,8 +300,8 @@ class Statistics extends Base { } $stmt = $this->mysqli->prepare(" SELECT - ROUND(IFNULL(SUM(IF(our_result='Y', IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0)), 0), 0) AS valid, - ROUND(IFNULL(SUM(IF(our_result='N', IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0)), 0), 0) AS invalid + IFNULL(SUM(IF(our_result='Y', IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0)), 0) AS valid, + IFNULL(SUM(IF(our_result='N', IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0)), 0) AS invalid FROM " . $this->share->getTableName() . " WHERE UNIX_TIMESTAMP(time) > IFNULL((SELECT MAX(time) FROM " . $this->block->getTableName() . "), 0)"); if ( $this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result() ) @@ -316,8 +323,8 @@ class Statistics extends Base { } $stmt = $this->mysqli->prepare(" SELECT - ROUND(IFNULL(SUM(IF(our_result='Y', IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0), 0) AS valid, - ROUND(IFNULL(SUM(IF(our_result='N', IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0), 0) AS invalid, + IFNULL(SUM(IF(our_result='Y', IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0) AS valid, + IFNULL(SUM(IF(our_result='N', IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0) AS invalid, u.id AS id, u.donate_percent AS donate_percent, u.is_anonymous AS is_anonymous, @@ -368,11 +375,11 @@ class Statistics extends Base { if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data; $stmt = $this->mysqli->prepare(" SELECT - ROUND(IFNULL(SUM(IF(our_result='Y', IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0)), 0), 0) AS valid, - ROUND(IFNULL(SUM(IF(our_result='N', IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0)), 0), 0) AS invalid + IFNULL(SUM(IF(our_result='Y', IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0)), 0) AS valid, + IFNULL(SUM(IF(our_result='N', IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0)), 0) AS invalid FROM " . $this->share->getTableName() . " WHERE username LIKE ? - AND UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(b.time) FROM " . $this->block->getTableName() . " AS b),0)"); + AND UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(b.time) FROM " . $this->block->getTableName() . " AS b),0)"); $username = $username . ".%"; if ($stmt && $stmt->bind_param("s", $username) && $stmt->execute() && $result = $stmt->get_result()) return $this->memcache->setCache(__FUNCTION__ . $account_id, $result->fetch_assoc()); @@ -451,16 +458,19 @@ class Statistics extends Base { /** * Fetch all user hashrates based on shares and archived shares + * Store it in cache, also keep a copy of the data internally to + * return it for further processing * @return data array Set of all user stats **/ - public function getAllUserMiningStats($interval=180) { + public function fetchAllUserMiningStats($interval=180) { $this->debug->append("STA " . __METHOD__, 4); $stmt = $this->mysqli->prepare(" SELECT a.id AS id, a.username AS account, + COUNT(DISTINCT t1.username) AS workers, IFNULL(SUM(t1.difficulty), 0) AS shares, - ROUND(COUNT(t1.id) / ?, 2) AS sharerate, + ROUND(SUM(t1.difficulty) / ?, 2) AS sharerate, IFNULL(AVG(IF(difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), difficulty)), 0) AS avgsharediff FROM ( SELECT @@ -489,18 +499,51 @@ class Statistics extends Base { $aData['data'][$row['id']] = $row; $aData['data'][$row['id']]['hashrate'] = $this->coin->calcHashrate($row['shares'], $interval); } + $this->allUserMiningStats = $aData; return $this->memcache->setStaticCache(STATISTICS_ALL_USER_HASHRATES, $aData, 600); } else { return $this->sqlError(); } } + /** + * Store our gathered data into our statistic table for users + * @param aData array Data created by fetchAllUserMiningStats + * @return bool true or false + **/ + public function storeAllUserMiningStatsSnapshot($aData) { + $this->debug->append("STA " . __METHOD__, 4); + if (!isset($aData['data'])) return false; + // initilize + $timestamp = time(); // Store all entries with the same timestamp to reduce cardinality + $ok = 0; + $failed = 0; + foreach ($aData['data'] as $key => $aUserData) { + $stmt = $this->mysqli->prepare(" + INSERT INTO " . $this->getUserStatsTableName() . " + ( account_id, hashrate, workers, sharerate, timestamp ) VALUES ( ?, ?, ?, ?, ?)"); + if ($this->checkStmt($stmt) && $stmt->bind_param("ididi", $aUserData['id'], $aUserData['hashrate'], $aUserData['workers'], $aUserData['sharerate'], $timestamp) && $stmt->execute() ) { + $ok++; + } else { + $failed++; + } + } + return array('ok' => $ok, 'failed' => $failed); + } + + /** + * Fetch unpaid PPS shares for an account + * @param username string Username + * @param account_id int User ID + * @param last_paid_pps_id int Last paid out share by pps_payout cron + * @return data int Sum of unpaid diff1 shares + **/ public function getUserUnpaidPPSShares($username, $account_id=NULL, $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(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)), 0), 0) AS total + IFNULL(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)), 0) AS total FROM " . $this->share->getTableName() . " WHERE username LIKE ? AND id > ? @@ -515,7 +558,7 @@ class Statistics extends Base { * Get Shares per x interval by user * @param username string username * @param $account_id int account id - * @return data integer Current Sharerate in shares/s + * @return data integer Current Sharerate in diff1 shares/s **/ public function getUserMiningStats($username, $account_id=NULL, $interval=180) { $this->debug->append("STA " . __METHOD__, 4); @@ -532,7 +575,7 @@ class Statistics extends Base { if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data; $stmt = $this->mysqli->prepare(" SELECT - IFNULL(COUNT(*) / ?, 0) AS sharerate, + IFNULL(SUM(difficulty) / ?, 0) AS sharerate, IFNULL(SUM(difficulty), 0) AS shares, IFNULL(AVG(difficulty), 0) AS avgsharediff FROM ( @@ -603,7 +646,7 @@ class Statistics extends Base { a.username AS account, a.donate_percent AS donate_percent, a.is_anonymous AS is_anonymous, - ROUND(IFNULL(SUM(IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)), 0), 0) AS shares + IFNULL(SUM(IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)), 0) AS shares FROM " . $this->share->getTableName() . " AS s LEFT JOIN " . $this->user->getTableName() . " AS a ON SUBSTRING_INDEX( s.username, '.', 1 ) = a.username @@ -655,78 +698,24 @@ class Statistics extends Base { * @param $account_id int account id * @return data array NOT FINISHED YET **/ - public function getHourlyHashrateByAccount($username, $account_id=NULL) { + public function getHourlyMiningStatsByAccount($account_id, $format='array', $days = 1) { $this->debug->append("STA " . __METHOD__, 4); if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data; $stmt = $this->mysqli->prepare(" SELECT - id, - IFNULL(SUM(IF(difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), difficulty)), 0) AS shares, - HOUR(time) AS hour - FROM " . $this->share->getTableName() . " - WHERE time <= FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(NOW())/(60*60))*(60*60)) - AND time >= FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(NOW())/(60*60))*(60*60)) - INTERVAL 24 HOUR - AND our_result = 'Y' - AND username LIKE ? - GROUP BY HOUR(time) - UNION - SELECT - share_id, - IFNULL(SUM(IF(difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), difficulty)), 0) AS shares, - HOUR(time) AS hour - FROM " . $this->share->getArchiveTableName() . " - WHERE time <= FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(NOW())/(60*60))*(60*60)) - AND time >= FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(NOW())/(60*60))*(60*60)) - INTERVAL 24 HOUR - AND our_result = 'Y' - AND username LIKE ? - GROUP BY HOUR(time)"); - $username = $username . ".%"; - if ($this->checkStmt($stmt) && $stmt->bind_param('ss', $username, $username) && $stmt->execute() && $result = $stmt->get_result()) { - $iStartHour = date('G'); - // Initilize array - for ($i = 0; $i < 24; $i++) $aData[($iStartHour + $i) % 24] = 0; - // Fill data - while ($row = $result->fetch_assoc()) $aData[$row['hour']] += (int) $this->coin->calcHashrate($row['shares'], 3600); - return $this->memcache->setCache(__FUNCTION__ . $account_id, $aData); - } - return $this->sqlError(); - } - - /** - * get Hourly hashrate for the pool - * @param none - * @return data array NOT FINISHED YET - **/ - public function getHourlyHashrateByPool() { - $this->debug->append("STA " . __METHOD__, 4); - if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__)) return $data; - $stmt = $this->mysqli->prepare(" - SELECT - id, - IFNULL(SUM(IF(s.difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)), 0) AS shares, - HOUR(s.time) AS hour - FROM " . $this->share->getTableName() . " AS s - WHERE time <= FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(NOW())/(60*60))*(60*60)) - AND time >= FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(NOW())/(60*60))*(60*60)) - INTERVAL 24 HOUR - AND our_result = 'Y' - GROUP BY HOUR(time) - UNION - SELECT - share_id, - IFNULL(SUM(IF(s.difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)), 0) AS shares, - HOUR(s.time) AS hour - FROM " . $this->share->getArchiveTableName() . " AS s - WHERE time <= FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(NOW())/(60*60))*(60*60)) - AND time >= FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(NOW())/(60*60))*(60*60)) - INTERVAL 24 HOUR - AND our_result = 'Y' - GROUP BY HOUR(time)"); - if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) { - $iStartHour = date('G'); - // Initilize array - for ($i = 0; $i < 24; $i++) $aData[($iStartHour + $i) % 24] = 0; - // Fill data - while ($row = $result->fetch_assoc()) $aData[$row['hour']] += (int) $this->coin->calcHashrate($row['shares'], 3600); - return $this->memcache->setCache(__FUNCTION__, $aData); + timestamp, + FROM_UNIXTIME(timestamp, '%Y-%m-%d %H:%i') AS time, + AVG(hashrate) AS hashrate, + AVG(workers) AS workers, + AVG(sharerate) AS sharerate + FROM " . $this->getUserStatsTableName() . " + WHERE FROM_UNIXTIME(timestamp) >= DATE_SUB(NOW(), INTERVAL $days DAY) + AND account_id = ? + GROUP BY DAY(FROM_UNIXTIME(timestamp)), HOUR(FROM_UNIXTIME(timestamp))"); + if ($this->checkStmt($stmt) && $stmt->bind_param('i', $account_id) && $stmt->execute() && $result = $stmt->get_result()) { + $aData = $result->fetch_all(MYSQLI_ASSOC); + if ($format == 'json') $aData = json_encode($aData); + return $this->memcache->setCache(__FUNCTION__ . $account_id . $format, $aData); } return $this->sqlError(); } @@ -744,7 +733,7 @@ class Statistics extends Base { if ($this->config['payout_system'] != 'pps') { if (@$value1['valid'] > 0 && @$value2['valid'] > 0) { $this->config['reward_type'] == 'fixed' ? $reward = $this->config['reward'] : $reward = $this->block->getAverageAmount(); - $aEstimates['block'] = round(( (int)$value2['valid'] / (int)$value1['valid'] ) * (float)$reward, 8); + $aEstimates['block'] = round(( (float)$value2['valid'] / (float)$value1['valid'] ) * (float)$reward, 8); $bNoFees == 0 ? $aEstimates['fee'] = round(((float)$this->config['fees'] / 100) * (float)$aEstimates['block'], 8) : $aEstimates['fee'] = 0; $aEstimates['donation'] = round((( (float)$dDonate / 100) * ((float)$aEstimates['block'] - (float)$aEstimates['fee'])), 8); $aEstimates['payout'] = round((float)$aEstimates['block'] - (float)$aEstimates['donation'] - (float)$aEstimates['fee'], 8); @@ -789,7 +778,7 @@ class Statistics extends Base { SELECT IFNULL(COUNT(id), 0) as count, IFNULL(AVG(difficulty), 0) as average, - IFNULL(ROUND(SUM(shares)), 0) as shares, + IFNULL(SUM(shares), 0) as shares, IFNULL(SUM(amount), 0) as rewards FROM " . $this->block->getTableName() . " WHERE FROM_UNIXTIME(time) > DATE_SUB(now(), INTERVAL ? HOUR) @@ -914,6 +903,17 @@ class Statistics extends Base { return $this->memcache->setCache(__FUNCTION__, $result->fetch_object()->total); return $this->sqlError(); } + + /** + * Purge older entries from our statistics_users table + **/ + public function purgeUserStats($days = 1) { + // Fallbacks if unset + $stmt = $this->mysqli->prepare("DELETE FROM " . $this->getUserStatsTableName() . " WHERE FROM_UNIXTIME(timestamp) <= DATE_SUB(NOW(), INTERVAL ? DAY)"); + if ($this->checkStmt($stmt) && $stmt->bind_param('i', $days) && $stmt->execute()) + return $stmt->affected_rows; + return $this->sqlError(); + } } $statistics = new Statistics(); diff --git a/include/classes/tools.class.php b/include/classes/tools.class.php index c42f5159..06b3626e 100644 --- a/include/classes/tools.class.php +++ b/include/classes/tools.class.php @@ -81,6 +81,8 @@ class Tools extends Base { return 'cryptorush'; } else if (preg_match('/mintpal.com/', $url)) { return 'mintpal'; + } else if (preg_match('/bittrex.com/', $url)) { + return 'bittrex'; } $this->setErrorMessage("API URL unknown"); return false; @@ -116,6 +118,9 @@ class Tools extends Base { case 'mintpal': return @$aData['0']['last_price']; break; + case 'bittrex': + return @$aData['result']['Last']; + break; } } else { $this->setErrorMessage("Got an invalid response from ticker API"); diff --git a/include/classes/transaction.class.php b/include/classes/transaction.class.php index d01b53e3..8920419b 100644 --- a/include/classes/transaction.class.php +++ b/include/classes/transaction.class.php @@ -355,7 +355,7 @@ class Transaction extends Base { a.id, a.username, a.ap_threshold, - a.coin_address, + ca.coin_address, IFNULL( ROUND( ( @@ -370,11 +370,13 @@ class Transaction extends Base { ON t.block_id = b.id LEFT JOIN " . $this->user->getTableName() . " AS a ON t.account_id = a.id - WHERE t.archived = 0 AND a.ap_threshold > 0 AND a.coin_address IS NOT NULL AND a.coin_address != '' + LEFT JOIN " . $this->coin_address->getTableName() . " AS ca + ON ca.account_id = a.id + WHERE t.archived = 0 AND a.ap_threshold > 0 AND ca.coin_address IS NOT NULL AND ca.coin_address != '' AND ca.currency = ? GROUP BY t.account_id HAVING confirmed > a.ap_threshold AND confirmed > " . $this->config['txfee_auto'] . " LIMIT ?"); - if ($this->checkStmt($stmt) && $stmt->bind_param('i', $limit) && $stmt->execute() && $result = $stmt->get_result()) + if ($this->checkStmt($stmt) && $stmt->bind_param('si', $this->config['currency'], $limit) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_all(MYSQLI_ASSOC); return $this->sqlError(); } @@ -446,7 +448,7 @@ class Transaction extends Base { a.id, a.username, a.ap_threshold, - a.coin_address, + ca.coin_address, p.id AS payout_id, IFNULL( ROUND( @@ -464,11 +466,13 @@ class Transaction extends Base { ON t.account_id = p.account_id LEFT JOIN " . $this->block->getTableName() . " AS b ON t.block_id = b.id - WHERE p.completed = 0 AND t.archived = 0 AND a.coin_address IS NOT NULL AND a.coin_address != '' + LEFT JOIN " . $this->coin_address->getTableName() . " AS ca + ON ca.account_id = a.id + WHERE p.completed = 0 AND t.archived = 0 AND ca.currency = ? AND ca.coin_address IS NOT NULL AND ca.coin_address != '' GROUP BY t.account_id HAVING confirmed > " . $this->config['txfee_manual'] . " LIMIT ?"); - if ($this->checkStmt($stmt) && $stmt->bind_param('i', $limit) && $stmt->execute() && $result = $stmt->get_result()) + if ($this->checkStmt($stmt) && $stmt->bind_param('si', $this->config['currency'], $limit) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_all(MYSQLI_ASSOC); return $this->sqlError('E0050'); } @@ -478,6 +482,7 @@ $transaction = new Transaction(); $transaction->setMemcache($memcache); $transaction->setNotification($notification); $transaction->setDebug($debug); +$transaction->setCoinAddress($coin_address); $transaction->setMysql($mysqli); $transaction->setConfig($config); $transaction->setBlock($block); diff --git a/include/classes/user.class.php b/include/classes/user.class.php index 7cb0d58b..91172e90 100644 --- a/include/classes/user.class.php +++ b/include/classes/user.class.php @@ -163,7 +163,7 @@ class User extends Base { $invitation->setDebug($this->debug); $invitation->setLog($this->log); $stmt = $this->mysqli->prepare(" - SELECT COUNT(i.account_id) AS invitationcount,a.id,a.username,a.email, + SELECT COUNT(i.account_id) AS invitationcount,a.id,a.username,a.email, (SELECT COUNT(account_id) FROM " . $invitation->getTableName() . " WHERE account_id = i.account_id AND is_activated = 1 GROUP BY account_id) AS activated FROM " . $invitation->getTableName() . " AS i LEFT JOIN " . $this->getTableName() . " AS a @@ -340,38 +340,20 @@ class User extends Base { $this->debug->append("STA " . __METHOD__, 4); $stmt = $this->mysqli->prepare(" SELECT - id, username, coin_address, ap_threshold - FROM " . $this->getTableName() . " - WHERE ap_threshold > 0 - AND coin_address IS NOT NULL + a.id, a.username, ca.coin_address AS coin_address, a.ap_threshold + FROM " . $this->getTableName() . " AS a + LEFT JOIN " . $this->coin_address->getTableName() . " AS ca + ON a.id = ca.account_id + WHERE ap_threshold > 0 AND ca.currency = ? + AND ca.coin_address IS NOT NULL "); - if ( $this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) { + if ( $this->checkStmt($stmt) && $stmt->bind_param('s', $this->config['currency']) && $stmt->execute() && $result = $stmt->get_result()) { return $result->fetch_all(MYSQLI_ASSOC); } $this->debug->append("Unable to fetch users with AP set"); return false; } - /** - * Fetch users coin address - * @param userID int UserID - * @return data string Coin Address - **/ - public function getCoinAddress($userID) { - $this->debug->append("STA " . __METHOD__, 4); - return $this->getSingle($userID, 'coin_address', 'id'); - } - - /** - * Check if a coin address exists already - * @param address string Coin Address - * @return bool True of false - **/ - public function existsCoinAddress($address) { - $this->debug->append("STA " . __METHOD__, 4); - return $this->getSingle($address, 'coin_address', 'coin_address', 's') === $address; - } - /** * Fetch users donation value * @param userID int UserID @@ -514,12 +496,12 @@ class User extends Base { $this->setErrorMessage('Donation above allowed 100% limit'); return false; } - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + if ($email != 'hidden' && $email != NULL && !filter_var($email, FILTER_VALIDATE_EMAIL)) { $this->setErrorMessage('Invalid email address'); return false; } if (!empty($address)) { - if ($address != $this->getCoinAddress($userID) && $this->existsCoinAddress($address)) { + if ($address != $this->coin_address->getCoinAddress($userID) && $this->coin_address->existsCoinAddress($address)) { $this->setErrorMessage('Address is already in use'); return false; } @@ -558,11 +540,23 @@ class User extends Base { } } + // If we hide our email or it's not set, fetch current one to update + if ($email == 'hidden' || $email == NULL) + $email = $this->getUserEmailById($userID); // We passed all validation checks so update the account - $stmt = $this->mysqli->prepare("UPDATE $this->table SET coin_address = ?, ap_threshold = ?, donate_percent = ?, email = ?, timezone = ?, is_anonymous = ? WHERE id = ?"); - if ($this->checkStmt($stmt) && $stmt->bind_param('sddssii', $address, $threshold, $donate, $email, $timezone, $is_anonymous, $userID) && $stmt->execute()) { + $stmt = $this->mysqli->prepare("UPDATE $this->table SET ap_threshold = ?, donate_percent = ?, email = ?, timezone = ?, is_anonymous = ? WHERE id = ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param('ddssii', $threshold, $donate, $email, $timezone, $is_anonymous, $userID) && $stmt->execute()) { $this->log->log("info", $this->getUserName($userID)." updated their account details"); - return true; + // Update coin address too + if ($address) { + if ($this->coin_address->update($userID, $address)) { + return true; + } + } else { + if ($this->coin_address->remove($userID, $address)) { + return true; + } + } } // Catchall $this->setErrorMessage('Failed to update your account'); @@ -703,22 +697,18 @@ class User extends Base { $this->debug->append("Fetching user information for user id: $userID"); $stmt = $this->mysqli->prepare(" SELECT - id, username, pin, api_key, is_admin, is_anonymous, email, timezone, no_fees, - IFNULL(donate_percent, '0') as donate_percent, coin_address, ap_threshold - FROM $this->table + id AS id, username, pin, api_key, is_admin, is_anonymous, email, timezone, no_fees, + IFNULL(donate_percent, '0') as donate_percent, ap_threshold + FROM " . $this->getTableName() . " WHERE id = ? LIMIT 0,1"); - if ($this->checkStmt($stmt)) { - $stmt->bind_param('i', $userID); - if (!$stmt->execute()) { - $this->debug->append('Failed to execute statement'); - return false; - } - $result = $stmt->get_result(); + if ($this->checkStmt($stmt) && $stmt->bind_param('i', $userID) && $stmt->execute() && $result = $stmt->get_result()) { + $aData = $result->fetch_assoc(); + $aData['coin_address'] = $this->coin_address->getCoinAddress($userID); $stmt->close(); - return $result->fetch_assoc(); + return $aData; } $this->debug->append("Failed to fetch user information for $userID"); - return false; + return $this->sqlError(); } /** @@ -742,6 +732,10 @@ class User extends Base { return false; } if (!is_null($coinaddress)) { + if ($this->coin_address->existsCoinAddress($coinaddress)) { + $this->setErrorMessage('Coin address is already taken'); + return false; + } if (!$this->bitcoin->validateaddress($coinaddress)) { $this->setErrorMessage('Coin address is not valid'); return false; @@ -755,7 +749,7 @@ class User extends Base { $this->setErrorMessage( 'This e-mail address is already taken' ); return false; } - if (strlen($password1) < 8) { + if (strlen($password1) < 8) { $this->setErrorMessage( 'Password is too short, minimum of 8 characters required' ); return false; } @@ -801,15 +795,15 @@ class User extends Base { ! $this->setting->getValue('accounts_confirm_email_disabled') ? $is_locked = 1 : $is_locked = 0; $is_admin = 0; $stmt = $this->mysqli->prepare(" - INSERT INTO $this->table (username, pass, email, signup_timestamp, pin, api_key, is_locked, coin_address) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO $this->table (username, pass, email, signup_timestamp, pin, api_key, is_locked) + VALUES (?, ?, ?, ?, ?, ?, ?) "); } else { $is_locked = 0; $is_admin = 1; $stmt = $this->mysqli->prepare(" - INSERT INTO $this->table (username, pass, email, signup_timestamp, pin, api_key, is_admin, is_locked, coin_address) - VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?) + INSERT INTO $this->table (username, pass, email, signup_timestamp, pin, api_key, is_admin, is_locked) + VALUES (?, ?, ?, ?, ?, ?, 1, ?) "); } @@ -820,7 +814,9 @@ class User extends Base { $username_clean = strip_tags($username); $signup_time = time(); - if ($this->checkStmt($stmt) && $stmt->bind_param('sssissis', $username_clean, $password_hash, $email1, $signup_time, $pin_hash, $apikey_hash, $is_locked, $coinaddress) && $stmt->execute()) { + if ($this->checkStmt($stmt) && $stmt->bind_param('sssissi', $username_clean, $password_hash, $email1, $signup_time, $pin_hash, $apikey_hash, $is_locked) && $stmt->execute()) { + $new_account_id = $this->mysqli->insert_id; + if (!is_null($coinaddress)) $this->coin_address->add($new_account_id, $coinaddress); if (! $this->setting->getValue('accounts_confirm_email_disabled') && $is_admin != 1) { if ($token = $this->token->createToken('confirm_email', $stmt->insert_id)) { $aData['username'] = $username_clean; @@ -843,7 +839,8 @@ class User extends Base { } else { $this->setErrorMessage( 'Unable to register' ); $this->debug->append('Failed to insert user into DB: ' . $this->mysqli->error); - if ($stmt->sqlstate == '23000') $this->setErrorMessage( 'Username, email or Coinaddress already registered' ); + echo $this->mysqli->error; + if ($stmt->sqlstate == '23000') $this->setErrorMessage( 'Username or email already registered' ); return false; } return false; @@ -997,4 +994,5 @@ $user->setMail($mail); $user->setToken($oToken); $user->setBitcoin($bitcoin); $user->setSetting($setting); +$user->setCoinAddress($coin_address); $user->setErrorCodes($aErrorCodes); diff --git a/include/config/admin_settings.inc.php b/include/config/admin_settings.inc.php index 4f861830..2ae6716e 100644 --- a/include/config/admin_settings.inc.php +++ b/include/config/admin_settings.inc.php @@ -160,6 +160,13 @@ $aSettings['statistics'][] = array( '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.' ); +$aSettings['statistics'][] = array( + 'display' => 'Graphing Days', 'type' => 'text', + 'size' => 25, + 'default' => 1, + 'name' => 'statistics_graphing_days', 'value' => $setting->getValue('statistics_graphing_days'), + 'tooltip' => 'How many days to graph out on the statistics -> graphs page.' +); $aSettings['statistics'][] = array( 'display' => 'Block Statistics Count', 'type' => 'text', 'size' => 25, diff --git a/include/config/monitor_crons.inc.php b/include/config/monitor_crons.inc.php index 732c1bdc..d3726539 100644 --- a/include/config/monitor_crons.inc.php +++ b/include/config/monitor_crons.inc.php @@ -3,15 +3,13 @@ // Small helper array that may be used on some page controllers to // fetch the crons we wish to monitor switch ($config['payout_system']) { - case 'pplns': - $sPayoutSystem = $config['payout_system'] . '_payout'; - break; - case 'pps': - $sPayoutSystem = $config['payout_system'] . '_payout'; - break; case 'prop': $sPayoutSystem = 'proportional_payout'; break; + default: // pps && pplns land here + $sPayoutSystem = $config['payout_system'] . '_payout'; } $aMonitorCrons = array('statistics','tickerupdate','notifications','tables_cleanup','findblock',$sPayoutSystem,'blockupdate','payouts'); + +?> diff --git a/include/lib/jsonRPCClient.php b/include/lib/jsonRPCClient.php index ba09ba63..b6675f2d 100644 --- a/include/lib/jsonRPCClient.php +++ b/include/lib/jsonRPCClient.php @@ -108,7 +108,10 @@ class jsonRPCClient { curl_setopt($ch, CURLOPT_USERPWD, $url['user'] . ":" . $url['pass']); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $request); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + // curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); $response = curl_exec($ch); + if (curl_errno($ch)) throw new Exception('RPC call failed: ' . curl_error($ch)); if ($this->debug) $this->debug_output[] = 'Response: ' . $response; $response = json_decode($response, true); $resultStatus = curl_getinfo($ch); @@ -116,7 +119,6 @@ class jsonRPCClient { if ($resultStatus['http_code'] == '401') throw new Exception('RPC call did not return 200: Authentication failed'); throw new Exception('RPC call did not return 200: HTTP error: ' . $resultStatus['http_code'] . ' - JSON Response: [' . @$response['error']['code'] . '] ' . @$response['error']['message']); } - if (curl_errno($ch)) throw new Exception('RPC call failed: ' . curl_error($ch)); curl_close($ch); // final checks and return diff --git a/include/pages/account/edit.inc.php b/include/pages/account/edit.inc.php index 78782ad4..234f68d6 100644 --- a/include/pages/account/edit.inc.php +++ b/include/pages/account/edit.inc.php @@ -104,7 +104,7 @@ if ($user->isAuthenticated()) { $_SESSION['POPUP'][] = array('CONTENT' => 'You have not yet unlocked account withdrawls.', 'TYPE' => 'alert alert-danger'); } else if ($aBalance['confirmed'] < $config['mp_threshold']) { $_SESSION['POPUP'][] = array('CONTENT' => 'Payout must be greater or equal than ' . $config['mp_threshold'] . '.', 'TYPE' => 'info'); - } else if (!$user->getCoinAddress($_SESSION['USERDATA']['id'])) { + } else if (!$coin_address->getCoinAddress($_SESSION['USERDATA']['id'])) { $_SESSION['POPUP'][] = array('CONTENT' => 'You have no payout address set.', 'TYPE' => 'alert alert-danger'); } else { $user->log->log("info", $_SESSION['USERDATA']['username']." requesting manual payout"); diff --git a/include/pages/account/workers.inc.php b/include/pages/account/workers.inc.php index eeb9fb26..a672e11c 100644 --- a/include/pages/account/workers.inc.php +++ b/include/pages/account/workers.inc.php @@ -4,12 +4,10 @@ $defflip = (!cfip()) ? exit(header('HTTP/1.1 401 Unauthorized')) : 1; if ($user->isAuthenticated()) { - if (!$user->getCoinAddress($_SESSION['USERDATA']['id']) AND $setting->getValue('disable_worker_edit')) { - + if (!$coin_address->getCoinAddress($_SESSION['USERDATA']['id']) AND $setting->getValue('disable_worker_edit')) { $_SESSION['POPUP'][] = array('CONTENT' => 'You have no payout address set.', 'TYPE' => 'alert alert-danger'); $_SESSION['POPUP'][] = array('CONTENT' => 'You can not add workers unless a valid Payout Address is set in your User Settings.', 'TYPE' => 'alert alert-danger'); $smarty->assign('CONTENT', 'disabled.tpl'); - } else { switch (@$_REQUEST['do']) { case 'delete': diff --git a/include/pages/admin/newsletter.inc.php b/include/pages/admin/newsletter.inc.php index 69204b01..fed4f342 100644 --- a/include/pages/admin/newsletter.inc.php +++ b/include/pages/admin/newsletter.inc.php @@ -20,7 +20,7 @@ if ($setting->getValue('notifications_disable_pool_newsletter', 0) == 1) { $iSuccess = 0; foreach ($user->getAllAssoc() as $aData) { $aUserNotificationSettings = $notification->getNotificationSettings($aData['id']); - if ($aData['is_locked'] != 0 || $aUserNotificationSettings['newsletter'] != 1) continue; + if ($aData['is_locked'] != 0 || $aUserNotificationSettings['newsletter'] != 1 || empty($aData['email'])) continue; $aData['subject'] = $_REQUEST['data']['subject']; $aData['CONTENT'] = $_REQUEST['data']['content']; if (!$mail->sendMail('newsletter/body', $aData, true)) { diff --git a/include/pages/api.inc.php b/include/pages/api.inc.php index 01cd20f5..32fd0a34 100644 --- a/include/pages/api.inc.php +++ b/include/pages/api.inc.php @@ -5,7 +5,7 @@ $defflip = (!cfip()) ? exit(header('HTTP/1.1 401 Unauthorized')) : 1; $api->isActive(); // Check for valid API key -$id = $user->checkApiKey($_REQUEST['api_key']); +$id = $user->checkApiKey(@$_REQUEST['api_key']); header('HTTP/1.1 400 Bad Request'); die('400 Bad Request'); diff --git a/include/pages/api/public.inc.php b/include/pages/api/public.inc.php index c6b128d6..6446b5e5 100644 --- a/include/pages/api/public.inc.php +++ b/include/pages/api/public.inc.php @@ -19,7 +19,9 @@ echo json_encode( 'workers' => $worker->getCountAllActiveWorkers(), 'shares_this_round' => $aShares['valid'], 'last_block' => $aLastBlock['height'], - 'network_hashrate' => $dNetworkHashrate + 'network_hashrate' => $dNetworkHashrate, + 'fee' => $config['fees'], + 'payout' => $config['payout_system'] ) ); diff --git a/include/pages/dashboard.inc.php b/include/pages/dashboard.inc.php index e813c834..b21b7b11 100644 --- a/include/pages/dashboard.inc.php +++ b/include/pages/dashboard.inc.php @@ -52,6 +52,7 @@ if ($user->isAuthenticated()) { $smarty->assign('BLOCKSFOUND', $aLastBlocks); $smarty->assign('DISABLED_DASHBOARD', $setting->getValue('disable_dashboard')); $smarty->assign('DISABLED_DASHBOARD_API', $setting->getValue('disable_dashboard_api')); + $smarty->assign('DISABLED_API', $setting->getValue('disable_api')); $smarty->assign('ESTIMATES', array('shares' => $iEstShares, 'percent' => $dEstPercent)); $smarty->assign('NETWORK', array('difficulty' => $dDifficulty, 'block' => $iBlock, 'EstNextDifficulty' => $dEstNextDifficulty, 'EstTimePerBlock' => $dExpectedTimePerBlock, 'BlocksUntilDiffChange' => $iBlocksUntilDiffChange)); $smarty->assign('INTERVAL', $interval / 60); diff --git a/include/pages/login.inc.php b/include/pages/login.inc.php index d58f9213..8d4a05df 100644 --- a/include/pages/login.inc.php +++ b/include/pages/login.inc.php @@ -18,28 +18,30 @@ if ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_ena } } -if ($setting->getValue('maintenance') && !$user->isAdmin($user->getUserIdByEmail($_POST['username']))) { - $_SESSION['POPUP'][] = array('CONTENT' => 'You are not allowed to login during maintenace.', 'TYPE' => 'info'); -} else if (!empty($_POST['username']) && !empty($_POST['password'])) { - // Check if recaptcha is enabled, process form data if valid - if (!$setting->getValue('recaptcha_enabled') || !$setting->getValue('recaptcha_enabled_logins') || ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_enabled_logins') && $rsp->is_valid)) { - if (!$config['csrf']['enabled'] || $config['csrf']['enabled'] && $csrftoken->valid) { - // check if login is correct - if ($user->checkLogin(@$_POST['username'], @$_POST['password']) ) { - $port = ($_SERVER["SERVER_PORT"] == "80" || $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]); - $location = (@$_SERVER['HTTPS'] == "on") ? 'https://' : 'http://'; - $location .= $_SERVER['SERVER_NAME'] . $port . $_SERVER['SCRIPT_NAME']; - $location.= '?page=dashboard'; - if (!headers_sent()) header('Location: ' . $location); - exit(''); +if (!empty($_POST['username']) && !empty($_POST['password'])) { + if ($setting->getValue('maintenance') && !$user->isAdmin($user->getUserIdByEmail($_POST['username']))) { + $_SESSION['POPUP'][] = array('CONTENT' => 'You are not allowed to login during maintenace.', 'TYPE' => 'alert alert-info'); + } else { + // Check if recaptcha is enabled, process form data if valid + if (!$setting->getValue('recaptcha_enabled') || !$setting->getValue('recaptcha_enabled_logins') || ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_enabled_logins') && $rsp->is_valid)) { + if (!$config['csrf']['enabled'] || $config['csrf']['enabled'] && $csrftoken->valid) { + // check if login is correct + if ($user->checkLogin(@$_POST['username'], @$_POST['password']) ) { + $port = ($_SERVER["SERVER_PORT"] == "80" || $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]); + $location = (@$_SERVER['HTTPS'] == "on") ? 'https://' : 'http://'; + $location .= $_SERVER['SERVER_NAME'] . $port . $_SERVER['SCRIPT_NAME']; + $location.= '?page=dashboard'; + if (!headers_sent()) header('Location: ' . $location); + exit(''); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to login: '.$user->getError(), 'TYPE' => 'alert alert-danger'); + } } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to login: '.$user->getError(), 'TYPE' => 'alert alert-danger'); + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'alert alert-warning'); } } else { - $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'alert alert-warning'); + $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid Captcha, please try again.', 'TYPE' => 'alert alert-danger'); } - } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid Captcha, please try again.', 'TYPE' => 'alert alert-danger'); } } // Load login template diff --git a/include/pages/statistics/graphs.inc.php b/include/pages/statistics/graphs.inc.php index 0a19492d..a212e941 100644 --- a/include/pages/statistics/graphs.inc.php +++ b/include/pages/statistics/graphs.inc.php @@ -4,11 +4,9 @@ $defflip = (!cfip()) ? exit(header('HTTP/1.1 401 Unauthorized')) : 1; if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { $debug->append('No cached version available, fetching from backend', 3); if ($user->isAuthenticated()) { - $aHourlyHashRates = $statistics->getHourlyHashrateByAccount($_SESSION['USERDATA']['username'], $_SESSION['USERDATA']['id']); - $aPoolHourlyHashRates = $statistics->getHourlyHashrateByPool(); + $aHourlyMiningStats = $statistics->getHourlyMiningStatsByAccount($_SESSION['USERDATA']['id'], 'json', $setting->getValue('statistics_graphing_days', 1)); } - $smarty->assign("YOURHASHRATES", @$aHourlyHashRates); - $smarty->assign("POOLHASHRATES", @$aPoolHourlyHashRates); + $smarty->assign('YOURMININGSTATS', @$aHourlyMiningStats); } else { $debug->append('Using cached page', 3); } diff --git a/include/smarty_globals.inc.php b/include/smarty_globals.inc.php index ea13a6d1..f8549287 100644 --- a/include/smarty_globals.inc.php +++ b/include/smarty_globals.inc.php @@ -66,6 +66,7 @@ $aGlobal = array( 'coinaddresscheck' => $config['check_valid_coinaddress'], 'csrf' => $config['csrf'], 'config' => array( + 'sharediffprecision' => $coin->getShareDifficultyPrecision(), 'date' => $setting->getValue('system_date_format', '%m/%d/%Y %H:%M:%S'), 'website_design' => $setting->getValue('website_design'), 'poolnav_enabled' => $setting->getValue('poolnav_enabled'), diff --git a/include/version.inc.php b/include/version.inc.php index 91d93ee8..46e122fb 100644 --- a/include/version.inc.php +++ b/include/version.inc.php @@ -2,7 +2,7 @@ $defflip = (!cfip()) ? exit(header('HTTP/1.1 401 Unauthorized')) : 1; define('MPOS_VERSION', '0.0.4'); -define('DB_VERSION', '0.0.11'); +define('DB_VERSION', '0.0.14'); define('CONFIG_VERSION', '0.0.8'); define('HASH_VERSION', 1); diff --git a/scripts/test_email.php b/scripts/test_email.php new file mode 100755 index 00000000..838e846b --- /dev/null +++ b/scripts/test_email.php @@ -0,0 +1,19 @@ + $setting->getValue('system_error_email'), + 'subject' => 'Test email from mining pool', + 'coinname' => $config['gettingstarted']['coinname'], + 'stratumurl' => $config['gettingstarted']['stratumurl'], + 'stratumport' => $config['gettingstarted']['stratumport'] +); + +if (!$mail->sendMail('notifications/test_email', $aMailData)) + echo "Failed to send test email" . PHP_EOL; diff --git a/scripts/validate_addresses.php b/scripts/validate_addresses.php index 15ad46b2..83c314cc 100755 --- a/scripts/validate_addresses.php +++ b/scripts/validate_addresses.php @@ -42,6 +42,7 @@ echo 'Validating all coin addresses. This may take some time.' . PHP_EOL . PHP_E printf($mask, 'Username', 'E-Mail', 'Address', 'Status'); foreach ($users as $aData) { + $aData['coin_address'] = $coin_address->getCoinAddress($aData['id']); if (empty($aData['coin_address']) && $aData['is_locked'] == 0) { $status = 'UNSET'; } else if ($aData['is_locked'] == 1) { diff --git a/scripts/validate_users.php b/scripts/validate_users.php index fe80e2aa..cd99bbb6 100755 --- a/scripts/validate_users.php +++ b/scripts/validate_users.php @@ -36,7 +36,6 @@ $username = $user['username']; $loggedIp = $user['loggedIp']; $lastLogin = $user['last_login']; - $coinAddress = $user['coin_address']; $mailAddress = $user['email']; $everLoggedIn = !empty($lastLogin); diff --git a/sql/000_base_structure.sql b/sql/000_base_structure.sql index 85e3e67c..b9887a51 100644 --- a/sql/000_base_structure.sql +++ b/sql/000_base_structure.sql @@ -53,6 +53,16 @@ CREATE TABLE IF NOT EXISTS `blocks` ( KEY `time` (`time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Discovered blocks persisted from Litecoin Service'; +CREATE TABLE IF NOT EXISTS `coin_addresses` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `account_id` int(11) NOT NULL, + `currency` varchar(5) NOT NULL, + `coin_address` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `coin_address` (`coin_address`), + KEY `account_id` (`account_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + CREATE TABLE IF NOT EXISTS `invitations` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `account_id` int(11) unsigned NOT NULL, @@ -134,7 +144,7 @@ CREATE TABLE IF NOT EXISTS `settings` ( UNIQUE KEY `setting` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.11'); +INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.13'); CREATE TABLE IF NOT EXISTS `shares` ( `id` bigint(30) NOT NULL AUTO_INCREMENT, @@ -229,12 +239,15 @@ CREATE TABLE IF NOT EXISTS `transactions` ( KEY `account_id_archived` (`account_id`,`archived`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -CREATE TABLE IF NOT EXISTS `templates` ( - `template` varchar(255) NOT NULL, - `active` tinyint(1) NOT NULL DEFAULT 0, - `content` mediumtext, - `modified_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`template`) +CREATE TABLE `statistics_users` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `account_id` int(11) NOT NULL, + `hashrate` int(11) NOT NULL, + `workers` int(11) NOT NULL, + `sharerate` float NOT NULL, + `timestamp` int(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `account_id_timestamp` (`account_id`,`timestamp`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; diff --git a/templates/bootstrap/about/pool/default.tpl b/templates/bootstrap/about/pool/default.tpl index 7c300f9f..b51a45a8 100644 --- a/templates/bootstrap/about/pool/default.tpl +++ b/templates/bootstrap/about/pool/default.tpl @@ -7,7 +7,7 @@