Merge pull request #40 from TheSerapher/next

Next to Master
This commit is contained in:
Sebastian Grewe 2013-05-21 12:12:28 -07:00
commit d3266c3ff7
22 changed files with 645 additions and 231 deletions

View File

@ -3,7 +3,7 @@ Description
mmcFE-ng is a web frontend for Pooled LTC Mining. mmcFE-ng is a web frontend for Pooled LTC Mining.
This is based on mmcFE, the original work by Greedi: The web frontend layout is based on mmcFE, the original work by Greedi:
https://github.com/Greedi/mmcFE https://github.com/Greedi/mmcFE
After working a few days trying to learn to run my own pool and the After working a few days trying to learn to run my own pool and the
@ -12,6 +12,10 @@ understand how it works. While doing so I also migrated the existing
code over to my own framework so maintenance would be easier in the code over to my own framework so maintenance would be easier in the
future. future.
**NOTE**: This project is still under development and commits are happening on a daily basis.
I do not recommend using this for a live setup as of yet. Wait for the later Release Candidate
if you wish to run your pool with it. Testing pools are much appreciated though!
Requirements Requirements
============ ============
@ -22,7 +26,7 @@ in the appropriate forums.
* Apache2 * Apache2
* libapache2-mod-php5 * libapache2-mod-php5
* PHP 5.4+ (5.3 might work too) * PHP 5.4+
* php5-mysqlnd * php5-mysqlnd
* php5-memcached * php5-memcached
* MySQL Server * MySQL Server
@ -31,6 +35,24 @@ in the appropriate forums.
* pushpoold * pushpoold
* litecoind * litecoind
Features
========
The following feature have been implemented so far:
* Use of memcache for statistics instead of a cronjob
* Web User accounts
* Worker accounts
* Worker activity (live, past 10 minutes)
* Worker hashrates (live, past 10 minutes)
* Pool statistics
* Minimal Block statistics
* Pool donations
* Pool fees
* Manual payout with 0.1 LTC fee
* Auto payout with 0.1 LTC fee
* Transaction list (confirmed and unconfirmed)
Installation Installation
============ ============
@ -76,7 +98,8 @@ Memcache
Please install and start a default memcache instance. Not only would you Please install and start a default memcache instance. Not only would you
need one for `pushpoold` but the statistics page is storing data in need one for `pushpoold` but the statistics page is storing data in
`memcache` as well to improve performance. `memcache` as well to improve performance. Your memcache can be
configured in the global configuration file (see below).
Configuration Configuration
------------- -------------
@ -123,8 +146,9 @@ me know by creating an [Issue][1] marked as `Feature Request`.
Disclaimer Disclaimer
========== ==========
This is a *WIP Project*. Most functionality is now added, the core This is a **WIP Project**. Most functionality is now added, the core
features are available and the backend cronjobs are working. If you features are available and the backend cronjobs are working. I would not recommend
encounter any problems related to the code please create a new [Issue][1] running this on a live pool yet. You can play around and test basic functionality but
wait for any live deployment for at least a stable Release Candidate.
[1]: https://github.com/TheSerapher/php-mmcfe-ng/issues "Issue" [1]: https://github.com/TheSerapher/php-mmcfe-ng/issues "Issue"

71
cronjobs/auto_payout.php Executable file
View File

