Merge branch 'next'
This commit is contained in:
commit
1437a9eae1
@ -34,7 +34,7 @@ if ( $bitcoin->can_connect() !== true ) {
|
||||
}
|
||||
|
||||
// Fetch all unconfirmed blocks
|
||||
$aAllBlocks = $block->getAllUnconfirmed($config['confirmations']);
|
||||
$aAllBlocks = $block->getAllUnconfirmed(max($config['network_confirmations'],$config['confirmations']));
|
||||
|
||||
$log->logInfo("ID\tHeight\tBlockhash\tConfirmations");
|
||||
foreach ($aAllBlocks as $iIndex => $aBlock) {
|
||||
|
||||
@ -51,7 +51,7 @@ foreach ($aAllBlocks as $iIndex => $aBlock) {
|
||||
$pplns_target = $config['pplns']['shares']['default'];
|
||||
}
|
||||
|
||||
if (!$aBlock['accounted']) {
|
||||
if (!$aBlock['accounted'] && $aBlock['height'] > $setting->getValue('last_accounted_block_height')) {
|
||||
$iPreviousShareId = @$aAllBlocks[$iIndex - 1]['share_id'] ? $aAllBlocks[$iIndex - 1]['share_id'] : 0;
|
||||
$iCurrentUpstreamId = $aBlock['share_id'];
|
||||
if (!is_numeric($iCurrentUpstreamId)) {
|
||||
@ -175,6 +175,9 @@ foreach ($aAllBlocks as $iIndex => $aBlock) {
|
||||
$log->logFatal('Failed to insert new Donation transaction to database for ' . $aData['username']);
|
||||
}
|
||||
|
||||
// Store this blocks height as last accounted for
|
||||
$setting->setValue('last_accounted_block_height', $aBlock['height']);
|
||||
|
||||
// Move counted shares to archive before this blockhash upstream share
|
||||
if (!$share->moveArchive($iCurrentUpstreamId, $aBlock['id'], $iPreviousShareId))
|
||||
$log->logError('Failed to copy shares to archive table');
|
||||
@ -191,6 +194,22 @@ foreach ($aAllBlocks as $iIndex => $aBlock) {
|
||||
$monitoring->setStatus($cron_name . "_status", "okerror", 1);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
$aMailData = array(
|
||||
'email' => $setting->getValue('website_email'),
|
||||
'subject' => 'Payout processing aborted',
|
||||
'Error' => 'Potential double payout detected. All payouts halted until fixed!',
|
||||
'BlockID' => $aBlock['id'],
|
||||
'Block Height' => $aBlock['height'],
|
||||
'Block Share ID' => $aBlock['share_id']
|
||||
);
|
||||
if (!$mail->sendMail('notifications/error', $aMailData))
|
||||
$log->logError(" Failed sending notifications: " . $notification->getError() . "\n");
|
||||
$log->logFatal('Potential double payout detected. Aborted.');
|
||||
$monitoring->setStatus($cron_name . "_active", "yesno", 0);
|
||||
$monitoring->setStatus($cron_name . "_message", "message", "Block height for block too low! Potential double payout detected.");
|
||||
$monitoring->setStatus($cron_name . "_status", "okerror", 1);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ $count = 0;
|
||||
// Table header for account shares
|
||||
$log->logInfo("ID\tUsername\tValid\tInvalid\tPercentage\tPayout\t\tDonation\tFee");
|
||||
foreach ($aAllBlocks as $iIndex => $aBlock) {
|
||||
if (!$aBlock['accounted']) {
|
||||
if (!$aBlock['accounted'] && $aBlock['height'] > $setting->getValue('last_accounted_block_height')) {
|
||||
$iPreviousShareId = @$aAllBlocks[$iIndex - 1]['share_id'] ? $aAllBlocks[$iIndex - 1]['share_id'] : 0;
|
||||
$iCurrentUpstreamId = $aBlock['share_id'];
|
||||
$aAccountShares = $share->getSharesForAccounts($iPreviousShareId, $aBlock['share_id']);
|
||||
@ -82,7 +82,7 @@ foreach ($aAllBlocks as $iIndex => $aBlock) {
|
||||
number_format($aData['percentage'], 8) . "\t" .
|
||||
number_format($aData['payout'], 8) . "\t" .
|
||||
number_format($aData['donation'], 8) . "\t" .
|
||||
number_format($aData['fee']), 8);
|
||||
number_format($aData['fee'], 8));
|
||||
|
||||
// Update user share statistics
|
||||
if (!$statistics->updateShareStatistics($aData, $aBlock['id']))
|
||||
@ -100,6 +100,9 @@ foreach ($aAllBlocks as $iIndex => $aBlock) {
|
||||
$log->logFatal('Failed to insert new Donation transaction to database for ' . $aData['username']);
|
||||
}
|
||||
|
||||
// Add block as accounted for into settings table
|
||||
$setting->setValue('last_accounted_block_height', $aBlock['height']);
|
||||
|
||||
// Move counted shares to archive before this blockhash upstream share
|
||||
if (!$share->moveArchive($iCurrentUpstreamId, $aBlock['id'], $iPreviousShareId))
|
||||
$log->logError('Failed to copy shares to archive');
|
||||
@ -119,6 +122,22 @@ foreach ($aAllBlocks as $iIndex => $aBlock) {
|
||||
$monitoring->setStatus($cron_name . "_status", "okerror", 1);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
$log->logFatal('Possible double payout detected. Aborted.');
|
||||
$aMailData = array(
|
||||
'email' => $setting->getValue('website_email'),
|
||||
'subject' => 'Payout Failure: Double Payout',
|
||||
'Error' => 'Possible double payout detected',
|
||||
'BlockID' => $aBlock['id'],
|
||||
'Block Height' => $aBlock['height'],
|
||||
'Block Share ID' => $aBlock['share_id']
|
||||
);
|
||||
if (!$mail->sendMail('notifications/error', $aMailData))
|
||||
$log->logError(" Failed sending notifications: " . $notification->getError() . "\n");
|
||||
$monitoring->setStatus($cron_name . "_active", "yesno", 0);
|
||||
$monitoring->setStatus($cron_name . "_message", "message", 'Possible double payout detected. Aborted.');
|
||||
$monitoring->setStatus($cron_name . "_status", "okerror", 1);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -242,7 +242,12 @@ class Statistics {
|
||||
public function getUserShares($account_id) {
|
||||
$this->debug->append("STA " . __METHOD__, 4);
|
||||
// Dual-caching, try statistics cron first, then fallback to local, then fallbock to SQL
|
||||
if ($data = $this->memcache->get(STATISTICS_ALL_USER_SHARES)) return @$data['data'][$account_id];
|
||||
if ($data = $this->memcache->get(STATISTICS_ALL_USER_SHARES)) {
|
||||
if (array_key_exists($account_id, $data['data']))
|
||||
return $data['data'][$account_id];
|
||||
// We have no cached value, we return defaults
|
||||
return array('valid' => 0, 'invalid' => 0, 'donate_percent' => 0, 'is_anonymous' => 0);
|
||||
}
|
||||
if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
|
||||
$stmt = $this->mysqli->prepare("
|
||||
SELECT
|
||||
@ -529,6 +534,31 @@ class Statistics {
|
||||
$this->debug->append("Failed to fetch hourly hashrate: " . $this->mysqli->error);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* get user estimated payouts based on share counts
|
||||
* @param aRoundShares array Round shares
|
||||
* @param aUserShares array User shares
|
||||
* @param dDonate double User donation setting
|
||||
* @param bNoFees bool User no-fees option setting
|
||||
* @return aEstimates array User estimations
|
||||
**/
|
||||
public function getUserEstimates($aRoundShares, $aUserShares, $dDonate, $bNoFees) {
|
||||
$this->debug->append("STA " . __METHOD__, 4);
|
||||
// Fetch some user information that we need
|
||||
if (@$aRoundShares['valid'] > 0 && @$aUserShares['valid'] > 0) {
|
||||
$aEstimates['block'] = round(( (int)$aUserShares['valid'] / (int)$aRoundShares['valid'] ) * (float)$this->config['reward'], 8);
|
||||
$bNoFees == 0 ? $aEstimates['fee'] = round(((float)$this->config['fees'] / 100) * (float)$aEstimates['block'], 8) : $aEstimates['fee'] = 0;
|
||||
$aEstimates['donation'] = round((( (float)$dDonate / 100) * ((float)$aEstimates['block'] - (float)$aEstimates['fee'])), 8);
|
||||
$aEstimates['payout'] = round((float)$aEstimates['block'] - (float)$aEstimates['donation'] - (float)$aEstimates['fee'], 8);
|
||||
} else {
|
||||
$aEstimates['block'] = 0;
|
||||
$aEstimates['fee'] = 0;
|
||||
$aEstimates['donation'] = 0;
|
||||
$aEstimates['payout'] = 0;
|
||||
}
|
||||
return $aEstimates;
|
||||
}
|
||||
}
|
||||
|
||||
$statistics = new Statistics($debug, $mysqli, $config, $share, $user, $block, $memcache);
|
||||
|
||||
@ -46,15 +46,18 @@ class User {
|
||||
public function getUserNameByEmail($email) {
|
||||
return $this->getSingle($email, 'username', 'email', 's');
|
||||
}
|
||||
public function getUserId($username) {
|
||||
return $this->getSingle($username, 'id', 'username', 's');
|
||||
public function getUserId($username, $lower=false) {
|
||||
return $this->getSingle($username, 'id', 'username', 's', $lower);
|
||||
}
|
||||
public function getUserEmail($username) {
|
||||
return $this->getSingle($username, 'email', 'username', 's');
|
||||
public function getUserEmail($username, $lower=false) {
|
||||
return $this->getSingle($username, 'email', 'username', 's', $lower);
|
||||
}
|
||||
public function getUserNoFee($id) {
|
||||
return $this->getSingle($id, 'no_fees', 'id');
|
||||
}
|
||||
public function getUserDonatePercent($id) {
|
||||
return $this->getDonatePercent($id);
|
||||
}
|
||||
public function getUserAdmin($id) {
|
||||
return $this->getSingle($id, 'is_admin', 'id');
|
||||
}
|
||||
@ -130,7 +133,7 @@ class User {
|
||||
return false;
|
||||
}
|
||||
if (filter_var($username, FILTER_VALIDATE_EMAIL)) {
|
||||
$this->debug->append("Username is an e-mail", 2);
|
||||
$this->debug->append("Username is an e-mail: $username", 2);
|
||||
if (!$username = $this->getUserNameByEmail($username)) {
|
||||
$this->setErrorMessage("Invalid username or password.");
|
||||
return false;
|
||||
@ -179,9 +182,12 @@ class User {
|
||||
* @param type string Type of value
|
||||
* @return array Return result
|
||||
**/
|
||||
private function getSingle($value, $search='id', $field='id', $type="i") {
|
||||
private function getSingle($value, $search='id', $field='id', $type="i", $lower=false) {
|
||||
$this->debug->append("STA " . __METHOD__, 4);
|
||||
$stmt = $this->mysqli->prepare("SELECT $search FROM $this->table WHERE $field = ? LIMIT 1");
|
||||
$sql = "SELECT $search FROM $this->table WHERE";
|
||||
$lower ? $sql .= " LOWER($field) = LOWER(?)" : $sql .= " $field = ?";
|
||||
$sql .= " LIMIT 1";
|
||||
$stmt = $this->mysqli->prepare($sql);
|
||||
if ($this->checkStmt($stmt)) {
|
||||
$stmt->bind_param($type, $value);
|
||||
$stmt->execute();
|
||||
@ -388,16 +394,13 @@ class User {
|
||||
$this->debug->append("STA " . __METHOD__, 4);
|
||||
$user = array();
|
||||
$password_hash = $this->getHash($password);
|
||||
$stmt = $this->mysqli->prepare("SELECT username, id, is_admin FROM $this->table WHERE username=? AND pass=? LIMIT 1");
|
||||
if ($this->checkStmt($stmt)) {
|
||||
$stmt->bind_param('ss', $username, $password_hash);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($row_username, $row_id, $row_admin);
|
||||
$stmt = $this->mysqli->prepare("SELECT username, id, is_admin FROM $this->table WHERE LOWER(username) = LOWER(?) AND pass = ? LIMIT 1");
|
||||
if ($this->checkStmt($stmt) && $stmt->bind_param('ss', $username, $password_hash) && $stmt->execute() && $stmt->bind_result($row_username, $row_id, $row_admin)) {
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
// Store the basic login information
|
||||
$this->user = array('username' => $row_username, 'id' => $row_id, 'is_admin' => $row_admin);
|
||||
return $username === $row_username;
|
||||
return strtolower($username) === strtolower($row_username);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -562,7 +565,7 @@ class User {
|
||||
$username_clean = strip_tags($username);
|
||||
|
||||
if ($this->checkStmt($stmt) && $stmt->bind_param('sssssi', $username_clean, $password_hash, $email1, $pin_hash, $apikey_hash, $is_locked) && $stmt->execute()) {
|
||||
if (! $this->setting->getValue('accounts_confirm_email_enabled') && $is_admin != 1) {
|
||||
if (! $this->setting->getValue('accounts_confirm_email_disabled') && $is_admin != 1) {
|
||||
if ($token = $this->token->createToken('confirm_email', $stmt->insert_id)) {
|
||||
$aData['username'] = $username_clean;
|
||||
$aData['token'] = $token;
|
||||
@ -638,20 +641,27 @@ class User {
|
||||
$this->serErrorMessage("Username must not be empty");
|
||||
return false;
|
||||
}
|
||||
if (!$aData['email'] = $this->getUserEmail($username)) {
|
||||
if (filter_var($username, FILTER_VALIDATE_EMAIL)) {
|
||||
$this->debug->append("Username is an e-mail: $username", 2);
|
||||
if (!$username = $this->getUserNameByEmail($username)) {
|
||||
$this->setErrorMessage("Invalid username or password.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!$aData['email'] = $this->getUserEmail($username, true)) {
|
||||
$this->setErrorMessage("Unable to find a mail address for user $username");
|
||||
return false;
|
||||
}
|
||||
if (!$aData['token'] = $this->token->createToken('password_reset', $this->getUserId($username))) {
|
||||
if (!$aData['token'] = $this->token->createToken('password_reset', $this->getUserId($username, true))) {
|
||||
$this->setErrorMessage('Unable to setup token for password reset');
|
||||
return false;
|
||||
}
|
||||
$aData['username'] = $username;
|
||||
$aData['username'] = $this->getUserName($this->getUserId($username, true));
|
||||
$aData['subject'] = 'Password Reset Request';
|
||||
if ($this->mail->sendMail('password/reset', $aData)) {
|
||||
return true;
|
||||
} else {
|
||||
$this->setErrorMessage("Unable to send mail to your address");
|
||||
$this->setErrorMessage('Unable to send mail to your address');
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
@ -37,6 +37,7 @@ $statistics->setGetCache(true);
|
||||
// Use caches for this one
|
||||
$aUserRoundShares = $statistics->getUserShares($user_id);
|
||||
$aRoundShares = $statistics->getRoundShares();
|
||||
$aEstimates = $statistics->getUserEstimates($aRoundShares, $aUserRoundShares, $user->getUserDonatePercent($user_id), $user->getUserNoFee($user_id));
|
||||
|
||||
// Apply pool modifiers
|
||||
$dPersonalHashrateAdjusted = $dPersonalHashrate * $dPersonalHashrateModifier;
|
||||
@ -46,7 +47,7 @@ $dNetworkHashrateAdjusted = $dNetworkHashrate / 1000 * $dNetworkHashrateModifier
|
||||
// Output JSON format
|
||||
$data = array(
|
||||
'raw' => array( 'personal' => array( 'hashrate' => $dPersonalHashrate ), 'pool' => array( 'hashrate' => $dPoolHashrate ), 'network' => array( 'hashrate' => $dNetworkHashrate / 1000 ) ),
|
||||
'personal' => array ( 'hashrate' => $dPersonalHashrateAdjusted, 'sharerate' => $dPersonalSharerate, 'shares' => $aUserRoundShares, 'balance' => $transaction->getBalance($user_id)),
|
||||
'personal' => array ( 'hashrate' => $dPersonalHashrateAdjusted, 'sharerate' => $dPersonalSharerate, 'shares' => $aUserRoundShares, 'balance' => $transaction->getBalance($user_id), 'estimates' => $aEstimates),
|
||||
'pool' => array( 'hashrate' => $dPoolHashrateAdjusted, 'shares' => $aRoundShares ),
|
||||
'network' => array( 'hashrate' => $dNetworkHashrateAdjusted, 'difficulty' => $dDifficulty, 'block' => $iBlock ),
|
||||
);
|
||||
|
||||
@ -128,17 +128,11 @@ if (@$_SESSION['USERDATA']['id']) {
|
||||
switch ($config['payout_system']) {
|
||||
case 'prop' || 'pplns':
|
||||
// Some estimations
|
||||
if (@$aRoundShares['valid'] > 0) {
|
||||
$aGlobal['userdata']['est_block'] = round(( (int)$aGlobal['userdata']['shares']['valid'] / (int)$aRoundShares['valid'] ) * (float)$config['reward'], 8);
|
||||
$aGlobal['userdata']['no_fees'] == 0 ? $aGlobal['userdata']['est_fee'] = round(((float)$config['fees'] / 100) * (float)$aGlobal['userdata']['est_block'], 8) : $aGlobal['userdata']['est_fee'] = 0;
|
||||
$aGlobal['userdata']['est_donation'] = round((( (float)$aGlobal['userdata']['donate_percent'] / 100) * ((float)$aGlobal['userdata']['est_block'] - (float)$aGlobal['userdata']['est_fee'])), 8);
|
||||
$aGlobal['userdata']['est_payout'] = round((float)$aGlobal['userdata']['est_block'] - (float)$aGlobal['userdata']['est_donation'] - (float)$aGlobal['userdata']['est_fee'], 8);
|
||||
} else {
|
||||
$aGlobal['userdata']['est_block'] = 0;
|
||||
$aGlobal['userdata']['est_fee'] = 0;
|
||||
$aGlobal['userdata']['est_donation'] = 0;
|
||||
$aGlobal['userdata']['est_payout'] = 0;
|
||||
}
|
||||
$aEstimates = $statistics->getUserEstimates($aRoundShares, $aGlobal['userdata']['shares'], $aGlobal['userdata']['donate_percent'], $aGlobal['userdata']['no_fees']);
|
||||
$aGlobal['userdata']['est_block'] = $aEstimates['block'];
|
||||
$aGlobal['userdata']['est_fee'] = $aEstimates['fee'];
|
||||
$aGlobal['userdata']['est_donation'] = $aEstimates['donation'];
|
||||
$aGlobal['userdata']['est_payout'] = $aEstimates['payout'];
|
||||
case 'pplns':
|
||||
$aGlobal['pplns']['target'] = $config['pplns']['shares']['default'];
|
||||
if ($aLastBlock = $block->getLast()) {
|
||||
|
||||
@ -40,11 +40,13 @@ background: #222222 url(../images/header_bg.png) repeat-x;
|
||||
}
|
||||
|
||||
header#header h1.site_title, header#header h2.section_title {
|
||||
white-space: nowrap;
|
||||
float: left;
|
||||
margin: 0;
|
||||
margin-right: 2.8%;
|
||||
padding-right: 1.8%;
|
||||
font-size: 22px;
|
||||
display: block;
|
||||
width: 23%;
|
||||
display: inline-block;
|
||||
height: 55px;
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
@ -63,7 +65,6 @@ header#header h2.section_title {
|
||||
text-align: left;
|
||||
text-indent: 4.5%;
|
||||
text-transform: uppercase;
|
||||
width: 68%;
|
||||
background: url(../images/header_shadow.png) no-repeat left top;
|
||||
}
|
||||
|
||||
@ -346,7 +347,7 @@ background: #ffffff;
|
||||
|
||||
#main .module header h3 {
|
||||
display: block;
|
||||
width: 60%;
|
||||
width: 90%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
@ -649,6 +650,15 @@ padding: 1% 0%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
fieldset a {
|
||||
height: 20px;
|
||||
padding-left: 10px;
|
||||
display: block;
|
||||
float: left;
|
||||
width: 92%;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
fieldset label {
|
||||
display: block;
|
||||
float: left;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
10
public/templates/mail/notifications/error.tpl
Normal file
10
public/templates/mail/notifications/error.tpl
Normal file
@ -0,0 +1,10 @@
|
||||
<html>
|
||||
<body>
|
||||
<h1>An error occured!</h1>
|
||||
<p>This should never happen. Please review the error output below.</p>
|
||||
|
||||
{foreach from=$DATA key=text item=message}
|
||||
{if $text != 'email' && $text != 'subject'}
|
||||
<p>{$text}: {$message}</p>
|
||||
{/if}
|
||||
{/foreach}
|
||||
@ -2,7 +2,7 @@
|
||||
<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>If you have an email set for your account, enter your username or email address to get your password reset</p>
|
||||
<p><input type="text" value="{$smarty.post.username|default:""}" name="username" required><input class="submit small" type="submit" value="Reset"></p>
|
||||
</form>
|
||||
{include file="global/block_footer.tpl"}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<td class="leftheader">Current Active Workers</td>
|
||||
<td>{$GLOBAL.workers}</td>
|
||||
</tr>
|
||||
{if $GLOBAL.website.blockexplorer.url}
|
||||
{if ! $GLOBAL.website.blockexplorer.disabled}
|
||||
<tr>
|
||||
<td class="leftheader">Next Network Block</td>
|
||||
<td>{$CURRENTBLOCK + 1} <font size="1"> (Current: <a href="{$GLOBAL.website.blockexplorer.url}{$CURRENTBLOCKHASH}" target="_new">{$CURRENTBLOCK})</a></font></td>
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<div class="module_content">
|
||||
<p>If you have an email set for your account, enter your username to get your password reset</p>
|
||||
<fieldset>
|
||||
<label>Username</label>
|
||||
<label>Username or E-Mail</label>
|
||||
<input type="text" name="username" value="{$smarty.post.username|default:""}" size="22" maxlength="20" required>
|
||||
</fieldset>
|
||||
<div class="clear"></div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user