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.
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
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
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
============
@ -22,7 +26,7 @@ in the appropriate forums.
* Apache2
* libapache2-mod-php5
* PHP 5.4+ (5.3 might work too)
* PHP 5.4+
* php5-mysqlnd
* php5-memcached
* MySQL Server
@ -31,6 +35,24 @@ in the appropriate forums.
* pushpoold
* 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
============
@ -76,7 +98,8 @@ Memcache
Please install and start a default memcache instance. Not only would you
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
-------------
@ -123,8 +146,9 @@ me know by creating an [Issue][1] marked as `Feature Request`.
Disclaimer
==========
This is a *WIP Project*. Most functionality is now added, the core
features are available and the backend cronjobs are working. If you
encounter any problems related to the code please create a new [Issue][1]
This is a **WIP Project**. Most functionality is now added, the core
features are available and the backend cronjobs are working. I would not recommend
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"

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);
$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());
if (empty($aAccountShares)) {
verbose("\nNo shares found for this block\n\n");
@ -58,24 +58,45 @@ foreach ($aAllBlocks as $iIndex => $aBlock) {
if (!$block->setShares($aBlock['id'], $iRoundShares))
$strStatus = "Shares Failed";
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) {
// Payout based on shares, PPS system
$aData['percentage'] = number_format(round(( 100 / $iRoundShares ) * $aData['valid'], 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" .
$aData['username'] . "\t" .
$aData['valid'] . "\t" .
$aData['invalid'] . "\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";
// Update user share statistics
if (!$statistics->updateShareStatistics($aData, $aBlock['id']))
$strStatus = "Stats Failed";
// Add new credit transaction
if (!$transaction->addTransaction($aData['id'], $aData['payout'], 'Credit', $aBlock['id']))
$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");

View File

@ -2,6 +2,7 @@
require_once(CLASS_DIR . '/debug.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 . '/smarty.inc.php');
// 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 . '/transaction.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;
}
/**
* We fill our data array here
* @param string $msg Debug Message

View File

@ -4,21 +4,29 @@
if (!defined('SECURITY'))
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 {
private $sError = '';
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->mysqli = $mysqli;
$this->share = $share;
$this->config = $config;
$this->user = $user;
$this->block = $block;
$this->memcache = $memcache;
$this->debug->append("Instantiated Share class", 2);
}
// get and set methods
/* Some basic get and set methods
**/
private function setErrorMessage($msg) {
$this->sError = $msg;
}
@ -35,7 +43,26 @@ class Statistics {
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) {
if ($data = $this->memcache->get(__FUNCTION__ . $limit)) return $data;
$stmt = $this->mysqli->prepare("
SELECT b.*, a.username as finder
FROM " . $this->block->getTableName() . " AS b
@ -43,11 +70,19 @@ class Statistics {
ON b.account_id = a.id
ORDER BY height DESC LIMIT ?");
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
$this->debug->append("Failed to find blocks:" . $this->mysqli->error);
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) {
$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;
@ -56,7 +91,14 @@ class Statistics {
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() {
if ($data = $this->memcache->get(__FUNCTION__)) return $data;
$stmt = $this->mysqli->prepare("
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)
) AS sum");
// 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);
return false;
}
/**
* Same as getCurrentHashrate but for Shares
* @param none
* @return data object Our share rate in shares per second
**/
public function getCurrentShareRate() {
if ($data = $this->memcache->get(__FUNCTION__)) return $data;
$stmt = $this->mysqli->prepare("
SELECT ROUND(SUM(sharerate) / 600, 2) AS sharerate FROM
(
@ -78,13 +126,19 @@ class Statistics {
UNION ALL
SELECT COUNT(id) AS sharerate FROM " . $this->share->getArchiveTableName() . " WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE)
) 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
$this->debug->append("Failed to fetch share rate: " . $this->mysqli->error);
return false;
}
/**
* Get total shares for this round, since last block found
* @param none
* @return data array invalid and valid shares
**/
public function getRoundShares() {
if ($data = $this->memcache->get(__FUNCTION__)) return $data;
$stmt = $this->mysqli->prepare("
SELECT
( SELECT IFNULL(count(id), 0)
@ -95,13 +149,20 @@ class Statistics {
FROM " . $this->share->getTableName() . "
WHERE UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(time) FROM blocks),0)
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
$this->debug->append("Failed to fetch round shares: " . $this->mysqli->error);
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) {
if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
$stmt = $this->mysqli->prepare("
SELECT
(
@ -109,7 +170,7 @@ class Statistics {
FROM " . $this->share->getTableName() . " AS s,
" . $this->user->getTableName() . " AS u
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 u.id = ?
) AS valid,
@ -118,17 +179,24 @@ class Statistics {
FROM " . $this->share->getTableName() . " AS s,
" . $this->user->getTableName() . " AS u
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 u.id = ?
) 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
$this->debug->append("Unable to fetch user round shares: " . $this->mysqli->error);
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) {
if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
$stmt = $this->mysqli->prepare("
SELECT ROUND(COUNT(s.id) * POW(2, " . $this->config['difficulty'] . ")/600/1000) AS hashrate
FROM " . $this->share->getTableName() . " AS s,
@ -136,13 +204,20 @@ class Statistics {
WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
AND s.time > DATE_SUB(now(), INTERVAL 10 MINUTE)
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
$this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error);
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) {
if ($data = $this->memcache->get(__FUNCTION__ . $worker_id)) return $data;
$stmt = $this->mysqli->prepare("
SELECT ROUND(COUNT(s.id) * POW(2,21)/600/1000) AS hashrate
FROM " . $this->share->getTableName() . " AS s,
@ -150,40 +225,62 @@ class Statistics {
WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
AND s.time > DATE_SUB(now(), INTERVAL 10 MINUTE)
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
$this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error);
return false;
}
public function getTopContributors($limit=15) {
$stmt = $this->mysqli->prepare("
SELECT
ROUND(COUNT(id) / 60 / 10, 2) AS sharesps,
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() && $hashrates= $stmt->get_result()) {
$aHashData = $hashrates->fetch_all(MYSQLI_ASSOC);
$stmt->close();
} else {
/**
* get our top contributors for either shares or hashrate
* @param type string shares or hashes
* @param limit int Limit result to $limit
* @return data array Users with shares, account or hashrate, account
**/
public function getTopContributors($type='shares', $limit=15) {
if ($data = $this->memcache->get(__FUNCTION__ . $type . $limit)) return $data;
switch ($type) {
case 'shares':
$stmt = $this->mysqli->prepare("
SELECT
COUNT(id) AS shares,
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;
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) {
if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
$stmt = $this->mysqli->prepare("
SELECT
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.id = ?
GROUP BY HOUR(time)");
if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $account_id, $account_id) && $stmt->execute() && $hourlyhashrates = $stmt->get_result())
return $hourlyhashrates->fetch_all(MYSQLI_ASSOC);
if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $account_id, $account_id) && $stmt->execute() && $result = $stmt->get_result())
return $this->setCache(__FUNCTION__ . $account_id, $result->fetch_all(MYSQLI_ASSOC), 3600);
// Catchall
$this->debug->append("Failed to fetch hourly hashrate: " . $this->mysqli->error);
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 $tableBlocks = 'blocks';
public function __construct($debug, $mysqli, $config) {
public function __construct($debug, $mysqli, $config, $block) {
$this->debug = $debug;
$this->mysqli = $mysqli;
$this->config = $config;
$this->block = $block;
$this->debug->append("Instantiated Transaction class", 2);
}
@ -24,10 +25,10 @@ class Transaction {
return $this->sError;
}
public function addTransaction($account_id, $amount, $type='Credit', $block_id=NULL, $coin_address=NULL, $fee=0) {
$stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, amount, block_id, type, coin_address, fee_amount) VALUES (?, ?, ?, ?, ?, ?)");
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) VALUES (?, ?, ?, ?, ?)");
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()) {
$this->setErrorMessage("Failed to store transaction");
$stmt->close();
@ -75,23 +76,33 @@ class Transaction {
public function getBalance($account_id) {
$stmt = $this->mysqli->prepare("
SELECT IFNULL(c.credit, 0) - IFNULL(d.debit,0) AS balance
FROM (
SELECT t.account_id, sum(t.amount) AS credit
SELECT ROUND(IFNULL(t1.credit, 0) - IFNULL(t2.debit, 0) - IFNULL(t3.other, 0), 8) AS balance
FROM
(
SELECT sum(t.amount) AS credit
FROM $this->table AS t
LEFT JOIN $this->tableBlocks AS b ON t.block_id = b.id
WHERE type = 'Credit'
AND b.confirmations > ?
AND t.account_id = ? ) AS c
LEFT JOIN (
SELECT t.account_id, sum(amount) AS debit
LEFT JOIN " . $this->block->getTableName() . " AS b ON t.block_id = b.id
WHERE t.type = 'Credit'
AND b.confirmations >= ?
AND t.account_id = ?
) AS t1,
(
SELECT sum(t.amount) AS debit
FROM $this->table AS t
WHERE type IN ('Debit_MP','Debit_AP')
AND t.account_id = ? ) AS d
ON c.account_id = d.account_id
WHERE t.type IN ('Debit_MP', 'Debit_AP')
AND t.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)) {
$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()) {
$this->debug->append("Unable to execute statement: " . $stmt->error);
$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');
}
/**
* Check user login
* @param username string Username
* @param password string Password
* @return bool
**/
public function checkLogin($username, $password) {
$this->debug->append("Checking login for $username with password $password", 2);
if ( $this->checkUserPassword($username, $password) ) {
@ -43,6 +49,12 @@ class User {
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) {
$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");
@ -55,6 +67,14 @@ class User {
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") {
$stmt = $this->mysqli->prepare("SELECT $search FROM $this->table WHERE $field = ? LIMIT 1");
if ($this->checkStmt($stmt)) {
@ -68,12 +88,56 @@ class User {
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)) {
$stmt->bind_param($field['type'].'i', $field['value'], $userID);
$stmt->execute();

View File

@ -32,7 +32,13 @@ class Worker {
}
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) {
$username = $this->user->getUserName($account_id);
foreach ($data as $key => $value) {
@ -47,23 +53,32 @@ class Worker {
}
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) {
$stmt = $this->mysqli->prepare("
SELECT $this->table.username, $this->table.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 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 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
WHERE account_id = ?");
if ($this->checkStmt($stmt)) {
if (!$stmt->bind_param('i', $account_id)) return false;
if (!$stmt->execute()) return false;
$result = $stmt->get_result();
$stmt->close();
if ($this->checkStmt($stmt) && $stmt->bind_param('i', $account_id) && $stmt->execute() && $result = $stmt->get_result())
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;
}
/**
* 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() {
$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)) {
@ -77,6 +92,15 @@ class Worker {
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) {
$username = $this->user->getUserName($account_id);
$workerName = "$username.$workerName";
@ -92,6 +116,13 @@ class Worker {
}
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) {
$stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE account_id = ? AND id = ?");
if ($this->checkStmt($stmt)) {

View File

@ -25,9 +25,18 @@ $config = array(
'name' => 'The Pool',
'slogan' => 'Resistance is futile',
),
'difficulty' => '31', // Target difficulty for this pool
'reward' => '50', // Reward for finding blocks
'confirmations' => '120', // Confirmations per block found to credit transactions
'fees' => 0,
'difficulty' => '31', // Target difficulty for this pool as set in pushpoold json
'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(
'type' => 'http', // http or https are supported
'host' => 'localhost:9332',

View File

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

View File

@ -8,11 +8,11 @@ if (!defined('SECURITY'))
if ($bitcoin->can_connect() === true){
if (!$dDifficulty = $memcache->get('dDifficulty')) {
$dDifficulty = $bitcoin->query('getdifficulty');
$memcache->set('dDifficulty', $dDifficulty, 60);
$memcache->set('dDifficulty', $dDifficulty);
}
if (!$iBlock = $memcache->get('iBlock')) {
$iBlock = $bitcoin->query('getblockcount');
$memcache->set('iBlock', $iBlock, 60);
$memcache->set('iBlock', $iBlock);
}
} else {
$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');
}
if (!$aHourlyHashRates = $memcache->get('mmcfe_' . $_SESSION['USERDATA']['id'] . '_hourlyhashrate')) {
$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');
}
$aHourlyHashRates = $statistics->getHourlyHashrateByAccount($_SESSION['USERDATA']['id']);
// Propagate content our template
$smarty->assign("YOURHASHRATES", $aHourlyHashRates);

View File

@ -7,34 +7,11 @@ if (!defined('SECURITY'))
// Globally available variables
$debug->append('Global smarty variables', 3);
// Store some stuff in memcache prior to assigning it to Smarty
if (!$aRoundShares = $memcache->get('aRoundShares')) {
$debug->append('STA Fetching aRoundShares from database');
$aRoundShares = $statistics->getRoundShares();
$debug->append('END Fetching aRoundShares from database');
$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);
}
// Fetch some data
$aRoundShares = $statistics->getRoundShares();
$iCurrentActiveWorkers = $worker->getCountAllActiveWorkers();
$iCurrentPoolHashrate = $statistics->getCurrentHashrate();
$iCurrentPoolShareRate = $statistics->getCurrentShareRate();
$aGlobal = array(
'slogan' => $config['website']['slogan'],
@ -43,30 +20,24 @@ $aGlobal = array(
'sharerate' => $iCurrentPoolShareRate,
'workers' => $iCurrentActiveWorkers,
'roundshares' => $aRoundShares,
'fees' => $config['fees'],
'confirmations' => $config['confirmations'],
'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();
// Balance should also not be cached
$aGlobal['userdata']['balance'] = $transaction->getBalance($_SESSION['USERDATA']['id']);
// Other userdata that we can cache savely
if (!$aGlobal['userdata']['shares'] = $memcache->get('global_' . $_SESSION['USERDATA']['id'] . '_shares')) {
$debug->append('STA Loading user shares from database');
$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);
}
$aGlobal['userdata']['shares'] = $statistics->getUserShares($_SESSION['USERDATA']['id']);
$aGlobal['userdata']['hashrate'] = $statistics->getUserHashrate($_SESSION['USERDATA']['id']);
if (!$aGlobal['userdata']['hashrate'] = $memcache->get('global_' . $_SESSION['USERDATA']['id'] . '_hashrate') ) {
$debug->append('STA Loading user hashrate from database');
$aGlobal['userdata']['hashrate'] = $statistics->getUserHashrate($_SESSION['USERDATA']['id']);
$debug->append('END Loading user hashrate from database');
$memcache->set('global_' . $_SESSION['USERDATA']['id'] . '_hashrate', $aGlobal['userdata']['hashrate'], 70);
}
// Some estimations
$aGlobal['userdata']['est_block'] = round(( (int)$aGlobal['userdata']['shares']['valid'] / (int)$aRoundShares['valid'] ) * (int)$config['reward'], 3);
$aGlobal['userdata']['est_donation'] = round((( $aGlobal['userdata']['donate_percent'] / 100) * $aGlobal['userdata']['est_block']), 3);
$aGlobal['userdata']['est_fee'] = round((($config['fees'] / 100) * ($aGlobal['userdata']['est_block'] - $aGlobal['userdata']['est_donation'])), 3);
$aGlobal['userdata']['est_payout'] = round($aGlobal['userdata']['est_block'] - $aGlobal['userdata']['est_donation'] - $aGlobal['userdata']['est_fee'], 3);
// Make it available in Smarty
$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)}
<div class="block_content tab_content" id="Confirmed" style="clear:;">
<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">
<thead style="font-size:13px;">
<tr>
@ -19,12 +14,18 @@
</thead>
<tbody style="font-size:12px;">
{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"}">
<td>{$TRANSACTIONS[transaction].id}</td>
<td>{$TRANSACTIONS[transaction].timestamp}</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><font color="{if $TRANSACTIONS[transaction].type == Credit}green{else}red{/if}">{$TRANSACTIONS[transaction].amount}</td>
</tr>
@ -32,11 +33,15 @@
{/section}
</tbody>
</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>
</div>
<div class="block_content tab_content" id="Unconfirmed" style="">
<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">
<thead style="font-size:13px;">
<tr>
@ -50,24 +55,33 @@
</thead>
<tbody style="font-size:12px;">
{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"}">
<td>{$TRANSACTIONS[transaction].id}</td>
<td>{$TRANSACTIONS[transaction].timestamp}</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><font color="{if $TRANSACTIONS[transaction].type == Credit}green{else}red{/if}">{$TRANSACTIONS[transaction].amount}</td>
</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}
{/section}
<tr>
<td colspan="5"><b>Unconfirmed Totals:</b></td>
<td><b>{$sum}</b></td>
<td><b>{$credits - $debits}</b></td>
</tr>
</tbody>
</table>
<p><font color="" sizeze="1">Listed are your estimated rewards and donations/fees for all blocks awaiting {$GLOBAL.confirmations} confirmations.</font></p>
</center>
</div>
{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_head">
<div class="bheadl"></div>
<div class="bheadr"></div>
<h1>Dashboard</h1>
</div>
<div class="block_content" style="padding-top:10px;">
<p>
<b><u>Your Current Hashrate</u></b><br/>
<i><b>{$GLOBAL.userdata.hashrate} KH/s</b></i><br/><br/>
<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/>
Your Valid: <b><i>{$GLOBAL.userdata.shares.valid}</i><font size='1px'></font></b><br/>
Pool Valid: <b><i>{$GLOBAL.roundshares.valid}</i> <font size='1px'></font></b><br/><br>
<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/>
Pool Valid: <b><i>{$GLOBAL.roundshares.valid}</i></b><br>
Pool Inalid: <b><i>{$GLOBAL.roundshares.invalid}</i></b><br>
Your Invalid: <b><i>{$GLOBAL.userdata.shares.invalid}</i><font size='1px'></font></b><br/><br>
<u><b>Round Estimate</b></u><font size='1'></font></u><br>
<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>
<u><b>Account Balance</b></u><br><b><i>{$GLOBAL.userdata.balance|default:"0"}</i><font size='1px'> LTC</font></b><br/><br>
</p>
</div>
<div class="bendl"></div>
<div class="bendr"></div>
<div class="block" style="clear:none; margin-top:15px; margin-left:13px;">
<div class="block_head">
<div class="bheadl"></div>
<div class="bheadr"></div>
<h1>Dashboard</h1>
</div>
<div class="block_content" style="padding-top:10px;">
<table class="sidebar">
<tr><td colspan="2"><b>Your Current Hashrate</b></td></tr>
<tr><td colspan="2">{$GLOBAL.userdata.hashrate} KH/s</td></tr>
<tr>
<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>
</tr>
<tr>
<td><b>Your Valid<b></td>
<td><i>{$GLOBAL.userdata.shares.valid}</i><font size='1px'></font></b></td>
</tr>
<tr>
<td><b>Pool Valid</td>
<td><i>{$GLOBAL.roundshares.valid}</i> <font size='1px'></font></b></td>
</tr>
<tr>
<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>
</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 class="bendl"></div>
<div class="bendr"></div>
</div>

View File

@ -16,7 +16,7 @@
{section block $BLOCKSFOUND}
<tr class="{cycle values="odd,even"}">
<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].time|date_format:"%d/%m/%Y %H:%M:%S"}</td>
<td>{$BLOCKSFOUND[block].difficulty|number_format:"8"}</td>
@ -27,6 +27,6 @@
</table>
</center>
<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>
{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="Top 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" 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="statistics/pool/contributors_shares.tpl"}
{include file="statistics/pool/contributors_hashrate.tpl"}
{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;">

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
-- version 3.5.1
-- version 3.5.8.1deb1
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Generation Time: May 16, 2013 at 09:25 PM
-- Server version: 5.5.31-log
-- PHP Version: 5.4.15
-- Generation Time: May 20, 2013 at 07:35 PM
-- Server version: 5.5.31-0ubuntu0.13.04.1
-- PHP Version: 5.4.9-4ubuntu2
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
@ -17,7 +17,7 @@ SET time_zone = "+00:00";
/*!40101 SET NAMES utf8 */;
--
-- Database: `mmcfe_ng_db`
-- Database: `mmcfe_ng`
--
-- --------------------------------------------------------
@ -63,7 +63,7 @@ CREATE TABLE IF NOT EXISTS `blocks` (
PRIMARY KEY (`id`),
UNIQUE KEY `height` (`height`,`blockhash`),
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,
PRIMARY KEY (`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`),
KEY `account_id` (`account_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` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`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,
`amount` double DEFAULT '0',
`fee_amount` float DEFAULT '0',
`block_id` int(255) DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `block_id` (`block_id`),
KEY `account_id` (`account_id`),
KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------