PageRenderTime 70ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/public/include/classes/statistics.class.php

https://github.com/usrio/php-mmcfe-ng
PHP | 778 lines | 591 code | 40 blank | 147 comment | 150 complexity | 96407336d568a415b902f2456d27ca0e MD5 | raw file
Possible License(s): Apache-2.0
  1. <?php
  2. // Make sure we are called from index.php
  3. if (!defined('SECURITY'))
  4. die('Hacking attempt');
  5. /*
  6. * We give access to plenty of statistics through this class
  7. * Statistics should be non-intrusive and not change any
  8. * rows in our database to ensure data integrity for the backend
  9. **/
  10. class Statistics {
  11. private $sError = '';
  12. private $table = 'statistics_shares';
  13. private $getcache = true;
  14. public function __construct($debug, $mysqli, $config, $share, $user, $block, $memcache) {
  15. $this->debug = $debug;
  16. $this->mysqli = $mysqli;
  17. $this->share = $share;
  18. $this->config = $config;
  19. $this->user = $user;
  20. $this->block = $block;
  21. $this->memcache = $memcache;
  22. $this->debug->append("Instantiated Share class", 2);
  23. }
  24. /* Some basic get and set methods
  25. **/
  26. private function setErrorMessage($msg) {
  27. $this->sError = $msg;
  28. }
  29. public function getError() {
  30. return $this->sError;
  31. }
  32. // Disable fetching values from cache
  33. public function setGetCache($set=false) {
  34. $this->getcache = $set;
  35. }
  36. public function getGetCache() {
  37. return $this->getcache;
  38. }
  39. private function checkStmt($bState) {
  40. if ($bState ===! true) {
  41. $this->debug->append("Failed to prepare statement: " . $this->mysqli->error);
  42. $this->setErrorMessage('Failed to prepare statement');
  43. return false;
  44. }
  45. return true;
  46. }
  47. /**
  48. * Fetch last found blocks by time
  49. **/
  50. function getLastValidBlocksbyTime($aTimeFrame) {
  51. $this->debug->append("STA " . __METHOD__, 4);
  52. if ($data = $this->memcache->get(__FUNCTION__ . $aTimeFrame)) return $data;
  53. $date = new DateTime();
  54. $actualTime = $date->getTimestamp();
  55. $aTimeDiff = $actualTime - $aTimeFrame;
  56. if ($aTimeFrame == 0) $aTimeDiff = 0;
  57. $stmt = $this->mysqli->prepare("
  58. SELECT COUNT(id) AS count FROM " . $this->block->getTableName() . "
  59. WHERE confirmations > 0
  60. AND time >= ?
  61. ");
  62. if ($this->checkStmt($stmt) && $stmt->bind_param('i', $aTimeDiff) && $stmt->execute() && $result = $stmt->get_result())
  63. return $this->memcache->setCache(__FUNCTION__ . $aTimeFrame, $result->fetch_object()->count);
  64. $this->debug->append("Failed to get valid Blocks by time: ". $this->mysqli->error);
  65. return false;
  66. }
  67. function getLastOrphanBlocksbyTime($aTimeFrame) {
  68. $this->debug->append("STA " . __METHOD__, 4);
  69. if ($data = $this->memcache->get(__FUNCTION__ . $aTimeFrame)) return $data;
  70. $date = new DateTime();
  71. $actualTime = $date->getTimestamp();
  72. $aTimeDiff = $actualTime - $aTimeFrame;
  73. if ($aTimeFrame == 0) $aTimeDiff = 0;
  74. $stmt = $this->mysqli->prepare("
  75. SELECT COUNT(id) AS count FROM " . $this->block->getTableName() . "
  76. WHERE confirmations = -1
  77. AND time >= ?
  78. ");
  79. if ($this->checkStmt($stmt) && $stmt->bind_param('i', $aTimeDiff) && $stmt->execute() && $result = $stmt->get_result())
  80. return $this->memcache->setCache(__FUNCTION__ . $aTimeFrame, $result->fetch_object()->count);
  81. $this->debug->append("Failed to get orphan Blocks by time: ". $this->mysqli->error);
  82. return false;
  83. }
  84. /**
  85. * Get our last $limit blocks found
  86. * @param limit int Last limit blocks
  87. * @return array
  88. **/
  89. public function getBlocksFound($limit=10) {
  90. $this->debug->append("STA " . __METHOD__, 4);
  91. if ($data = $this->memcache->get(__FUNCTION__ . $limit)) return $data;
  92. $stmt = $this->mysqli->prepare("
  93. SELECT
  94. b.*,
  95. a.username AS finder,
  96. a.is_anonymous AS is_anonymous,
  97. ROUND((difficulty * POW(2, 32 - " . $this->config['target_bits'] . ")) / POW(2, (" . $this->config['difficulty'] . " -16)), 0) AS estshares
  98. FROM " . $this->block->getTableName() . " AS b
  99. LEFT JOIN " . $this->user->getTableName() . " AS a
  100. ON b.account_id = a.id
  101. ORDER BY height DESC LIMIT ?");
  102. if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $result = $stmt->get_result())
  103. return $this->memcache->setCache(__FUNCTION__ . $limit, $result->fetch_all(MYSQLI_ASSOC), 5);
  104. // Catchall
  105. $this->debug->append("Failed to find blocks:" . $this->mysqli->error);
  106. return false;
  107. }
  108. /**
  109. * Get our last $limit blocks found by height
  110. * @param limit int Last limit blocks
  111. * @return array
  112. **/
  113. public function getBlocksFoundHeight($iHeight=0, $limit=10) {
  114. $this->debug->append("STA " . __METHOD__, 4);
  115. if ($data = $this->memcache->get(__FUNCTION__ . $iHeight . $limit)) return $data;
  116. $stmt = $this->mysqli->prepare("
  117. SELECT
  118. b.*,
  119. a.username AS finder,
  120. a.is_anonymous AS is_anonymous,
  121. ROUND((difficulty * POW(2, 32 - " . $this->config['target_bits'] . ")) / POW(2, (" . $this->config['difficulty'] . " -16)), 0) AS estshares
  122. FROM " . $this->block->getTableName() . " AS b
  123. LEFT JOIN " . $this->user->getTableName() . " AS a
  124. ON b.account_id = a.id
  125. WHERE b.height <= ?
  126. ORDER BY height DESC LIMIT ?");
  127. if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $iHeight, $limit) && $stmt->execute() && $result = $stmt->get_result())
  128. return $this->memcache->setCache(__FUNCTION__ . $iHeight . $limit, $result->fetch_all(MYSQLI_ASSOC), 5);
  129. // Catchall
  130. $this->debug->append("Failed to find blocks:" . $this->mysqli->error);
  131. return false;
  132. }
  133. /**
  134. * Currently the only function writing to the database
  135. * Stored per block user statistics of valid and invalid shares
  136. * @param aStats array Array with user id, valid and invalid shares
  137. * @param iBlockId int Block ID as store in the Block table
  138. * @return bool
  139. **/
  140. public function updateShareStatistics($aStats, $iBlockId) {
  141. $this->debug->append("STA " . __METHOD__, 4);
  142. $stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, valid, invalid, block_id) VALUES (?, ?, ?, ?)");
  143. if ($this->checkStmt($stmt) && $stmt->bind_param('iiii', $aStats['id'], $aStats['valid'], $aStats['invalid'], $iBlockId) && $stmt->execute()) return true;
  144. // Catchall
  145. $this->debug->append("Failed to update share stats: " . $this->mysqli->error);
  146. return false;
  147. }
  148. /**
  149. * update user statistics of valid and invalid pplns shares
  150. **/
  151. public function updatePPLNSShareStatistics($aStats, $iBlockId) {
  152. $this->debug->append("STA " . __METHOD__, 4);
  153. $stmt = $this->mysqli->prepare("
  154. UPDATE $this->table SET pplns_valid = ?, pplns_invalid = ? WHERE account_id = ? AND block_id = ?");
  155. if ($this->checkStmt($stmt) && $stmt->bind_param('iiii', $aStats['valid'], $aStats['invalid'], $aStats['id'], $iBlockId) && $stmt->execute()) return true;
  156. // Catchall
  157. $this->debug->append("Failed to update pplns share stats: " . $this->mysqli->error);
  158. return false;
  159. }
  160. /**
  161. * insert user statistics of valid and invalid pplns shares "rbpplns"
  162. **/
  163. public function insertPPLNSShareStatistics($aStats, $iBlockId) {
  164. $this->debug->append("STA " . __METHOD__, 4);
  165. $stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, valid, invalid, pplns_valid, pplns_invalid, block_id) VALUES (?, 0, 0, ?, ?, ?)");
  166. if ($this->checkStmt($stmt) && $stmt->bind_param('iiii', $aStats['id'], $aStats['valid'], $aStats['invalid'], $iBlockId) && $stmt->execute()) return true;
  167. // Catchall
  168. $this->debug->append("Failed to insert pplns share stats: " . $this->mysqli->error);
  169. return false;
  170. }
  171. /**
  172. * Fetch the share ID from stats for rbpplns
  173. **/
  174. function getIdShareStatistics($aStats, $iBlockId) {
  175. $stmt = $this->mysqli->prepare("
  176. SELECT id AS id FROM $this->table
  177. WHERE account_id = ? AND block_id = ?
  178. ");
  179. if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $aStats['id'], $iBlockId) && $stmt->execute() && $result = $stmt->get_result())
  180. return $result->fetch_object()->id;
  181. return false;
  182. }
  183. /**
  184. * Get our current pool hashrate for the past 10 minutes across both
  185. * shares and shares_archive table
  186. * @param none
  187. * @return data object Return our hashrateas an object
  188. **/
  189. public function getCurrentHashrate($interval=600) {
  190. $this->debug->append("STA " . __METHOD__, 4);
  191. if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__)) return $data;
  192. $stmt = $this->mysqli->prepare("
  193. SELECT
  194. (
  195. (
  196. SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * POW(2, " . $this->config['target_bits'] . ") / ? / 1000), 0) AS hashrate
  197. FROM " . $this->share->getTableName() . "
  198. WHERE time > DATE_SUB(now(), INTERVAL ? SECOND)
  199. AND our_result = 'Y'
  200. ) + (
  201. SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * POW(2, " . $this->config['target_bits'] . ") / ? / 1000), 0) AS hashrate
  202. FROM " . $this->share->getArchiveTableName() . "
  203. WHERE time > DATE_SUB(now(), INTERVAL ? SECOND)
  204. AND our_result = 'Y'
  205. )
  206. ) AS hashrate
  207. FROM DUAL");
  208. // Catchall
  209. if ($this->checkStmt($stmt) && $stmt->bind_param('iiii', $interval, $interval, $interval, $interval) && $stmt->execute() && $result = $stmt->get_result() ) return $this->memcache->setCache(__FUNCTION__, $result->fetch_object()->hashrate);
  210. $this->debug->append("Failed to get hashrate: " . $this->mysqli->error);
  211. return false;
  212. }
  213. /**
  214. * Same as getCurrentHashrate but for Shares
  215. * @param none
  216. * @return data object Our share rate in shares per second
  217. **/
  218. public function getCurrentShareRate($interval=600) {
  219. $this->debug->append("STA " . __METHOD__, 4);
  220. if ($data = $this->memcache->get(__FUNCTION__)) return $data;
  221. $stmt = $this->mysqli->prepare("
  222. SELECT
  223. (
  224. (
  225. SELECT ROUND(COUNT(id) / ?, 2) AS sharerate
  226. FROM " . $this->share->getTableName() . "
  227. WHERE time > DATE_SUB(now(), INTERVAL ? SECOND)
  228. AND our_result = 'Y'
  229. ) + (
  230. SELECT ROUND(COUNT(id) / ?, 2) AS sharerate
  231. FROM " . $this->share->getArchiveTableName() . "
  232. WHERE time > DATE_SUB(now(), INTERVAL ? SECOND)
  233. AND our_result = 'Y'
  234. )
  235. ) AS sharerate
  236. FROM DUAL");
  237. if ($this->checkStmt($stmt) && $stmt->bind_param('iiii', $interval, $interval, $interval, $interval) && $stmt->execute() && $result = $stmt->get_result() ) return $this->memcache->setCache(__FUNCTION__, $result->fetch_object()->sharerate);
  238. // Catchall
  239. $this->debug->append("Failed to fetch share rate: " . $this->mysqli->error);
  240. return false;
  241. }
  242. /**
  243. * Get total shares for this round, since last block found
  244. * @param none
  245. * @return data array invalid and valid shares
  246. **/
  247. public function getRoundShares() {
  248. $this->debug->append("STA " . __METHOD__, 4);
  249. // Try the statistics cron cache, then function cache, then fallback to SQL
  250. if ($data = $this->memcache->get(STATISTICS_ALL_USER_SHARES)) {
  251. $this->debug->append("Found data in statistics cache", 2);
  252. $total = array('valid' => 0, 'invalid' => 0);
  253. foreach ($data['data'] as $aUser) {
  254. $total['valid'] += $aUser['valid'];
  255. $total['invalid'] += $aUser['invalid'];
  256. }
  257. return $total;
  258. }
  259. if ($data = $this->memcache->get(STATISTICS_ROUND_SHARES)) {
  260. $this->debug->append("Found data in local cache", 2);
  261. return $data;
  262. }
  263. $stmt = $this->mysqli->prepare("
  264. SELECT
  265. ROUND(IFNULL(SUM(IF(our_result='Y', IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0)), 0) / POW(2, (" . $this->config['difficulty'] . " - 16)), 0) AS valid,
  266. ROUND(IFNULL(SUM(IF(our_result='N', IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0)), 0) / POW(2, (" . $this->config['difficulty'] . " - 16)), 0) AS invalid
  267. FROM " . $this->share->getTableName() . "
  268. WHERE UNIX_TIMESTAMP(time) > IFNULL((SELECT MAX(time) FROM " . $this->block->getTableName() . "), 0)");
  269. if ( $this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result() )
  270. return $this->memcache->setCache(STATISTICS_ROUND_SHARES, $result->fetch_assoc());
  271. // Catchall
  272. $this->debug->append("Failed to fetch round shares: " . $this->mysqli->error);
  273. return false;
  274. }
  275. /**
  276. * Get amount of shares for a all users
  277. * Used in statistics cron to refresh memcache data
  278. * @param account_id int User ID
  279. * @return data array invalid and valid share counts
  280. **/
  281. public function getAllUserShares() {
  282. $this->debug->append("STA " . __METHOD__, 4);
  283. if (! $data = $this->memcache->get(STATISTICS_ALL_USER_SHARES)) {
  284. $data['share_id'] = 0;
  285. $data['data'] = array();
  286. }
  287. $stmt = $this->mysqli->prepare("
  288. SELECT
  289. ROUND(IFNULL(SUM(IF(our_result='Y', IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0) / POW(2, (" . $this->config['difficulty'] . " - 16)), 0) AS valid,
  290. ROUND(IFNULL(SUM(IF(our_result='N', IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0) / POW(2, (" . $this->config['difficulty'] . " - 16)), 0) AS invalid,
  291. u.id AS id,
  292. u.donate_percent AS donate_percent,
  293. u.is_anonymous AS is_anonymous,
  294. u.username AS username
  295. FROM " . $this->share->getTableName() . " AS s,
  296. " . $this->user->getTableName() . " AS u
  297. WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
  298. AND UNIX_TIMESTAMP(s.time) > IFNULL(
  299. (
  300. SELECT MAX(b.time)
  301. FROM " . $this->block->getTableName() . " AS b
  302. ) ,0 )
  303. AND s.id > ?
  304. GROUP BY u.id");
  305. if ($stmt && $stmt->bind_param('i', $data['share_id']) && $stmt->execute() && $result = $stmt->get_result()) {
  306. $data_new = array();
  307. while ($row = $result->fetch_assoc()) {
  308. if (! array_key_exists($row['id'], $data['data'])) {
  309. $data['data'][$row['id']] = $row;
  310. } else {
  311. $data['data'][$row['id']]['valid'] += $row['valid'];
  312. $data['data'][$row['id']]['invalid'] += $row['invalid'];
  313. $data['data'][$row['id']]['donate_percent'] = $row['donate_percent'];
  314. $data['data'][$row['id']]['is_anonymous'] = $row['is_anonymous'];
  315. }
  316. }
  317. $data['share_id'] = $this->share->getMaxShareId();
  318. return $this->memcache->setCache(STATISTICS_ALL_USER_SHARES, $data);
  319. }
  320. // Catchall
  321. $this->debug->append("Unable to fetch all users round shares: " . $this->mysqli->error);
  322. return false;
  323. }
  324. /**
  325. * Get amount of shares for a specific user
  326. * @param account_id int User ID
  327. * @return data array invalid and valid share counts
  328. **/
  329. public function getUserShares($account_id) {
  330. $this->debug->append("STA " . __METHOD__, 4);
  331. // Dual-caching, try statistics cron first, then fallback to local, then fallbock to SQL
  332. if ($data = $this->memcache->get(STATISTICS_ALL_USER_SHARES)) {
  333. if (array_key_exists($account_id, $data['data']))
  334. return $data['data'][$account_id];
  335. // We have no cached value, we return defaults
  336. return array('valid' => 0, 'invalid' => 0, 'donate_percent' => 0, 'is_anonymous' => 0);
  337. }
  338. if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
  339. $stmt = $this->mysqli->prepare("
  340. SELECT
  341. ROUND(IFNULL(SUM(IF(our_result='Y', IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0) / POW(2, (" . $this->config['difficulty'] . " - 16)), 0) AS valid,
  342. ROUND(IFNULL(SUM(IF(our_result='N', IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0) / POW(2, (" . $this->config['difficulty'] . " - 16)), 0) AS invalid
  343. FROM " . $this->share->getTableName() . " AS s,
  344. " . $this->user->getTableName() . " AS u
  345. WHERE
  346. u.username = SUBSTRING_INDEX( s.username, '.', 1 )
  347. AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM " . $this->block->getTableName() . " AS b),0)
  348. AND u.id = ?");
  349. if ($stmt && $stmt->bind_param("i", $account_id) && $stmt->execute() && $result = $stmt->get_result())
  350. return $this->memcache->setCache(__FUNCTION__ . $account_id, $result->fetch_assoc());
  351. // Catchall
  352. $this->debug->append("Unable to fetch user round shares: " . $this->mysqli->error);
  353. return false;
  354. }
  355. /**
  356. * Admin panel specific query
  357. * @return data array invlid and valid shares for all accounts
  358. **/
  359. public function getAllUserStats($filter='%') {
  360. $this->debug->append("STA " . __METHOD__, 4);
  361. if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__ . $filter)) return $data;
  362. $stmt = $this->mysqli->prepare("
  363. SELECT
  364. a.id AS id,
  365. a.is_admin as is_admin,
  366. a.is_locked as is_locked,
  367. a.no_fees as no_fees,
  368. a.username AS username,
  369. a.donate_percent AS donate_percent,
  370. a.email AS email,
  371. ROUND(IFNULL(SUM(IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)), 0) / POW(2, (" . $this->config['difficulty'] . " - 16)), 0) AS shares
  372. FROM " . $this->user->getTableName() . " AS a
  373. LEFT JOIN " . $this->share->getTableName() . " AS s
  374. ON a.username = SUBSTRING_INDEX( s.username, '.', 1 )
  375. WHERE
  376. a.username LIKE ?
  377. GROUP BY username
  378. ORDER BY username");
  379. if ($this->checkStmt($stmt) && $stmt->bind_param('s', $filter) && $stmt->execute() && $result = $stmt->get_result()) {
  380. return $this->memcache->setCache(__FUNCTION__ . $filter, $result->fetch_all(MYSQLI_ASSOC));
  381. }
  382. }
  383. /**
  384. * Same as getUserShares for Hashrate
  385. * @param account_id integer User ID
  386. * @return data integer Current Hashrate in khash/s
  387. **/
  388. public function getUserHashrate($account_id, $interval=600) {
  389. $this->debug->append("STA " . __METHOD__, 4);
  390. if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
  391. $stmt = $this->mysqli->prepare("
  392. SELECT
  393. (
  394. SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * POW(2, " . $this->config['target_bits'] . ") / ? / 1000), 0) AS hashrate
  395. FROM " . $this->share->getTableName() . " AS s,
  396. " . $this->user->getTableName() . " AS u
  397. WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
  398. AND our_result = 'Y'
  399. AND s.time > DATE_SUB(now(), INTERVAL ? SECOND)
  400. AND u.id = ?
  401. ) + (
  402. SELECT IFNULL(ROUND(SUM(IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty)) * POW(2, " . $this->config['target_bits'] . ") / ? / 1000), 0) AS hashrate
  403. FROM " . $this->share->getArchiveTableName() . " AS s,
  404. " . $this->user->getTableName() . " AS u
  405. WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
  406. AND our_result = 'Y'
  407. AND s.time > DATE_SUB(now(), INTERVAL ? SECOND)
  408. AND u.id = ?
  409. ) AS hashrate
  410. FROM DUAL");
  411. if ($this->checkStmt($stmt) && $stmt->bind_param("iiiiii", $interval, $interval, $account_id, $interval, $interval, $account_id) && $stmt->execute() && $result = $stmt->get_result() )
  412. return $this->memcache->setCache(__FUNCTION__ . $account_id, $result->fetch_object()->hashrate);
  413. // Catchall
  414. $this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error);
  415. return false;
  416. }
  417. public function getUserUnpaidPPSShares($account_id, $last_paid_pps_id) {
  418. $this->debug->append("STA " . __METHOD__, 4);
  419. if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
  420. $stmt = $this->mysqli->prepare("
  421. SELECT
  422. 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
  423. FROM " . $this->share->getTableName() . " AS s
  424. JOIN " . $this->user->getTableName() . " AS a
  425. ON a.username = SUBSTRING_INDEX( s.username, '.', 1 )
  426. AND a.id = ?
  427. AND s.id > ?
  428. WHERE our_result = 'Y'");
  429. if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $account_id, $last_paid_pps_id) && $stmt->execute() && $result = $stmt->get_result() )
  430. return $this->memcache->setCache(__FUNCTION__ . $account_id, $result->fetch_object()->total);
  431. $this->debug->append("Failed fetching average share dificulty: " . $this->mysqli->error, 3);
  432. return 0;
  433. }
  434. /**
  435. * Get average share difficulty across all workers for user
  436. * @param account_id int Account ID
  437. * @param interval int Data interval in seconds
  438. * @return double Share difficulty or 0
  439. **/
  440. public function getUserShareDifficulty($account_id, $interval=600) {
  441. $this->debug->append("STA " . __METHOD__, 4);
  442. if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
  443. $stmt = $this->mysqli->prepare("
  444. SELECT
  445. IFNULL(AVG(IF(difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), difficulty)), 0) AS avgsharediff,
  446. COUNT(s.id) AS total
  447. FROM " . $this->share->getTableName() . " AS s JOIN " . $this->user->getTableName() . " AS a
  448. ON a.username = SUBSTRING_INDEX( s.username, '.', 1 )
  449. WHERE s.time > DATE_SUB(now(), INTERVAL ? SECOND)
  450. AND our_result = 'Y'
  451. AND a.id = ?");
  452. if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $interval, $account_id) && $stmt->execute() && $result = $stmt->get_result() )
  453. return $this->memcache->setCache(__FUNCTION__ . $account_id, $result->fetch_object()->avgsharediff);
  454. $this->debug->append("Failed fetching average share dificulty: " . $this->mysqli->error, 3);
  455. return 0;
  456. }
  457. /**
  458. * Same as getUserHashrate for Sharerate
  459. * @param account_id integer User ID
  460. * @return data integer Current Sharerate in shares/s
  461. **/
  462. public function getUserSharerate($account_id, $interval=600) {
  463. $this->debug->append("STA " . __METHOD__, 4);
  464. if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
  465. $stmt = $this->mysqli->prepare("
  466. SELECT
  467. (
  468. (
  469. SELECT COUNT(s.id) / ? AS sharerate
  470. FROM " . $this->share->getTableName() . " AS s,
  471. " . $this->user->getTableName() . " AS u
  472. WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
  473. AND our_result = 'Y'
  474. AND s.time > DATE_SUB(now(), INTERVAL ? SECOND)
  475. AND u.id = ?
  476. ) + (
  477. SELECT COUNT(s.id) / ? AS sharerate
  478. FROM " . $this->share->getArchiveTableName() . " AS s,
  479. " . $this->user->getTableName() . " AS u
  480. WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
  481. AND our_result = 'Y'
  482. AND s.time > DATE_SUB(now(), INTERVAL ? SECOND)
  483. AND u.id = ?
  484. )
  485. ) AS sharerate
  486. FROM DUAL");
  487. if ($this->checkStmt($stmt) && $stmt->bind_param("iiiiii", $interval, $interval, $account_id, $interval, $interval, $account_id) && $stmt->execute() && $result = $stmt->get_result() )
  488. return $this->memcache->setCache(__FUNCTION__ . $account_id, $result->fetch_object()->sharerate);
  489. // Catchall
  490. $this->debug->append("Failed to fetch sharerate: " . $this->mysqli->error);
  491. return false;
  492. }
  493. /**
  494. * Get hashrate for a specific worker
  495. * @param worker_id int Worker ID to fetch hashrate for
  496. * @return data int Current hashrate in khash/s
  497. **/
  498. public function getWorkerHashrate($worker_id) {
  499. $this->debug->append("STA " . __METHOD__, 4);
  500. if ($data = $this->memcache->get(__FUNCTION__ . $worker_id)) return $data;
  501. $stmt = $this->mysqli->prepare("
  502. 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
  503. FROM " . $this->share->getTableName() . " AS s,
  504. " . $this->user->getTableName() . " AS u
  505. WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
  506. AND our_result = 'Y'
  507. AND s.time > DATE_SUB(now(), INTERVAL 600 SECOND)
  508. AND u.id = ?");
  509. if ($this->checkStmt($stmt) && $stmt->bind_param("i", $account_id) && $stmt->execute() && $result = $stmt->get_result() )
  510. return $this->memcache->setCache(__FUNCTION__ . $worker_id, $result->fetch_object()->hashrate);
  511. // Catchall
  512. $this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error);
  513. return false;
  514. }
  515. /**
  516. * get our top contributors for either shares or hashrate
  517. * @param type string shares or hashes
  518. * @param limit int Limit result to $limit
  519. * @return data array Users with shares, account or hashrate, account
  520. **/
  521. public function getTopContributors($type='shares', $limit=15) {
  522. $this->debug->append("STA " . __METHOD__, 4);
  523. if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__ . $type . $limit)) return $data;
  524. switch ($type) {
  525. case 'shares':
  526. if ($data = $this->memcache->get(STATISTICS_ALL_USER_SHARES)) {
  527. // Use global cache to build data, if we have any data there
  528. if (!empty($data['data']) && is_array($data['data'])) {
  529. foreach($data['data'] as $key => $aUser) {
  530. $shares[$key] = $aUser['valid'];
  531. $username[$key] = $aUser['username'];
  532. }
  533. array_multisort($shares, SORT_DESC, $username, SORT_ASC, $data['data']);
  534. $count = 0;
  535. foreach ($data['data'] as $key => $aUser) {
  536. if ($count == $limit) break;
  537. $count++;
  538. $data_new[$key]['shares'] = $aUser['valid'];
  539. $data_new[$key]['account'] = $aUser['username'];
  540. $data_new[$key]['donate_percent'] = $aUser['donate_percent'];
  541. $data_new[$key]['is_anonymous'] = $aUser['is_anonymous'];
  542. }
  543. return $data_new;
  544. }
  545. }
  546. // No cached data, fallback to SQL and cache in local cache
  547. $stmt = $this->mysqli->prepare("
  548. SELECT
  549. a.username AS account,
  550. a.donate_percent AS donate_percent,
  551. a.is_anonymous AS is_anonymous,
  552. ROUND(IFNULL(SUM(IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)), 0) / POW(2, (" . $this->config['difficulty'] . " - 16)), 0) AS shares
  553. FROM " . $this->share->getTableName() . " AS s
  554. LEFT JOIN " . $this->user->getTableName() . " AS a
  555. ON SUBSTRING_INDEX( s.username, '.', 1 ) = a.username
  556. WHERE our_result = 'Y'
  557. GROUP BY account
  558. ORDER BY shares DESC
  559. LIMIT ?");
  560. if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $result = $stmt->get_result())
  561. return $this->memcache->setCache(__FUNCTION__ . $type . $limit, $result->fetch_all(MYSQLI_ASSOC));
  562. $this->debug->append("Fetching shares failed: ");
  563. return false;
  564. break;
  565. case 'hashes':
  566. $stmt = $this->mysqli->prepare("
  567. SELECT
  568. a.username AS account,
  569. a.donate_percent AS donate_percent,
  570. a.is_anonymous AS is_anonymous,
  571. IFNULL(ROUND(SUM(t1.difficulty) * POW(2, " . $this->config['target_bits'] . ") / 600 / 1000, 2), 0) AS hashrate
  572. FROM
  573. (
  574. 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'
  575. UNION ALL
  576. SELECT IFNULL(IF(difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), difficulty), 0) AS difficulty, username FROM " . $this->share->getArchiveTableName() ." WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) AND our_result = 'Y'
  577. ) AS t1
  578. LEFT JOIN " . $this->user->getTableName() . " AS a
  579. ON SUBSTRING_INDEX( t1.username, '.', 1 ) = a.username
  580. GROUP BY account
  581. ORDER BY hashrate DESC LIMIT ?");
  582. if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $result = $stmt->get_result())
  583. return $this->memcache->setCache(__FUNCTION__ . $type . $limit, $result->fetch_all(MYSQLI_ASSOC));
  584. $this->debug->append("Fetching shares failed: " . $this->mysqli->error);
  585. return false;
  586. break;
  587. }
  588. }
  589. /**
  590. * get Hourly hashrate for a user
  591. * @param account_id int User ID
  592. * @return data array NOT FINISHED YET
  593. **/
  594. public function getHourlyHashrateByAccount($account_id) {
  595. $this->debug->append("STA " . __METHOD__, 4);
  596. if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
  597. $stmt = $this->mysqli->prepare("
  598. SELECT
  599. 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,
  600. HOUR(s.time) AS hour
  601. FROM " . $this->share->getTableName() . " AS s, accounts AS a
  602. WHERE time < NOW() - INTERVAL 1 HOUR
  603. AND our_result = 'Y'
  604. AND time > NOW() - INTERVAL 25 HOUR
  605. AND a.username = SUBSTRING_INDEX( s.username, '.', 1 )
  606. AND a.id = ?
  607. GROUP BY HOUR(time)
  608. UNION ALL
  609. SELECT
  610. 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,
  611. HOUR(s.time) AS hour
  612. FROM " . $this->share->getArchiveTableName() . " AS s, accounts AS a
  613. WHERE time < NOW() - INTERVAL 1 HOUR
  614. AND our_result = 'Y'
  615. AND time > NOW() - INTERVAL 25 HOUR
  616. AND a.username = SUBSTRING_INDEX( s.username, '.', 1 )
  617. AND a.id = ?
  618. GROUP BY HOUR(time)");
  619. if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $account_id, $account_id) && $stmt->execute() && $result = $stmt->get_result()) {
  620. $iStartHour = date('G');
  621. // Initilize array
  622. for ($i = 0; $i < 24; $i++) $aData[($iStartHour + $i) % 24] = 0;
  623. // Fill data
  624. while ($row = $result->fetch_assoc()) $aData[$row['hour']] = $row['hashrate'];
  625. return $this->memcache->setCache(__FUNCTION__ . $account_id, $aData);
  626. }
  627. // Catchall
  628. $this->debug->append("Failed to fetch hourly hashrate: " . $this->mysqli->error);
  629. return false;
  630. }
  631. /**
  632. * get Hourly hashrate for the pool
  633. * @param none
  634. * @return data array NOT FINISHED YET
  635. **/
  636. public function getHourlyHashrateByPool() {
  637. $this->debug->append("STA " . __METHOD__, 4);
  638. if ($this->getGetCache() && $data = $this->memcache->get(__FUNCTION__)) return $data;
  639. $stmt = $this->mysqli->prepare("
  640. SELECT
  641. 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,
  642. HOUR(s.time) AS hour
  643. FROM " . $this->share->getTableName() . " AS s
  644. WHERE time < NOW() - INTERVAL 1 HOUR
  645. AND time > NOW() - INTERVAL 25 HOUR
  646. AND our_result = 'Y'
  647. GROUP BY HOUR(time)
  648. UNION ALL
  649. SELECT
  650. 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,
  651. HOUR(s.time) AS hour
  652. FROM " . $this->share->getArchiveTableName() . " AS s
  653. WHERE time < NOW() - INTERVAL 1 HOUR
  654. AND time > NOW() - INTERVAL 25 HOUR
  655. AND our_result = 'Y'
  656. GROUP BY HOUR(time)");
  657. if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) {
  658. $iStartHour = date('G');
  659. // Initilize array
  660. for ($i = 0; $i < 24; $i++) $aData[($iStartHour + $i) % 24] = 0;
  661. // Fill data
  662. while ($row = $result->fetch_assoc()) $aData[$row['hour']] = (int) $row['hashrate'];
  663. return $this->memcache->setCache(__FUNCTION__, $aData);
  664. }
  665. // Catchall
  666. $this->debug->append("Failed to fetch hourly hashrate: " . $this->mysqli->error);
  667. return false;
  668. }
  669. /**
  670. * get user estimated payouts based on share counts
  671. * @param value1 mixed Round shares OR share rate
  672. * @param value2 mixed User shares OR share difficulty
  673. * @param dDonate double User donation setting
  674. * @param bNoFees bool User no-fees option setting
  675. * @return aEstimates array User estimations
  676. **/
  677. public function getUserEstimates($value1, $value2, $dDonate, $bNoFees, $ppsvalue=0) {
  678. $this->debug->append("STA " . __METHOD__, 4);
  679. if ($this->config['payout_system'] != 'pps') {
  680. if (@$value1['valid'] > 0 && @$value2['valid'] > 0) {
  681. $aEstimates['block'] = round(( (int)$value2['valid'] / (int)$value1['valid'] ) * (float)$this->config['reward'], 8);
  682. $bNoFees == 0 ? $aEstimates['fee'] = round(((float)$this->config['fees'] / 100) * (float)$aEstimates['block'], 8) : $aEstimates['fee'] = 0;
  683. $aEstimates['donation'] = round((( (float)$dDonate / 100) * ((float)$aEstimates['block'] - (float)$aEstimates['fee'])), 8);
  684. $aEstimates['payout'] = round((float)$aEstimates['block'] - (float)$aEstimates['donation'] - (float)$aEstimates['fee'], 8);
  685. } else {
  686. $aEstimates['block'] = 0;
  687. $aEstimates['fee'] = 0;
  688. $aEstimates['donation'] = 0;
  689. $aEstimates['payout'] = 0;
  690. }
  691. } else {
  692. // Hack so we can use this method for PPS estimates too
  693. // value1 = shares/s
  694. // value2 = avg share difficulty
  695. if (@$value1 > 0 && @$value2 > 0) {
  696. $hour = 60 * 60;
  697. $pps = $value1 * $value2 * $ppsvalue;
  698. $aEstimates['hours1'] = $pps * $hour;
  699. $aEstimates['hours24'] = $pps * 24 * $hour;
  700. $aEstimates['days7'] = $pps * 24 * 7 * $hour;
  701. $aEstimates['days14'] = $pps * 14 * 24 * $hour;
  702. $aEstimates['days30'] = $pps * 30 * 24 * $hour;
  703. } else {
  704. $aEstimates['hours1'] = 0;
  705. $aEstimates['hours24'] = 0;
  706. $aEstimates['days7'] = 0;
  707. $aEstimates['days14'] = 0;
  708. $aEstimates['days30'] = 0;
  709. }
  710. }
  711. return $aEstimates;
  712. }
  713. /**
  714. * Get pool stats last 24 hours
  715. * @param limit int Last number of hours
  716. * @return array
  717. **/
  718. public function getPoolStatsHours($hour=24) {
  719. $this->debug->append("STA " . __METHOD__, 4);
  720. if ($data = $this->memcache->get(__FUNCTION__ . $hour)) return $data;
  721. $stmt = $this->mysqli->prepare("
  722. SELECT
  723. IFNULL(COUNT(id), 0) as count,
  724. IFNULL(AVG(difficulty), 0) as average,
  725. IFNULL(ROUND(SUM((difficulty * 65536) / POW(2, (" . $this->config['difficulty'] . " -16))), 0), 0) AS expected,
  726. IFNULL(ROUND(SUM(shares)), 0) as shares,
  727. IFNULL(SUM(amount), 0) as rewards
  728. FROM " . $this->block->getTableName() . "
  729. WHERE FROM_UNIXTIME(time) > DATE_SUB(now(), INTERVAL ? HOUR)
  730. AND confirmations >= 1");
  731. if ($this->checkStmt($stmt) && $stmt->bind_param("i", $hour) && $stmt->execute() && $result = $stmt->get_result())
  732. return $this->memcache->setCache(__FUNCTION__ . $hour, $result->fetch_assoc());
  733. // Catchall
  734. $this->debug->append("Failed to get pool statistics:" . $this->mysqli->error);
  735. return false;
  736. }
  737. }
  738. $statistics = new Statistics($debug, $mysqli, $config, $share, $user, $block, $memcache);