Merge pull request #95 from TheSerapher/next

Next to Master
This commit is contained in:
Sebastian Grewe 2013-06-03 04:07:52 -07:00
commit 86381f7052
61 changed files with 1354 additions and 345 deletions

60
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,60 @@
# How to contribute
Third-party patches are much appreciated. This is a rather large project
and a single person can not work on it 24/7 to address all issues and
feature requests. If you feel comfortable with PHP and Smarty you should
consider following this contribution guide!
## Getting Started
* Make sure you have a [GitHub account][4].
* Submit an [Issue][1] for your issue, assuming one does not already exist.
* Clearly describe the issue including steps to reproduce when it is a bug.
* Make sure you fill in the earliest version that you know has the issue.
* Fork the repository into your GitHub account
## Making Changes
* Create a topic branch from where you want to base your work.
* This is usually the `next` branch.
* Only target release branches if you are certain your fix must be on that
branch.
* To quickly create a topic branch based on `next`; `git branch
fix/next/my_contribution next` then checkout the new branch with `git
checkout fix/next/my_contribution`. Please avoid working directly on the
`next` branch.
* Make commits of logical units.
* Check for unnecessary whitespace with `git diff --check` before committing.
* Make sure your commit messages are in the proper format.
````
(#99999) Make the example in CONTRIBUTING imperative and concrete
Without this patch applied the example commit message in the CONTRIBUTING
document is not a concrete example. This is a problem because the
contributor is left to imagine what the commit message should look like
based on a description rather than an example. This patch fixes the
problem by making the example concrete and imperative.
The first line is a real life imperative statement with an issue number
from our issue tracker. The body describes the behavior without the patch,
why this is a problem, and how the patch fixes the problem when applied.
````
## Submitting Changes
* Push your changes to a topic branch in your fork of the repository.
* Submit a pull request to the origin repository.
* Update your issue that you have submitted code and are ready for it to be reviewed.
* Include a link to the pull request in the ticket
# Additional Resources
* [Issue Tracker][1]
* [General GitHub documentation][2]
* [GitHub pull request documentation][3]
[1]: https://github.com/TheSerapher/php-mmcfe-ng/issues "Issue"
[2]: http://help.github.com/ "GitHub documentation"
[3]: http://help.github.com/send-pull-requests/ "GitHub pull request documentation"
[4]: https://github.com/signup/free "GitHub account"

120
README.md
View File

@ -1,7 +1,7 @@
Description
===========
mmcFE-ng is a web frontend for Pooled LTC Mining.
mmcFE-ng is a web frontend for Pooled LTC Mining. A pool using this interface is running at http://pool.grewe.ca
The web frontend layout is based on mmcFE, the original work by Greedi:
https://github.com/Greedi/mmcFE
@ -23,6 +23,13 @@ I was hoping to keep this out of the README but apparently people remove or chan
at the bottom of the page. For those of you finding my project and are willing to appreciate the work
with some hard earned LTC feel free to donate to my LTC address: `Lge95QR2frp9y1wJufjUPCycVsg5gLJPW8`
Donors
======
These people have supported this project with a donation:
* [obigal](https://github.com/obigal)
Requirements
============
@ -36,6 +43,7 @@ in the appropriate forums.
* PHP 5.4+
* php5-mysqlnd
* php5-memcached
* php5-curl
* MySQL Server
* mysql-server
* memcached
@ -47,6 +55,10 @@ Features
The following feature have been implemented so far:
* Reward Systems
* Propotional
* (Planned) PPS
* (Planned) PPLNS
* Use of memcache for statistics instead of a cronjob
* Web User accounts
* Worker accounts
@ -63,107 +75,27 @@ The following feature have been implemented so far:
Installation
============
Please ensure you fullfill the minimal installation requirements listed above
and install any missing packages or software.
Please take a look at the [Quick Start Guide](https://github.com/TheSerapher/php-mmcfe-ng/wiki/Quick-Start-Guide). This will give you
an idea how to setup `mmcfe-ng`.
Download Source
---------------
Contributing
============
Download the (stable) master branch from Github:
You can contribute to this project in different ways:
```
git clone -b master git://github.com/TheSerapher/php-mmcfe-ng.git mmcfe-ng
```
* Report outstanding issues and bugs by creating an [Issue][1]
* Suggest feature enhancements also via [Issues][1]
* Fork the project, create a branch and file a pull request to improve the code itself
Or, if you are not using git, use the ZIP file provided:
Contact
=======
```
wget https://github.com/TheSerapher/php-mmcfe-ng/archive/master.zip
unzip master.zip
mv php-mmcfe-ng-master mmcfe-ng
```
Permissions
-----------
Please ensure your webuser (e.g. `www-data`, `apache`) has write access to
the `mmcfe-ng/public/templates/compile` folder! Otherwise compiled
templates can not be stored:
```
sudo chown www-data mmcfe-ng/public/templates/compile
```
Apache2 Configuration
---------------------
Please point your website document root to the `mmcfe-ng/public` folder
and enable auto-index for `index.php`.
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. Your memcache can be
configured in the global configuration file (see below).
Configuration
-------------
Please create the `mmcfe-ng/public/include/config/global.inc.php`
configuration from the supplied template
`mmcfe-ng/public/include/config/global.inc.dist.php`.
Pushpoold
---------
Please ensure the passwords are read from the proper table by adding this to your configuration:
```
# database settings
"database" : {
"engine" : "mysql",
"port" : "3306",
"name" : "mmcfeng_database_name",
"username" : "someuser",
"password" : "somepass",
"sharelog" : true,
"stmt.pwdb":"SELECT `password` FROM `workers` WHERE `username` = ?",
"stmt.sharelog":"INSERT INTO shares (rem_host, username, our_result, upstream_result, reason, solution) VALUES (?, ?, ?, ?, ?, ?)"
},
```
Database
========
Now that the software is ready we need to import the database.
You will find the SQL file in the `mmcfe-ng/sql` folder.
Import this file into an existing database and you should
have the proper structure ready.
TODO
====
I tried to cover most features available in mmcFE. There might be some missing still
(like graphs, some stats) but if you figure there is a core function missing please let
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. 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"
You can find me on Freenode.net, #mmcfe-ng.
License and Author
==================
Copyright 2012, Sebastian Grewe
Copyright 2012, Sebastian Grewe
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -177,3 +109,5 @@ 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.
[1]: https://github.com/TheSerapher/php-mmcfe-ng/issues "Issue"

View File

@ -33,7 +33,18 @@ $aAllBlocks = $block->getAllUnconfirmed($config['confirmations']);
verbose("ID\tBlockhash\tConfirmations\t\n");
foreach ($aAllBlocks as $iIndex => $aBlock) {
$aBlockInfo = $bitcoin->query('getblock', $aBlock['blockhash']);
// Fetch this blocks transaction details to find orphan blocks
$aTxDetails = $bitcoin->query('gettransaction', $aBlockInfo['tx'][0]);
verbose($aBlock['id'] . "\t" . $aBlock['blockhash'] . "\t" . $aBlock['confirmations'] . " -> " . $aBlockInfo['confirmations'] . "\t");
if ($aTxDetails['details'][0]['category'] == 'orphan') {
// We have an orphaned block, we need to invalidate all transactions for this one
if ($transaction->setOrphan($aBlock['id']) && $block->setConfirmations($aBlock['id'], -1)) {
verbose("ORPHAN\n");
} else {
verbose("ORPHAN_ERR");
}
continue;
}
if ($aBlock['confirmations'] == $aBlockInfo['confirmations']) {
verbose("SKIPPED\n");
} else if ($block->setConfirmations($aBlock['id'], $aBlockInfo['confirmations'])) {

View File

@ -23,7 +23,8 @@ limitations under the License.
require_once('shared.inc.php');
// Fetch our last block found from the DB as a starting point
$strLastBlockHash = @$block->getLast()->blockhash;
$aLastBlock = @$block->getLast();
$strLastBlockHash = $aLastBlock['blockhash'];
if (!$strLastBlockHash) {
$strLastBlockHash = '';
}
@ -39,28 +40,63 @@ if ( $bitcoin->can_connect() === true ){
// Nothing to do so bail out
if (empty($aTransactions['transactions'])) {
verbose("No new transactions since last block\n");
exit(0);
}
} else {
// Table header
verbose("Blockhash\t\tHeight\tAmount\tConfirmations\tDiff\t\tTime\t\t\tStatus\n");
// Table header
verbose("Blockhash\t\tHeight\tAmount\tConfirmations\tDiff\t\tTime\t\t\tStatus\n");
foreach ($aTransactions['transactions'] as $iIndex => $aData) {
if ( $aData['category'] == 'generate' || $aData['category'] == 'immature' ) {
$aBlockInfo = $bitcoin->query('getblock', $aData['blockhash']);
$aData['height'] = $aBlockInfo['height'];
$aData['difficulty'] = $aBlockInfo['difficulty'];
verbose(substr($aData['blockhash'], 0, 15) . "...\t" .
$aData['height'] . "\t" .
$aData['amount'] . "\t" .
$aData['confirmations'] . "\t\t" .
$aData['difficulty'] . "\t" .
strftime("%Y-%m-%d %H:%M:%S", $aData['time']) . "\t");
if ( $block->addBlock($aData) ) {
verbose("Added\n");
} else {
verbose("Failed" . "\n");
// Let us add those blocks as unaccounted
foreach ($aTransactions['transactions'] as $iIndex => $aData) {
if ( $aData['category'] == 'generate' || $aData['category'] == 'immature' ) {
$aBlockInfo = $bitcoin->query('getblock', $aData['blockhash']);
$aData['height'] = $aBlockInfo['height'];
$aData['difficulty'] = $aBlockInfo['difficulty'];
verbose(substr($aData['blockhash'], 0, 15) . "...\t" .
$aData['height'] . "\t" .
$aData['amount'] . "\t" .
$aData['confirmations'] . "\t\t" .
$aData['difficulty'] . "\t" .
strftime("%Y-%m-%d %H:%M:%S", $aData['time']) . "\t");
if ( $block->addBlock($aData) ) {
verbose("Added\n");
} else {
verbose("Failed" . "\n");
}
}
}
}
// Now with our blocks added we can scan for their upstream shares
$aAllBlocks = $block->getAllUnaccounted('ASC');
// Loop through our unaccounted blocks
verbose("Block ID\tBlock Height\tShare ID\tFinder\t\t\tStatus\n");
foreach ($aAllBlocks as $iIndex => $aBlock) {
if (empty($aBlock['share_id'])) {
// Fetch this blocks upstream ID
if ($share->setUpstream($block->getLastUpstreamId())) {
$iCurrentUpstreamId = $share->getUpstreamId();
$iAccountId = $user->getUserId($share->getUpstreamFinder());
} else {
verbose("Unable to fetch blocks upstream share\n");
verbose($share->getError() . "\n");
continue;
}
// Store new information
$strStatus = "OK";
if (!$block->setShareId($aBlock['id'], $iCurrentUpstreamId))
$strStatus = "Share ID Failed";
if (!$block->setFinder($aBlock['id'], $iAccountId))
$strStatus = "Finder Failed";
verbose(
$aBlock['id'] . "\t\t"
. $aBlock['height'] . "\t\t"
. $iCurrentUpstreamId . "\t\t"
. "[$iAccountId] " . $user->getUserName($iAccountId) . "\t\t"
. $strStatus
. "\n"
);
}
}
?>

View File

@ -32,33 +32,30 @@ if (empty($aAllBlocks)) {
$count = 0;
foreach ($aAllBlocks as $iIndex => $aBlock) {
if (!$aBlock['accounted']) {
if ($share->setUpstream(@$aAllBlocks[$iIndex - 1]['time'])) {
$share->setLastUpstreamId();
}
$iPreviousShareId = $aAllBlocks[$iIndex - 1]['share_id'] ? $aAllBlocks[$iIndex - 1]['share_id'] : 0;
$iCurrentUpstreamId = $aBlock['share_id'];
$aAccountShares = $share->getSharesForAccounts($iPreviousShareId, $aBlock['share_id']);
$iRoundShares = $share->getRoundShares($iPreviousShareId, $aBlock['share_id']);
if ($share->setUpstream($aBlock['time'])) {
$iCurrentUpstreamId = $share->getUpstreamId();
} else {
verbose("Unable to fetch blocks upstream share\n");
verbose($share->getError() . "\n");
continue;
}
$aAccountShares = $share->getSharesForAccounts($share->getLastUpstreamId(), $iCurrentUpstreamId);
$iRoundShares = $share->getRoundShares($share->getLastUpstreamId(), $iCurrentUpstreamId);
// Table header for block details
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" . $user->getUserName($aBlock['account_id']) . "\t" . $iCurrentUpstreamId . "\t\t" . $iPreviousShareId);
if (empty($aAccountShares)) {
verbose("\nNo shares found for this block\n\n");
sleep(2);
continue;
}
$strStatus = "OK";
if (!$block->setFinder($aBlock['id'], $user->getUserId($share->getUpstreamFinder())))
$strStatus = "Finder Failed";
// Store share information for this block
if (!$block->setShares($aBlock['id'], $iRoundShares))
$strStatus = "Shares Failed";
verbose("\t\t$strStatus\n\n");
// Table header for account shares
verbose("ID\tUsername\tValid\tInvalid\tPercentage\tPayout\t\tDonation\tFee\t\tStatus\n");
// Loop through all accounts that have found shares for this round
foreach ($aAccountShares as $key => $aData) {
// Payout based on shares, PPS system
$aData['percentage'] = number_format(round(( 100 / $iRoundShares ) * $aData['valid'], 8), 8);
@ -99,10 +96,19 @@ foreach ($aAllBlocks as $iIndex => $aBlock) {
$strStatus = "Donation Failed";
verbose("\t$strStatus\n");
}
verbose("------------------------------------------------------------------------\n\n");
// Move counted shares to archive before this blockhash upstream share
$share->moveArchive($share->getLastUpstreamId(), $iCurrentUpstreamId, $aBlock['id']);
$block->setAccounted($aBlock['id']);
if ($config['archive_shares']) $share->moveArchive($iCurrentUpstreamId, $aBlock['id'], $iPreviousShareId);
// Delete all accounted shares
if (!$share->deleteAccountedShares($iCurrentUpstreamId, $iPreviousShareId)) {
verbose("\nERROR : Failed to delete accounted shares from $iPreviousShareId to $iCurrentUpstreamId, aborting!\n");
exit(1);
}
// Mark this block as accounted for
if (!$block->setAccounted($aBlock['id'])) {
verbose("\nERROR : Failed to mark block as accounted! Aborting!\n");
}
verbose("------------------------------------------------------------------------\n\n");
}
}

74
cronjobs/run-crons.sh Executable file
View File

@ -0,0 +1,74 @@
#!/bin/bash
#########################
# #
# Configuration Options #
# #
#########################
# PHP Detections, if this fails hard code it
PHP_BIN=$( which php )
# Path to PID file, needs to be writable by user running this
PIDFILE='/tmp/mmcfe-ng-cron.pid'
# Location of our cronjobs, assume current directory
CRONHOME='.'
# List of cruns to execute
CRONS="findblock.php proportional_payout.php blockupdate.php auto_payout.php tickerupdate.php"
# Additional arguments to pass to cronjobs
CRONARGS="-v"
# Output additional runtime information
VERBOSE="0"
################################################################
# #
# You probably don't need to change anything beyond this point #
# #
################################################################
# Change working director to CRONHOME
if ! cd $CRONHOME 2>/dev/null; then
echo "Unable to change to working directory \$CRONHOME: $CRONHOME"
exit 1
fi
# Confiuration checks
if [[ -z $PHP_BIN || ! -x $PHP_BIN ]]; then
echo "Unable to locate you php binary."
exit 1
fi
if [[ ! -e 'shared.inc.php' ]]; then
echo "Not in cronjobs folder, please ensure \$CRONHOME is set!"
exit 1
fi
# Our PID of this shell
PID=$$
if [[ -e $PIDFILE ]]; then
echo "Cron seems to be running already"
RUNPID=$( cat $PIDFILE )
if ps fax | grep -q "^\<$RUNPID\>"; then
echo "Process found in process table, aborting"
exit 1
else
echo "Process $RUNPID not found. Plese remove $PIDFILE if process is indeed dead."
exit 1
fi
fi
# Write our PID file
echo $PID > $PIDFILE
for cron in $CRONS; do
[[ $VERBOSE == 1 ]] && echo "Running $cron, see output below for details"
$PHP_BIN $cron $CRONARGS
done
# Remove pidfile
rm -f $PIDFILE

36
cronjobs/tickerupdate.php Executable file
View File

@ -0,0 +1,36 @@
#!/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');
// Include additional file not set in autoloader
require_once(BASEPATH . CLASS_DIR . '/tools.class.php');
verbose("Running updates\n");
if ($aData = $tools->getApi($config['price']['url'], $config['price']['target'])) {
if (!$setting->setValue('price', $aData['ticker']['last']))
verbose("ERR Table update failed");
} else {
verbose("ERR Failed download JSON data from " . $config['price']['url'].$config['price']['target'] . "\n");
}
?>

View File

@ -3,6 +3,7 @@
require_once(CLASS_DIR . '/debug.class.php');
require_once(CLASS_DIR . '/bitcoin.class.php');
require_once(CLASS_DIR . '/statscache.class.php');
require_once(CLASS_DIR . '/bitcoinwrapper.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,4 +13,4 @@ require_once(CLASS_DIR . '/share.class.php');
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');
require_once(CLASS_DIR . '/setting.class.php');

View File

@ -898,6 +898,3 @@ class BitcoinClient extends jsonrpc_client {
return $this->query("getaddressesbyaccount", $account);
}
}
// Auto-load this class
$bitcoin = new BitcoinClient($config['wallet']['type'], $config['wallet']['username'], $config['wallet']['password'], $config['wallet']['host']);

View File

@ -0,0 +1,45 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
/**
* We use a wrapper class around BitcoinClient to add
* some basic caching functionality and some debugging
**/
class BitcoinWrapper extends BitcoinClient {
public function __construct($type, $username, $password, $host, $debug, $memcache) {
$this->type = $type;
$this->username = $username;
$this->password = $password;
$this->host = $host;
// $this->debug is already used
$this->oDebug = $debug;
$this->memcache = $memcache;
return parent::__construct($this->type, $this->username, $this->password, $this->host);
}
/**
* Wrap variouns methods to add caching
**/
public function getblockcount() {
$this->oDebug->append("STA " . __METHOD__, 4);
if ($data = $this->memcache->get(__FUNCTION__)) return $data;
return $this->memcache->setCache(__FUNCTION__, parent::getblockcount());
}
public function getdifficulty() {
$this->oDebug->append("STA " . __METHOD__, 4);
if ($data = $this->memcache->get(__FUNCTION__)) return $data;
return $this->memcache->setCache(__FUNCTION__, parent::getdifficulty());
}
public function getestimatedtime($iCurrentPoolHashrate) {
$this->oDebug->append("STA " . __METHOD__, 4);
if ($iCurrentPoolHashrate == 0) return 0;
if ($data = $this->memcache->get(__FUNCTION__)) return $data;
$dDifficulty = parent::getdifficulty();
return $this->memcache->setCache(__FUNCTION__, $dDifficulty * pow(2,32) / $iCurrentPoolHashrate);
}
}
// Load this wrapper
$bitcoin = new BitcoinWrapper($config['wallet']['type'], $config['wallet']['username'], $config['wallet']['password'], $config['wallet']['host'], $debug, $memcache);

View File

@ -27,17 +27,27 @@ class Block {
return $this->table;
}
/**
* Specific method to fetch the latest block found
* @param none
* @return data array Array with database fields as keys
**/
public function getLast() {
$stmt = $this->mysqli->prepare("SELECT * FROM $this->table ORDER BY height DESC LIMIT 1");
if ($this->checkStmt($stmt)) {
$stmt->execute();
$result = $stmt->get_result();
$stmt->close();
return $result->fetch_object();
return $result->fetch_assoc();
}
return false;
}
/**
* Fetch all unaccounted blocks
* @param order string Sort order, default ASC
* @return data array Array with database fields as keys
**/
public function getAllUnaccounted($order='ASC') {
$stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE accounted = 0 ORDER BY height $order");
if ($this->checkStmt($stmt)) {
@ -49,8 +59,13 @@ class Block {
return false;
}
/**
* Fetch all unconfirmed blocks from table
* @param confirmations int Required confirmations to consider block confirmed
* @return data array Array with database fields as keys
**/
public function getAllUnconfirmed($confirmations='120') {
$stmt = $this->mysqli->prepare("SELECT id, blockhash, confirmations FROM $this->table WHERE confirmations < ?");
$stmt = $this->mysqli->prepare("SELECT id, blockhash, confirmations FROM $this->table WHERE confirmations < ? AND confirmations > -1");
if ($this->checkStmt($stmt)) {
$stmt->bind_param("i", $confirmations);
$stmt->execute();
@ -61,6 +76,12 @@ class Block {
return false;
}
/**
* Update confirmations for an existing block
* @param block_id int Block ID to update
* @param confirmations int New confirmations value
* @return bool
**/
public function setConfirmations($block_id, $confirmations) {
$stmt = $this->mysqli->prepare("UPDATE $this->table SET confirmations = ? WHERE id = ?");
if ($this->checkStmt($stmt)) {
@ -72,6 +93,11 @@ class Block {
return false;
}
/**
* Fetch all blocks ordered by DESC height
* @param order string ASC or DESC ordering
* @return data array Array with database fields as keys
**/
public function getAll($order='DESC') {
$stmt = $this->mysqli->prepare("SELECT * FROM $this->table ORDER BY height $order");
if ($this->checkStmt($stmt)) {
@ -83,6 +109,11 @@ class Block {
return false;
}
/**
* Add new new block to the database
* @param block array Block data as an array, see bind_param
* @return bool
**/
public function addBlock($block) {
$stmt = $this->mysqli->prepare("INSERT INTO $this->table (height, blockhash, confirmations, amount, difficulty, time) VALUES (?, ?, ?, ?, ?, ?)");
if ($this->checkStmt($stmt)) {
@ -99,6 +130,23 @@ class Block {
return false;
}
public function getLastUpstreamId() {
$stmt = $this->mysqli->prepare("
SELECT MAX(share_id) AS share_id FROM $this->table
");
if ($this->checkStmt($stmt) && $stmt->execute() && $stmt->bind_result($share_id) && $stmt->fetch())
return $share_id ? $share_id : 0;
// Catchall
return false;
}
/**
* Update a single column within a single row
* @param block_id int Block ID to update
* @param field string Column name to update
* @param value string Value to insert
* @return bool
**/
private function updateSingle($block_id, $field, $value) {
$stmt = $this->mysqli->prepare("UPDATE $this->table SET $field = ? WHERE id = ?");
if ($this->checkStmt($stmt)) {
@ -114,19 +162,49 @@ class Block {
return false;
}
/**
* Set finder of a block
* @param block_id int Block ID
* @param account_id int Account ID of finder
* @return bool
**/
public function setFinder($block_id, $account_id=NULL) {
return $this->updateSingle($block_id, 'account_id', $account_id);
}
/**
* Set finding share for a block
* @param block_id int Block ID
* @param share_id int Upstream valid share ID
* @return bool
**/
public function setShareId($block_id, $share_id) {
return $this->updateSingle($block_id, 'share_id', $share_id);
}
/**
* Set counted shares for a block
* @param block_id int Block ID
* @param shares int Share count
* @return bool
**/
public function setShares($block_id, $shares=NULL) {
return $this->updateSingle($block_id, 'shares', $shares);
}
/**
* Set block to be accounted for
* @param block_id int Block ID
* @return bool
**/
public function setAccounted($block_id=NULL) {
if (empty($block_id)) return false;
return $this->updateSingle($block_id, 'accounted', 1);
}
/**
* Helper function
**/
private function checkStmt($bState) {
if ($bState ===! true) {
$this->debug->append("Failed to prepare statement: " . $this->mysqli->error);
@ -137,4 +215,5 @@ class Block {
}
}
// Automatically load our class for furhter usage
$block = new Block($debug, $mysqli, SALT);

View File

@ -0,0 +1,55 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
class Setting {
public function __construct($debug, $mysqli, $salt) {
$this->debug = $debug;
$this->mysqli = $mysqli;
$this->salt = $salt;
$this->table = 'settings';
}
/**
* Fetch a value from our table
* @param name string Setting name
* @return value string Value
**/
public function getValue($name) {
$query = $this->mysqli->prepare("SELECT value FROM $this->table WHERE name=? LIMIT 1");
if ($query) {
$query->bind_param('s', $name);
$query->execute();
$query->bind_result($value);
$query->fetch();
$query->close();
} else {
$this->debug->append("Failed to fetch variable $name from $this->table");
return false;
}
return $value;
}
/**
* Insert or update a setting
* @param name string Name of the variable
* @param value string Variable value
* @return bool
**/
public function setValue($name, $value) {
$stmt = $this->mysqli->prepare("
INSERT INTO $this->table (name, value)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE value = ?
");
if ($stmt && $stmt->bind_param('sss', $name, $value, $value) && $stmt->execute())
return true;
$this->debug->append("Failed to set $name to $value");
echo $this->mysqli->error;
return false;
}
}
$setting = new Setting($debug, $mysqli, SALT);

View File

@ -1,31 +0,0 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
class Settings {
public function __construct($debug, $mysqli, $salt) {
$this->debug = $debug;
$this->mysqli = $mysqli;
$this->salt = $salt;
$this->table = 'settings';
}
public function getValue($name) {
$query = $this->mysqli->prepare("SELECT value FROM $this->table WHERE setting=? LIMIT 1");
if ($query) {
$query->bind_param('s', $name);
$query->execute();
$query->bind_result($value);
$query->fetch();
$query->close();
} else {
$this->debug->append("Failed to fetch variable $name from $this->table");
return false;
}
return $value;
}
}
$settings = new Settings($debug, $mysqli, SALT);

View File

@ -27,13 +27,29 @@ class Share {
return $this->sError;
}
/**
* Fetch archive tables name for this class
* @param none
* @return data string Table name
**/
public function getArchiveTableName() {
return $this->tableArchive;
}
/**
* Fetch normal table name for this class
* @param none
* @return data string Table name
**/
public function getTableName() {
return $this->table;
}
/**
* Get all valid shares for this round
* @param previous_upstream int Previous found share accepted by upstream to limit results
* @param current_upstream int Current upstream accepted share
* @return data int Total amount of counted shares
**/
public function getRoundShares($previous_upstream=0, $current_upstream) {
$stmt = $this->mysqli->prepare("SELECT
count(id) as total
@ -51,6 +67,12 @@ class Share {
return false;
}
/**
* Fetch all shares grouped by accounts to count share per account
* @param previous_upstream int Previous found share accepted by upstream to limit results
* @param current_upstream int Current upstream accepted share
* @return data array username, valid and invalid shares from account
**/
public function getSharesForAccounts($previous_upstream=0, $current_upstream) {
$stmt = $this->mysqli->prepare("SELECT
a.id,
@ -90,29 +112,41 @@ class Share {
return false;
}
public function moveArchive($previous_upstream=0, $current_upstream,$block_id) {
/**
* Move accounted shares to archive table, this step is optional
* @param previous_upstream int Previous found share accepted by upstream to limit results
* @param current_upstream int Current upstream accepted share
* @param block_id int Block ID to assign shares to a specific block
* @return bool
**/
public function moveArchive($current_upstream, $block_id, $previous_upstream=0) {
$archive_stmt = $this->mysqli->prepare("INSERT INTO $this->tableArchive (share_id, username, our_result, upstream_result, block_id, time)
SELECT id, username, our_result, upstream_result, ?, time
FROM $this->table
WHERE id BETWEEN ? AND ?");
$delete_stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE id BETWEEN ? AND ?");
if ($this->checkStmt($archive_stmt) && $this->checkStmt($delete_stmt)) {
$archive_stmt->bind_param('iii', $block_id, $previous_upstream, $current_upstream);
$archive_stmt->execute();
$delete_stmt->bind_param('ii', $previous_upstream, $current_upstream);
$delete_stmt->execute();
$delete_stmt->close();
if ($this->checkStmt($archive_stmt) && $archive_stmt->bind_param('iii', $block_id, $previous_upstream, $current_upstream) && $archive_stmt->execute()) {
$archive_stmt->close();
return true;
}
// Catchall
return false;
}
public function deleteAccountedShares($current_upstream, $previous_upstream=0) {
$stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE id BETWEEN ? AND ?");
if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $previous_upstream, $current_upstream) && $stmt->execute())
return true;
// Catchall
return false;
}
/**
* Set/get last found share accepted by upstream: id and accounts
**/
public function setLastUpstreamId() {
$this->iLastUpstreamId = @$this->oUpstream->id ? $this->oUpstream->id : 0;
}
public function getLastUpstreamId() {
return @$this->iLastUpstreamId;
return @$this->iLastUpstreamId ? @$this->iLastUpstreamId : 0;
}
public function getUpstreamFinder() {
return @$this->oUpstream->account;
@ -120,27 +154,33 @@ class Share {
public function getUpstreamId() {
return @$this->oUpstream->id;
}
public function setUpstream($time='') {
$stmt = $this->mysqli->prepare("SELECT
/**
* Find upstream accepted share that should be valid for a specific block
* Assumptions:
* * Shares are matching blocks in ASC order
* * We can skip all upstream shares of previously found ones used in a block
* @param last int Skips all shares up to last to find new share
* @return bool
**/
public function setUpstream($last=0) {
$stmt = $this->mysqli->prepare("
SELECT
SUBSTRING_INDEX( `username` , '.', 1 ) AS account, id
FROM $this->table
WHERE upstream_result = 'Y'
AND UNIX_TIMESTAMP(time) BETWEEN ? AND (? + 1)
AND id > ?
ORDER BY id ASC LIMIT 1");
if ($this->checkStmt($stmt)) {
$stmt->bind_param('ii', $time, $time);
$stmt->execute();
if (! $result = $stmt->get_result()) {
$this->setErrorMessage("No result returned from query");
$stmt->close();
}
$stmt->close();
if ($this->checkStmt($stmt) && $stmt->bind_param('i', $last) && $stmt->execute() && $result = $stmt->get_result()) {
$this->oUpstream = $result->fetch_object();
return true;
}
// Catchall
return false;
}
/**
* Helper function
**/
private function checkStmt($bState) {
if ($bState ===! true) {
$this->debug->append("Failed to prepare statement: " . $this->mysqli->error);

View File

@ -43,25 +43,13 @@ 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) {
$this->debug->append("STA " . __METHOD__, 4);
if ($data = $this->memcache->get(__FUNCTION__ . $limit)) return $data;
$stmt = $this->mysqli->prepare("
SELECT b.*, a.username as finder
@ -70,7 +58,7 @@ 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 $this->setCache(__FUNCTION__ . $limit, $result->fetch_all(MYSQLI_ASSOC), 5);
return $this->memcache->setCache(__FUNCTION__ . $limit, $result->fetch_all(MYSQLI_ASSOC), 5);
// Catchall
$this->debug->append("Failed to find blocks:" . $this->mysqli->error);
return false;
@ -84,6 +72,7 @@ class Statistics {
* @return bool
**/
public function updateShareStatistics($aStats, $iBlockId) {
$this->debug->append("STA " . __METHOD__, 4);
$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;
// Catchall
@ -98,6 +87,7 @@ class Statistics {
* @return data object Return our hashrateas an object
**/
public function getCurrentHashrate() {
$this->debug->append("STA " . __METHOD__, 4);
if ($data = $this->memcache->get(__FUNCTION__)) return $data;
$stmt = $this->mysqli->prepare("
SELECT SUM(hashrate) AS hashrate FROM
@ -107,7 +97,7 @@ 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 $this->setCache(__FUNCTION__, $result->fetch_object()->hashrate);
if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result() ) return $this->memcache->setCache(__FUNCTION__, $result->fetch_object()->hashrate);
$this->debug->append("Failed to get hashrate: " . $this->mysqli->error);
return false;
}
@ -118,6 +108,7 @@ class Statistics {
* @return data object Our share rate in shares per second
**/
public function getCurrentShareRate() {
$this->debug->append("STA " . __METHOD__, 4);
if ($data = $this->memcache->get(__FUNCTION__)) return $data;
$stmt = $this->mysqli->prepare("
SELECT ROUND(SUM(sharerate) / 600, 2) AS sharerate FROM
@ -126,7 +117,7 @@ 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 $this->setCache(__FUNCTION__, $result->fetch_object()->sharerate);
if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result() ) return $this->memcache->setCache(__FUNCTION__, $result->fetch_object()->sharerate);
// Catchall
$this->debug->append("Failed to fetch share rate: " . $this->mysqli->error);
return false;
@ -138,6 +129,7 @@ class Statistics {
* @return data array invalid and valid shares
**/
public function getRoundShares() {
$this->debug->append("STA " . __METHOD__, 4);
if ($data = $this->memcache->get(__FUNCTION__)) return $data;
$stmt = $this->mysqli->prepare("
SELECT
@ -150,7 +142,7 @@ class Statistics {
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 $this->setCache(__FUNCTION__, $result->fetch_assoc());
return $this->memcache->setCache(__FUNCTION__, $result->fetch_assoc());
// Catchall
$this->debug->append("Failed to fetch round shares: " . $this->mysqli->error);
return false;
@ -162,6 +154,7 @@ class Statistics {
* @return data array invalid and valid share counts
**/
public function getUserShares($account_id) {
$this->debug->append("STA " . __METHOD__, 4);
if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
$stmt = $this->mysqli->prepare("
SELECT
@ -184,7 +177,7 @@ class Statistics {
AND u.id = ?
) AS invalid");
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());
return $this->memcache->setCache(__FUNCTION__ . $account_id, $result->fetch_assoc());
// Catchall
$this->debug->append("Unable to fetch user round shares: " . $this->mysqli->error);
return false;
@ -205,7 +198,7 @@ class Statistics {
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 $this->setCache(__FUNCTION__ . $account_id, $result->fetch_object()->hashrate);
return $this->memcache->setCache(__FUNCTION__ . $account_id, $result->fetch_object()->hashrate);
// Catchall
$this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error);
return false;
@ -217,6 +210,7 @@ class Statistics {
* @return data int Current hashrate in khash/s
**/
public function getWorkerHashrate($worker_id) {
$this->debug->append("STA " . __METHOD__, 4);
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
@ -226,7 +220,7 @@ class Statistics {
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 $this->setCache(__FUNCTION__ . $worker_id, $result->fetch_object()->hashrate);
return $this->memcache->setCache(__FUNCTION__ . $worker_id, $result->fetch_object()->hashrate);
// Catchall
$this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error);
return false;
@ -239,6 +233,7 @@ class Statistics {
* @return data array Users with shares, account or hashrate, account
**/
public function getTopContributors($type='shares', $limit=15) {
$this->debug->append("STA " . __METHOD__, 4);
if ($data = $this->memcache->get(__FUNCTION__ . $type . $limit)) return $data;
switch ($type) {
case 'shares':
@ -251,7 +246,7 @@ class Statistics {
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));
return $this->memcache->setCache(__FUNCTION__ . $type . $limit, $result->fetch_all(MYSQLI_ASSOC));
$this->debug->append("Fetching shares failed: ");
return false;
break;
@ -266,7 +261,7 @@ class Statistics {
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));
return $this->memcache->setCache(__FUNCTION__ . $type . $limit, $result->fetch_all(MYSQLI_ASSOC));
$this->debug->append("Fetching shares failed: ");
return false;
break;
@ -280,6 +275,7 @@ class Statistics {
* @return data array NOT FINISHED YET
**/
public function getHourlyHashrateByAccount($account_id) {
$this->debug->append("STA " . __METHOD__, 4);
if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
$stmt = $this->mysqli->prepare("
SELECT
@ -300,7 +296,7 @@ class Statistics {
AND a.id = ?
GROUP BY HOUR(time)");
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);
return $this->memcache->setCache(__FUNCTION__ . $account_id, $result->fetch_all(MYSQLI_ASSOC), 3600);
// Catchall
$this->debug->append("Failed to fetch hourly hashrate: " . $this->mysqli->error);
return false;

View File

@ -43,6 +43,19 @@ class StatsCache extends Memcached {
$this->debug->append("Key not found", 3);
}
}
/**
* 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->set($key, $data, $expiration);
return $data;
}
}
$memcache = new StatsCache($config, $debug);

View File

@ -0,0 +1,46 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
/**
* Helper class for our cronjobs
* Implements some common cron tasks outside
* the scope of our web application
**/
class Tools {
public function __construct($debug) {
$this->debug = $debug;
}
/**
* Fetch JSON data from an API
* @param url string API URL
* @param target string API method
* @param auth array Optional authentication data to be sent with
* @return dec array JSON decoded PHP array
**/
public function getApi($url, $target, $auth=NULL) {
static $ch = null;
static $ch = null;
if (is_null($ch)) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; BTCE PHP client; '.php_uname('s').'; PHP/'.phpversion().')');
}
curl_setopt($ch, CURLOPT_URL, $url . $target);
// curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
// run the query
$res = curl_exec($ch);
if ($res === false) throw new Exception('Could not get reply: '.curl_error($ch));
$dec = json_decode($res, true);
if (!$dec) throw new Exception('Invalid data received, please make sure connection is working and requested API exists');
return $dec;
}
}
$tools = new Tools($debug);

View File

@ -25,6 +25,15 @@ class Transaction {
return $this->sError;
}
/**
* Add a new transaction to our class table
* @param account_id int Account ID to book transaction for
* @param amount float Coin amount
* @param type string Transaction type [Credit, Debit_AP, Debit_MP, Fee, Donation, Orphan_Credit, Orphan_Fee, Orphan_Donation]
* @param block_id int Block ID to link transaction to [optional]
* @param coin_address string Coin address for this transaction [optional]
* @return bool
**/
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)) {
@ -38,10 +47,55 @@ class Transaction {
return false;
}
public function addDebit($account_id, $amount, $type='AP') {
/**
* Sometimes transactions become orphans when a block associated to them is orphaned
* Updates the transaction types to Orphan_<type>
* @param block_id int Orphaned block ID
* @return bool
**/
public function setOrphan($block_id) {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("
UPDATE $this->table
SET type = 'Orphan_Credit'
WHERE type = 'Credit'
AND block_id = ?
");
if (!($this->checkStmt($stmt) && $stmt->bind_param('i', $block_id) && $stmt->execute())) {
$this->debug->append("Failed to set orphan credit transactions for $block_id");
return false;
}
$stmt = $this->mysqli->prepare("
UPDATE $this->table
SET type = 'Orphan_Fee'
WHERE type = 'Fee'
AND block_id = ?
");
if (!($this->checkStmt($stmt) && $stmt->bind_param('i', $block_id) && $stmt->execute())) {
$this->debug->append("Failed to set orphan fee transactions for $block_id");
return false;
}
$stmt = $this->mysqli->prepare("
UPDATE $this->table
SET type = 'Orphan_Donation'
WHERE type = 'Donation'
AND block_id = ?
");
if (!($this->checkStmt($stmt) && $stmt->bind_param('i', $block_id) && $stmt->execute())) {
$this->debug->append("Failed to set orphan donation transactions for $block_id");
return false;
}
return true;
}
/**
* Get all transactions from start for account_id
* @param account_id int Account ID
* @param start int Starting point, id of transaction
* @return data array Database fields as defined in SELECT
**/
public function getTransactions($account_id, $start=0) {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("
SELECT
t.id AS id,
@ -74,7 +128,50 @@ class Transaction {
return true;
}
/**
* Get total balance for all users locked in wallet
* @param none
* @return data double Amount locked for users
**/
public function getLockedBalance() {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("
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->block->getTableName() . " AS b ON t.block_id = b.id
WHERE t.type = 'Credit'
AND b.confirmations >= ?
) AS t1,
(
SELECT sum(t.amount) AS debit
FROM $this->table AS t
WHERE t.type IN ('Debit_MP', 'Debit_AP')
) AS t2,
(
SELECT sum(t.amount) AS other
FROM transactions AS t
LEFT JOIN " . $this->block->getTableName() . " AS b ON t.block_id = b.id
WHERE t.type IN ('Donation','Fee')
AND b.confirmations >= ?
) AS t3");
if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $this->config['confirmations'], $this->config['confirmations']) && $stmt->execute() && $stmt->bind_result($dBalance) && $stmt->fetch())
return $dBalance;
// Catchall
$this->setErrorMessage('Unable to find locked credits for all users');
$this->debug->append('MySQL query failed : ' . $this->mysqli->error);
return false;
}
/**
* Get an accounts total balance
* @param account_id int Account ID
* @return data float Credit - Debit - Fees - Donation
**/
public function getBalance($account_id) {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("
SELECT ROUND(IFNULL(t1.credit, 0) - IFNULL(t2.debit, 0) - IFNULL(t3.other, 0), 8) AS balance
FROM

View File

@ -11,10 +11,11 @@ class User {
private $user = array();
private $tableAccountBalance = 'accountBalance';
public function __construct($debug, $mysqli, $salt) {
public function __construct($debug, $mysqli, $salt, $config) {
$this->debug = $debug;
$this->mysqli = $mysqli;
$this->salt = $salt;
$this->config = $config;
$this->debug->append("Instantiated User class", 2);
}
@ -34,6 +35,27 @@ class User {
return $this->getSingle($username, 'id', 'username', 's');
}
public function getUserEmail($username) {
return $this->getSingle($username, 'email', 'username', 's');
}
public function getUserToken($id) {
return $this->getSingle($id, 'token', 'id');
}
public function getIdFromToken($token) {
return $this->getSingle($token, 'id', 'token', 's');
}
public function setUserToken($id) {
$field = array(
'name' => 'token',
'type' => 's',
'value' => hash('sha256', $id.time().$this->salt)
);
return $this->updateSingle($id, $field);
}
/**
* Check user login
* @param username string Username
@ -41,6 +63,7 @@ class User {
* @return bool
**/
public function checkLogin($username, $password) {
$this->debug->append("STA " . __METHOD__, 4);
$this->debug->append("Checking login for $username with password $password", 2);
if ( $this->checkUserPassword($username, $password) ) {
$this->createSession($username);
@ -56,6 +79,7 @@ class User {
* @return bool
**/
public function checkPin($userId, $pin=false) {
$this->debug->append("STA " . __METHOD__, 4);
$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");
$pin_hash = hash('sha256', $pin.$this->salt);
@ -76,6 +100,7 @@ class User {
* @return array Return result
**/
private function getSingle($value, $search='id', $field='id', $type="i") {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("SELECT $search FROM $this->table WHERE $field = ? LIMIT 1");
if ($this->checkStmt($stmt)) {
$stmt->bind_param($type, $value);
@ -94,6 +119,7 @@ class User {
* @return data array All users with payout setup
**/
public function getAllAutoPayout() {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("
SELECT
id, username, coin_address, ap_threshold
@ -115,6 +141,7 @@ class User {
* @return data string Coin Address
**/
public function getCoinAddress($userID) {
$this->debug->append("STA " . __METHOD__, 4);
return $this->getSingle($userID, 'coin_address', 'id');
}
@ -124,6 +151,7 @@ class User {
* @return data string Coin Address
**/
public function getDonatePercent($userID) {
$this->debug->append("STA " . __METHOD__, 4);
$dPercent = $this->getSingle($userID, 'donate_percent', 'id');
if ($dPercent > 100) $dPercent = 100;
if ($dPercent < 0) $dPercent = 0;
@ -136,18 +164,17 @@ class User {
* @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();
$stmt->close();
private function updateSingle($id, $field) {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("UPDATE $this->table SET " . $field['name'] . " = ? WHERE id = ? LIMIT 1");
if ($this->checkStmt($stmt) && $stmt->bind_param($field['type'].'i', $field['value'], $id) && $stmt->execute())
return true;
}
$this->debug->append("Unable to update " . $field['name'] . " with " . $field['value'] . " for ID $id");
return false;
}
private function checkStmt($bState) {
$this->debug->append("STA " . __METHOD__, 4);
if ($bState ===! true) {
$this->debug->append("Failed to prepare statement: " . $this->mysqli->error);
$this->setErrorMessage('Internal application Error');
@ -156,7 +183,16 @@ class User {
return true;
}
/**
* Update the accounts password
* @param userID int User ID
* @param current string Current password
* @param new1 string New password
* @param new2 string New password confirmation
* @return bool
**/
public function updatePassword($userID, $current, $new1, $new2) {
$this->debug->append("STA " . __METHOD__, 4);
if ($new1 !== $new2) {
$this->setErrorMessage( 'New passwords do not match' );
return false;
@ -180,7 +216,16 @@ class User {
return false;
}
/**
* Update account information from the edit account page
* @param userID int User ID
* @param address string new coin address
* @param threshold float auto payout threshold
* @param donat float donation % of income
* @return bool
**/
public function updateAccount($userID, $address, $threshold, $donate) {
$this->debug->append("STA " . __METHOD__, 4);
$bUser = false;
$threshold = min(250, max(0, floatval($threshold)));
if ($threshold < 1) $threshold = 0.0;
@ -196,7 +241,30 @@ class User {
return false;
}
/**
* Check API key for authentication
* @param key string API key hash
* @return bool
**/
public function checkApiKey($key) {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("SELECT api_key, id FROM $this->table WHERE api_key = ? LIMIT 1");
if ($this->checkStmt($stmt) && $stmt->bind_param("s", $key) && $stmt->execute() && $stmt->bind_result($api_key, $id) && $stmt->fetch()) {
if ($api_key === $key)
return $id;
}
header("HTTP/1.1 401 Unauthorized");
die('Access denied');
}
/**
* Check a password for a user
* @param username string Username
* @param password string Password
* @return bool
**/
private function checkUserPassword($username, $password) {
$this->debug->append("STA " . __METHOD__, 4);
$user = array();
$stmt = $this->mysqli->prepare("SELECT username, id FROM $this->table WHERE username=? AND pass=? LIMIT 1");
if ($this->checkStmt($stmt)) {
@ -212,7 +280,13 @@ class User {
return false;
}
/**
* Create a PHP session for a user
* @param username string Username to create session for
* @return none
**/
private function createSession($username) {
$this->debug->append("STA " . __METHOD__, 4);
$this->debug->append("Log in user to _SESSION", 2);
session_regenerate_id(true);
$_SESSION['AUTHENTICATED'] = '1';
@ -220,25 +294,41 @@ class User {
$_SESSION['USERDATA'] = $this->user;
}
/**
* Log out current user, destroy the session
* @param none
* @return true
**/
public function logoutUser() {
$this->debug->append("STA " . __METHOD__, 4);
session_destroy();
session_regenerate_id(true);
return true;
}
/**
* Fetch this classes table name
* @return table string This classes table name
**/
public function getTableName() {
$this->debug->append("STA " . __METHOD__, 4);
return $this->table;
}
/**
* Fetch some basic user information to store for later user
* @param userID int User ID
* return data array Database fields as used in SELECT
**/
public function getUserData($userID) {
$this->debug->append("STA " . __METHOD__, 4);
$this->debug->append("Fetching user information for user id: $userID");
$stmt = $this->mysqli->prepare("
SELECT
id, username, pin, pass, admin,
id, username, pin, api_key, admin,
IFNULL(donate_percent, '0') as donate_percent, coin_address, ap_threshold
FROM $this->table
WHERE id = ? LIMIT 0,1");
echo $this->mysqli->error;
if ($this->checkStmt($stmt)) {
$stmt->bind_param('i', $userID);
if (!$stmt->execute()) {
@ -253,7 +343,18 @@ class User {
return false;
}
/**
* Register a new user in the system
* @param username string Username
* @param password1 string Password
* @param password2 string Password verification
* @param pin int 4 digit PIN code
* @param email1 string Email address
* @param email2 string Email confirmation
* @return bool
**/
public function register($username, $password1, $password2, $pin, $email1='', $email2='') {
$this->debug->append("STA " . __METHOD__, 4);
if (strlen($password1) < 8) {
$this->setErrorMessage( 'Password is too short, minimum of 8 characters required' );
return false;
@ -275,15 +376,23 @@ class User {
return false;
}
$apikey = hash("sha256",$username.$salt);
$stmt = $this->mysqli->prepare("
INSERT INTO $this->table (username, pass, email, pin, api_key)
VALUES (?, ?, ?, ?, ?)
");
if ($this->mysqli->query("SELECT id FROM $this->table LIMIT 1")->num_rows > 0) {
$stmt = $this->mysqli->prepare("
INSERT INTO $this->table (username, pass, email, pin, api_key)
VALUES (?, ?, ?, ?, ?)
");
} else {
$stmt = $this->mysqli->prepare("
INSERT INTO $this->table (username, pass, email, pin, api_key, admin)
VALUES (?, ?, ?, ?, ?, 1)
");
}
if ($this->checkStmt($stmt)) {
$stmt->bind_param('sssss', $username, hash("sha256", $password1.$this->salt), $email1, hash("sha256", $pin.$this->salt), $apikey);
if (!$stmt->execute()) {
$this->setErrorMessage( 'Unable to register' );
if ($stmt->sqlstate == '23000') $this->setErrorMessage( 'Username already exists' );
echo $this->mysqli->error;
return false;
}
$stmt->close();
@ -291,6 +400,77 @@ class User {
}
return false;
}
/**
* User a one time token to reset a password
* @param token string one time token
* @param new1 string New password
* @param new2 string New password verification
* @return bool
**/
public function useToken($token, $new1, $new2) {
$this->debug->append("STA " . __METHOD__, 4);
if ($id = $this->getIdFromToken($token)) {
if ($new1 !== $new2) {
$this->setErrorMessage( 'New passwords do not match' );
return false;
}
if ( strlen($new1) < 8 ) {
$this->setErrorMessage( 'New password is too short, please use more than 8 chars' );
return false;
}
$new = hash('sha256', $new1.$this->salt);
$stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ?, token = NULL WHERE id = ? AND token = ?");
if ($this->checkStmt($stmt) && $stmt->bind_param('sis', $new, $id, $token) && $stmt->execute() && $stmt->affected_rows === 1) {
return true;
}
} else {
$this->setErrorMessage("Unable find user for your token");
return false;
}
return false;
}
/**
* Reset a password by sending a password reset mail
* @param username string Username to reset password for
* @param smarty object Smarty object for mail templating
* @return bool
**/
public function resetPassword($username, $smarty) {
$this->debug->append("STA " . __METHOD__, 4);
// Fetch the users mail address
if (!$email = $this->getUserEmail($username)) {
$this->setErrorMessage("Unable to find a mail address for user $username");
return false;
}
if (!$this->setUserToken($this->getUserId($username))) {
$this->setErrorMessage("Unable to setup token for password reset");
return false;
}
// Send password reset link
if (!$token = $this->getUserToken($this->getUserId($username))) {
$this->setErrorMessage("Unable fetch token for password reset");
return false;
}
$smarty->assign('TOKEN', $token);
$smarty->assign('USERNAME', $username);
$smarty->assign('WEBSITENAME', $this->config['website']['name']);
$headers = 'From: Website Administration <' . $this->config['website']['email'] . ">\n";
$headers .= "MIME-Version: 1.0\n";
$headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n";
if (mail($email,
$smarty->fetch('templates/mail/subject.tpl'),
$smarty->fetch('templates/mail/body.tpl'),
$headers)) {
return true;
} else {
$this->setErrorMessage("Unable to send mail to your address");
return false;
}
return false;
}
}
$user = new User($debug, $mysqli, SALT);
// Make our class available automatically
$user = new User($debug, $mysqli, SALT, $config);

View File

@ -6,7 +6,7 @@ if (!defined('SECURITY'))
class Worker {
private $sError = '';
private $table = 'workers';
private $table = 'pool_worker';
public function __construct($debug, $mysqli, $user, $share, $config) {
$this->debug = $debug;
@ -41,6 +41,7 @@ class Worker {
* @return bool
**/
public function updateWorkers($account_id, $data) {
$this->debug->append("STA " . __METHOD__, 4);
$username = $this->user->getUserName($account_id);
foreach ($data as $key => $value) {
// Prefix the WebUser to Worker name
@ -61,6 +62,7 @@ class Worker {
* @return mixed array Workers and their settings or false
**/
public function getWorkers($account_id) {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("
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,
@ -81,6 +83,7 @@ class Worker {
* @return data mixed int count if any workers are active, false otherwise
**/
public function getCountAllActiveWorkers() {
$this->debug->append("STA " . __METHOD__, 4);
$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 (!$stmt->execute()) {
@ -103,6 +106,7 @@ class Worker {
* @return bool
**/
public function addWorker($account_id, $workerName, $workerPassword) {
$this->debug->append("STA " . __METHOD__, 4);
$username = $this->user->getUserName($account_id);
$workerName = "$username.$workerName";
$stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, username, password) VALUES(?, ?, ?)");
@ -125,6 +129,7 @@ class Worker {
* @return bool
**/
public function deleteWorker($account_id, $id) {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE account_id = ? AND id = ?");
if ($this->checkStmt($stmt)) {
$stmt->bind_param('ii', $account_id, $id);

View File

@ -21,12 +21,20 @@ define('DEBUG', 0);
define('SALT', 'PLEASEMAKEMESOMETHINGRANDOM');
$config = array(
'price' => array(
'url' => 'https://btc-e.com/api/2',
'target' => '/ltc_usd/ticker'
),
'website' => array(
'name' => 'The Pool',
'slogan' => 'Resistance is futile',
'email' => 'test@example.com', // Mail address used for notifications
),
'archive_shares' => true, // Store accounted shares in archive table?
'blockexplorer' => 'http://explorer.litecoin.net/search?q=', // URL for block searches, prefixed to each block number
'chaininfo' => 'http://allchains.info', // Link to Allchains for Difficulty information
'fees' => 0,
'difficulty' => '31', // Target difficulty for this pool as set in pushpoold json
'difficulty' => '20', // 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(

View File

@ -37,7 +37,7 @@ if ( ! $user->checkPin($_SESSION['USERDATA']['id'], $_POST['authPin']) && $_POST
if ($continue == true && $transaction->addTransaction($_SESSION['USERDATA']['id'], $dBalance, 'Debit_MP', NULL, $sCoinAddress))
$_SESSION['POPUP'][] = array('CONTENT' => 'Transaction completed', 'TYPE' => 'success');
} else {
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service', 'TYPE' => 'errormsg');
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to litecoind RPC service', 'TYPE' => 'errormsg');
}
} else {
$_SESSION['POPUP'][] = array('CONTENT' => 'Insufficient funds, you need more than 0.1 LTC to cover transaction fees', 'TYPE' => 'errormsg');
@ -61,6 +61,7 @@ if ( ! $user->checkPin($_SESSION['USERDATA']['id'], $_POST['authPin']) && $_POST
break;
}
}
// Tempalte specifics
$smarty->assign("CONTENT", "default.tpl");
?>

View File

@ -0,0 +1,12 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
// Check for valid API key
$id = $user->checkApiKey($_REQUEST['api_key']);
header('HTTP/1.1 400 Bad Request');
die('400 Bad Request');
?>

View File

@ -0,0 +1,24 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
// Check user token
$id = $user->checkApiKey($_REQUEST['api_key']);
if ($bitcoin->can_connect() === true){
if (!$iBlock = $memcache->get('iBlock')) {
$iBlock = $bitcoin->query('getblockcount');
$memcache->set('iBlock', $iBlock);
}
} else {
$iBlock = 0;
}
// Output JSON format
echo json_encode(array('getblockcount' => $iBlock));
// Supress master template
$supress_master = 1;
?>

View File

@ -0,0 +1,20 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
// Check user token
$id = $user->checkApiKey($_REQUEST['api_key']);
// Set a sane limit, overwrite with URL parameter
$iLimit = 10;
if (@$_REQUEST['limit'])
$iLimit = $_REQUEST['limit'];
// Output JSON format
echo json_encode(array('getblocksfound' => $statistics->getBlocksFound($iLimit)));
// Supress master template
$supress_master = 1;
?>

View File

@ -0,0 +1,15 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
// Check user token
$id = $user->checkApiKey($_REQUEST['api_key']);
// Output JSON format
echo json_encode(array('getcurrentworkers' => $worker->getCountAllActiveWorkers()));
// Supress master template
$supress_master = 1;
?>

View File

@ -0,0 +1,25 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
// Check user token
$id = $user->checkApiKey($_REQUEST['api_key']);
// Fetch data from litecoind
if ($bitcoin->can_connect() === true){
if (!$dDifficulty = $memcache->get('dDifficulty')) {
$memcache->set('dDifficulty', $dDifficulty);
$dDifficulty = $bitcoin->query('getdifficulty');
}
} else {
$iDifficulty = 1;
}
// Output JSON format
echo json_encode(array('getdifficulty' => $dDifficulty));
// Supress master template
$supress_master = 1;
?>

View File

@ -0,0 +1,18 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
// Check user token
$id = $user->checkApiKey($_REQUEST['api_key']);
// Estimated time to find the next block
$iCurrentPoolHashrate = $statistics->getCurrentHashrate() * 1000;
// Output JSON format
echo json_encode(array('getestimatedtime' => $bitcoin->getestimatedtime($iCurrentPoolHashrate)));
// Supress master template
$supress_master = 1;
?>

View File

@ -0,0 +1,15 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
// Check user token
$id = $user->checkApiKey($_REQUEST['api_key']);
// Output JSON format
echo json_encode(array('getpoolhashrate' => $statistics->getCurrentHashrate()));
// Supress master template
$supress_master = 1;
?>

View File

@ -0,0 +1,15 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
// Check user token
$id = $user->checkApiKey($_REQUEST['api_key']);
// Output JSON format
echo json_encode(array('getpoolsharerate' => $statistics->getCurrentShareRate()));
// Supress master template
$supress_master = 1;
?>

View File

@ -0,0 +1,26 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
// Check user token
$id = $user->checkApiKey($_REQUEST['api_key']);
// Fetch our last block found
$aBlocksFoundData = $statistics->getBlocksFound(1);
// Time since last block
$now = new DateTime( "now" );
if (!empty($aBlocksFoundData)) {
$dTimeSinceLast = ($now->getTimestamp() - $aBlocksFoundData[0]['time']);
} else {
$dTimeSinceLast = 0;
}
// Output JSON format
echo json_encode(array('gettimesincelastblock' => $dTimeSinceLast));
// Supress master template
$supress_master = 1;
?>

View File

@ -0,0 +1,9 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
// Tempalte specifics
$smarty->assign("CONTENT", "default.tpl");
?>

View File

@ -0,0 +1,17 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
if ($_POST['do'] == 'useToken') {
if ($user->useToken($_POST['token'], $_POST['newPassword'], $_POST['newPassword2'])) {
$_SESSION['POPUP'][] = array('CONTENT' => 'Password reset complete! Please login.');
} else {
$_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg');
}
}
// Tempalte specifics
$smarty->assign("CONTENT", "default.tpl");
?>

View File

@ -0,0 +1,16 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
// Process password reset request
if ($user->resetPassword($_POST['username'], $smarty)) {
$_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mail account to finish your password reset');
} else {
$_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg');
}
// Tempalte specifics, user default template by parent page
$smarty->assign("CONTENT", "../default.tpl");
?>

View File

@ -10,7 +10,7 @@ if ($bitcoin->can_connect() === true){
} else {
$iDifficulty = 1;
$iBlock = 0;
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg');
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to litecoind RPC service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg');
}
$smarty->assign("CURRENTBLOCK", $iBlock);

View File

@ -0,0 +1,22 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
// Grab the last blocks found
$iLimit = 30;
$aBlocksFoundData = $statistics->getBlocksFound($iLimit);
$aBlockData = $aBlocksFoundData[0];
// Propagate content our template
$smarty->assign("BLOCKSFOUND", $aBlocksFoundData);
$smarty->assign("BLOCKLIMIT", $iLimit);
if ($_SESSION['AUTHENTICATED']) {
$smarty->assign("CONTENT", "blocks_found.tpl");
} else {
$smarty->assign("CONTENT", "default.tpl");
}
?>

View File

@ -6,28 +6,23 @@ if (!defined('SECURITY'))
// Fetch data from litecoind
if ($bitcoin->can_connect() === true){
if (!$dDifficulty = $memcache->get('dDifficulty')) {
$dDifficulty = $bitcoin->query('getdifficulty');
$memcache->set('dDifficulty', $dDifficulty);
}
if (!$iBlock = $memcache->get('iBlock')) {
$iBlock = $bitcoin->query('getblockcount');
$memcache->set('iBlock', $iBlock);
}
$dDifficulty = $bitcoin->getdifficulty();
$iBlock = $bitcoin->getblockcount();
} else {
$iDifficulty = 1;
$dDifficulty = 1;
$iBlock = 0;
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg');
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to litecoind RPC service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg');
}
// Top share contributors
$aContributorsShares = $statistics->getTopContributors('shares', 15);
// Top hash contributors
$aContributorsHashes = $statistics->getTopContributors('hashes', 15);
$aContributorsHashes = $statistics->getTopContributors('hashes', 15);
// Grab the last 10 blocks found
$aBlocksFoundData = $statistics->getBlocksFound(10);
$iLimit = 10;
$aBlocksFoundData = $statistics->getBlocksFound($iLimit);
$aBlockData = $aBlocksFoundData[0];
// Estimated time to find the next block
@ -47,6 +42,7 @@ if (!empty($aBlockData)) {
$smarty->assign("ESTTIME", $iEstTime);
$smarty->assign("TIMESINCELAST", $dTimeSinceLast);
$smarty->assign("BLOCKSFOUND", $aBlocksFoundData);
$smarty->assign("BLOCKLIMIT", $iLimit);
$smarty->assign("CONTRIBSHARES", $aContributorsShares);
$smarty->assign("CONTRIBHASHES", $aContributorsHashes);
$smarty->assign("CURRENTBLOCK", $iBlock);

View File

@ -6,18 +6,12 @@ if (!defined('SECURITY'))
// Fetch data from litecoind
if ($bitcoin->can_connect() === true){
if (!$dDifficulty = $memcache->get('dDifficulty')) {
$dDifficulty = $bitcoin->query('getdifficulty');
$memcache->set('dDifficulty', $dDifficulty);
}
if (!$iBlock = $memcache->get('iBlock')) {
$iBlock = $bitcoin->query('getblockcount');
$memcache->set('iBlock', $iBlock);
}
$dDifficulty = $bitcoin->getdifficulty();
$iBlock = $bitcoin->getblockcount();
} else {
$iDifficulty = 1;
$iBlock = 0;
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg');
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to litecoind RPC service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg');
}
$aHourlyHashRates = $statistics->getHourlyHashrateByAccount($_SESSION['USERDATA']['id']);

View File

@ -22,22 +22,27 @@ $aGlobal = array(
'roundshares' => $aRoundShares,
'fees' => $config['fees'],
'confirmations' => $config['confirmations'],
'reward' => $config['reward']
'reward' => $config['reward'],
'price' => $setting->getValue('price'),
'blockexplorer' => $config['blockexplorer'],
'chaininfo' => $config['chaininfo']
);
// We don't want these session infos cached
$aGlobal['userdata'] = $_SESSION['USERDATA']['id'] ? $user->getUserData($_SESSION['USERDATA']['id']) : array();
$aGlobal['userdata']['balance'] = $transaction->getBalance($_SESSION['USERDATA']['id']);
if (@$_SESSION['USERDATA']['id']) {
$aGlobal['userdata'] = $_SESSION['USERDATA']['id'] ? $user->getUserData($_SESSION['USERDATA']['id']) : array();
$aGlobal['userdata']['balance'] = $transaction->getBalance($_SESSION['USERDATA']['id']);
// Other userdata that we can cache savely
$aGlobal['userdata']['shares'] = $statistics->getUserShares($_SESSION['USERDATA']['id']);
$aGlobal['userdata']['hashrate'] = $statistics->getUserHashrate($_SESSION['USERDATA']['id']);
// Other userdata that we can cache savely
$aGlobal['userdata']['shares'] = $statistics->getUserShares($_SESSION['USERDATA']['id']);
$aGlobal['userdata']['hashrate'] = $statistics->getUserHashrate($_SESSION['USERDATA']['id']);
// Some estimations
$aGlobal['userdata']['est_block'] = round(( (int)$aGlobal['userdata']['shares']['valid'] / (int)$aRoundShares['valid'] ) * (int)$config['reward'], 3);
$aGlobal['userdata']['est_fee'] = round(($config['fees'] / 100) * $aGlobal['userdata']['est_block'], 3);
$aGlobal['userdata']['est_donation'] = round((( $aGlobal['userdata']['donate_percent'] / 100) * ($aGlobal['userdata']['est_block'] - $aGlobal['userdata']['est_fee'])), 3);
$aGlobal['userdata']['est_payout'] = round($aGlobal['userdata']['est_block'] - $aGlobal['userdata']['est_donation'] - $aGlobal['userdata']['est_fee'], 3);
// Some estimations
$aGlobal['userdata']['est_block'] = round(( (int)$aGlobal['userdata']['shares']['valid'] / (int)$aRoundShares['valid'] ) * (int)$config['reward'], 3);
$aGlobal['userdata']['est_fee'] = round(($config['fees'] / 100) * $aGlobal['userdata']['est_block'], 3);
$aGlobal['userdata']['est_donation'] = round((( $aGlobal['userdata']['donate_percent'] / 100) * ($aGlobal['userdata']['est_block'] - $aGlobal['userdata']['est_fee'])), 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);

View File

@ -1,4 +1,3 @@
* { padding:0; margin:0; }
body {
@ -460,6 +459,14 @@ a:hover {
font-size: 11px;
}
.block table tr td.right{
text-align: right;
}
.block table tr th.right{
text-align: right;
}
.block table tr td.delete a { color: #666; }
.block table tr td.delete a:hover { color: #dd0000; }
@ -854,7 +861,7 @@ a:hover {
/* Block sidebar */
.block.withsidebar .bendl {
/* width: 171px; */
width: 191px;
background: url(../images/bendsb.gif) bottom left no-repeat;
}
@ -865,7 +872,7 @@ a:hover {
}
.block.withsidebar .block_content .sidebar {
width: 190px;
width: 210px;
float: left;
font-size: 11px;
}

View File

@ -0,0 +1,10 @@
<html>
<body>
<p>Hello {$USERNAME},</p><br />
<p>You have requested a password reset through our online form. In order to complete the request please follow this link:</p>
<p>http://{$smarty.server.SERVER_NAME}{$smarty.server.PHP_SELF}?page=password&action=change&token={$TOKEN}</p>
<p>You will be asked to change your password. You can then use this new password to login to your account.</p>
<p>Cheers,</p>
<p>Website Administration</p>
</body>
</html>

View File

@ -0,0 +1 @@
[ {$WEBSITENAME} ] Password Reset Request

View File

@ -6,6 +6,7 @@
<table>
<tbody><tr><td>Username: </td><td>{$GLOBAL.userdata.username}</td></tr>
<tr><td>User Id: </td><td>{$GLOBAL.userdata.id}</td></tr>
<tr><td>API Key: </td><td>{$GLOBAL.userdata.api_key}</td></tr>
<tr><td>Payment Address: </td><td><input type="text" name="paymentAddress" value="{$smarty.request.paymentAddress|default:$GLOBAL.userdata.coin_address|escape}" size="40"></td></tr>
<tr><td>Donation %: </td><td><input type="text" name="donatePercent" value="{$smarty.request.donatePercent|default:$GLOBAL.userdata.donate_percent|escape}" size="4"><font size="1"> [donation amount in percent (example: 0.5)]</font></td></tr>
<tr><td>Automatic Payout Threshold: </td><td valign="top"><input type="text" name="payoutThreshold" value="{$smarty.request.payoutThreshold|default:$GLOBAL.userdata.ap_threshold|escape}" size="5" maxlength="5"> <font size="1">[1-250 LTC. Set to '0' for no auto payout]</font></td></tr>

View File

@ -1,4 +1,4 @@
{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,Orphan)}
<div class="block_content tab_content" id="Confirmed" style="clear:;">
<center>
<table cellpadding="1" cellspacing="1" width="98%" class="sortable">
@ -77,11 +77,55 @@
{/section}
<tr>
<td colspan="5"><b>Unconfirmed Totals:</b></td>
<td><b>{$credits - $debits}</b></td>
<td><b>{$credits|default - $debits|default}</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>
<div class="block_content tab_content" id="Orphan" style="">
<center>
<table cellpadding="1" cellspacing="1" width="98%" class="sortable">
<thead style="font-size:13px;">
<tr>
<th class="header" style="cursor: pointer;">TX #</th>
<th class="header" style="cursor: pointer;">Date</th>
<th class="header" style="cursor: pointer;">TX Type</th>
<th class="header" style="cursor: pointer;">Payment Address</th>
<th class="header" style="cursor: pointer;">Block #</th>
<th class="header" style="cursor: pointer;">Amount</th>
</tr>
</thead>
<tbody style="font-size:12px;">
{section transaction $TRANSACTIONS}
{if (
$TRANSACTIONS[transaction].type == 'Orphan_Credit'
or $TRANSACTIONS[transaction].type == 'Orphan_Donation'
or $TRANSACTIONS[transaction].type == 'Orphan_Fee'
)}
<tr class="{cycle values="odd,even"}">
<td>{$TRANSACTIONS[transaction].id}</td>
<td>{$TRANSACTIONS[transaction].timestamp}</td>
<td>{$TRANSACTIONS[transaction].type}</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 == Orphan_Credit}green{else}red{/if}">{$TRANSACTIONS[transaction].amount}</td>
</tr>
{if $TRANSACTIONS[transaction].type == Orphan_Credit}
{assign var="orphan_credits" value="`$orphan_credits+$TRANSACTIONS[transaction].amount`"}
{else}
{assign var="orphan_debits" value="`$orphan_debits+$TRANSACTIONS[transaction].amount`"}
{/if}
{/if}
{/section}
<tr>
<td colspan="5"><b>Orphaned Totals:</b></td>
<td><b>{$orphan_credits|default - $orphan_debits|default}</b></td>
</tr>
</tbody>
</table>
<p><font color="" sizeze="1">Listed are your orphaned transactions for blocks not part of the main blockchain.</font></p>
</center>
</div>
{include file="global/block_footer.tpl"}

View File

@ -11,8 +11,8 @@
<li><strong>Create account.</strong>
<br>
<ul>
<li>Register <a href="/register">here</a>, or login if you already have account</li>
<li>Create a <a href="/accountworkers">worker</a> that will be used by the miner to login</li>
<li>Register <a href="{$smarty.server.PHP_SELF}?page=register">here</a>, or login if you already have account</li>
<li>Create a <a href="{$smarty.server.PHP_SELF}?page=account&action=workers">worker</a> that will be used by the miner to login</li>
</ul>
</li><li><strong>Download a miner.</strong>
@ -49,7 +49,7 @@ Start your miner using the following parameters:
If you use a command-line miner, type:
<pre> ./minerd --url http://pool.grewe.ca:8337/ --userpass <em>Weblogin</em>.<em>Worker</em>:<em>Worker password</em>
</pre>
If you want, you can create additional slaves with usernames and passwords of your choice <a href="accountworkers">Here<br></a><br>
If you want, you can create additional workers with usernames and passwords of your choice <a href="{$smarty.server.PHP_SELF}?page=account&action=workers">Here<br></a><br>
</li>
<li><strong>Create a Litecoin address to recieve payments.</strong>
<ul>

View File

@ -1,9 +1,9 @@
<div class="block{if $ALIGN} small {$ALIGN}{/if}" style="{if $BLOCK_STYLE}{$BLOCK_STYLE}{else}clear:none;{/if}">
<div class="block{if $ALIGN|default} small {$ALIGN}{/if}" style="{if $BLOCK_STYLE|default}{$BLOCK_STYLE}{else}clear:none;{/if}">
<div class="block_head">
<div class="bheadl"></div>
<div class="bheadr"></div>
<h2>{$BLOCK_HEADER|default:"UNKNOWN BLOCK"}</h2>
{if $BUTTONS}
{if $BUTTONS|default}
<ul class="tabs">
{foreach from=$BUTTONS item=name}
<li style="font-size:9px;"><a href="#{$name}">{$name}</a></li>
@ -11,4 +11,4 @@
</ul>
{/if}
</div>
<div class="block_content" style="{if $STYLE}{$STYLE}{else}padding:10px;{/if}">
<div class="block_content" style="{if $STYLE|default}{$STYLE}{else}padding:10px;{/if}">

View File

@ -4,6 +4,7 @@
<div id="ministats">
<table border="0">
<tr>
<td><li>LTC/usd: {$GLOBAL.price|default:"n/a"}&nbsp;&nbsp;&nbsp;&nbsp;</li></td>
<td><li>Pool Hashrate: {$GLOBAL.hashrate / 1000} MH/s&nbsp;&nbsp;&nbsp;&nbsp;</li></td>
<td><li>Pool Sharerate: {$GLOBAL.sharerate} Shares/s&nbsp;&nbsp;&nbsp;&nbsp;</li></td>
<td><li>Pool Workers: {$GLOBAL.workers}&nbsp;&nbsp;&nbsp;&nbsp;</li></td>

View File

@ -4,5 +4,5 @@
<p><input type="password" name="password" value="" id="passForm" maxlength="20"></p>
<center><p><input type="submit" class="submit small" value="Login"></p></center>
</form>
<center><p><a href="/lostPass"><font size="1">Forgot your password?</font></a></p></center>
<center><p><a href="{$smarty.server.PHP_SELF}?page=password"><font size="1">Forgot your password?</font></a></p></center>
{include file="global/block_footer.tpl"}

View File

@ -1,4 +1,4 @@
{if $GLOBAL.motd}
{if $GLOBAL.motd|default}
<div id="generic_infobox" style="margin-left:3px;">
<font color="orange" size="1">***Message of the Day***<br></font>
<font size="1">{$GLOBAL.motd|escape:'html'}</font>

View File

@ -1,6 +1,6 @@
<ul id="nav">
<li><a href="{$smarty.server.PHP_SELF}">Home</a></li>
{if $smarty.session.AUTHENTICATED == 1}
{if $smarty.session.AUTHENTICATED|default:"0" == 1}
<li><a href="">My Account</a>
<ul>
<li><a href="{$smarty.server.PHP_SELF}?page=account&action=edit">Edit Account</a></li>
@ -10,21 +10,20 @@
</ul>
</li>
{/if}
{if $smarty.session.AUTHENTICATED == 1 && $GLOBAL.userdata.admin == 1}<li><a href="#">Admin Panel</a></li>{/if}
{if $smarty.session.AUTHENTICATED|default:"0" == 1 && $GLOBAL.userdata.admin == 1}<li><a href="#">Admin Panel</a></li>{/if}
<li><a href="{$smarty.server.PHP_SELF}?page=statistics">Statistics</a>
<ul>
<li><a href="{$smarty.server.PHP_SELF}?page=statistics&action=pool">Pool Stats</a></li>
{if $smarty.session.AUTHENTICATED}<li><a href="{$smarty.server.PHP_SELF}?page=statistics&action=block">Block Stats</a></li>{/if}
{if $smarty.session.AUTHENTICATED|default}<li><a href="{$smarty.server.PHP_SELF}?page=statistics&action=blocks">Block Stats</a></li>{/if}
</ul>
</li>
<li><a href="{$smarty.server.PHP_SELF}?page=gettingstarted">Getting Started</a></li>
<li><a href="{$smarty.server.PHP_SELF}?page=support">Support</a></li>
<li><a href="{$smarty.server.PHP_SELF}?page=about&action=pplns">About</a>
<li><a href="{$smarty.server.PHP_SELF}?page=about&action=pool">About</a>
<ul>
<li><a href="{$smarty.server.PHP_SELF}?page=about&action=pplns">PPLNS Payout</a></li>
<li><a href="{$smarty.server.PHP_SELF}?page=about&action=pool">This Pool</a></li>
</ul>
</li>
<li><a href="{$smarty.server.PHP_SELF}?page=news">News</a></li>
{if $smarty.session.AUTHENTICATED == 1}<li><a href="{$smarty.server.PHP_SELF}?page=logout">Logout</a></li>{else}<li><a href="{$smarty.server.PHP_SELF}?page=register">Register</a></li>{/if}
{if $smarty.session.AUTHENTICATED|default == 1}<li><a href="{$smarty.server.PHP_SELF}?page=logout">Logout</a></li>{else}<li><a href="{$smarty.server.PHP_SELF}?page=register">Register</a></li>{/if}
</ul>

View File

@ -5,57 +5,57 @@
<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>
<table class="sidebar" style="width: 196px">
<tr><td colspan="2"><b>Your Hashrate</b></td></tr>
<tr><td colspan="2" class="right">{$GLOBAL.userdata.hashrate|number_format} 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>
<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>
<td class="right"><i>{$GLOBAL.userdata.shares.valid|number_format}</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>
<td class="right"><i>{$GLOBAL.roundshares.valid|number_format}</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>
<td class="right"><i>{$GLOBAL.roundshares.valid|number_format}</i></td>
</tr>
<tr>
<td><b>Pool Invalid</b></td>
<td><i>{$GLOBAL.roundshares.invalid}</i></td>
<td class="right"><i>{$GLOBAL.roundshares.invalid|number_format}</i></td>
</tr>
<tr>
<td><b>Your Invalid</b></td>
<td><i>{$GLOBAL.userdata.shares.invalid}</i><font size='1px'></font></td>
<td class="right"><i>{$GLOBAL.userdata.shares.invalid|number_format}</i><font size='1px'></font></td>
</tr>
<tr>
<td colspan="2"><b><u>Round Estimate</u></b></td>
<td colspan="2"><b><u>LTC Round Estimate</u></b></td>
</tr>
<tr>
<td><b>Block</b></td>
<td>{$GLOBAL.userdata.est_block} LTC</td>
<td class="right">{$GLOBAL.userdata.est_block|number_format:"3"}</td>
</tr>
<tr>
<td><b>Fees</b></td>
<td>{$GLOBAL.userdata.est_fee} LTC</td>
<td class="right">{$GLOBAL.userdata.est_fee|number_format:"3"}</td>
</tr>
<tr>
<td><b>Donation</b></td>
<td>{$GLOBAL.userdata.est_donation} LTC</td>
<td class="right">{$GLOBAL.userdata.est_donation|number_format:"3"}</td>
</tr>
<tr>
<td><b>Payout</b></td>
<td>{$GLOBAL.userdata.est_payout} LTC</td>
<td class="right">{$GLOBAL.userdata.est_payout|number_format:"3"}</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>
<tr><td colspan="2" class="right"><b>{$GLOBAL.userdata.balance|default:"0"} LTC</td></tr>
</table>
</div>
<div class="bendl"></div>

View File

@ -1,5 +1,5 @@
{if $GLOBAL.userdata.username}
<h2>Welcome, {$smarty.session.USERDATA.username} <font size='1px'><b>Active Account</b>: <b>{$GLOBAL.fees}%</b> Pool Fee</font> <font size='1px'><i>(You are <a href='/osList'>donating</a> <b></i>{$GLOBAL.userdata.donate_percent}%</b> <i>of your earnings)</i></font></h2>
{if $GLOBAL.userdata.username|default}
<h2>Welcome, {$smarty.session.USERDATA.username} <font size='1px'><b>Active Account</b>: <b>{$GLOBAL.fees}%</b> Pool Fee</font> <font size='1px'><i>(You are <a href='{$smarty.server.PHP_SELF}?page=account&action=edit'>donating</a> <b></i>{$GLOBAL.userdata.donate_percent}%</b> <i>of your earnings)</i></font></h2>
{else}
<h2>Welcome guest, <font size="1px"> please <a href="{$smarty.server.PHP_SELF}?page=register">register</a> to user this pool.</font></h2>
{/if}

View File

@ -4,7 +4,7 @@
<title>ThePool</title>
<meta http-equiv="X-UA-Compatible" content="IE=7" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
<link rel="shortcut icon" href="#" />
<link rel="stylesheet" href="{$PATH}/css/mainstyle.css" type="text/css" />
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="{$PATH}/js/jquery.browser.js"></script>
@ -45,14 +45,14 @@
</div>
<div class="block_content">
<div class="sidebar">
{if $smarty.session.AUTHENTICATED}
{if $smarty.session.AUTHENTICATED|default}
{include file="global/sidebar.tpl"}
{else}
{include file="global/login.tpl"}
{/if}
</div>
<div class="sidebar_content" id="sb1">
{if is_array($smarty.session.POPUP)}
<div class="sidebar_content" id="sb1" style="margin-left: 13px">
{if is_array($smarty.session.POPUP|default)}
{section popup $smarty.session.POPUP}
<div class="message {$smarty.session.POPUP[popup].TYPE|default:"success"}"><p>{$smarty.session.POPUP[popup].CONTENT}</p></div>
{/section}

View File

@ -0,0 +1,12 @@
{include file="global/block_header.tpl" BLOCK_HEADER="Change Password"}
<form action="{$smarty.server.PHP_SELF}" method="post">
<input type="hidden" name="token" value="{$smarty.request.token|escape}">
<input type="hidden" name="page" value="{$smarty.request.page|escape}">
<input type="hidden" name="action" value="{$smarty.request.action|escape}">
<input type="hidden" name="do" value="useToken">
<table>
<tr><td>New Password: </td><td><input type="password" name="newPassword"></td></tr>
<tr><td>New Password Repeat: </td><td><input type="password" name="newPassword2"></td></tr>
</tbody></table>
<input type="submit" class="submit long" value="Change Password"></form>
{include file="global/block_footer.tpl"}

View File

@ -0,0 +1,8 @@
{include file="global/block_header.tpl" BLOCK_HEADER="Reset Password" BLOCK_STYLE="clear:none;"}
<form action="" method="POST">
<input type="hidden" name="page" value="password">
<input type="hidden" name="action" value="reset">
<p>If you have an email set for your account, enter your username to get your password reset</p>
<p><input type="text" value="{$smarty.post.username}" name="username"><input class="submit small" type="submit" value="Reset"></p>
</form>
{include file="global/block_footer.tpl"}

View File

@ -1,4 +1,4 @@
{include file="global/block_header.tpl" BLOCK_HEADER="Last 10 Blocks Found" BLOCK_STYLE="clear:none;" BUTTONS=array(More)}
{include file="global/block_header.tpl" BLOCK_HEADER="Last $BLOCKLIMIT Blocks Found" BLOCK_STYLE="clear:none;"}
<center>
<table class="stats_lastblocks" width="100%" style="font-size:13px;">
<thead>
@ -7,20 +7,25 @@
<th scope="col" align="left">Validity</th>
<th scope="col" align="left">Finder</th>
<th scope="col" align="left">Date / Time</th>
<th scope="col" align="left">Difficulty</th>
<th scope="col" align="left">Shares</th>
<th class="right" scope="col">Difficulty</th>
<th class="right" scope="col">Shares</th>
</tr>
</thead>
<tbody>
{assign var=rank value=1}
{section block $BLOCKSFOUND}
<tr class="{cycle values="odd,even"}">
<td>{$BLOCKSFOUND[block].height}</td>
<td>{if $BLOCKSFOUND[block].confirmations >= $GLOBAL.confirmations}<font color="green">Confirmed</font>{else}{$GLOBAL.confirmations - $BLOCKSFOUND[block].confirmations} left{/if}</td>
<td><a href="{$GLOBAL.blockexplorer}{$BLOCKSFOUND[block].height}" target="_blank">{$BLOCKSFOUND[block].height}</a></td>
<td>
{if $BLOCKSFOUND[block].confirmations >= $GLOBAL.confirmations}
<font color="green">Confirmed</font>
{else if $BLOCKSFOUND[block].confirmations == -1}
<font color="red">Orphan</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>
<td>{$BLOCKSFOUND[block].shares|number_format}</td>
<td class="right">{$BLOCKSFOUND[block].difficulty|number_format:"8"}</td>
<td class="right">{$BLOCKSFOUND[block].shares|number_format}</td>
</tr>
{/section}
</tbody>

View File

@ -17,29 +17,30 @@
</tr>
<tr>
<td class="leftheader">Next Network Block</td>
<td><a href="http://explorer.litecoin.net/search?q={$CURRENTBLOCK + 1}" target="_new">{$CURRENTBLOCK + 1}</a> &nbsp;&nbsp;<font size="1"> (Current: <a href="http://explorer.litecoin.net/search?q={$CURRENTBLOCK}" target="_new">{$CURRENTBLOCK})</a></font></td>
<td><a href="{$GLOBAL.blockexplorer}{$CURRENTBLOCK + 1}" target="_new">{$CURRENTBLOCK + 1}</a> &nbsp;&nbsp;<font size="1"> (Current: <a href="{$GLOBAL.blockexplorer}{$CURRENTBLOCK}" target="_new">{$CURRENTBLOCK})</a></font></td>
</tr>
<tr>
<td class="leftheader">Last Block Found</td>
<td><a href="http://explorer.litecoin.net/search?q={$LASTBLOCK}" target="_new">{$LASTBLOCK|default:"0"}</a></td>
<td><a href="{$GLOBAL.blockexplorer}{$LASTBLOCK}" target="_new">{$LASTBLOCK|default:"0"}</a></td>
</tr>
<tr>
<td class="leftheader">Current Difficulty</td>
<td><a href="http://allchains.info" target="_new"><font size="2">{$DIFFICULTY}</font></a></td>
<td><a href="{$GLOBAL.chaininfo}" target="_new"><font size="2">{$DIFFICULTY}</font></a></td>
</tr>
<tr>
<td class="leftheader">Est. Avg. Time per Round</td>
<td>{$ESTTIME|seconds_to_words}</td>
</tr>
<tr>
<td class="leftheader">Est. Avg. Shares per Round</td>
<td>{($ESTTIME * $GLOBAL.sharerate)|number_format:"0"}</td>
</tr>
<tr>
<td class="leftheader">Time Since Last Block</td>
<td>{$TIMESINCELAST|seconds_to_words}</td>
</tr>
</tbody>
</table>
<ul>
<li><font color="orange">Server stats are also available in JSON format <a href="/api" target="_api">HERE</a></font></li>
</ul>
{include file="global/block_footer.tpl"}

View File

@ -5,8 +5,8 @@
<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>
<th class="right" scope="col">KH/s</th>
<th class="right">Ł/Day<font size="1"> (est)</font></th>
</tr>
</thead>
<tbody>
@ -16,16 +16,16 @@
<tr{if $GLOBAL.userdata.username == $CONTRIBHASHES[contrib].account}{assign var=listed value=1} style="background-color:#99EB99;"{else} class="{cycle values="odd,even"}"{/if}>
<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>
<td class="right">{$CONTRIBHASHES[contrib].hashrate|number_format}</td>
<td class="right">{math equation="round(reward / ( diff * pow(2,32) / ( hashrate * 1000 ) / 3600 / 24), 3)" diff=$DIFFICULTY reward=$REWARD hashrate=$CONTRIBHASHES[contrib].hashrate}</td>
</tr>
{/section}
{if $listed != 1}
<tr style="background-color:#99EB99;">
<td>n/a</td>
<td>{$GLOBAL.userdata.username}</td>
<td>{$GLOBAL.userdata.hashrate}</td>
<td>{math equation="round(reward / ( diff * pow(2,32) / ( hashrate * 1000 ) / 3600 / 24), 3)" diff=$DIFFICULTY reward=$REWARD hashrate=$GLOBAL.userdata.hashrate}</td>
<td class="right">{$GLOBAL.userdata.hashrate}</td>
<td class="right">{math equation="round(reward / ( diff * pow(2,32) / ( hashrate * 1000 ) / 3600 / 24), 3)" diff=$DIFFICULTY reward=$REWARD hashrate=$GLOBAL.userdata.hashrate}</td>
</tr>
{/if}
</tbody>

View File

@ -4,8 +4,8 @@
<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>
<th scope="col">User Name</th>
<th class="right" scope="col">Shares</th>
</tr>
</thead>
<tbody>
@ -15,14 +15,14 @@
<tr{if $GLOBAL.userdata.username == $CONTRIBSHARES[hashrate].account}{assign var=listed value=1} style="background-color:#99EB99;"{else} class="{cycle values="odd,even"}"{/if}>
<td>{$rank++}</td>
<td>{$CONTRIBSHARES[hashrate].account}</td>
<td>{$CONTRIBSHARES[hashrate].shares|number_format}</td>
<td class="right">{$CONTRIBSHARES[hashrate].shares|number_format}</td>
</tr>
{/section}
{if $listed != 1}
<tr style="background-color:#99EB99;">
<td>n/a</td>
<td>{$GLOBAL.userdata.username}</td>
<td>{$GLOBAL.userdata.shares.valid}</td>
<td class="right">{$GLOBAL.userdata.shares.valid}</td>
</tr>
{/if}
</tbody>

View File

@ -6,10 +6,8 @@
</div>
<div class="block_content" style="padding:10px;">
<p>
</p><p>As mentioned in various places this site is experimental and does not come with any support nor warranty.</p>
<p>Nevertheless, you can find contact information on my main website: <a href="http://www.grewe.ca">http://www.grewe.ca</a></p>
<p>This product comes 'as-is' without any warranty. Please check the Apache License, Version 2.0, for details.</p>
<p>If you would like to get in touch you can find me on Freenode IRC @ #mmcfe-ng.</p>
</div> <!-- nested block ends -->
<div class="bendl"></div>

View File

@ -3,7 +3,7 @@
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Generation Time: May 20, 2013 at 07:35 PM
-- Generation Time: May 31, 2013 at 02:31 PM
-- Server version: 5.5.31-0ubuntu0.13.04.1
-- PHP Version: 5.4.9-4ubuntu2
@ -36,6 +36,7 @@ CREATE TABLE IF NOT EXISTS `accounts` (
`sessionTimeoutStamp` int(255) DEFAULT NULL,
`pin` varchar(255) NOT NULL COMMENT 'four digit pin to allow account changes',
`api_key` varchar(255) DEFAULT NULL,
`token` varchar(65) DEFAULT NULL,
`donate_percent` float DEFAULT '0',
`ap_threshold` float DEFAULT '0',
`coin_address` varchar(255) DEFAULT NULL,
@ -53,17 +54,18 @@ CREATE TABLE IF NOT EXISTS `blocks` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`height` int(10) unsigned NOT NULL,
`blockhash` char(65) NOT NULL,
`confirmations` int(10) unsigned NOT NULL,
`confirmations` int(10) NOT NULL,
`amount` double NOT NULL,
`difficulty` double NOT NULL,
`time` int(11) NOT NULL,
`accounted` tinyint(1) NOT NULL DEFAULT '0',
`account_id` int(255) unsigned DEFAULT NULL,
`shares` int(255) unsigned DEFAULT NULL,
`share_id` int(255) DEFAULT NULL,
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';
-- --------------------------------------------------------
@ -72,9 +74,10 @@ CREATE TABLE IF NOT EXISTS `blocks` (
--
CREATE TABLE IF NOT EXISTS `settings` (
`setting` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`value` varchar(255) DEFAULT NULL,
PRIMARY KEY (`setting`)
PRIMARY KEY (`name`),
UNIQUE KEY `setting` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
@ -97,7 +100,7 @@ CREATE TABLE IF NOT EXISTS `shares` (
KEY `upstream_result` (`upstream_result`),
KEY `our_result` (`our_result`),
KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
@ -112,10 +115,11 @@ CREATE TABLE IF NOT EXISTS `shares_archive` (
`our_result` enum('Y','N') DEFAULT NULL,
`upstream_result` enum('Y','N') DEFAULT NULL,
`block_id` int(10) unsigned NOT NULL,
`time` datetime DEFAULT NULL,
`time` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `share_id` (`share_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Archive shares for potential later debugging purposes';
UNIQUE KEY `share_id` (`share_id`),
KEY `time` (`time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Archive shares for potential later debugging purposes';
-- --------------------------------------------------------
@ -132,7 +136,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,7 +147,7 @@ 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','Fee','Donation') DEFAULT NULL,
`type` enum('Credit','Debit_MP','Debit_AP','Donation','Fee','Orphan_Credit','Orphan_Fee','Orphan_Donation') DEFAULT NULL,
`coin_address` varchar(255) DEFAULT NULL,
`amount` double DEFAULT '0',
`block_id` int(255) DEFAULT NULL,
@ -152,15 +156,15 @@ CREATE TABLE IF NOT EXISTS `transactions` (
KEY `block_id` (`block_id`),
KEY `account_id` (`account_id`),
KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Table structure for table `workers`
-- Table structure for table `pool_worker`
--
CREATE TABLE IF NOT EXISTS `workers` (
CREATE TABLE IF NOT EXISTS `pool_worker` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`account_id` int(255) NOT NULL,
`username` char(50) DEFAULT NULL,
@ -169,7 +173,7 @@ CREATE TABLE IF NOT EXISTS `workers` (
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
KEY `account_id` (`account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;