This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | $defflip = (!cfip()) ? exit(header('HTTP/1.1 401 Unauthorized')) : 1; |
||
3 | |||
4 | class Share Extends Base { |
||
5 | protected $table = 'shares'; |
||
6 | protected $tableArchive = 'shares_archive'; |
||
7 | private $oUpstream; |
||
8 | private $iLastUpstreamId; |
||
9 | // This defines each share |
||
10 | public $rem_host, $username, $our_result, $upstream_result, $reason, $solution, $time, $difficulty; |
||
11 | |||
12 | /** |
||
13 | * Fetch archive tables name for this class |
||
14 | * @param none |
||
15 | * @return data string Table name |
||
16 | **/ |
||
17 | public function getArchiveTableName() { |
||
18 | return $this->tableArchive; |
||
19 | } |
||
20 | |||
21 | /** |
||
22 | * Fetch a single share by ID |
||
23 | * @param id int Share ID |
||
24 | * @return array Share data |
||
25 | **/ |
||
26 | public function getShareById($id) { |
||
27 | return $this->getSingleAssoc($id); |
||
28 | } |
||
29 | |||
30 | /** |
||
31 | * Update an entire shares data |
||
32 | **/ |
||
33 | public function updateShareById($id, $data) { |
||
34 | $this->debug->append("STA " . __METHOD__, 4); |
||
35 | $sql = "UPDATE $this->table SET"; |
||
36 | $start = true; |
||
37 | // Remove ID column |
||
38 | unset($data['id']); |
||
39 | foreach ($data as $column => $value) { |
||
40 | $start == true ? $sql .= " $column = ? " : $sql .= ", $column = ?"; |
||
0 ignored issues
–
show
|
|||
41 | $start = false; |
||
42 | switch($column) { |
||
43 | case 'difficulty': |
||
44 | $this->addParam('d', $value); |
||
45 | break; |
||
46 | default: |
||
47 | $this->addParam('s', $value); |
||
48 | break; |
||
49 | } |
||
50 | } |
||
51 | $sql .= " WHERE id = ? LIMIT 1"; |
||
52 | $this->addParam('i', $id); |
||
53 | $stmt = $this->mysqli->prepare($sql); |
||
54 | View Code Duplication | if ($this->checkStmt($stmt) && call_user_func_array( array($stmt, 'bind_param'), $this->getParam()) && $stmt->execute()) |
|
55 | return true; |
||
56 | return $this->sqlError(); |
||
57 | } |
||
58 | |||
59 | /** |
||
60 | * Get last inserted Share ID from Database |
||
61 | * Used for PPS calculations without moving to archive |
||
62 | **/ |
||
63 | public function getLastInsertedShareId() { |
||
64 | $stmt = $this->mysqli->prepare("SELECT MAX(id) AS id FROM $this->table"); |
||
65 | if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) |
||
66 | return $result->fetch_object()->id; |
||
67 | return $this->sqlError(); |
||
68 | } |
||
69 | |||
70 | /** |
||
71 | * Get all valid shares for this round |
||
72 | * @param previous_upstream int Previous found share accepted by upstream to limit results |
||
73 | * @param current_upstream int Current upstream accepted share |
||
74 | * @return data int Total amount of counted shares |
||
75 | **/ |
||
76 | public function getRoundShares($previous_upstream=0, $current_upstream) { |
||
77 | $stmt = $this->mysqli->prepare("SELECT |
||
78 | IFNULL(SUM(IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty)), 0) AS total |
||
79 | FROM $this->table AS s |
||
80 | LEFT JOIN " . $this->user->getTableName() . " AS a |
||
81 | ON a.username = SUBSTRING_INDEX( s.username , '.', 1 ) |
||
82 | WHERE s.id > ? AND s.id <= ? AND s.our_result = 'Y' AND a.is_locked != 2 |
||
83 | "); |
||
84 | if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $previous_upstream, $current_upstream) && $stmt->execute() && $result = $stmt->get_result()) |
||
85 | return $result->fetch_object()->total; |
||
86 | return $this->sqlError(); |
||
87 | } |
||
88 | |||
89 | /** |
||
90 | * Fetch all shares grouped by accounts to count share per account |
||
91 | * @param previous_upstream int Previous found share accepted by upstream to limit results |
||
92 | * @param current_upstream int Current upstream accepted share |
||
93 | * @param limit int Limit to this amount of shares for PPLNS |
||
94 | * @return data array username, valid and invalid shares from account |
||
95 | **/ |
||
96 | public function getSharesForAccounts($previous_upstream=0, $current_upstream) { |
||
97 | $stmt = $this->mysqli->prepare(" |
||
98 | SELECT |
||
99 | a.id, |
||
100 | SUBSTRING_INDEX( s.username , '.', 1 ) as username, |
||
101 | a.no_fees AS no_fees, |
||
102 | IFNULL(SUM(IF(our_result='Y', IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0) AS valid, |
||
103 | IFNULL(SUM(IF(our_result='N', IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0) AS invalid |
||
104 | FROM $this->table AS s |
||
105 | LEFT JOIN " . $this->user->getTableName() . " AS a |
||
106 | ON a.username = SUBSTRING_INDEX( s.username , '.', 1 ) |
||
107 | WHERE s.id > ? AND s.id <= ? AND a.is_locked != 2 |
||
108 | GROUP BY username DESC |
||
109 | "); |
||
110 | if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $previous_upstream, $current_upstream) && $stmt->execute() && $result = $stmt->get_result()) |
||
111 | return $result->fetch_all(MYSQLI_ASSOC); |
||
112 | return $this->sqlError(); |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Fetch the highest available share ID from archive |
||
117 | **/ |
||
118 | function getMaxArchiveShareId() { |
||
119 | $stmt = $this->mysqli->prepare("SELECT MAX(share_id) AS share_id FROM $this->tableArchive"); |
||
120 | if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) |
||
121 | return $result->fetch_object()->share_id; |
||
122 | return $this->sqlError(); |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * We need a certain amount of valid archived shares |
||
127 | * param left int Left/lowest share ID |
||
128 | * param right int Right/highest share ID |
||
129 | * return array data Returns an array with usernames as keys for easy access |
||
130 | **/ |
||
131 | function getArchiveShares($iCount) { |
||
132 | $iMaxId = $this->getMaxArchiveShareId(); |
||
133 | $iMinId = $this->getMinArchiveShareId($iCount); |
||
134 | $stmt = $this->mysqli->prepare(" |
||
135 | SELECT |
||
136 | a.id, |
||
137 | SUBSTRING_INDEX( s.username , '.', 1 ) as account, |
||
138 | a.no_fees AS no_fees, |
||
139 | IFNULL(SUM(IF(our_result='Y', IF(s.difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0) AS valid, |
||
140 | IFNULL(SUM(IF(our_result='N', IF(s.difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0) AS invalid |
||
141 | FROM $this->tableArchive AS s |
||
142 | LEFT JOIN " . $this->user->getTableName() . " AS a |
||
143 | ON a.username = SUBSTRING_INDEX( s.username , '.', 1 ) |
||
144 | WHERE s.share_id > ? AND s.share_id <= ? AND a.is_locked != 2 |
||
145 | GROUP BY account DESC"); |
||
146 | if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $iMinId, $iMaxId) && $stmt->execute() && $result = $stmt->get_result()) { |
||
147 | $aData = NULL; |
||
148 | while ($row = $result->fetch_assoc()) { |
||
149 | $aData[strtolower($row['account'])] = $row; |
||
150 | } |
||
151 | if (is_array($aData)) return $aData; |
||
152 | } |
||
153 | return $this->sqlError(); |
||
154 | } |
||
155 | |||
156 | /** |
||
157 | * We keep shares only up to a certain point |
||
158 | * This can be configured by the user. |
||
159 | * @return return bool true or false |
||
160 | **/ |
||
161 | public function purgeArchive() { |
||
162 | // Fallbacks if unset |
||
163 | if (!isset($this->config['archive']['purge'])) $this->config['archive']['purge'] = 5; |
||
164 | |||
165 | $stmt = $this->mysqli->prepare("SELECT CEIL(COUNT(id) / 100 * ?) AS count FROM $this->tableArchive"); |
||
166 | View Code Duplication | if ($this->checkStmt($stmt) && $stmt->bind_param('i', $this->config['archive']['purge']) && $stmt->execute() && $result = $stmt->get_result()) { |
|
167 | $limit = $result->fetch_object()->count; |
||
168 | } else { |
||
169 | return $this->sqlError(); |
||
170 | } |
||
171 | $stmt->close(); |
||
172 | $stmt = $this->mysqli->prepare(" |
||
173 | DELETE FROM $this->tableArchive WHERE time < ( |
||
174 | SELECT MIN(time) FROM ( |
||
175 | SELECT MIN(time) AS time |
||
176 | FROM $this->tableArchive |
||
177 | WHERE block_id = ( |
||
178 | SELECT MIN(id) AS minid FROM ( |
||
179 | SELECT id FROM " . $this->block->getTableName() . " ORDER BY height DESC LIMIT ? |
||
180 | ) AS minheight |
||
181 | ) UNION SELECT DATE_SUB(now(), INTERVAL ? MINUTE) AS time |
||
182 | ) AS mintime |
||
183 | ) LIMIT $limit |
||
184 | "); |
||
185 | if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $this->config['archive']['maxrounds'], $this->config['archive']['maxage']) && $stmt->execute()) |
||
186 | return $stmt->affected_rows; |
||
187 | return $this->sqlError(); |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * Move accounted shares to archive table, this step is optional |
||
192 | * @param previous_upstream int Previous found share accepted by upstream to limit results |
||
193 | * @param current_upstream int Current upstream accepted share |
||
194 | * @param block_id int Block ID to assign shares to a specific block |
||
195 | * @return bool |
||
196 | **/ |
||
197 | public function moveArchive($current_upstream, $block_id, $previous_upstream=0) { |
||
198 | if ($this->config['payout_system'] != 'pplns') { |
||
199 | // We don't need archived shares that much, so only archive as much as configured |
||
200 | $sql = " |
||
201 | INSERT INTO $this->tableArchive (share_id, username, our_result, upstream_result, block_id, time, difficulty) |
||
202 | SELECT id, username, our_result, upstream_result, ?, time, IF(difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), difficulty) AS difficulty |
||
203 | FROM $this->table |
||
204 | WHERE id > ? AND id <= ? |
||
205 | AND time >= DATE_SUB(now(), INTERVAL " . $this->config['archive']['maxage'] . " MINUTE)"; |
||
206 | } else { |
||
207 | // PPLNS needs archived shares for later rounds, so we have to copy them all |
||
208 | $sql = " |
||
209 | INSERT INTO $this->tableArchive (share_id, username, our_result, upstream_result, block_id, time, difficulty) |
||
210 | SELECT id, username, our_result, upstream_result, ?, time, IF(difficulty=0, pow(2, (" . $this->config['difficulty'] . " - 16)), difficulty) AS difficulty |
||
211 | FROM $this->table |
||
212 | WHERE id > ? AND id <= ?"; |
||
213 | } |
||
214 | $archive_stmt = $this->mysqli->prepare($sql); |
||
215 | if ($this->checkStmt($archive_stmt) && $archive_stmt->bind_param('iii', $block_id, $previous_upstream, $current_upstream) && $archive_stmt->execute()) |
||
216 | return true; |
||
217 | return $this->sqlError(); |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Delete accounted shares from shares table |
||
222 | * @param current_upstream int Current highest upstream ID |
||
223 | * @param previous_upstream int Previous upstream ID |
||
224 | * @return bool true or false |
||
225 | **/ |
||
226 | public function deleteAccountedShares($current_upstream, $previous_upstream=0) { |
||
227 | // Fallbacks if unset |
||
228 | if (!isset($this->config['purge']['shares'])) $this->config['purge']['shares'] = 25000; |
||
229 | if (!isset($this->config['purge']['sleep'])) $this->config['purge']['sleep'] = 1; |
||
230 | |||
231 | $affected = 1; |
||
232 | while ($affected > 0) { |
||
233 | // Sleep first to allow any IO to cleanup |
||
234 | sleep($this->config['purge']['sleep']); |
||
235 | $stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE id > ? AND id <= ? ORDER BY id LIMIT " . $this->config['purge']['shares']); |
||
236 | $start = microtime(true); |
||
237 | View Code Duplication | if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $previous_upstream, $current_upstream) && $stmt->execute()) { |
|
238 | $affected = $stmt->affected_rows; |
||
239 | } else { |
||
240 | return $this->sqlError(); |
||
241 | } |
||
242 | } |
||
243 | return true; |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * Set/get last found share accepted by upstream: id and accounts |
||
248 | **/ |
||
249 | public function setLastUpstreamId() { |
||
250 | $this->iLastUpstreamId = @$this->oUpstream->id ? $this->oUpstream->id : 0; |
||
251 | } |
||
252 | public function getLastUpstreamId() { |
||
253 | return @$this->iLastUpstreamId ? @$this->iLastUpstreamId : 0; |
||
254 | } |
||
255 | public function getUpstreamFinder() { |
||
256 | return @$this->oUpstream->account; |
||
257 | } |
||
258 | public function getUpstreamWorker() { |
||
259 | return @$this->oUpstream->worker; |
||
260 | } |
||
261 | public function getUpstreamShareId() { |
||
262 | return @$this->oUpstream->id; |
||
263 | } |
||
264 | /** |
||
265 | * Find upstream accepted share that should be valid for a specific block |
||
266 | * Assumptions: |
||
267 | * * Shares are matching blocks in ASC order |
||
268 | * * We can skip all upstream shares of previously found ones used in a block |
||
269 | * @param last int Skips all shares up to last to find new share |
||
270 | * @return bool |
||
271 | **/ |
||
272 | public function findUpstreamShare($aBlock, $last=0) { |
||
273 | // Many use stratum, so we create our stratum check first |
||
274 | $version = pack("I*", sprintf('%08d', $aBlock['version'])); |
||
275 | $previousblockhash = pack("H*", swapEndian($aBlock['previousblockhash'])); |
||
276 | $merkleroot = pack("H*", swapEndian($aBlock['merkleroot']) ); |
||
277 | $time = pack("I*", $aBlock['time']); |
||
278 | $bits = pack("H*", swapEndian($aBlock['bits'])); |
||
279 | $nonce = pack("I*", $aBlock['nonce']); |
||
280 | $header_bin = $version . $previousblockhash . $merkleroot . $time . $bits . $nonce; |
||
281 | $header_hex = implode(unpack("H*", $header_bin)); |
||
282 | |||
283 | // Stratum supported blockhash solution entry |
||
284 | $stmt = $this->mysqli->prepare("SELECT SUBSTRING_INDEX( `username` , '.', 1 ) AS account, username as worker, id FROM $this->table WHERE solution = ? LIMIT 1"); |
||
285 | View Code Duplication | if ($this->checkStmt($stmt) && $stmt->bind_param('s', $aBlock['hash']) && $stmt->execute() && $result = $stmt->get_result()) { |
|
286 | $this->oUpstream = $result->fetch_object(); |
||
287 | $this->share_type = 'stratum_blockhash'; |
||
288 | if (!empty($this->oUpstream->account) && !empty($this->oUpstream->worker) && is_int($this->oUpstream->id)) |
||
289 | return true; |
||
290 | } |
||
291 | |||
292 | // Stratum scrypt hash check |
||
293 | $scrypt_hash = swapEndian(bin2hex(Scrypt::calc($header_bin, $header_bin, 1024, 1, 1, 32))); |
||
294 | $stmt = $this->mysqli->prepare("SELECT SUBSTRING_INDEX( `username` , '.', 1 ) AS account, username as worker, id FROM $this->table WHERE solution = ? LIMIT 1"); |
||
295 | View Code Duplication | if ($this->checkStmt($stmt) && $stmt->bind_param('s', $scrypt_hash) && $stmt->execute() && $result = $stmt->get_result()) { |
|
296 | $this->oUpstream = $result->fetch_object(); |
||
297 | $this->share_type = 'stratum_solution'; |
||
298 | if (!empty($this->oUpstream->account) && !empty($this->oUpstream->worker) && is_int($this->oUpstream->id)) |
||
299 | return true; |
||
300 | } |
||
301 | |||
302 | // Failed to fetch via startum solution, try pushpoold |
||
303 | // Fallback to pushpoold solution type |
||
304 | $ppheader = sprintf('%08d', $aBlock['version']) . word_reverse($aBlock['previousblockhash']) . word_reverse($aBlock['merkleroot']) . dechex($aBlock['time']) . $aBlock['bits'] . dechex($aBlock['nonce']); |
||
305 | $stmt = $this->mysqli->prepare("SELECT SUBSTRING_INDEX( `username` , '.', 1 ) AS account, username as worker, id FROM $this->table WHERE solution LIKE CONCAT(?, '%') LIMIT 1"); |
||
306 | View Code Duplication | if ($this->checkStmt($stmt) && $stmt->bind_param('s', $ppheader) && $stmt->execute() && $result = $stmt->get_result()) { |
|
307 | $this->oUpstream = $result->fetch_object(); |
||
308 | $this->share_type = 'pp_solution'; |
||
309 | if (!empty($this->oUpstream->account) && !empty($this->oUpstream->worker) && is_int($this->oUpstream->id)) |
||
310 | return true; |
||
311 | } |
||
312 | |||
313 | // Still no match, try upstream result with timerange |
||
314 | $stmt = $this->mysqli->prepare(" |
||
315 | SELECT |
||
316 | SUBSTRING_INDEX( `username` , '.', 1 ) AS account, username as worker, id |
||
317 | FROM $this->table |
||
318 | WHERE upstream_result = 'Y' |
||
319 | AND id > ? |
||
320 | AND UNIX_TIMESTAMP(time) >= ? |
||
321 | AND UNIX_TIMESTAMP(time) <= ( ? + 60 ) |
||
322 | ORDER BY id ASC LIMIT 1"); |
||
323 | View Code Duplication | if ($this->checkStmt($stmt) && $stmt->bind_param('iii', $last, $aBlock['time'], $aBlock['time']) && $stmt->execute() && $result = $stmt->get_result()) { |
|
324 | $this->oUpstream = $result->fetch_object(); |
||
325 | $this->share_type = 'upstream_share'; |
||
326 | if (!empty($this->oUpstream->account) && !empty($this->oUpstream->worker) && is_int($this->oUpstream->id)) |
||
327 | return true; |
||
328 | } |
||
329 | |||
330 | // We failed again, now we take ANY result matching the timestamp |
||
331 | $stmt = $this->mysqli->prepare(" |
||
332 | SELECT |
||
333 | SUBSTRING_INDEX( `username` , '.', 1 ) AS account, username as worker, id |
||
334 | FROM $this->table |
||
335 | WHERE our_result = 'Y' |
||
336 | AND id > ? |
||
337 | AND UNIX_TIMESTAMP(time) >= ? |
||
338 | ORDER BY id ASC LIMIT 1"); |
||
339 | View Code Duplication | if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $last, $aBlock['time']) && $stmt->execute() && $result = $stmt->get_result()) { |
|
340 | $this->oUpstream = $result->fetch_object(); |
||
341 | $this->share_type = 'any_share'; |
||
342 | if (!empty($this->oUpstream->account) && !empty($this->oUpstream->worker) && is_int($this->oUpstream->id)) |
||
343 | return true; |
||
344 | } |
||
345 | $this->setErrorMessage($this->getErrorMsg('E0052', $aBlock['height'])); |
||
346 | return false; |
||
347 | } |
||
348 | |||
349 | /** |
||
350 | * Fetch the lowest needed share ID from shares |
||
351 | **/ |
||
352 | View Code Duplication | function getMinimumShareId($iCount, $current_upstream) { |
|
353 | $stmt = $this->mysqli->prepare(" |
||
354 | SELECT MIN(b.id) AS id FROM |
||
355 | ( |
||
356 | SELECT id, @total := @total + IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty) AS total |
||
357 | FROM $this->table, (SELECT @total := 0) AS a |
||
358 | WHERE our_result = 'Y' |
||
359 | AND id <= ? AND @total < ? |
||
360 | ORDER BY id DESC |
||
361 | ) AS b |
||
362 | WHERE total <= ?"); |
||
363 | if ($this->checkStmt($stmt) && $stmt->bind_param('iii', $current_upstream, $iCount, $iCount) && $stmt->execute() && $result = $stmt->get_result()) |
||
364 | return $result->fetch_object()->id; |
||
365 | return $this->sqlError(); |
||
366 | } |
||
367 | |||
368 | /** |
||
369 | * Fetch the lowest needed share ID from archive |
||
370 | **/ |
||
371 | View Code Duplication | function getMinArchiveShareId($iCount) { |
|
372 | $stmt = $this->mysqli->prepare(" |
||
373 | SELECT MIN(b.share_id) AS share_id FROM |
||
374 | ( |
||
375 | SELECT share_id, @total := @total + IF(difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), difficulty) AS total |
||
376 | FROM $this->tableArchive, (SELECT @total := 0) AS a |
||
377 | WHERE our_result = 'Y' |
||
378 | AND @total < ? |
||
379 | ORDER BY share_id DESC |
||
380 | ) AS b |
||
381 | WHERE total <= ? |
||
382 | "); |
||
383 | if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $iCount, $iCount) && $stmt->execute() && $result = $stmt->get_result()) |
||
384 | return $result->fetch_object()->share_id; |
||
385 | return $this->sqlError(); |
||
386 | } |
||
387 | } |
||
388 | |||
389 | $share = new Share(); |
||
390 | $share->setDebug($debug); |
||
391 | $share->setMysql($mysqli); |
||
392 | $share->setConfig($config); |
||
393 | $share->setUser($user); |
||
394 | $share->setBlock($block); |
||
395 | $share->setErrorCodes($aErrorCodes); |
||
396 |
When comparing two booleans, it is generally considered safer to use the strict comparison operator.