mirror of
https://github.com/LBRYFoundation/pool.git
synced 2025-09-23 20:00:42 +00:00
payouts: detect wallet send timeouts, using tx field
also create a command line script to find the missing db payouts.
This commit is contained in:
parent
c888a6597f
commit
9a8c40c71b
2 changed files with 293 additions and 21 deletions
193
web/yaamp/commands/PayoutCommand.php
Normal file
193
web/yaamp/commands/PayoutCommand.php
Normal file
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
/**
|
||||
* PayoutCommand is a console command :
|
||||
* - check: compare wallet's chain history and database payouts
|
||||
*
|
||||
* To use this command, enter the following on the command line:
|
||||
* <pre>
|
||||
* yiimp payout check LYB
|
||||
* </pre>
|
||||
*
|
||||
* @property string $help The command description.
|
||||
*
|
||||
*/
|
||||
class PayoutCommand extends CConsoleCommand
|
||||
{
|
||||
protected $basePath;
|
||||
|
||||
/**
|
||||
* Execute the action.
|
||||
* @param array $args command line parameters specific for this command
|
||||
* @return integer non zero application exit code after printing help
|
||||
*/
|
||||
public function run($args)
|
||||
{
|
||||
$runner=$this->getCommandRunner();
|
||||
$commands=$runner->commands;
|
||||
|
||||
$root = realpath(Yii::app()->getBasePath().DIRECTORY_SEPARATOR.'..');
|
||||
$this->basePath = str_replace(DIRECTORY_SEPARATOR, '/', $root);
|
||||
|
||||
$command = arraySafeVal($args,0);
|
||||
$coinsym = arraySafeVal($args,1);
|
||||
|
||||
if (!isset($args[1]) || empty($coinsym)) {
|
||||
|
||||
echo "Yiimp payout command\n";
|
||||
echo "Usage: yiimp payout check <symbol>\n";
|
||||
return 1;
|
||||
|
||||
} elseif ($command == 'check') {
|
||||
|
||||
$nbUpdated = $this->checkPayouts($coinsym);
|
||||
echo "total updated: $nbUpdated\n";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the command description.
|
||||
* @return string the command description.
|
||||
*/
|
||||
public function getHelp()
|
||||
{
|
||||
return parent::getHelp().'payout check';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check in a wallet completed payouts and missing/extra ones
|
||||
*/
|
||||
public function checkPayouts($symbol)
|
||||
{
|
||||
$nbUpdated = 0; $nbCreated = 0;
|
||||
|
||||
$coin = getdbosql('db_coins', "symbol=:symbol", array(':symbol'=>$symbol));
|
||||
if (!$coin) {
|
||||
echo "wallet $symbol not found!\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get users using the coin...
|
||||
$users = dbolist("SELECT DISTINCT A.id AS userid, A.username AS username ".
|
||||
"FROM accounts A LEFT JOIN coins C ON C.id = A.coinid ".
|
||||
"WHERE A.coinid={$coin->id} AND A.balance > 0.0"
|
||||
);
|
||||
$ids = array();
|
||||
foreach ($users as $uids) {
|
||||
$uid = (int) $uids['userid'];
|
||||
$ids[$uid] = $uids['username'];
|
||||
}
|
||||
if (empty($ids))
|
||||
return 0;
|
||||
|
||||
// Get their payouts
|
||||
$dbPayouts = new db_payouts;
|
||||
$payouts = $dbPayouts->findAll(array(
|
||||
'condition'=>"account_id IN (".implode(',',array_keys($ids)).')',
|
||||
'order'=>'time DESC',
|
||||
));
|
||||
|
||||
if (empty($payouts))
|
||||
return 0;
|
||||
|
||||
$remote = new Bitcoin($coin->rpcuser, $coin->rpcpasswd, $coin->rpchost, $coin->rpcport);
|
||||
$rawtxs = $remote->listtransactions('', 25000);
|
||||
|
||||
foreach ($ids as $uid => $user_addr)
|
||||
{
|
||||
$totalsent = 0.0; $totalpayouts = 0.0;
|
||||
// search the last time the user balance was 0
|
||||
$since = dboscalar("SELECT time FROM balanceuser WHERE userid=:uid AND balance <= 0 ORDER BY time DESC",
|
||||
array(':uid'=>$uid)
|
||||
);
|
||||
|
||||
//$since = 0;
|
||||
if (empty($since)) $since = time()-(7*24*3600);
|
||||
|
||||
echo "User was at 0 at this date: ".strftime('%F %c', $since)."\n";
|
||||
|
||||
// Get their payouts
|
||||
$payouts = $dbPayouts->findAll(array(
|
||||
'condition'=>"account_id=$uid AND time > ".intval($since),
|
||||
'order'=>'time DESC',
|
||||
));
|
||||
|
||||
// filter user raw transactions
|
||||
foreach ($rawtxs as $ntx => $tx) {
|
||||
$time = arraySafeVal($tx,'time');
|
||||
if ($time < $since) continue;
|
||||
$match = false;
|
||||
if (arraySafeVal($tx,'category') == 'send' && arraySafeVal($tx,'address') == $user_addr) {
|
||||
$amount = abs(arraySafeVal($tx,'amount'));
|
||||
$txid = arraySafeVal($tx,'txid');
|
||||
$totalsent += $amount + (float) abs(arraySafeVal($tx,'fee'));
|
||||
|
||||
foreach ($payouts as $payout) {
|
||||
if ($payout->tx == $txid && round($payout->amount) == round($amount)) {
|
||||
$totalpayouts += $amount + (float) abs(arraySafeVal($tx,'fee'));
|
||||
$match = true;
|
||||
if (arraySafeVal($tx, 'confirmations') > 5) {
|
||||
$payout->completed = 1;
|
||||
$nbUpdated += $payout->save();
|
||||
//echo "tx {$payout->tx} {$payout->amount} $symbol confirmed\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$match) {
|
||||
/*
|
||||
// uncomment to do it manually on insufficient founds (need manual checks)
|
||||
$payout = new db_payouts;
|
||||
$payout->account_id = $uid;
|
||||
$payout->tx = $txid;
|
||||
$payout->time = $time;
|
||||
$payout->completed = 1;
|
||||
$payout->amount = $amount;
|
||||
$payout->fee = abs(arraySafeVal($tx,'fee'));
|
||||
$nbCreated += $payout->save();
|
||||
$user = getdbo('db_accounts', $uid);
|
||||
if ($user) {
|
||||
$user->balance = floatval($user->balance) - $amount;
|
||||
dborun("UPDATE balanceuser SET balance = (balance - $amount) WHERE userid=$uid AND time>=$time");
|
||||
$user->save();
|
||||
}
|
||||
$match = true;
|
||||
*/
|
||||
$time = strftime('%F %c', $time);
|
||||
echo "extra user tx $txid $time $amount $symbol\n";
|
||||
}
|
||||
}
|
||||
//if (0 && !$match && arraySafeVal($tx,'category') == 'send') {
|
||||
// $time = strftime('%F %c', $time);
|
||||
// $txid = arraySafeVal($tx,'txid');
|
||||
// $amount = abs(arraySafeVal($tx,'amount'));
|
||||
// $address = arraySafeVal($tx,'address');
|
||||
// echo "unknown tx $txid $time $amount $symbol to $address\n";
|
||||
//}
|
||||
}
|
||||
|
||||
// get the extra payouts
|
||||
$payouts = $dbPayouts->findAll(array(
|
||||
'condition'=>"completed=0 AND account_id=$uid AND time > ".intval($since),
|
||||
'order'=>'time DESC',
|
||||
));
|
||||
|
||||
$totaldiff = $totalsent - $totalpayouts;
|
||||
if ($totaldiff > 0.0) {
|
||||
// search payouts not in db
|
||||
foreach ($payouts as $payout) {
|
||||
$time = strftime('%F %c', $payout->time);
|
||||
echo "extra db tx: $time {$payout->tx} {$payout->amount} $symbol\n";
|
||||
}
|
||||
}
|
||||
echo "Total sent to $user_addr: $totalsent (real) $totalpayouts (db) -> Diff $totaldiff $symbol\n";
|
||||
}
|
||||
|
||||
if ($nbCreated)
|
||||
echo "$nbUpdated payouts confirmed, $nbUpdated payouts created\n";
|
||||
else if ($nbUpdated)
|
||||
echo "$nbUpdated payouts confirmed\n";
|
||||
return $nbCreated;
|
||||
}
|
||||
|
||||
}
|
|
@ -84,10 +84,21 @@ function BackendCoinPayments($coin)
|
|||
return;
|
||||
}
|
||||
|
||||
if($info['balance']-0.001 < $total_to_pay)
|
||||
$coef = 1.0;
|
||||
if($info['balance']-0.001 < $total_to_pay && $coin->symbol!='BTC')
|
||||
{
|
||||
debuglog("$coin->symbol wallet insufficient funds for payment {$info['balance']} < $total_to_pay");
|
||||
return;
|
||||
$msg = "$coin->symbol: insufficient funds for payment {$info['balance']} < $total_to_pay!";
|
||||
debuglog($msg);
|
||||
send_email_alert('payouts', "$coin->symbol payout problem detected", $msg);
|
||||
|
||||
$coef = 0.5; // so pay half for now...
|
||||
$total_to_pay = $total_to_pay * $coef;
|
||||
foreach ($addresses as $key => $val) {
|
||||
$addresses[$key] = $val * $coef;
|
||||
}
|
||||
// still not possible, skip payment
|
||||
if ($info['balance']-0.001 < $total_to_pay)
|
||||
return;
|
||||
}
|
||||
|
||||
if($coin->symbol=='BTC')
|
||||
|
@ -116,15 +127,9 @@ function BackendCoinPayments($coin)
|
|||
}
|
||||
}
|
||||
|
||||
debuglog("paying $total_to_pay $coin->symbol min is $min");
|
||||
|
||||
$tx = $remote->sendmany('', $addresses, 1, '');
|
||||
if(!$tx)
|
||||
{
|
||||
debuglog($remote->error);
|
||||
return;
|
||||
}
|
||||
debuglog("paying $total_to_pay {$coin->symbol}");
|
||||
|
||||
$payouts = array();
|
||||
foreach($users as $user)
|
||||
{
|
||||
$user = getdbo('db_accounts', $user->id);
|
||||
|
@ -133,21 +138,95 @@ function BackendCoinPayments($coin)
|
|||
$payout = new db_payouts;
|
||||
$payout->account_id = $user->id;
|
||||
$payout->time = time();
|
||||
$payout->amount = bitcoinvaluetoa($user->balance);
|
||||
$payout->amount = bitcoinvaluetoa($user->balance*$coef);
|
||||
$payout->fee = 0;
|
||||
$payout->tx = $tx;
|
||||
$payout->save();
|
||||
|
||||
$user->balance = 0;
|
||||
$user->save();
|
||||
if ($payout->save()) {
|
||||
$payouts[$payout->id] = $user->id;
|
||||
|
||||
$user->balance = bitcoinvaluetoa(floatval($user->balance) - (floatval($user->balance)*$coef));
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
|
||||
// sometimes the wallet take too much time to answer, so use tx field to double check
|
||||
set_time_limit(120);
|
||||
|
||||
$tx = $remote->sendmany('', $addresses, 1, '');
|
||||
if(!$tx) {
|
||||
debuglog($remote->error);
|
||||
return;
|
||||
}
|
||||
|
||||
// save processed payouts (tx)
|
||||
foreach($payouts as $id => $uid) {
|
||||
$payout = getdbo('db_payouts', $id);
|
||||
if ($payout && $payout->id == $id) {
|
||||
$payout->tx = $tx;
|
||||
$payout->save();
|
||||
} else {
|
||||
debuglog("payout $id for $uid not found!");
|
||||
}
|
||||
}
|
||||
|
||||
debuglog("payment done");
|
||||
sleep(5);
|
||||
|
||||
sleep(2);
|
||||
|
||||
// Search for previous payouts not executed (no tx)
|
||||
$addresses = array(); $payouts = array();
|
||||
$mailmsg = '';
|
||||
foreach($users as $user)
|
||||
{
|
||||
$amount_failed = 0.0;
|
||||
$failed = getdbolist('db_payouts', "account_id=:uid AND IFNULL(tx,'') = '' ORDER BY time", array(':uid'=>$user->id));
|
||||
if (!empty($failed)) {
|
||||
foreach ($failed as $payout) {
|
||||
$amount_failed += floatval($payout->amount);
|
||||
$payout->delete();
|
||||
}
|
||||
if ($amount_failed > 0.0) {
|
||||
debuglog("Found failed payment(s) for {$user->username}, $amount_failed {$coin->symbol}!");
|
||||
|
||||
$payout = new db_payouts;
|
||||
$payout->account_id = $user->id;
|
||||
$payout->time = time();
|
||||
$payout->amount = $amount_failed;
|
||||
$payout->fee = 0;
|
||||
if ($payout->save())
|
||||
$payouts[$payout->id] = $user->id;
|
||||
|
||||
$mailmsg .= "{$amount_failed} {$coin->symbol} to {$user->username} - user id {$user->id}\n";
|
||||
|
||||
$addresses[$user->username] = $amount_failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// redo failed payouts
|
||||
if (!empty($addresses))
|
||||
{
|
||||
$tx = $remote->sendmany('', $addresses, 1, '');
|
||||
|
||||
if(empty($tx)) {
|
||||
debuglog($remote->error);
|
||||
send_email_alert('payouts', "{$coin->symbol} payout problems detected", $mailmsg);
|
||||
} else {
|
||||
foreach ($payouts as $id => $uid) {
|
||||
$payout = getdbo('db_payouts', $id);
|
||||
if ($payout && $payout->id == $id) {
|
||||
$payout->tx = $tx;
|
||||
$payout->save();
|
||||
} else {
|
||||
debuglog("payout retry $id for $uid not found!");
|
||||
}
|
||||
}
|
||||
|
||||
$mailmsg .= "\ntxid $tx\n";
|
||||
send_email_alert('payouts', "{$coin->symbol} payout problems resolved", $mailmsg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue