326 lines
12 KiB
PHP
326 lines
12 KiB
PHP
<?php
|
||
// start.php
|
||
require __DIR__ . '/vendor/autoload.php';
|
||
require __DIR__ . '/helper.php';
|
||
require __DIR__ . '/config.php';
|
||
require __DIR__ . '/version.php';
|
||
|
||
use Workerman\Worker;
|
||
use Elliptic\EC;
|
||
use kornrunner\Keccak;
|
||
use Workerman\Timer;
|
||
use GuzzleHttp\Client;
|
||
use GuzzleHttp\Exception\RequestException;
|
||
use Medoo\Medoo;
|
||
|
||
|
||
// CLI 参数解析(可选)
|
||
#$options = getopt('', ['workers::', 'repeat::', 'numeric-only::', 'logfile::']);
|
||
#echo($options['repeat']);
|
||
$workerCount = isset($options['workers']) ? (int)$options['workers'] : cpu_count()*4;
|
||
$minRepeat = isset($options['repeat']) ? (int)$options['repeat'] : 4; // 默认至少 4 个相同
|
||
$requireNumeric = isset($options['numeric-only']) ? (bool)$options['numeric-only'] : false;
|
||
$logFile = isset($options['logfile']) ? $options['logfile'] : (__DIR__ . '/found.log');
|
||
$detailInfo = isset($options['detailInfo']) ? (bool)$options['detailInfo'] : false;
|
||
$staticSecond = isset($options['staticSecond']) ? (int)$options['staticSecond'] : 10;
|
||
$apiKey=isset($options['apikey']) ? $options['apikey'] : 'none';
|
||
echo "Configuration: VerboseOpt={$detailInfo}, Progress:{$workerCount}, SameNumbers={$minRepeat}, NumericOnly=" . ($requireNumeric ? 'true' : 'false') . "\n";
|
||
if(!isset($options['redis'])||!isset($options['redis']['ip'])||!isset($options['redis']['port'])){
|
||
exit("🚨Lack of Redis Configuration, now exit……\n");
|
||
}
|
||
if(!isset($options['db'])){
|
||
exit("🚨Lack of Database Configuration, now exit……\n");
|
||
}else{
|
||
$dbConfig=$options['db'];
|
||
}
|
||
if($apiKey=='none'){
|
||
exit("🚨Lack of EtherScan API Key, now exit……\n You can apply for an APIKey through https://etherscan.io/apidashboard \n");
|
||
}
|
||
|
||
$Rat = new Worker();
|
||
$GLOBALS['mainid']=posix_getpid(); #父进程PID
|
||
$Rat->name="Glod Rat";
|
||
$Rat->count = max(1, $workerCount);
|
||
$GDS = new GlobalData\Server('127.0.0.1', 2207);
|
||
$Rat->onWorkerStart = function($Rat) use ($minRepeat, $requireNumeric,$detailInfo,$options) {
|
||
global $gd;
|
||
$gd = new \GlobalData\Client('127.0.0.1:2207');
|
||
$gd->count=0;
|
||
$pid = posix_getpid();
|
||
$cid=$pid-$GLOBALS['mainid'];
|
||
if($detailInfo){
|
||
echo "[Rat $cid]启动 PID={$pid}\n";
|
||
}
|
||
$redis = new Redis();
|
||
$redis->connect($options['redis']['ip'], $options['redis']['port']);
|
||
if($detailInfo){
|
||
echo "[Rat $cid] Connecting Redis……\n";
|
||
}
|
||
if(isset($options['redis']['password'])&&$options['redis']['password']!=false){
|
||
if($detailInfo){
|
||
echo "[Rat $cid] Redis Authing……\n";
|
||
}
|
||
$redis->auth($options['redis']['password']);
|
||
}
|
||
if($redis->ping(1)&&$detailInfo){
|
||
echo "[Rat $cid] Redis Successfully Connected\n";
|
||
}
|
||
|
||
$ec = new EC('secp256k1');
|
||
$start_time = microtime(true);
|
||
$staticCount=0;
|
||
|
||
// 无限循环,持续生成地址并检查
|
||
while (true) {
|
||
// 生成随机私钥(32 字节)
|
||
try {
|
||
$privBin = random_bytes(32);
|
||
} catch (Exception $e) {
|
||
// random_bytes 失败(极少见),跳过一次循环
|
||
echo "\033[33m[Rat $cid] PID={$pid} random_bytes failed: " . $e->getMessage() . ",thus jumped this loop\n
|
||
This is an expected but extremely rare error. It should not affect normal operation. \n
|
||
However, if this error persists, please be vigilant.\n
|
||
This Rat will sleep for 1000 ms\033[0m\n";
|
||
usleep(1000);
|
||
continue;
|
||
}
|
||
$privHex = bin2hex($privBin);
|
||
|
||
// 由私钥生成公钥(未压缩)
|
||
$keyPair = $ec->keyFromPrivate($privHex);
|
||
$pubHex = $keyPair->getPublic(false, 'hex'); // '04' + x + y
|
||
$pubHexNoPrefix = (strpos($pubHex, '04') === 0) ? substr($pubHex, 2) : $pubHex;
|
||
|
||
// keccak256 公钥并取地址
|
||
$hash = Keccak::hash(hex2bin($pubHexNoPrefix), 256); // 返回 hex
|
||
$address = '0x' . substr($hash, -40);
|
||
|
||
$staticCount=$staticCount+1;
|
||
if (microtime(true) - $start_time >= 5) {
|
||
if($detailInfo){
|
||
echo "[Rat $cid] digged $staticCount address in past 5s \n";
|
||
}
|
||
$gd->count=$gd->count+$staticCount;
|
||
$staticCount=0;
|
||
$start_time = microtime(true);
|
||
}
|
||
|
||
|
||
// 检查末尾连续相同字符(只检查尾部连续段)
|
||
$addrRaw = strtolower(substr($address, 2)); // 纯 hex(40 chars)
|
||
$len = strlen($addrRaw);
|
||
$lastChar = $addrRaw[$len - 1];
|
||
$count = 1;
|
||
for ($i = $len - 2; $i >= 0; $i--) {
|
||
if ($addrRaw[$i] === $lastChar) $count++;
|
||
else break;
|
||
}
|
||
|
||
// 如果满足重复数量,并且(可选)最后字符为数字
|
||
if ($count >= $minRepeat) {
|
||
if ($requireNumeric && !ctype_digit($lastChar)) {
|
||
// 不满足数字要求,继续
|
||
} else {
|
||
// 匹配成功 —— 调用 scanwallet 并记录(但不退出,继续寻找)
|
||
try {
|
||
if($detailInfo){
|
||
echo "[Rat $cid] PID={$pid} Found Address {$address} (ended with {$count} x '{$lastChar}')\n";
|
||
}
|
||
$gd->scount=$gd->scount+1;
|
||
$result = [
|
||
'pid' => $pid,
|
||
'private_key' => $privHex,
|
||
'timestamp' => time(),
|
||
'count'=> $count,
|
||
'char'=> $lastChar,
|
||
];
|
||
$redis->sadd('goldRat_set', $address);
|
||
$redis->hset('goldRat_hash', $address, json_encode($result));
|
||
} catch (Throwable $t) {
|
||
if($detailInfo){
|
||
echo "\033[31m ❌[Rat $cid] PID={$pid} ERROR OCCURRED : " . $t->getMessage() . "\033[0m\n";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 若想降低 CPU 占用可在这里加短暂 sleep(但会降低查找速度)
|
||
// usleep(0); // 等同于 yield
|
||
}
|
||
if($detailInfo){
|
||
echo "\033[31m ⚠[Rat $cid] PID={$pid} Unexceptedly Exited The Loop \033[0m\n";
|
||
}
|
||
};
|
||
$Rat->onWorkerStop = function($Rat) {
|
||
$pid = posix_getpid();
|
||
$cid=$pid-$GLOBALS['mainid'];
|
||
echo "[Rat $cid] PID={$pid} 已终止\n";
|
||
};
|
||
|
||
|
||
|
||
$Static = new Worker();
|
||
$Static->name='Static&Timer Service';
|
||
$Static->onWorkerStart = function($Static)use($staticSecond,$detailInfo,$apiKey) {
|
||
$GLOBALS['starttime']=microtime(true);
|
||
$GLOBALS['lc']=0;
|
||
$GLOBALS['lsc']=0;
|
||
$gda = new \GlobalData\Client('127.0.0.1:2207');
|
||
$gda->second_token=5;
|
||
$gda->minute_token=60;
|
||
$gda->lastToken='--';
|
||
$gda->lastTokenTime='will shown after 1min';
|
||
Timer::add(1, function()use($staticSecond,$detailInfo,$gda,$apiKey){
|
||
$gda->second_token= $gda->second_token+5;
|
||
});
|
||
Timer::add(60, function()use($staticSecond,$detailInfo,$gda,$apiKey){
|
||
if($detailInfo){
|
||
echo "Go to ask EtherScan for API useage";
|
||
}
|
||
$client = new Client([
|
||
'base_uri' => 'https://api.etherscan.io/v2/',
|
||
'timeout' => 10.0,
|
||
]);
|
||
$params = [
|
||
'query' => [
|
||
'module' => 'getapilimit',
|
||
'action' => 'getapilimit',
|
||
'apikey' => $apiKey
|
||
]
|
||
];
|
||
try {
|
||
$response = $client->get('api', $params);
|
||
$statusCode = $response->getStatusCode();
|
||
if ($statusCode == 200) {
|
||
$data = json_decode($response->getBody(), true);
|
||
if ($data['status'] === '1') {
|
||
$lastminute=secondsUntilUtcDayEnd()*60;
|
||
$addToken=$data['result']['creditsAvailable']/60;
|
||
$gda->lastToken=$data['result']['creditsAvailable'];
|
||
$gda->lastTokenTime=$data['result']['intervalExpiryTimespan'];
|
||
if($detailInfo){
|
||
echo "✅[Timmer]EtherScan Got the credit successfully\n";
|
||
}
|
||
} else {
|
||
if($detailInfo){
|
||
echo "❌[Timmer]EtherScan Get Credit Failed\n";
|
||
print_r($data);
|
||
}
|
||
$addToken=0;
|
||
}
|
||
}else{
|
||
if($detailInfo){
|
||
if($detailInfo){
|
||
echo "\033[31m❌[Timmer]EtherScan Http Request Failed: HTTP" . $statusCode . "\033[0m\n";
|
||
}
|
||
}
|
||
$addToken=0;
|
||
}
|
||
} catch (RequestException $e) {
|
||
echo "\033[31m❌[Timmer]EtherScan Http Request Failed: " . $e->getMessage() . "\033[0m\n";
|
||
$addToken=0;
|
||
}
|
||
$gda->minute_token= $gda->minute_token+$addToken;
|
||
});
|
||
Timer::add($staticSecond, function()use($staticSecond,$detailInfo,$gda){
|
||
$staticCount=$gda->count;
|
||
$avgCount=($staticCount-$GLOBALS['lc'])/$staticSecond;
|
||
$successCount=$gda->scount;
|
||
$creditAvailable=$gda->lastToken;
|
||
$resetCountdown=$gda->lastTokenTime;
|
||
$avgsCount=($successCount-$GLOBALS['lsc']);
|
||
$totalSpeed= ($successCount / (microtime(true)-$GLOBALS['starttime'])) * 3600;
|
||
$running=formatSeconds(microtime(true)-$GLOBALS['starttime']);
|
||
if(!$detailInfo){
|
||
echo "\033[H"; // 光标回到左上角
|
||
echo "\033[J";
|
||
echo " \033[33m
|
||
.88888. dP dP 888888ba dP
|
||
d8' `88 88 88 88 `8b 88
|
||
88 .d8888b. 88 .d888b88 a88aaaa8P' .d8888b. d8888P
|
||
88 YP88 88' `88 88 88' `88 88 `8b. 88' `88 88
|
||
Y8. .88 88. .88 88 88. .88 88 88 88. .88 88
|
||
`88888' `88888P' dP `88888P8 dP dP `88888P8 dP \033[0m
|
||
====================================================
|
||
|| Gold Rat v".GR_VERSION." Build ".GR_BUILD." ||
|
||
||Dig out wallet address with the beautiful number||
|
||
|| As well as a CPU Benchmark tool ||
|
||
====================================================
|
||
";
|
||
}
|
||
echo "[STATIC] in last $staticSecond s:
|
||
Avg: $avgCount adds/s, Total: $staticCount adds
|
||
Succeed: $avgsCount adds, Total: $successCount adds
|
||
Total Diging Speed: $totalSpeed adds/hour
|
||
Total Runing Time: $running
|
||
----------------API CREDITS INFO--------------------
|
||
EtherScan.io API
|
||
Available: $creditAvailable Credits
|
||
Reset Countdown: $resetCountdown
|
||
====================================================
|
||
Enter Ctrl+C to Exit.
|
||
————————————————————————————————————————————————————
|
||
©Gold Rat by LayFi.de
|
||
\n";
|
||
|
||
$GLOBALS['lc']=$staticCount;
|
||
$GLOBALS['lsc']=$successCount;
|
||
|
||
});
|
||
};
|
||
require __DIR__ . '/scanner.php';
|
||
require __DIR__ . '/writeToDB.php';
|
||
|
||
$Scanner=new Worker();
|
||
$Scanner->name='Scanner';
|
||
$Scanner->count=1;
|
||
$Scanner->onWorkerStart = function($Scanner)use($detailInfo,$options,$apiKey,$dbConfig){
|
||
usleep(1000);#延迟1秒启动,防止与Rat争抢redis并发
|
||
$pid = posix_getpid();
|
||
if($detailInfo){
|
||
echo "[Scanner] PID={$pid} Started\n";
|
||
}
|
||
$redis = new Redis();
|
||
$redis->connect($options['redis']['ip'], $options['redis']['port']);
|
||
if($detailInfo){
|
||
echo "[Scanner] Connecting Redis……\n";
|
||
}
|
||
if(isset($options['redis']['password'])&&$options['redis']['password']!=false){
|
||
if($detailInfo){
|
||
echo "[Scanner] Redis Authing……\n";
|
||
}
|
||
$redis->auth($options['redis']['password']);
|
||
}
|
||
if($redis->ping(1)&&$detailInfo){
|
||
echo "[Scanner] Redis Successfully Connected\n";
|
||
}
|
||
$setKey = 'goldRat_set';
|
||
$hashKey = 'goldRat_hash';
|
||
while(true){
|
||
$address = $redis->spop($setKey);
|
||
if ($address === false) {
|
||
usleep(1000); #休眠1s
|
||
}else{
|
||
$json = $redis->hget($hashKey, $address);
|
||
if($detailInfo){
|
||
echo "[Scanner] Take out and start scanning: $address\n";
|
||
}
|
||
if(scanwallet($address,$apiKey,$detailInfo)){
|
||
$active='true';
|
||
echo "\033[32m🎉[Scanner] $address found active!\033[0m\n";
|
||
writeToDB($address,$dbConfig,$json,true);
|
||
}else{
|
||
$active='false';
|
||
if($detailInfo){
|
||
echo "[Scanner] $address found nothing \n";
|
||
}
|
||
writeToDB($address,$dbConfig,$json,false);
|
||
}
|
||
$redis->hdel($hashKey, $address);
|
||
}
|
||
}
|
||
};
|
||
|
||
|
||
|
||
Worker::runAll();
|