-
Notifications
You must be signed in to change notification settings - Fork 0
/
runStats-bitcore.php
500 lines (394 loc) · 16.5 KB
/
runStats-bitcore.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
<?php
// Connect mysql
$conn = include_once('config-bitcore.php');
// RPC wallet
include_once('wallet-rpc.php');
include_once('easybitcoin.php');
/**
* Helper class
* @var minerHelper.php
*/
include_once('web/minerHelper.php');
$ip_check = minerHelper::getClientIp();
$sapi_type = php_sapi_name();
if(substr($sapi_type, 0, 3) == 'cli' || empty($_SERVER['REMOTE_ADDR'])) {
print "shell current time: " . time() . "\n";
} else {
print "webserver - your IP: " . $ip_check;
exit();
}
// Pool total hashrate
updatePoolHashrate($conn);
// Update earnings
updateEarnings($conn);
// Send payouts
sendPayouts($conn, 1425);
// Update overall network hashrates and store in Redis cache
updateNetworkHashrate($conn, 1425);
function updatePoolHashrate($db) {
$t = time() - 2 * 60;
// Delete old connections
$stmt = $db->prepare("DELETE FROM stratums WHERE time < :time");
$stmt->execute([':time' => $t]);
// Delete old workers
$stmt = $db->prepare("DELETE FROM workers WHERE pid NOT IN (SELECT pid FROM stratums)");
$stmt->execute();
//
// Long term stats (pool and individual users)
//
$tm = floor(time() / 60 / 60) * 60 * 60;
foreach (minerHelper::miner_getAlgos() as $algo_key => $algo) {
$check_shares = $db->prepare("SELECT count(*) AS total_share_count FROM shares WHERE valid = 1 AND coinid = :coin_id");
$check_shares->execute([':coin_id' => $algo_key]);
// How many shares are submitted
$tt_share_check = $check_shares->fetch(PDO::FETCH_ASSOC);
// Add stats entry if we have at least 10 entries from each active miner (when block is found the shares are reset causing stats issues)
$active_miners = minerHelper::countMiners($db, $algo_key)['total_count'];
if ($tt_share_check['total_share_count'] > ($active_miners * 7)) {
$pool_rate = minerHelper::getPoolHashrate($db, $algo);
// Insert total pool hashrate stats
$stmt = $db->prepare("INSERT INTO hashstats(time, hashrate, earnings, algo) VALUES(:time, :hashrate, :earnings, :algo)");
$stmt->execute([':time' => $t, ':hashrate' => $pool_rate['hashrate'], ':earnings' => null, ':algo' => $algo]);
// Individual user stats
$user_hashstats = minerHelper::getUserPoolHashrate($db, $algo);
foreach ($user_hashstats as $user_hash_data) {
$stmt = $db->prepare("INSERT INTO hashuser(userid, time, hashrate, hashrate_bad, algo) VALUES(:userid, :time, :hashrate, :hashrate_bad, :algo)");
$stmt->execute([':userid' => $user_hash_data['userid'], ':time' => $t, ':hashrate' => $user_hash_data['hashrate'], ':hashrate_bad' => 0, ':algo' => $algo]);
}
// @TODO -> Store pool and user earnings too??
print minerHelper::Itoa2($pool_rate['hashrate']) . 'h/s' . "\n";
print 'Done stats calc' . "\n";
} else {
print 'Not inserting stats' . "\n";
}
}
}
/**
* Update earnings when block is found
* @param $db
*/
function updateEarnings($db) {
print "version: 1.9 - network total hashrate" . "\n";
// Get all new blocks
$stmt = $db->prepare("SELECT * FROM blocks WHERE category = :category ORDER by time");
$stmt->execute([
':category' => 'new'
]);
$new_blocks = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach($new_blocks as $db_block) {
// Check for the coin details
$stmt = $db->prepare("SELECT * FROM coins WHERE id = :coin_id");
$stmt->execute([
':coin_id' => $db_block['coin_id']
]);
$coin_info = $stmt->fetch(PDO::FETCH_OBJ);
// New Wallet RPC call
$remote_check = new WalletRPC($coin_info);
// Get more detailed info about the block we found
$block = $remote_check->getblock($db_block['blockhash']);
// Check for block transaction
$block_tx = $remote_check->gettransaction($block['tx'][0]);
// If we found the transaction
if (!empty($block_tx)) {
// Get the reward from the block we found
$reward = $block_tx['amount'];
// Yet immature tx
if ($reward <= 0) {
// Check for immature transaction
if (!empty($block_tx['details'])) {
if (!empty($block_tx['details'][0]['amount']) && !empty($block_tx['details'][0]['category']) && $block_tx['details'][0]['category'] == 'immature') {
print 'Processing immature block';
$reward = $block_tx['details'][0]['amount'];
}
}
}
// We continue if reward is set, when the block is found the reward is not set for few seconds
if ($reward > 0) {
// Remove not valid shares first
$stmt = $db->prepare("DELETE FROM shares WHERE coinid != :coin_id");
$stmt->execute([':coin_id' => 1425]);
// How much is the block reward
$db_block['reward'] = $reward;
// Save tx hash
$db_block['tx_hash'] = $block_tx['txid'];
$stmt = $db->prepare("SELECT SUM(difficulty) as total_hash FROM shares WHERE valid = :valid AND algo = :coin_id");
$stmt->execute([':coin_id' => minerHelper::miner_getAlgos()[$db_block['coin_id']], ':valid' => 1]);
$total_hash_power = $stmt->fetch(PDO::FETCH_ASSOC);
print 'Total hash power: ' . $total_hash_power['total_hash'] . "\n";
$stmt = $db->prepare("SELECT userid, SUM(difficulty) AS total_user_hash FROM shares WHERE valid = :valid AND algo=:coin_id GROUP BY userid");
$stmt->execute([':coin_id' => minerHelper::miner_getAlgos()[$db_block['coin_id']], ':valid' => 1]);
$hash_users = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($hash_users as $hash_user) {
print 'Total hash power user ID: ' . $hash_user['userid'] . ' -- ' . $hash_user['total_user_hash'] . "\n";
// Calculate how much each user will earn
$amount = $reward * $hash_user['total_user_hash'] / $total_hash_power['total_hash'];
print 'Earned: ' . $amount . "\n";
// Earned amount
$amount_earned = minerHelper::takePoolFee($amount, minerHelper::miner_getAlgos()[$db_block['coin_id']]);
print 'Earned - fee deducted: ' . $amount_earned . "\n";
// Get some user related info
$user_data = minerHelper::getAccount($db, $hash_user['userid']);
// Save the earning for each user when block is found
minerHelper::addEarning($db, $user_data, $db_block, $amount_earned);
}
// When all earnings saved set the block from 'new' to 'immature'
// So the other script can trigger, calculate number of confirmations, once confirmed update the earnings to mature
$stmt = $db->prepare("UPDATE blocks SET category = :category, amount = :amount, txhash = :txhash WHERE id = :block_id");
$stmt->execute([':category' => 'immature', ':block_id' => $db_block['id'], ':amount' => $db_block['reward'], ':txhash' => $db_block['tx_hash']]);
$hash_time = time() - 3 * 60;
// Delete shares where we calculated the earnings
$stmt = $db->prepare("DELETE FROM shares WHERE algo = :algo AND coinid = :coin_id AND time < :time_offset");
$stmt->execute([':algo' => minerHelper::miner_getAlgos()[$db_block['coin_id']], ':coin_id' => $db_block['coin_id'], ':time_offset' => $hash_time]);
}
}
// The cron run every minute if more than 1 block is found every minute causing issue so we break after 1 block
break;
}
/////////////////////////////////////////////////
/// //////// Update exisiting blocks /// ////////
///
/// //////// /// ////////
// Update all 'immature' blocks
$stmt = $db->prepare("SELECT * FROM blocks WHERE category = :category ORDER by time");
$stmt->execute([
':category' => 'immature'
]);
$immature_blocks = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach($immature_blocks as $db_block) {
// Check for the coin details
$stmt = $db->prepare("SELECT * FROM coins WHERE id = :coin_id");
$stmt->execute([
':coin_id' => $db_block['coin_id']
]);
$coin_info = $stmt->fetch(PDO::FETCH_OBJ);
// New Wallet RPC call
$remote_check = new WalletRPC($coin_info);
if (!empty($db_block['txhash'])) {
$block_tx = $remote_check->gettransaction($db_block['txhash']);
print 'Confirmations: ' . $block_tx['confirmations'] . "\n";
// Check if the block is confirmed
if ($block_tx['confirmations'] > 100) {
// mature the block
$category = 'mature';
// @TODO
// When mature balance > 0.5 do a payout and deduct user balance
// Total paid -> sum(payouts)
}
else {
$category = 'immature';
}
// Update block confirmations
$stmt = $db->prepare("UPDATE blocks SET confirmations = :confirmations, category = :category WHERE id = :block_id");
$stmt->execute([
':confirmations' => $block_tx['confirmations'],
':block_id' => $db_block['id'],
':category' => $category
]);
}
else {
// check orphan blocks?
print 'empty tx hash -> orphan block?';
}
}
/////////////////////////////////////////////////
/// //////// Update exisiting blocks /// ////////
///
/// //////// /// ////////
// Update all 'mature' blocks
// Mature earnings, calculate user balance
$stmt = $db->prepare("SELECT * FROM blocks WHERE category = :category ORDER by time");
$stmt->execute([
':category' => 'mature'
]);
$mature_blocks = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach($mature_blocks as $db_block) {
print 'Processing block: ' . $db_block['id'] . ' -- ' . $db_block['height'] . "\n";
$stmt = $db->prepare("SELECT userid, SUM(amount) AS immature_balance FROM earnings where blockid = :block_id AND status = -1 GROUP BY userid");
$stmt->execute([
':block_id' => $db_block['id']
]);
// Mature earnings, calculate user balance
$immature_balances = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($immature_balances as $immature_balance) {
print 'user ID: ' . $immature_balance['userid'] . ' -- ' . $immature_balance['immature_balance'] . "\n";
// Update pending user balance
$stmt = $db->prepare("UPDATE accounts SET balance = balance + :balance WHERE id = :userid");
$stmt->execute([
':balance' => $immature_balance['immature_balance'],
':userid' => $immature_balance['userid']
]);
// Mature earnings
$stmt = $db->prepare("UPDATE earnings SET status = 1 WHERE status = -1 AND userid = :userid AND blockid = :block_id");
$stmt->execute([
':userid' => $immature_balance['userid'],
':block_id' => $db_block['id']
]);
}
// Set block category to 'settled' which means the earnings matured for this block and ready to be paid
$stmt = $db->prepare("UPDATE blocks SET category = :category WHERE id = :block_id");
$stmt->execute([
':category' => 'settled',
':block_id' => $db_block['id']
]);
}
}
/**
* Function to check for users with balances pending to payout
* @param $db
*/
function sendPayouts($db, $coin_id = 1425) {
// Check for the coin details
$stmt = $db->prepare("SELECT * FROM coins WHERE id = :coin_id");
$stmt->execute([
':coin_id' => $coin_id
]);
// Get coin data
$coin_info = $stmt->fetch(PDO::FETCH_OBJ);
// Connect to wallet
$remote = new WalletRPC($coin_info);
$min_payout = minerHelper::miner_getMinPayouts()[minerHelper::miner_getAlgos()[$coin_id]];
print 'Wallet min. payout: ' . $min_payout . "\n";
$info = $remote->getinfo();
if(!$info) {
print "Send payouts: can't connect to " . $coin_info->symbol . " wallet" . "\n";
return;
}
// Get the accounts which due payout
$stmt = $db->prepare("SELECT * FROM accounts WHERE balance > :min_payout AND coinid = :coin_id ORDER BY balance DESC");
$stmt->execute([
':min_payout' => $min_payout,
':coin_id' => $coin_id
]);
$balances = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($balances as $user_account) {
print 'Send payouts: ' . $user_account['id'] . ' -- ' . $user_account['balance'] . "\n";
// Try to clear the balance
$tx = $remote->sendtoaddress($user_account['username'], round($user_account['balance'], 8));
if(!$tx) {
$error = $remote->error;
print "Send payouts ERROR: " . $error . ' -- ' . $user_account['username'] . ' -- ' . $user_account['balance'];
}
else {
// Add entry about the transaction
$stmt = $db->prepare("INSERT INTO payouts(account_id, time, amount, fee, tx, idcoin) VALUES(:account_id, :time, :amount, :fee, :tx, :idcoin)");
$stmt->execute([
':account_id' => $user_account['id'],
':time' => time(),
':amount' => $user_account['balance'],
':fee' => 0,
':tx' => $tx,
':idcoin' => $coin_id
]);
// Deduct user balance
$stmt = $db->prepare("UPDATE accounts SET balance = balance - :payout WHERE id = :userid");
$stmt->execute([
':payout' => $user_account['balance'],
':userid' => $user_account['id']
]);
}
}
// Check if we need to process extra payouts!
$now = time();
$nextFullHour = date("H", $now + (3600 - $now % 3600));
$nextFullMin = date("i", $now + (60 - $now % 60));
$hours_to_process = ['00', '04', '08', '12', '16', '20'];
$minutes_to_process = ['20', '46'];
if (in_array($nextFullHour, $hours_to_process) && in_array($nextFullMin, $minutes_to_process)) {
print 'Activate extra payouts' . "\n";
// Send extra payouts (above 0.01)
sendExtraPayouts($db, $coin_id, 0.01);
}
else {
print $nextFullHour . ' -- ' . $nextFullMin . "\n";
}
}
/**
* Function to check for users with balances pending to payout
* @param $db
*/
function sendExtraPayouts($db, $coin_id = 1425, $extra_payout = FALSE) {
// Check for the coin details
$stmt = $db->prepare("SELECT * FROM coins WHERE id = :coin_id");
$stmt->execute([
':coin_id' => $coin_id
]);
// Get coin data
$coin_info = $stmt->fetch(PDO::FETCH_OBJ);
// Connect to wallet
$remote = new WalletRPC($coin_info);
$min_payout = minerHelper::miner_getMinPayouts()[minerHelper::miner_getAlgos()[$coin_id]];
print 'Wallet min. payout: ' . $min_payout . "\n";
// Process extra payout
if (!empty($extra_payout)) {
print 'Activating extra payout: ' . $extra_payout . "\n";
$min_payout = $extra_payout;
}
$info = $remote->getinfo();
if(!$info) {
print "Send payouts: can't connect to " . $coin_info->symbol . " wallet" . "\n";
return;
}
// Get the accounts which due payout
$stmt = $db->prepare("SELECT * FROM accounts WHERE balance > :min_payout AND coinid = :coin_id ORDER BY balance DESC LIMIT 0, 300");
$stmt->execute([
':min_payout' => $min_payout,
':coin_id' => $coin_id
]);
$balances = $stmt->fetchAll(PDO::FETCH_ASSOC);
$accounts = [];
foreach ($balances as $user_account) {
$accounts[$user_account['username']] = round($user_account['balance'], 8);
}
// Sendmany transaction if we have tx id continue or throw error
$tx = $remote->sendmany('', $accounts, 1, '');
if(!$tx) {
$error = $remote->error;
print "Send payouts ERROR: " . $error . ' -- ' . json_encode($accounts);
}
else {
foreach ($balances as $user_account) {
print 'Sent payout for: ' . $user_account['id'] . '-- ' . $user_account['username'] . ' -- ' . $user_account['balance'] . "\n";
if(!$tx) {
$error = $remote->error;
print "Send payouts ERROR: " . $error . ' -- ' . json_encode($accounts);
}
else {
// Add entry about the transaction
$stmt = $db->prepare("INSERT INTO payouts(account_id, time, amount, fee, tx, idcoin) VALUES(:account_id, :time, :amount, :fee, :tx, :idcoin)");
$stmt->execute([
':account_id' => $user_account['id'],
':time' => time(),
':amount' => $user_account['balance'],
':fee' => 0,
':tx' => $tx,
':idcoin' => $coin_id
]);
// Deduct user balance
$stmt = $db->prepare("UPDATE accounts SET balance = 0 WHERE id = :userid");
$stmt->execute([
':userid' => $user_account['id']
]);
}
}
}
}
/**
* Update overall network hashrate for the coin and stores in Redis cache
*/
function updateNetworkHashrate($db, $coin_id = 1425) {
$stmt = $db->prepare("SELECT * FROM coins WHERE id = :coin_id");
$stmt->execute([
':coin_id' => $coin_id
]);
$coin_info = $stmt->fetch(PDO::FETCH_OBJ);
// New Wallet RPC call
$remote_check = new WalletRPC($coin_info);
$network_info = $remote_check->getmininginfo();
if (!empty($network_info)) {
$redis = include_once(__DIR__ . '/config-redis.php');
$data = [];
$data['hashrate_gh'] = $network_info['networkhashps'] / 1000 / 1000 / 1000;
$data['difficulty'] = $network_info['difficulty'];
$redis->set('network_info_' . $coin_id, json_encode($data));
}
}