@ -0,0 +1,71 @@
#!/usr/bin/php
<?php
/*
Copyright:: 2013, Sebastian Grewe
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Include all settings and classes
require_once('shared.inc.php');
if ($bitcoin->can_connect() !== true) {
verbose("Unable to connect to RPC server, exiting");
exit(1);
}
// Fetch all users with setup AP
$users = $user->getAllAutoPayout();
// Go through users and run transactions
if (! empty($users)) {
verbose("UserID\tUsername\tBalance\tThreshold\tAddress\t\t\t\t\tStatus\n\n");
foreach ($users as $aUserData) {
$dBalance = $transaction->getBalance($aUserData['id']);
verbose($aUserData['id'] . "\t" . $aUserData['username'] . "\t" . $dBalance . "\t" . $aUserData['ap_threshold'] . "\t\t" . $aUserData['coin_address'] . "\t");
// Only run if balance meets threshold and can pay the transaction fee
if ($dBalance > $aUserData['ap_threshold'] && $dBalance > 0.1) {
// Validate address against RPC
try {
$bitcoin->validateaddress($aUserData['coin_address']);
} catch (BitcoinClientException $e) {
verbose("VERIFY FAILED\n");
continue;
}
// Send balance - 0.1 Fee to address
try {
$bitcoin->sendtoaddress($aUserData['coin_address'], $dBalance - 0.1);
} catch (BitcoinClientException $e) {
verbose("SEND FAILED\n");
continue;
}
// Create transaction record
if ($transaction->addTransaction($aUserData['id'], $dBalance, 'Debit_AP', NULL, $aUserData['coin_address'], 0.1)) {
verbose("OK\n");
} else {
verbose("FAILED\n");
}
} else {
verbose("SKIPPED\n");
}
}
} else {
verbose("No user has configured their AP > 0\n");
}

View File

@ -45,7 +45,7 @@ foreach ($aAllBlocks as $iIndex => $aBlock) {
} }
$aAccountShares = $share->getSharesForAccounts($share->getLastUpstreamId(), $iCurrentUpstreamId); $aAccountShares = $share->getSharesForAccounts($share->getLastUpstreamId(), $iCurrentUpstreamId);
$iRoundShares = $share->getRoundShares($share->getLastUpstreamId(), $iCurrentUpstreamId); $iRoundShares = $share->getRoundShares($share->getLastUpstreamId(), $iCurrentUpstreamId);
verbose("ID\tHeight\tTime\t\tShares\tFinder\t\tShare ID\tPrev Share\tStatus\n"); verbose("ID\tHeight\tTime\t\tShares\tFinder\t\tShare ID\tPrev Share\t\tStatus\n");
verbose($aBlock['id'] . "\t" . $aBlock['height'] . "\t" . $aBlock['time'] . "\t" . $iRoundShares . "\t" . $share->getUpstreamFinder() . "\t" . $share->getUpstreamId() . "\t\t" . $share->getLastUpstreamId()); verbose($aBlock['id'] . "\t" . $aBlock['height'] . "\t" . $aBlock['time'] . "\t" . $iRoundShares . "\t" . $share->getUpstreamFinder() . "\t" . $share->getUpstreamId() . "\t\t" . $share->getLastUpstreamId());
if (empty($aAccountShares)) { if (empty($aAccountShares)) {
verbose("\nNo shares found for this block\n\n"); verbose("\nNo shares found for this block\n\n");
@ -58,24 +58,45 @@ foreach ($aAllBlocks as $iIndex => $aBlock) {
if (!$block->setShares($aBlock['id'], $iRoundShares)) if (!$block->setShares($aBlock['id'], $iRoundShares))
$strStatus = "Shares Failed"; $strStatus = "Shares Failed";
verbose("\t\t$strStatus\n\n"); verbose("\t\t$strStatus\n\n");
verbose("ID\tUsername\tValid\tInvalid\tPercentage\tPayout\t\tStatus\n"); verbose("ID\tUsername\tValid\tInvalid\tPercentage\tPayout\t\tDonation\tFee\t\tStatus\n");
foreach ($aAccountShares as $key => $aData) { foreach ($aAccountShares as $key => $aData) {
// Payout based on shares, PPS system
$aData['percentage'] = number_format(round(( 100 / $iRoundShares ) * $aData['valid'], 8), 8); $aData['percentage'] = number_format(round(( 100 / $iRoundShares ) * $aData['valid'], 8), 8);
$aData['payout'] = number_format(round(( $aData['percentage'] / 100 ) * $config['reward'], 8), 8); $aData['payout'] = number_format(round(( $aData['percentage'] / 100 ) * $config['reward'], 8), 8);
// Defaults
$aData['fee' ] = 0;
$aData['donation'] = 0;
if ($config['fees'] > 0)
$aData['fee'] = number_format(round($config['fees'] / 100 * $aData['payout'], 8), 8);
// Calculate donation amount, fees not included
$aData['donation'] = number_format(round($user->getDonatePercent($user->getUserId($aData['username'])) / 100 * ( $aData['payout'] - $aData['fee']), 8), 8);
// Verbose output of this users calculations
verbose($aData['id'] . "\t" . verbose($aData['id'] . "\t" .
$aData['username'] . "\t" . $aData['username'] . "\t" .
$aData['valid'] . "\t" . $aData['valid'] . "\t" .
$aData['invalid'] . "\t" . $aData['invalid'] . "\t" .
$aData['percentage'] . "\t" . $aData['percentage'] . "\t" .
$aData['payout'] . "\t"); $aData['payout'] . "\t" .
$aData['donation'] . "\t" .
$aData['fee'] . "\t");
// Do all database updates for block, statistics and payouts
$strStatus = "OK"; $strStatus = "OK";
// Update user share statistics
if (!$statistics->updateShareStatistics($aData, $aBlock['id'])) if (!$statistics->updateShareStatistics($aData, $aBlock['id']))
$strStatus = "Stats Failed"; $strStatus = "Stats Failed";
// Add new credit transaction
if (!$transaction->addTransaction($aData['id'], $aData['payout'], 'Credit', $aBlock['id'])) if (!$transaction->addTransaction($aData['id'], $aData['payout'], 'Credit', $aBlock['id']))
$strStatus = "Transaction Failed"; $strStatus = "Transaction Failed";
verbose("$strStatus\n"); // Add new donation debit
if ($aData['donation'] > 0)
if (!$transaction->addTransaction($aData['id'], $aData['donation'], 'Donation', $aBlock['id']))
$strStatus = "Donation Failed";
if ($aData['fee'] > 0 && $config['fees'] > 0)
if (!$transaction->addTransaction($aData['id'], $aData['fee'], 'Fee', $aBlock['id']))
$strStatus = "Fee Failed";
verbose("\t$strStatus\n");
} }
verbose("------------------------------------------------------------------------\n\n"); verbose("------------------------------------------------------------------------\n\n");

View File

@ -2,6 +2,7 @@
require_once(CLASS_DIR . '/debug.class.php'); require_once(CLASS_DIR . '/debug.class.php');
require_once(CLASS_DIR . '/bitcoin.class.php'); require_once(CLASS_DIR . '/bitcoin.class.php');
require_once(CLASS_DIR . '/statscache.class.php');
require_once(INCLUDE_DIR . '/database.inc.php'); require_once(INCLUDE_DIR . '/database.inc.php');
require_once(INCLUDE_DIR . '/smarty.inc.php'); require_once(INCLUDE_DIR . '/smarty.inc.php');
// Load classes that need the above as dependencies // Load classes that need the above as dependencies
@ -12,7 +13,3 @@ require_once(CLASS_DIR . '/worker.class.php');
require_once(CLASS_DIR . '/statistics.class.php'); require_once(CLASS_DIR . '/statistics.class.php');
require_once(CLASS_DIR . '/transaction.class.php'); require_once(CLASS_DIR . '/transaction.class.php');
require_once(CLASS_DIR . '/settings.class.php'); require_once(CLASS_DIR . '/settings.class.php');
// Use Memcache to store our data
$memcache = new Memcached();
$memcache->addServer('localhost', 11211);

View File

@ -66,7 +66,7 @@ class Debug {
} }
return $backtrace; return $backtrace;
} }
/** /**
* We fill our data array here * We fill our data array here
* @param string $msg Debug Message * @param string $msg Debug Message

View File

@ -4,21 +4,29 @@
if (!defined('SECURITY')) if (!defined('SECURITY'))
die('Hacking attempt'); die('Hacking attempt');
/*
* We give access to plenty of statistics through this class
* Statistics should be non-intrusive and not change any
* rows in our database to ensure data integrity for the backend
**/
class Statistics { class Statistics {
private $sError = ''; private $sError = '';
private $table = 'statistics_shares'; private $table = 'statistics_shares';
public function __construct($debug, $mysqli, $config, $share, $user, $block) { public function __construct($debug, $mysqli, $config, $share, $user, $block, $memcache) {
$this->debug = $debug; $this->debug = $debug;
$this->mysqli = $mysqli; $this->mysqli = $mysqli;
$this->share = $share; $this->share = $share;
$this->config = $config; $this->config = $config;
$this->user = $user; $this->user = $user;
$this->block = $block; $this->block = $block;
$this->memcache = $memcache;
$this->debug->append("Instantiated Share class", 2); $this->debug->append("Instantiated Share class", 2);
} }
// get and set methods /* Some basic get and set methods
**/
private function setErrorMessage($msg) { private function setErrorMessage($msg) {
$this->sError = $msg; $this->sError = $msg;
} }
@ -35,7 +43,26 @@ class Statistics {
return true; return true;
} }
/**
* Another wrapper, we want to store data in memcache and return the actual data
* for further processing
* @param key string Our memcache key
* @param data mixed Our data to store in Memcache
* @param expiration time Our expiration time, see Memcached documentation
* @return data mixed Return our stored data unchanged
**/
public function setCache($key, $data, $expiration=NULL) {
if ($this->config['memcache']['enabled']) $this->memcache->set($key, $data, $expiration);
return $data;
}
/**
* Get our last $limit blocks found
* @param limit int Last limit blocks
* @return array
**/
public function getBlocksFound($limit=10) { public function getBlocksFound($limit=10) {
if ($data = $this->memcache->get(__FUNCTION__ . $limit)) return $data;
$stmt = $this->mysqli->prepare(" $stmt = $this->mysqli->prepare("
SELECT b.*, a.username as finder SELECT b.*, a.username as finder
FROM " . $this->block->getTableName() . " AS b FROM " . $this->block->getTableName() . " AS b
@ -43,11 +70,19 @@ class Statistics {
ON b.account_id = a.id ON b.account_id = a.id
ORDER BY height DESC LIMIT ?"); ORDER BY height DESC LIMIT ?");
if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $result = $stmt->get_result()) if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $result = $stmt->get_result())
return $result->fetch_all(MYSQLI_ASSOC); return $this->setCache(__FUNCTION__ . $limit, $result->fetch_all(MYSQLI_ASSOC), 5);
// Catchall // Catchall
$this->debug->append("Failed to find blocks:" . $this->mysqli->error); $this->debug->append("Failed to find blocks:" . $this->mysqli->error);
return false; return false;
} }
/**
* Currently the only function writing to the database
* Stored per block user statistics of valid and invalid shares
* @param aStats array Array with user id, valid and invalid shares
* @param iBlockId int Block ID as store in the Block table
* @return bool
**/
public function updateShareStatistics($aStats, $iBlockId) { public function updateShareStatistics($aStats, $iBlockId) {
$stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, valid, invalid, block_id) VALUES (?, ?, ?, ?)"); $stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, valid, invalid, block_id) VALUES (?, ?, ?, ?)");
if ($this->checkStmt($stmt) && $stmt->bind_param('iiii', $aStats['id'], $aStats['valid'], $aStats['invalid'], $iBlockId) && $stmt->execute()) return true; if ($this->checkStmt($stmt) && $stmt->bind_param('iiii', $aStats['id'], $aStats['valid'], $aStats['invalid'], $iBlockId) && $stmt->execute()) return true;
@ -56,7 +91,14 @@ class Statistics {
return false; return false;
} }
/**
* Get our current pool hashrate for the past 10 minutes across both
* shares and shares_archive table
* @param none
* @return data object Return our hashrateas an object
**/
public function getCurrentHashrate() { public function getCurrentHashrate() {
if ($data = $this->memcache->get(__FUNCTION__)) return $data;
$stmt = $this->mysqli->prepare(" $stmt = $this->mysqli->prepare("
SELECT SUM(hashrate) AS hashrate FROM SELECT SUM(hashrate) AS hashrate FROM
( (
@ -65,12 +107,18 @@ class Statistics {
SELECT ROUND(COUNT(id) * POW(2, " . $this->config['difficulty'] . ")/600/1000) AS hashrate FROM " . $this->share->getArchiveTableName() . " WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) SELECT ROUND(COUNT(id) * POW(2, " . $this->config['difficulty'] . ")/600/1000) AS hashrate FROM " . $this->share->getArchiveTableName() . " WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE)
) AS sum"); ) AS sum");
// Catchall // Catchall
if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result() ) return $result->fetch_object()->hashrate; if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result() ) return $this->setCache(__FUNCTION__, $result->fetch_object()->hashrate);
$this->debug->append("Failed to get hashrate: " . $this->mysqli->error); $this->debug->append("Failed to get hashrate: " . $this->mysqli->error);
return false; return false;
} }
/**
* Same as getCurrentHashrate but for Shares
* @param none
* @return data object Our share rate in shares per second
**/
public function getCurrentShareRate() { public function getCurrentShareRate() {
if ($data = $this->memcache->get(__FUNCTION__)) return $data;
$stmt = $this->mysqli->prepare(" $stmt = $this->mysqli->prepare("
SELECT ROUND(SUM(sharerate) / 600, 2) AS sharerate FROM SELECT ROUND(SUM(sharerate) / 600, 2) AS sharerate FROM
( (
@ -78,13 +126,19 @@ class Statistics {
UNION ALL UNION ALL
SELECT COUNT(id) AS sharerate FROM " . $this->share->getArchiveTableName() . " WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) SELECT COUNT(id) AS sharerate FROM " . $this->share->getArchiveTableName() . " WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE)
) AS sum"); ) AS sum");
if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result() ) return $result->fetch_object()->sharerate; if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result() ) return $this->setCache(__FUNCTION__, $result->fetch_object()->sharerate);
// Catchall // Catchall
$this->debug->append("Failed to fetch share rate: " . $this->mysqli->error); $this->debug->append("Failed to fetch share rate: " . $this->mysqli->error);
return false; return false;
} }
/**
* Get total shares for this round, since last block found
* @param none
* @return data array invalid and valid shares
**/
public function getRoundShares() { public function getRoundShares() {
if ($data = $this->memcache->get(__FUNCTION__)) return $data;
$stmt = $this->mysqli->prepare(" $stmt = $this->mysqli->prepare("
SELECT SELECT
( SELECT IFNULL(count(id), 0) ( SELECT IFNULL(count(id), 0)
@ -95,13 +149,20 @@ class Statistics {
FROM " . $this->share->getTableName() . " FROM " . $this->share->getTableName() . "
WHERE UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(time) FROM blocks),0) WHERE UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(time) FROM blocks),0)
AND our_result = 'N' ) as invalid"); AND our_result = 'N' ) as invalid");
if ( $this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result() ) return $result->fetch_assoc(); if ( $this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result() )
return $this->setCache(__FUNCTION__, $result->fetch_assoc());
// Catchall // Catchall
$this->debug->append("Failed to fetch round shares: " . $this->mysqli->error); $this->debug->append("Failed to fetch round shares: " . $this->mysqli->error);
return false; return false;
} }
/**
* Get amount of shares for a specific user
* @param account_id int User ID
* @return data array invalid and valid share counts
**/
public function getUserShares($account_id) { public function getUserShares($account_id) {
if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
$stmt = $this->mysqli->prepare(" $stmt = $this->mysqli->prepare("
SELECT SELECT
( (
@ -109,7 +170,7 @@ class Statistics {
FROM " . $this->share->getTableName() . " AS s, FROM " . $this->share->getTableName() . " AS s,
" . $this->user->getTableName() . " AS u " . $this->user->getTableName() . " AS u
WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM blocks AS b),0) AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM " . $this->block->getTableName() . " AS b),0)
AND our_result = 'Y' AND our_result = 'Y'
AND u.id = ? AND u.id = ?
) AS valid, ) AS valid,
@ -118,17 +179,24 @@ class Statistics {
FROM " . $this->share->getTableName() . " AS s, FROM " . $this->share->getTableName() . " AS s,
" . $this->user->getTableName() . " AS u " . $this->user->getTableName() . " AS u
WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM blocks AS b),0) AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM " . $this->block->getTableName() . " AS b),0)
AND our_result = 'N' AND our_result = 'N'
AND u.id = ? AND u.id = ?
) AS invalid"); ) AS invalid");
if ($stmt && $stmt->bind_param("ii", $account_id, $account_id) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_assoc(); if ($stmt && $stmt->bind_param("ii", $account_id, $account_id) && $stmt->execute() && $result = $stmt->get_result())
return $this->setCache(__FUNCTION__ . $account_id, $result->fetch_assoc());
// Catchall // Catchall
$this->debug->append("Unable to fetch user round shares: " . $this->mysqli->error); $this->debug->append("Unable to fetch user round shares: " . $this->mysqli->error);
return false; return false;
} }
/**
* Same as getUserShares for Hashrate
* @param account_id integer User ID
* @return data integer Current Hashrate in khash/s
**/
public function getUserHashrate($account_id) { public function getUserHashrate($account_id) {
if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
$stmt = $this->mysqli->prepare(" $stmt = $this->mysqli->prepare("
SELECT ROUND(COUNT(s.id) * POW(2, " . $this->config['difficulty'] . ")/600/1000) AS hashrate SELECT ROUND(COUNT(s.id) * POW(2, " . $this->config['difficulty'] . ")/600/1000) AS hashrate
FROM " . $this->share->getTableName() . " AS s, FROM " . $this->share->getTableName() . " AS s,
@ -136,13 +204,20 @@ class Statistics {
WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
AND s.time > DATE_SUB(now(), INTERVAL 10 MINUTE) AND s.time > DATE_SUB(now(), INTERVAL 10 MINUTE)
AND u.id = ?"); AND u.id = ?");
if ($this->checkStmt($stmt) && $stmt->bind_param("i", $account_id) && $stmt->execute() && $result = $stmt->get_result() ) return $result->fetch_object()->hashrate; if ($this->checkStmt($stmt) && $stmt->bind_param("i", $account_id) && $stmt->execute() && $result = $stmt->get_result() )
return $this->setCache(__FUNCTION__ . $account_id, $result->fetch_object()->hashrate);
// Catchall // Catchall
$this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error); $this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error);
return false; return false;
} }
/**
* Get hashrate for a specific worker
* @param worker_id int Worker ID to fetch hashrate for
* @return data int Current hashrate in khash/s
**/
public function getWorkerHashrate($worker_id) { public function getWorkerHashrate($worker_id) {
if ($data = $this->memcache->get(__FUNCTION__ . $worker_id)) return $data;
$stmt = $this->mysqli->prepare(" $stmt = $this->mysqli->prepare("
SELECT ROUND(COUNT(s.id) * POW(2,21)/600/1000) AS hashrate SELECT ROUND(COUNT(s.id) * POW(2,21)/600/1000) AS hashrate
FROM " . $this->share->getTableName() . " AS s, FROM " . $this->share->getTableName() . " AS s,
@ -150,40 +225,62 @@ class Statistics {
WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
AND s.time > DATE_SUB(now(), INTERVAL 10 MINUTE) AND s.time > DATE_SUB(now(), INTERVAL 10 MINUTE)
AND u.id = ?"); AND u.id = ?");
if ($this->checkStmt($stmt) && $stmt->bind_param("i", $account_id) && $stmt->execute() && $result = $stmt->get_result() ) return $result->fetch_object()->hashrate; if ($this->checkStmt($stmt) && $stmt->bind_param("i", $account_id) && $stmt->execute() && $result = $stmt->get_result() )
return $this->setCache(__FUNCTION__ . $worker_id, $result->fetch_object()->hashrate);
// Catchall // Catchall
$this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error); $this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error);
return false; return false;
} }
public function getTopContributors($limit=15) { /**
$stmt = $this->mysqli->prepare(" * get our top contributors for either shares or hashrate
SELECT * @param type string shares or hashes
ROUND(COUNT(id) / 60 / 10, 2) AS sharesps, * @param limit int Limit result to $limit
ROUND(COUNT(id) * POW(2," . $this->config['difficulty'] . ")/600/1000,2) AS hashrate, * @return data array Users with shares, account or hashrate, account
SUBSTRING_INDEX( username, '.', 1 ) AS account **/
FROM " . $this->share->getTableName() . " public function getTopContributors($type='shares', $limit=15) {
WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) if ($data = $this->memcache->get(__FUNCTION__ . $type . $limit)) return $data;
GROUP BY account switch ($type) {
ORDER BY hashrate DESC LIMIT ?"); case 'shares':
if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $hashrates= $stmt->get_result()) { $stmt = $this->mysqli->prepare("
$aHashData = $hashrates->fetch_all(MYSQLI_ASSOC); SELECT
$stmt->close(); COUNT(id) AS shares,
} else { SUBSTRING_INDEX( username, '.', 1 ) AS account
FROM " . $this->share->getTableName() . "
GROUP BY account
ORDER BY shares DESC
LIMIT ?");
if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $result = $stmt->get_result())
return $this->setCache(__FUNCTION__ . $type . $limit, $result->fetch_all(MYSQLI_ASSOC));
$this->debug->append("Fetching shares failed: ");
return false; return false;
break;
case 'hashes':
$stmt = $this->mysqli->prepare("
SELECT
ROUND(COUNT(id) * POW(2," . $this->config['difficulty'] . ")/600/1000,2) AS hashrate,
SUBSTRING_INDEX( username, '.', 1 ) AS account
FROM " . $this->share->getTableName() . "
WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE)
GROUP BY account
ORDER BY hashrate DESC LIMIT ?");
if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $result = $stmt->get_result())
return $this->setCache(__FUNCTION__ . $type . $limit, $result->fetch_all(MYSQLI_ASSOC));
$this->debug->append("Fetching shares failed: ");
return false;
break;
} }
foreach ($aHashData as $key => $aData) {
$stmt = $this->mysqli->prepare("SELECT COUNT(id) FROM " . $this->share->getTableName() . " WHERE SUBSTRING_INDEX( username , '.', 1 ) = ?");
if ($stmt->bind_param("s", $aData['username']) && $stmt->execute() && $result = $stmt->get_result()) {
$aHashData[$key]['shares'] = $this->getUserShares($this->user->getUserId($aData['account']))['valid'];
} else {
continue;
}
}
return $aHashData;
} }
/**
* get Hourly hashrate for a user
* Not working yet since I was not able to solve this via SQL queries
* @param account_id int User ID
* @return data array NOT FINISHED YET
**/
public function getHourlyHashrateByAccount($account_id) { public function getHourlyHashrateByAccount($account_id) {
if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
$stmt = $this->mysqli->prepare(" $stmt = $this->mysqli->prepare("
SELECT SELECT
ROUND(COUNT(s.id) * POW(2, 12)/600/1000) AS hashrate, ROUND(COUNT(s.id) * POW(2, 12)/600/1000) AS hashrate,
@ -202,11 +299,12 @@ class Statistics {
AND a.username = SUBSTRING_INDEX( s.username, '.', 1 ) AND a.username = SUBSTRING_INDEX( s.username, '.', 1 )
AND a.id = ? AND a.id = ?
GROUP BY HOUR(time)"); GROUP BY HOUR(time)");
if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $account_id, $account_id) && $stmt->execute() && $hourlyhashrates = $stmt->get_result()) if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $account_id, $account_id) && $stmt->execute() && $result = $stmt->get_result())
return $hourlyhashrates->fetch_all(MYSQLI_ASSOC); return $this->setCache(__FUNCTION__ . $account_id, $result->fetch_all(MYSQLI_ASSOC), 3600);
// Catchall // Catchall
$this->debug->append("Failed to fetch hourly hashrate: " . $this->mysqli->error); $this->debug->append("Failed to fetch hourly hashrate: " . $this->mysqli->error);
return false; return false;
} }
} }
$statistics = new Statistics($debug, $mysqli, $config, $share, $user, $block);
$statistics = new Statistics($debug, $mysqli, $config, $share, $user, $block, $memcache);

View File

@ -0,0 +1,49 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
/**
* A wrapper class used to store values transparently in memcache
* Can be enabled or disabled through site configuration
* Also sets a default time if no time is passed to it to enforce caching
**/
class StatsCache extends Memcached {
public function __construct($config, $debug) {
$this->config = $config;
$this->debug = $debug;
if (! $config['memcache']['enabled'] ) $this->debug->append("Not storing any values in memcache");
return parent::__construct();
}
/**
* Wrapper around memcache->set
* Do not store values if memcache is disabled
**/
public function set($key, $value, $expiration=NULL) {
if (! $this->config['memcache']['enabled']) return false;
if (empty($expiration))
$expiration = $this->config['memcache']['expiration'] + rand( -$this->config['memcache']['splay'], $this->config['memcache']['splay']);
$this->debug->append("Storing " . $this->config['memcache']['keyprefix'] . "$key with expiration $expiration", 3);
return parent::set($this->config['memcache']['keyprefix'] . $key, $value, $expiration);
}
/**
* Wrapper around memcache->get
* Always return false if memcache is disabled
**/
public function get($key, $cache_cb = NULL, &$cas_token = NULL) {
if (! $this->config['memcache']['enabled']) return false;
$this->debug->append("Trying to fetch key " . $this->config['memcache']['keyprefix'] . "$key from cache", 3);
if ($data = parent::get($this->config['memcache']['keyprefix'].$key)) {
$this->debug->append("Found key in cache", 3);
return $data;
} else {
$this->debug->append("Key not found", 3);
}
}
}
$memcache = new StatsCache($config, $debug);
$memcache->addServer($config['memcache']['host'], $config['memcache']['port']);

View File

@ -9,10 +9,11 @@ class Transaction {
private $table = 'transactions'; private $table = 'transactions';
private $tableBlocks = 'blocks'; private $tableBlocks = 'blocks';
public function __construct($debug, $mysqli, $config) { public function __construct($debug, $mysqli, $config, $block) {
$this->debug = $debug; $this->debug = $debug;
$this->mysqli = $mysqli; $this->mysqli = $mysqli;
$this->config = $config; $this->config = $config;
$this->block = $block;
$this->debug->append("Instantiated Transaction class", 2); $this->debug->append("Instantiated Transaction class", 2);
} }
@ -24,10 +25,10 @@ class Transaction {
return $this->sError; return $this->sError;
} }
public function addTransaction($account_id, $amount, $type='Credit', $block_id=NULL, $coin_address=NULL, $fee=0) { public function addTransaction($account_id, $amount, $type='Credit', $block_id=NULL, $coin_address=NULL) {
$stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, amount, block_id, type, coin_address, fee_amount) VALUES (?, ?, ?, ?, ?, ?)"); $stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, amount, block_id, type, coin_address) VALUES (?, ?, ?, ?, ?)");
if ($this->checkStmt($stmt)) { if ($this->checkStmt($stmt)) {
$stmt->bind_param("idissd", $account_id, $amount, $block_id, $type, $coin_address, $fee); $stmt->bind_param("idiss", $account_id, $amount, $block_id, $type, $coin_address);
if ($stmt->execute()) { if ($stmt->execute()) {
$this->setErrorMessage("Failed to store transaction"); $this->setErrorMessage("Failed to store transaction");
$stmt->close(); $stmt->close();
@ -75,23 +76,33 @@ class Transaction {
public function getBalance($account_id) { public function getBalance($account_id) {
$stmt = $this->mysqli->prepare(" $stmt = $this->mysqli->prepare("
SELECT IFNULL(c.credit, 0) - IFNULL(d.debit,0) AS balance SELECT ROUND(IFNULL(t1.credit, 0) - IFNULL(t2.debit, 0) - IFNULL(t3.other, 0), 8) AS balance
FROM ( FROM
SELECT t.account_id, sum(t.amount) AS credit (
SELECT sum(t.amount) AS credit
FROM $this->table AS t FROM $this->table AS t
LEFT JOIN $this->tableBlocks AS b ON t.block_id = b.id LEFT JOIN " . $this->block->getTableName() . " AS b ON t.block_id = b.id
WHERE type = 'Credit' WHERE t.type = 'Credit'
AND b.confirmations > ? AND b.confirmations >= ?
AND t.account_id = ? ) AS c AND t.account_id = ?
LEFT JOIN ( ) AS t1,
SELECT t.account_id, sum(amount) AS debit (
SELECT sum(t.amount) AS debit
FROM $this->table AS t FROM $this->table AS t
WHERE type IN ('Debit_MP','Debit_AP') WHERE t.type IN ('Debit_MP', 'Debit_AP')
AND t.account_id = ? ) AS d AND t.account_id = ?
ON c.account_id = d.account_id ) AS t2,
(
SELECT sum(t.amount) AS other
FROM $this->table AS t
LEFT JOIN " . $this->block->getTableName() . " AS b ON t.block_id = b.id
WHERE t.type IN ('Donation','Fee')
AND b.confirmations >= ?
AND t.account_id = ?
) AS t3
"); ");
if ($this->checkStmt($stmt)) { if ($this->checkStmt($stmt)) {
$stmt->bind_param("iii", $this->config['confirmations'], $account_id, $account_id); $stmt->bind_param("iiiii", $this->config['confirmations'], $account_id, $account_id, $this->config['confirmations'], $account_id);
if (!$stmt->execute()) { if (!$stmt->execute()) {
$this->debug->append("Unable to execute statement: " . $stmt->error); $this->debug->append("Unable to execute statement: " . $stmt->error);
$this->setErrorMessage("Fetching balance failed"); $this->setErrorMessage("Fetching balance failed");
@ -104,4 +115,4 @@ class Transaction {
} }
} }
$transaction = new Transaction($debug, $mysqli, $config); $transaction = new Transaction($debug, $mysqli, $config, $block);

View File

@ -34,6 +34,12 @@ class User {
return $this->getSingle($username, 'id', 'username', 's'); return $this->getSingle($username, 'id', 'username', 's');
} }
/**
* Check user login
* @param username string Username
* @param password string Password
* @return bool
**/
public function checkLogin($username, $password) { public function checkLogin($username, $password) {
$this->debug->append("Checking login for $username with password $password", 2); $this->debug->append("Checking login for $username with password $password", 2);
if ( $this->checkUserPassword($username, $password) ) { if ( $this->checkUserPassword($username, $password) ) {
@ -43,6 +49,12 @@ class User {
return false; return false;
} }
/**
* Check the users PIN for confirmation
* @param userID int User ID
* @param pin int PIN to check
* @return bool
**/
public function checkPin($userId, $pin=false) { public function checkPin($userId, $pin=false) {
$this->debug->append("Confirming PIN for $userId and pin $pin", 2); $this->debug->append("Confirming PIN for $userId and pin $pin", 2);
$stmt = $this->mysqli->prepare("SELECT pin FROM $this->table WHERE id=? AND pin=? LIMIT 1"); $stmt = $this->mysqli->prepare("SELECT pin FROM $this->table WHERE id=? AND pin=? LIMIT 1");
@ -55,6 +67,14 @@ class User {
return $pin_hash === $row_pin; return $pin_hash === $row_pin;
} }
/**
* Get a single row from the table
* @param value string Value to search for
* @param search Return column to search for
* @param field string Search column
* @param type string Type of value
* @return array Return result
**/
private function getSingle($value, $search='id', $field='id', $type="i") { private function getSingle($value, $search='id', $field='id', $type="i") {
$stmt = $this->mysqli->prepare("SELECT $search FROM $this->table WHERE $field = ? LIMIT 1"); $stmt = $this->mysqli->prepare("SELECT $search FROM $this->table WHERE $field = ? LIMIT 1");
if ($this->checkStmt($stmt)) { if ($this->checkStmt($stmt)) {
@ -68,12 +88,56 @@ class User {
return false; return false;
} }
public function getCoinAddress($userID) { /**
return $this->getSingle($userID, 'coin_address', 'id', 's'); * Get all users that have auto payout setup
* @param none
* @return data array All users with payout setup
**/
public function getAllAutoPayout() {
$stmt = $this->mysqli->prepare("
SELECT
id, username, coin_address, ap_threshold
FROM " . $this->getTableName() . "
WHERE ap_threshold > 0
AND coin_address IS NOT NULL
");
if ( $this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) {
return $result->fetch_all(MYSQLI_ASSOC);
}
$this->debug->append("Unable to fetch users with AP set");
echo $this->mysqli->error;
return false;
} }
private function updateSingle($userID, $field, $table) { /**
$stmt = $this->mysqli->prepare("UPDATE $table SET " . $field['name'] . " = ? WHERE userId = ? LIMIT 1"); * Fetch users coin address
* @param userID int UserID
* @return data string Coin Address
**/
public function getCoinAddress($userID) {
return $this->getSingle($userID, 'coin_address', 'id');
}
/**
* Fetch users donation value
* @param userID int UserID
* @return data string Coin Address
**/
public function getDonatePercent($userID) {
$dPercent = $this->getSingle($userID, 'donate_percent', 'id');
if ($dPercent > 100) $dPercent = 100;
if ($dPercent < 0) $dPercent = 0;
return $dPercent;
}
/**
* Update a single row in a table
* @param userID int Account ID
* @param field string Field to update
* @return bool
**/
private function updateSingle($userID, $field) {
$stmt = $this->mysqli->prepare("UPDATE $this->table SET " . $field['name'] . " = ? WHERE userId = ? LIMIT 1");
if ($this->checkStmt($stmt)) { if ($this->checkStmt($stmt)) {
$stmt->bind_param($field['type'].'i', $field['value'], $userID); $stmt->bind_param($field['type'].'i', $field['value'], $userID);
$stmt->execute(); $stmt->execute();

View File

@ -32,7 +32,13 @@ class Worker {
} }
return true; return true;
} }
// Worker code, could possibly be moved to it's own class someday
/**
* Update worker list for a user
* @param account_id int User ID
* @param data array All workers and their settings
* @return bool
**/
public function updateWorkers($account_id, $data) { public function updateWorkers($account_id, $data) {
$username = $this->user->getUserName($account_id); $username = $this->user->getUserName($account_id);
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
@ -47,23 +53,32 @@ class Worker {
} }
return true; return true;
} }
/**
* Fetch all workers for an account
* @param account_id int User ID
* @return mixed array Workers and their settings or false
**/
public function getWorkers($account_id) { public function getWorkers($account_id) {
$stmt = $this->mysqli->prepare(" $stmt = $this->mysqli->prepare("
SELECT $this->table.username, $this->table.password, SELECT id, username, password,
( SELECT SIGN(count(id)) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) AS active, ( SELECT SIGN(COUNT(id)) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) AS active,
( SELECT ROUND(COUNT(id) * POW(2,21)/600/1000) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) AS hashrate ( SELECT ROUND(COUNT(id) * POW(2,21)/600/1000) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) AS hashrate
FROM $this->table FROM $this->table
WHERE account_id = ?"); WHERE account_id = ?");
if ($this->checkStmt($stmt)) { if ($this->checkStmt($stmt) && $stmt->bind_param('i', $account_id) && $stmt->execute() && $result = $stmt->get_result())
if (!$stmt->bind_param('i', $account_id)) return false;
if (!$stmt->execute()) return false;
$result = $stmt->get_result();
$stmt->close();
return $result->fetch_all(MYSQLI_ASSOC); return $result->fetch_all(MYSQLI_ASSOC);
} // Catchall
$this->setErrorMessage('Failed to fetch workers for your account');
$this->debug->append('Fetching workers failed: ' . $this->mysqli->error);
return false; return false;
} }
/**
* Get all currently active workers in the past 10 minutes
* @param none
* @return data mixed int count if any workers are active, false otherwise
**/
public function getCountAllActiveWorkers() { public function getCountAllActiveWorkers() {
$stmt = $this->mysqli->prepare("SELECT COUNT(DISTINCT username) AS total FROM " . $this->share->getTableName() . " WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE)"); $stmt = $this->mysqli->prepare("SELECT COUNT(DISTINCT username) AS total FROM " . $this->share->getTableName() . " WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE)");
if ($this->checkStmt($stmt)) { if ($this->checkStmt($stmt)) {
@ -77,6 +92,15 @@ class Worker {
return false; return false;
} }
/**
* Add new worker to an existing web account
* The webuser name is prefixed to the worker name
* Passwords are plain text for pushpoold
* @param account_id int User ID
* @param workerName string Worker name
* @param workerPassword string Worker password
* @return bool
**/
public function addWorker($account_id, $workerName, $workerPassword) { public function addWorker($account_id, $workerName, $workerPassword) {
$username = $this->user->getUserName($account_id); $username = $this->user->getUserName($account_id);
$workerName = "$username.$workerName"; $workerName = "$username.$workerName";
@ -92,6 +116,13 @@ class Worker {
} }
return false; return false;
} }
/**
* Delete existing worker from account
* @param account_id int User ID
* @param id int Worker ID
* @return bool
**/
public function deleteWorker($account_id, $id) { public function deleteWorker($account_id, $id) {
$stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE account_id = ? AND id = ?"); $stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE account_id = ? AND id = ?");
if ($this->checkStmt($stmt)) { if ($this->checkStmt($stmt)) {

View File

@ -25,9 +25,18 @@ $config = array(
'name' => 'The Pool', 'name' => 'The Pool',
'slogan' => 'Resistance is futile', 'slogan' => 'Resistance is futile',
), ),
'difficulty' => '31', // Target difficulty for this pool 'fees' => 0,
'reward' => '50', // Reward for finding blocks 'difficulty' => '31', // Target difficulty for this pool as set in pushpoold json
'confirmations' => '120', // Confirmations per block found to credit transactions 'reward' => '50', // Reward for finding blocks, fixed value but changes someday
'confirmations' => '120', // Confirmations per block needed to credit transactions
'memcache' => array(
'enabled' => true,
'host' => 'localhost', // Memcache Host
'port' => 11211, // Memcache Port
'keyprefix' => 'mmcfe_ng_', // Prefix for all keys
'expiration'=> '90', // Cache time
'splay' => '15' // Splay time
),
'wallet' => array( 'wallet' => array(
'type' => 'http', // http or https are supported 'type' => 'http', // http or https are supported
'host' => 'localhost:9332', 'host' => 'localhost:9332',

View File

@ -8,11 +8,11 @@ if (!defined('SECURITY'))
if ($bitcoin->can_connect() === true){ if ($bitcoin->can_connect() === true){
if (!$dDifficulty = $memcache->get('dDifficulty')) { if (!$dDifficulty = $memcache->get('dDifficulty')) {
$dDifficulty = $bitcoin->query('getdifficulty'); $dDifficulty = $bitcoin->query('getdifficulty');
$memcache->set('dDifficulty', $dDifficulty, 60); $memcache->set('dDifficulty', $dDifficulty);
} }
if (!$iBlock = $memcache->get('iBlock')) { if (!$iBlock = $memcache->get('iBlock')) {
$iBlock = $bitcoin->query('getblockcount'); $iBlock = $bitcoin->query('getblockcount');
$memcache->set('iBlock', $iBlock, 60); $memcache->set('iBlock', $iBlock);
} }
} else { } else {
$iDifficulty = 1; $iDifficulty = 1;
@ -20,23 +20,18 @@ if ($bitcoin->can_connect() === true){
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg'); $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg');
} }
if (!$aHashData = $memcache->get('aHashData')) { // Top share contributors
$debug->append('STA Fetching Hashrates from database'); $aContributorsShares = $statistics->getTopContributors('shares', 15);
$aHashData = $statistics->getTopContributors();
$memcache->set('aHashData', $aHashData, 60); // Top hash contributors
$debug->append('END Fetching Hashrates from database'); $aContributorsHashes = $statistics->getTopContributors('hashes', 15);
}
// Grab the last 10 blocks found // Grab the last 10 blocks found
$aBlocksFoundData = $statistics->getBlocksFound(10); $aBlocksFoundData = $statistics->getBlocksFound(10);
$aBlockData = $aBlocksFoundData[0]; $aBlockData = $aBlocksFoundData[0];
// Estimated time to find the next block // Estimated time to find the next block
if (!$iCurrentPoolHashrate = $memcache->get('iCurrentPoolHashrate')) { $iCurrentPoolHashrate = $statistics->getCurrentHashrate();
$debug->append('Fetching iCurrentPoolHashrate from database');
$iCurrentPoolHashrate = $statistics->getCurrentHashrate();
$memcache->set('iCurrentPoolHashrate', $iCurrentPoolHashrate, 60);
}
// Time in seconds, not hours, using modifier in smarty to translate // Time in seconds, not hours, using modifier in smarty to translate
$iEstTime = $dDifficulty * pow(2,32) / ($iCurrentPoolHashrate * 1000); $iEstTime = $dDifficulty * pow(2,32) / ($iCurrentPoolHashrate * 1000);
@ -52,7 +47,8 @@ if (!empty($aBlockData)) {
$smarty->assign("ESTTIME", $iEstTime); $smarty->assign("ESTTIME", $iEstTime);
$smarty->assign("TIMESINCELAST", $dTimeSinceLast); $smarty->assign("TIMESINCELAST", $dTimeSinceLast);
$smarty->assign("BLOCKSFOUND", $aBlocksFoundData); $smarty->assign("BLOCKSFOUND", $aBlocksFoundData);
$smarty->assign("TOPHASHRATES", $aHashData); $smarty->assign("CONTRIBSHARES", $aContributorsShares);
$smarty->assign("CONTRIBHASHES", $aContributorsHashes);
$smarty->assign("CURRENTBLOCK", $iBlock); $smarty->assign("CURRENTBLOCK", $iBlock);
$smarty->assign("LASTBLOCK", $aBlockData['height']); $smarty->assign("LASTBLOCK", $aBlockData['height']);
$smarty->assign("DIFFICULTY", $dDifficulty); $smarty->assign("DIFFICULTY", $dDifficulty);

View File

@ -8,11 +8,11 @@ if (!defined('SECURITY'))
if ($bitcoin->can_connect() === true){ if ($bitcoin->can_connect() === true){
if (!$dDifficulty = $memcache->get('dDifficulty')) { if (!$dDifficulty = $memcache->get('dDifficulty')) {
$dDifficulty = $bitcoin->query('getdifficulty'); $dDifficulty = $bitcoin->query('getdifficulty');
$memcache->set('dDifficulty', $dDifficulty, 60); $memcache->set('dDifficulty', $dDifficulty);
} }
if (!$iBlock = $memcache->get('iBlock')) { if (!$iBlock = $memcache->get('iBlock')) {
$iBlock = $bitcoin->query('getblockcount'); $iBlock = $bitcoin->query('getblockcount');
$memcache->set('iBlock', $iBlock, 60); $memcache->set('iBlock', $iBlock);
} }
} else { } else {
$iDifficulty = 1; $iDifficulty = 1;
@ -20,12 +20,7 @@ if ($bitcoin->can_connect() === true){
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg'); $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg');
} }
if (!$aHourlyHashRates = $memcache->get('mmcfe_' . $_SESSION['USERDATA']['id'] . '_hourlyhashrate')) { $aHourlyHashRates = $statistics->getHourlyHashrateByAccount($_SESSION['USERDATA']['id']);
$debug->append('STA Fetching hourly hashrates from database');
$aHourlyHashRates = $statistics->getHourlyHashrateByAccount($_SESSION['USERDATA']['id']);
$memcache->set('mmcfe_' . $_SESSION['USERDATA']['id'] . '_hourlyhashrate', $aHourlyHashRates, 600);
$debug->append('END Fetching hourly hashrates from database');
}
// Propagate content our template // Propagate content our template
$smarty->assign("YOURHASHRATES", $aHourlyHashRates); $smarty->assign("YOURHASHRATES", $aHourlyHashRates);

View File

@ -7,34 +7,11 @@ if (!defined('SECURITY'))
// Globally available variables // Globally available variables
$debug->append('Global smarty variables', 3); $debug->append('Global smarty variables', 3);
// Store some stuff in memcache prior to assigning it to Smarty // Fetch some data
if (!$aRoundShares = $memcache->get('aRoundShares')) { $aRoundShares = $statistics->getRoundShares();
$debug->append('STA Fetching aRoundShares from database'); $iCurrentActiveWorkers = $worker->getCountAllActiveWorkers();
$aRoundShares = $statistics->getRoundShares(); $iCurrentPoolHashrate = $statistics->getCurrentHashrate();
$debug->append('END Fetching aRoundShares from database'); $iCurrentPoolShareRate = $statistics->getCurrentShareRate();
$memcache->set('aRoundShares', $aRoundShares, 90);
}
if (!$iCurrentActiveWorkers = $memcache->get('iCurrentActiveWorkers')) {
$debug->append('STA Fetching iCurrentActiveWorkers from database');
$iCurrentActiveWorkers = $worker->getCountAllActiveWorkers();
$debug->append('END Fetching iCurrentActiveWorkers from database');
$memcache->set('iCurrentActiveWorkers', $iCurrentActiveWorkers, 80);
}
if (!$iCurrentPoolHashrate = $memcache->get('iCurrentPoolHashrate')) {
$debug->append('STA Fetching iCurrentPoolHashrate from database');
$iCurrentPoolHashrate = $statistics->getCurrentHashrate();
$debug->append('END Fetching iCurrentPoolHashrate from database');
$memcache->set('iCurrentPoolHashrate', $iCurrentPoolHashrate, 90);
}
if (!$iCurrentPoolShareRate = $memcache->get('iCurrentPoolShareRate')) {
$debug->append('STA Fetching iCurrentPoolShareRate from database');
$iCurrentPoolShareRate = $statistics->getCurrentShareRate();
$debug->append('END Fetching iCurrentPoolShareRate from database');
$memcache->set('iCurrentPoolShareRate', $iCurrentPoolShareRate, 90);
}
$aGlobal = array( $aGlobal = array(
'slogan' => $config['website']['slogan'], 'slogan' => $config['website']['slogan'],
@ -43,30 +20,24 @@ $aGlobal = array(
'sharerate' => $iCurrentPoolShareRate, 'sharerate' => $iCurrentPoolShareRate,
'workers' => $iCurrentActiveWorkers, 'workers' => $iCurrentActiveWorkers,
'roundshares' => $aRoundShares, 'roundshares' => $aRoundShares,
'fees' => $config['fees'],
'confirmations' => $config['confirmations'], 'confirmations' => $config['confirmations'],
'reward' => $config['reward'] 'reward' => $config['reward']
); );
// We don't want the session infos cached // We don't want these session infos cached
$aGlobal['userdata'] = $_SESSION['USERDATA']['id'] ? $user->getUserData($_SESSION['USERDATA']['id']) : array(); $aGlobal['userdata'] = $_SESSION['USERDATA']['id'] ? $user->getUserData($_SESSION['USERDATA']['id']) : array();
// Balance should also not be cached
$aGlobal['userdata']['balance'] = $transaction->getBalance($_SESSION['USERDATA']['id']); $aGlobal['userdata']['balance'] = $transaction->getBalance($_SESSION['USERDATA']['id']);
// Other userdata that we can cache savely // Other userdata that we can cache savely
if (!$aGlobal['userdata']['shares'] = $memcache->get('global_' . $_SESSION['USERDATA']['id'] . '_shares')) { $aGlobal['userdata']['shares'] = $statistics->getUserShares($_SESSION['USERDATA']['id']);
$debug->append('STA Loading user shares from database'); $aGlobal['userdata']['hashrate'] = $statistics->getUserHashrate($_SESSION['USERDATA']['id']);
$aGlobal['userdata']['shares'] = $statistics->getUserShares($_SESSION['USERDATA']['id']);
$debug->append('END Loading user shares from database');
$memcache->set('global_' . $_SESSION['USERDATA']['id'] . '_shares', $aGlobal['userdata']['shares'], 80);
}
if (!$aGlobal['userdata']['hashrate'] = $memcache->get('global_' . $_SESSION['USERDATA']['id'] . '_hashrate') ) { // Some estimations
$debug->append('STA Loading user hashrate from database'); $aGlobal['userdata']['est_block'] = round(( (int)$aGlobal['userdata']['shares']['valid'] / (int)$aRoundShares['valid'] ) * (int)$config['reward'], 3);
$aGlobal['userdata']['hashrate'] = $statistics->getUserHashrate($_SESSION['USERDATA']['id']); $aGlobal['userdata']['est_donation'] = round((( $aGlobal['userdata']['donate_percent'] / 100) * $aGlobal['userdata']['est_block']), 3);
$debug->append('END Loading user hashrate from database'); $aGlobal['userdata']['est_fee'] = round((($config['fees'] / 100) * ($aGlobal['userdata']['est_block'] - $aGlobal['userdata']['est_donation'])), 3);
$memcache->set('global_' . $_SESSION['USERDATA']['id'] . '_hashrate', $aGlobal['userdata']['hashrate'], 70); $aGlobal['userdata']['est_payout'] = round($aGlobal['userdata']['est_block'] - $aGlobal['userdata']['est_donation'] - $aGlobal['userdata']['est_fee'], 3);
}
// Make it available in Smarty // Make it available in Smarty
$smarty->assign('PATH', 'site_assets/' . THEME); $smarty->assign('PATH', 'site_assets/' . THEME);

2
public/templates/cache/README.md vendored Normal file
View File

@ -0,0 +1,2 @@
Please ensure the webserver has access to this folder to write the
caching templates.

View File

@ -1,11 +1,6 @@
{include file="global/block_header.tpl" BLOCK_HEADER="Transaction Log" BUTTONS=array(Confirmed,Unconfirmed)} {include file="global/block_header.tpl" BLOCK_HEADER="Transaction Log" BUTTONS=array(Confirmed,Unconfirmed)}
<div class="block_content tab_content" id="Confirmed" style="clear:;"> <div class="block_content tab_content" id="Confirmed" style="clear:;">
<center> <center>
<p>
<font color="" size="1">
<b>ATP</b> = Auto Threshold Payment, <b>MP</b> = Manual Payment, <b>Don_Fee</b> = donation amount + pool fees (if applicable)
</font>
</p>
<table cellpadding="1" cellspacing="1" width="98%" class="sortable"> <table cellpadding="1" cellspacing="1" width="98%" class="sortable">
<thead style="font-size:13px;"> <thead style="font-size:13px;">
<tr> <tr>
@ -19,12 +14,18 @@
</thead> </thead>
<tbody style="font-size:12px;"> <tbody style="font-size:12px;">
{section transaction $TRANSACTIONS} {section transaction $TRANSACTIONS}
{if (($TRANSACTIONS[transaction].type == 'Credit' and $TRANSACTIONS[transaction].confirmations >= $GLOBAL.confirmations) or $TRANSACTIONS[transaction].type != 'Credit')} {if (
($TRANSACTIONS[transaction].type == 'Credit' and $TRANSACTIONS[transaction].confirmations >= $GLOBAL.confirmations)
or ($TRANSACTIONS[transaction].type == 'Donation' and $TRANSACTIONS[transaction].confirmations >= $GLOBAL.confirmations)
or ($TRANSACTIONS[transaction].type == 'Fee' and $TRANSACTIONS[transaction].confirmations >= $GLOBAL.confirmations)
or $TRANSACTIONS[transaction].type == 'Debit_AP'
or $TRANSACTIONS[transaction].type == 'Debit_MP'
)}
<tr class="{cycle values="odd,even"}"> <tr class="{cycle values="odd,even"}">
<td>{$TRANSACTIONS[transaction].id}</td> <td>{$TRANSACTIONS[transaction].id}</td>
<td>{$TRANSACTIONS[transaction].timestamp}</td> <td>{$TRANSACTIONS[transaction].timestamp}</td>
<td>{$TRANSACTIONS[transaction].type}</td> <td>{$TRANSACTIONS[transaction].type}</td>
<td>{$TRANSACTIONS[transaction].sendAddress}</td> <td>{$TRANSACTIONS[transaction].coin_address}</td>
<td>{if $TRANSACTIONS[transaction].height == 0}n/a{else}{$TRANSACTIONS[transaction].height}{/if}</td> <td>{if $TRANSACTIONS[transaction].height == 0}n/a{else}{$TRANSACTIONS[transaction].height}{/if}</td>
<td><font color="{if $TRANSACTIONS[transaction].type == Credit}green{else}red{/if}">{$TRANSACTIONS[transaction].amount}</td> <td><font color="{if $TRANSACTIONS[transaction].type == Credit}green{else}red{/if}">{$TRANSACTIONS[transaction].amount}</td>
</tr> </tr>
@ -32,11 +33,15 @@
{/section} {/section}
</tbody> </tbody>
</table> </table>
<p>
<font color="" size="1">
<b>Credit_AP</b> = Auto Threshold Payment, <b>Credit_MP</b> = Manual Payment, <b>Donation</b> = Donation, <b>Fee</b> = Pool Fees (if applicable)
</font>
</p>
</center> </center>
</div> </div>
<div class="block_content tab_content" id="Unconfirmed" style=""> <div class="block_content tab_content" id="Unconfirmed" style="">
<center> <center>
<p><font color="" sizeze="1">Listed below are your estimated rewards and donations/fees for all blocks awaiting 120 confirmations.</font></p>
<table cellpadding="1" cellspacing="1" width="98%" class="sortable"> <table cellpadding="1" cellspacing="1" width="98%" class="sortable">
<thead style="font-size:13px;"> <thead style="font-size:13px;">
<tr> <tr>
@ -50,24 +55,33 @@
</thead> </thead>
<tbody style="font-size:12px;"> <tbody style="font-size:12px;">
{section transaction $TRANSACTIONS} {section transaction $TRANSACTIONS}
{if $TRANSACTIONS[transaction].type == 'Credit' && $TRANSACTIONS[transaction].confirmations < $GLOBAL.confirmations} {if (
$TRANSACTIONS[transaction].type == 'Credit' && $TRANSACTIONS[transaction].confirmations < $GLOBAL.confirmations
or ($TRANSACTIONS[transaction].type == 'Donation' and $TRANSACTIONS[transaction].confirmations < $GLOBAL.confirmations)
or ($TRANSACTIONS[transaction].type == 'Fee' and $TRANSACTIONS[transaction].confirmations < $GLOBAL.confirmations)
)}
<tr class="{cycle values="odd,even"}"> <tr class="{cycle values="odd,even"}">
<td>{$TRANSACTIONS[transaction].id}</td> <td>{$TRANSACTIONS[transaction].id}</td>
<td>{$TRANSACTIONS[transaction].timestamp}</td> <td>{$TRANSACTIONS[transaction].timestamp}</td>
<td>{$TRANSACTIONS[transaction].type}</td> <td>{$TRANSACTIONS[transaction].type}</td>
<td>{$TRANSACTIONS[transaction].sendAddress}</td> <td>{$TRANSACTIONS[transaction].coin_address}</td>
<td>{if $TRANSACTIONS[transaction].height == 0}n/a{else}{$TRANSACTIONS[transaction].height}{/if}</td> <td>{if $TRANSACTIONS[transaction].height == 0}n/a{else}{$TRANSACTIONS[transaction].height}{/if}</td>
<td><font color="{if $TRANSACTIONS[transaction].type == Credit}green{else}red{/if}">{$TRANSACTIONS[transaction].amount}</td> <td><font color="{if $TRANSACTIONS[transaction].type == Credit}green{else}red{/if}">{$TRANSACTIONS[transaction].amount}</td>
</tr> </tr>
{assign var="sum" value="`$sum+$TRANSACTIONS[transaction].amount`"} {if $TRANSACTIONS[transaction].type == Credit}
{assign var="credits" value="`$credits+$TRANSACTIONS[transaction].amount`"}
{else}
{assign var="debits" value="`$debits+$TRANSACTIONS[transaction].amount`"}
{/if}
{/if} {/if}
{/section} {/section}
<tr> <tr>
<td colspan="5"><b>Unconfirmed Totals:</b></td> <td colspan="5"><b>Unconfirmed Totals:</b></td>
<td><b>{$sum}</b></td> <td><b>{$credits - $debits}</b></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<p><font color="" sizeze="1">Listed are your estimated rewards and donations/fees for all blocks awaiting {$GLOBAL.confirmations} confirmations.</font></p>
</center> </center>
</div> </div>
{include file="global/block_footer.tpl"} {include file="global/block_footer.tpl"}

View File

@ -1,25 +1,63 @@
<div class="block" style="clear:none; margin-top:15px; margin-left:13px;"> <div class="block" style="clear:none; margin-top:15px; margin-left:13px;">
<div class="block_head"> <div class="block_head">
<div class="bheadl"></div> <div class="bheadl"></div>
<div class="bheadr"></div> <div class="bheadr"></div>
<h1>Dashboard</h1> <h1>Dashboard</h1>
</div> </div>
<div class="block_content" style="padding-top:10px;"> <div class="block_content" style="padding-top:10px;">
<p> <table class="sidebar">
<b><u>Your Current Hashrate</u></b><br/> <tr><td colspan="2"><b>Your Current Hashrate</b></td></tr>
<i><b>{$GLOBAL.userdata.hashrate} KH/s</b></i><br/><br/> <tr><td colspan="2">{$GLOBAL.userdata.hashrate} KH/s</td></tr>
<u><b>Unpaid Shares</b></u><span id='tt'><img src='{$PATH}/images/questionmark.png' height='15px' width='15px' title='Submitted shares between the last 120 confirms block until now.'></span><br/> <tr>
Your Valid: <b><i>{$GLOBAL.userdata.shares.valid}</i><font size='1px'></font></b><br/> <td colspan="2"><b><u>Unpaid Shares</u></b> <span id='tt'><img src='{$PATH}/images/questionmark.png' height='15px' width='15px' title='Submitted shares between the last 120 confirms block until now.'></span><td>
Pool Valid: <b><i>{$GLOBAL.roundshares.valid}</i> <font size='1px'></font></b><br/><br> </tr>
<u><b>Round Shares </b></u><span id='tt'><img src='{$PATH}/images/questionmark.png' height='15px' width='15px' title='Submitted shares since last found block (ie. round shares)'></span><br/> <tr>
Pool Valid: <b><i>{$GLOBAL.roundshares.valid}</i></b><br> <td><b>Your Valid<b></td>
Pool Inalid: <b><i>{$GLOBAL.roundshares.invalid}</i></b><br> <td><i>{$GLOBAL.userdata.shares.valid}</i><font size='1px'></font></b></td>
Your Invalid: <b><i>{$GLOBAL.userdata.shares.invalid}</i><font size='1px'></font></b><br/><br> </tr>
<u><b>Round Estimate</b></u><font size='1'></font></u><br> <tr>
<b><i>{math equation="round(( x / y ) * z, 8)" x=$GLOBAL.userdata.shares.valid y=$GLOBAL.roundshares.valid z=$GLOBAL.reward}</i> <font size='1px'>LTC</font></b><br><br> <td><b>Pool Valid</td>
<u><b>Account Balance</b></u><br><b><i>{$GLOBAL.userdata.balance|default:"0"}</i><font size='1px'> LTC</font></b><br/><br> <td><i>{$GLOBAL.roundshares.valid}</i> <font size='1px'></font></b></td>
</p> </tr>
</div> <tr>
<div class="bendl"></div> <td colspan="2"><b><u>Round Shares</u></b> <span id='tt'><img src='{$PATH}/images/questionmark.png' height='15px' width='15px' title='Submitted shares since last found block (ie. round shares)'></span></td>
<div class="bendr"></div> </tr>
<tr>
<td><b>Pool Valid</b></td>
<td><i>{$GLOBAL.roundshares.valid}</i></td>
</tr>
<tr>
<td><b>Pool Invalid</b></td>
<td><i>{$GLOBAL.roundshares.invalid}</i></td>
</tr>
<tr>
<td><b>Your Invalid</b></td>
<td><i>{$GLOBAL.userdata.shares.invalid}</i><font size='1px'></font></td>
</tr>
<tr>
<td colspan="2"><b><u>Round Estimate</u></b></td>
</tr>
<tr>
<td><b>Block</b></td>
<td>{$GLOBAL.userdata.est_block} LTC</td>
</tr>
<tr>
<td><b>Donation</b></td>
<td>{$GLOBAL.userdata.est_donation} LTC</td>
</tr>
<tr>
<td><b>Fees</b></td>
<td>{$GLOBAL.userdata.est_fee} LTC</td>
</tr>
<tr>
<td><b>Payout</b></td>
<td>{$GLOBAL.userdata.est_payout} LTC</td>
</tr>
<tr><td colspan="2">&nbsp;</td></tr>
<tr><td colspan="2"><b><u>Account Balance</u></b></td></tr>
<tr><td colspan="2"><b>{$GLOBAL.userdata.balance|default:"0"} LTC</td></tr>
</table>
</div> </div>
<div class="bendl"></div>
<div class="bendr"></div>
</div>

View File

@ -16,7 +16,7 @@
{section block $BLOCKSFOUND} {section block $BLOCKSFOUND}
<tr class="{cycle values="odd,even"}"> <tr class="{cycle values="odd,even"}">
<td>{$BLOCKSFOUND[block].height}</td> <td>{$BLOCKSFOUND[block].height}</td>
<td>{if $BLOCKSFOUND[block].confirmations >= 120}<font color="green">Confirmed</font>{else}{120 - $BLOCKSFOUND[block].confirmations} left{/if}</td> <td>{if $BLOCKSFOUND[block].confirmations >= $GLOBAL.confirmations}<font color="green">Confirmed</font>{else}{$GLOBAL.confirmations - $BLOCKSFOUND[block].confirmations} left{/if}</td>
<td>{$BLOCKSFOUND[block].finder|default:"unknown"}</td> <td>{$BLOCKSFOUND[block].finder|default:"unknown"}</td>
<td>{$BLOCKSFOUND[block].time|date_format:"%d/%m/%Y %H:%M:%S"}</td> <td>{$BLOCKSFOUND[block].time|date_format:"%d/%m/%Y %H:%M:%S"}</td>
<td>{$BLOCKSFOUND[block].difficulty|number_format:"8"}</td> <td>{$BLOCKSFOUND[block].difficulty|number_format:"8"}</td>
@ -27,6 +27,6 @@
</table> </table>
</center> </center>
<ul> <ul>
<li>Note: <font color="orange">Round Earnings are not credited until 120 confirms.</font></li> <li>Note: <font color="orange">Round Earnings are not credited until {$GLOBAL.confirmations} confirms.</font></li>
</ul> </ul>
{include file="global/block_footer.tpl"} {include file="global/block_footer.tpl"}

View File

@ -1,34 +1,8 @@
{include file="global/block_header.tpl" BLOCK_HEADER="Pool Statistics" BLOCK_STYLE="clear:none;"} {include file="global/block_header.tpl" BLOCK_HEADER="Pool Statistics" BLOCK_STYLE="clear:none;"}
{include file="global/block_header.tpl" BLOCK_HEADER="Top Contributers"}
<center> {include file="statistics/pool/contributors_shares.tpl"}
<table width="100%" border="0" style="font-size:13px;" class="sortable">
<thead> {include file="statistics/pool/contributors_hashrate.tpl"}
<tr style="background-color:#B6DAFF;">
<th align="left">Rank</th>
<th align="left" scope="col">User Name</th>
<th align="left" scope="col">KH/s</th>
<th align="left" scope="col">Shares</th>
<th align="left" scope="col">Shares/s</th>
<th align="left">Ł/Day<font size="1"> (est)</font></th>
</tr>
</thead>
<tbody>
{assign var=rank value=1}
{section hashrate $TOPHASHRATES}
<tr class="{cycle values="odd,even"}">
<td>{$rank++}</td>
<td>{$TOPHASHRATES[hashrate].account}</td>
<td>{$TOPHASHRATES[hashrate].hashrate|number_format}</td>
<td>{$TOPHASHRATES[hashrate].shares|number_format}</td>
<td>{$TOPHASHRATES[hashrate].sharesps}</td>
<td>{math equation="round(reward / ( diff * pow(2,32) / ( hashrate * 1000 ) / 3600 / 24),3)" diff=$DIFFICULTY reward=$REWARD hashrate=$TOPHASHRATES[hashrate].hashrate}</td>
</tr>
{/section}
</tbody>
</table>
<div id="pagination" class="pagination"></div>
</center>
{include file="global/block_footer.tpl"}
{include file="global/block_header.tpl" BLOCK_HEADER="Server Stats" BLOCK_STYLE="clear:all;" STYLE="padding-left:5px;padding-right:5px;"} {include file="global/block_header.tpl" BLOCK_HEADER="Server Stats" BLOCK_STYLE="clear:all;" STYLE="padding-left:5px;padding-right:5px;"}
<table class="" width="100%" style="font-size:13px;"> <table class="" width="100%" style="font-size:13px;">

View File

@ -0,0 +1,26 @@
{include file="global/block_header.tpl" ALIGN="left" BLOCK_HEADER="Top Hashrate Contributers"}
<center>
<table width="100%" border="0" style="font-size:13px;" class="sortable">
<thead>
<tr style="background-color:#B6DAFF;">
<th align="left">Rank</th>
<th align="left" scope="col">User Name</th>
<th align="left" scope="col">KH/s</th>
<th align="left">Ł/Day<font size="1"> (est)</font></th>
</tr>
</thead>
<tbody>
{assign var=rank value=1}
{section contrib $CONTRIBHASHES}
<tr class="{cycle values="odd,even"}">
<td>{$rank++}</td>
<td>{$CONTRIBHASHES[contrib].account}</td>
<td>{$CONTRIBHASHES[contrib].hashrate|number_format}</td>
<td>{math equation="round(reward / ( diff * pow(2,32) / ( hashrate * 1000 ) / 3600 / 24),3)" diff=$DIFFICULTY reward=$REWARD hashrate=$CONTRIBHASHES[contrib].hashrate}</td>
</tr>
{/section}
</tbody>
</table>
<div id="pagination" class="pagination"></div>
</center>
{include file="global/block_footer.tpl"}

View File

@ -0,0 +1,24 @@
{include file="global/block_header.tpl" ALIGN="right" BLOCK_HEADER="Top Share Contributers"}
<center>
<table width="100%" border="0" style="font-size:13px;" class="sortable">
<thead>
<tr style="background-color:#B6DAFF;">
<th align="left">Rank</th>
<th align="left" scope="col">User Name</th>
<th align="left" scope="col">Shares</th>
</tr>
</thead>
<tbody>
{assign var=rank value=1}
{section hashrate $CONTRIBSHARES}
<tr class="{cycle values="odd,even"}">
<td>{$rank++}</td>
<td>{$CONTRIBSHARES[hashrate].account}</td>
<td>{$CONTRIBSHARES[hashrate].shares|number_format}</td>
</tr>
{/section}
</tbody>
</table>
<div id="pagination" class="pagination"></div>
</center>
{include file="global/block_footer.tpl"}

View File

@ -1,11 +1,11 @@
-- phpMyAdmin SQL Dump -- phpMyAdmin SQL Dump
-- version 3.5.1 -- version 3.5.8.1deb1
-- http://www.phpmyadmin.net -- http://www.phpmyadmin.net
-- --
-- Host: localhost -- Host: localhost
-- Generation Time: May 16, 2013 at 09:25 PM -- Generation Time: May 20, 2013 at 07:35 PM
-- Server version: 5.5.31-log -- Server version: 5.5.31-0ubuntu0.13.04.1
-- PHP Version: 5.4.15 -- PHP Version: 5.4.9-4ubuntu2
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00"; SET time_zone = "+00:00";
@ -17,7 +17,7 @@ SET time_zone = "+00:00";
/*!40101 SET NAMES utf8 */; /*!40101 SET NAMES utf8 */;
-- --
-- Database: `mmcfe_ng_db` -- Database: `mmcfe_ng`
-- --
-- -------------------------------------------------------- -- --------------------------------------------------------
@ -63,7 +63,7 @@ CREATE TABLE IF NOT EXISTS `blocks` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `height` (`height`,`blockhash`), UNIQUE KEY `height` (`height`,`blockhash`),
KEY `time` (`time`) KEY `time` (`time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Discovered blocks persisted from Litecoin Service'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Discovered blocks persisted from Litecoin Service';
-- -------------------------------------------------------- -- --------------------------------------------------------
@ -115,7 +115,7 @@ CREATE TABLE IF NOT EXISTS `shares_archive` (
`time` datetime DEFAULT NULL, `time` datetime DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `share_id` (`share_id`) UNIQUE KEY `share_id` (`share_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Archive shares for potential later debugging purposes'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Archive shares for potential later debugging purposes';
-- -------------------------------------------------------- -- --------------------------------------------------------
@ -132,7 +132,7 @@ CREATE TABLE IF NOT EXISTS `statistics_shares` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `account_id` (`account_id`), KEY `account_id` (`account_id`),
KEY `block_id` (`block_id`) KEY `block_id` (`block_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- -------------------------------------------------------- -- --------------------------------------------------------
@ -143,17 +143,16 @@ CREATE TABLE IF NOT EXISTS `statistics_shares` (
CREATE TABLE IF NOT EXISTS `transactions` ( CREATE TABLE IF NOT EXISTS `transactions` (
`id` int(255) NOT NULL AUTO_INCREMENT, `id` int(255) NOT NULL AUTO_INCREMENT,
`account_id` int(255) unsigned NOT NULL, `account_id` int(255) unsigned NOT NULL,
`type` enum('Credit','Debit_MP','Debit_AP') DEFAULT NULL, `type` enum('Credit','Debit_MP','Debit_AP','Fee','Donation') DEFAULT NULL,
`coin_address` varchar(255) DEFAULT NULL, `coin_address` varchar(255) DEFAULT NULL,
`amount` double DEFAULT '0', `amount` double DEFAULT '0',
`fee_amount` float DEFAULT '0',
`block_id` int(255) DEFAULT NULL, `block_id` int(255) DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `block_id` (`block_id`), KEY `block_id` (`block_id`),
KEY `account_id` (`account_id`), KEY `account_id` (`account_id`),
KEY `type` (`type`) KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- -------------------------------------------------------- -- --------------------------------------------------------