diff options
Diffstat (limited to 'src/main.cpp')
| -rw-r--r-- | src/main.cpp | 332 |
1 files changed, 327 insertions, 5 deletions
diff --git a/src/main.cpp b/src/main.cpp index 1dbce58..f3de4d3 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -2,8 +2,26 @@ | |||
| 2 | // Distributed under the MIT/X11 software license, see the accompanying | 2 | // Distributed under the MIT/X11 software license, see the accompanying |
| 3 | // file license.txt or http://www.opensource.org/licenses/mit-license.php. | 3 | // file license.txt or http://www.opensource.org/licenses/mit-license.php. |
| 4 | 4 | ||
| 5 | #pragma push_macro("snprintf") | ||
| 6 | #undef snprintf | ||
| 7 | #include <nlohmann/json.hpp> | ||
| 8 | #pragma pop_macro("snprintf") | ||
| 9 | #ifdef snprintf | ||
| 10 | #undef snprintf | ||
| 11 | #endif | ||
| 5 | #include "headers.h" | 12 | #include "headers.h" |
| 6 | #include "sha.h" | 13 | #include "sha.h" |
| 14 | #include <atomic> | ||
| 15 | #include <mutex> | ||
| 16 | #include "btfaddr.h" | ||
| 17 | #include "btftunnel.h" | ||
| 18 | #ifndef _WIN32 | ||
| 19 | #include <sys/socket.h> | ||
| 20 | #include <netinet/in.h> | ||
| 21 | #include <arpa/inet.h> | ||
| 22 | #include <netdb.h> | ||
| 23 | #include <unistd.h> | ||
| 24 | #endif | ||
| 7 | 25 | ||
| 8 | 26 | ||
| 9 | 27 | ||
| @@ -55,7 +73,40 @@ bool fSoloMineTest = false; // /solomine: mine without requiring a peer (local t | |||
| 55 | int fGenerateBitcoins; | 73 | int fGenerateBitcoins; |
| 56 | int64 nTransactionFee = 0; | 74 | int64 nTransactionFee = 0; |
| 57 | CAddress addrIncoming; | 75 | CAddress addrIncoming; |
| 76 | int nMineMode = MINE_SOLO; | ||
| 77 | string strParticipantPool; // participant mode: pool .btf address | ||
| 78 | string strPoolName = "Bitflash Pool"; | ||
| 79 | string strPoolDashboardUrl; | ||
| 80 | double dPoolFeePercent = 0.0; | ||
| 81 | |||
| 82 | static std::atomic<uint64> gParticipantSharesSent{0}; | ||
| 83 | static std::atomic<uint64> gParticipantSharesAccepted{0}; | ||
| 84 | static std::atomic<uint64> gParticipantHashes{0}; | ||
| 85 | static std::atomic<uint64> gParticipantHashRateX1000{0}; | ||
| 86 | static std::mutex gParticipantStatusMutex; | ||
| 87 | static std::string gParticipantStatus; | ||
| 88 | |||
| 89 | void SetParticipantMiningStatus(const std::string& status) | ||
| 90 | { | ||
| 91 | std::lock_guard<std::mutex> lk(gParticipantStatusMutex); | ||
| 92 | if (gParticipantStatus == status) | ||
| 93 | return; | ||
| 94 | gParticipantStatus = status; | ||
| 95 | printf("PoolParticipantMiner: status -> %s\n", status.c_str()); | ||
| 96 | } | ||
| 58 | 97 | ||
| 98 | std::string GetParticipantMiningStatus() | ||
| 99 | { | ||
| 100 | std::lock_guard<std::mutex> lk(gParticipantStatusMutex); | ||
| 101 | return gParticipantStatus; | ||
| 102 | } | ||
| 103 | |||
| 104 | void GetParticipantMiningStats(uint64& sharesSent, uint64& sharesAccepted, double& hashRate) | ||
| 105 | { | ||
| 106 | sharesSent = gParticipantSharesSent.load(); | ||
| 107 | sharesAccepted = gParticipantSharesAccepted.load(); | ||
| 108 | hashRate = (double)gParticipantHashRateX1000.load() / 1000.0; | ||
| 109 | } | ||
| 59 | 110 | ||
| 60 | 111 | ||
| 61 | 112 | ||
| @@ -2228,8 +2279,281 @@ void BlockSHA256(const void* pin, unsigned int nBlocks, void* pout) | |||
| 2228 | } | 2279 | } |
| 2229 | 2280 | ||
| 2230 | 2281 | ||
| 2282 | // --------------------------------------------------------------------------- | ||
| 2283 | // Participant pool miner — connects to an external Stratum server, receives | ||
| 2284 | // RandomX jobs, hashes with the local CPU, and submits shares. | ||
| 2285 | // Called from BitcoinMiner() when nMineMode == MINE_PARTICIPANT. | ||
| 2286 | // --------------------------------------------------------------------------- | ||
| 2287 | |||
| 2288 | using json = nlohmann::json; | ||
| 2289 | |||
| 2290 | static std::string ToHexStr(const void* p, size_t n) | ||
| 2291 | { | ||
| 2292 | const unsigned char* b = (const unsigned char*)p; | ||
| 2293 | std::string s; s.reserve(n*2); | ||
| 2294 | static const char* H = "0123456789abcdef"; | ||
| 2295 | for (size_t i = 0; i < n; i++) { s += H[b[i]>>4]; s += H[b[i]&0xf]; } | ||
| 2296 | return s; | ||
| 2297 | } | ||
| 2298 | |||
| 2299 | static std::vector<unsigned char> FromHexStr(const std::string& s) | ||
| 2300 | { | ||
| 2301 | std::vector<unsigned char> v; | ||
| 2302 | for (size_t i = 0; i+1 < s.size(); i += 2) { | ||
| 2303 | auto h=[](char c)->int{ | ||
| 2304 | if(c>='0'&&c<='9')return c-'0'; | ||
| 2305 | if(c>='a'&&c<='f')return c-'a'+10; | ||
| 2306 | if(c>='A'&&c<='F')return c-'A'+10; | ||
| 2307 | return 0; | ||
| 2308 | }; | ||
| 2309 | v.push_back((unsigned char)((h(s[i])<<4)|h(s[i+1]))); | ||
| 2310 | } | ||
| 2311 | return v; | ||
| 2312 | } | ||
| 2313 | |||
| 2314 | static void UpdateParticipantHashRate(uint64 hashesSinceSample, int64 sampleStart) | ||
| 2315 | { | ||
| 2316 | int64 now = GetTime(); | ||
| 2317 | int64 elapsed = now - sampleStart; | ||
| 2318 | if (elapsed <= 0) | ||
| 2319 | elapsed = 1; | ||
| 2320 | double rate = (double)hashesSinceSample / (double)elapsed; | ||
| 2321 | gParticipantHashRateX1000.store((uint64)(rate * 1000.0)); | ||
| 2322 | } | ||
| 2323 | |||
| 2324 | static bool StratumSendLine(SOCKET s, const json& j) | ||
| 2325 | { | ||
| 2326 | std::string line = j.dump() + "\n"; | ||
| 2327 | int sent = 0, total = (int)line.size(); | ||
| 2328 | while (sent < total) { | ||
| 2329 | int r = send(s, line.c_str()+sent, total-sent, 0); | ||
| 2330 | if (r <= 0) return false; | ||
| 2331 | sent += r; | ||
| 2332 | } | ||
| 2333 | return true; | ||
| 2334 | } | ||
| 2335 | |||
| 2336 | static bool StratumRecvLine(SOCKET s, std::string& buf, std::string& line) | ||
| 2337 | { | ||
| 2338 | while (true) { | ||
| 2339 | size_t pos = buf.find('\n'); | ||
| 2340 | if (pos != std::string::npos) { | ||
| 2341 | line = buf.substr(0, pos); | ||
| 2342 | if (!line.empty() && line.back()=='\r') line.pop_back(); | ||
| 2343 | buf = buf.substr(pos+1); | ||
| 2344 | return true; | ||
| 2345 | } | ||
| 2346 | char tmp[4096]; | ||
| 2347 | int r = recv(s, tmp, sizeof(tmp)-1, 0); | ||
| 2348 | if (r <= 0) return false; | ||
| 2349 | tmp[r] = 0; | ||
| 2350 | buf += tmp; | ||
| 2351 | } | ||
| 2352 | } | ||
| 2353 | |||
| 2354 | static bool PoolParticipantMiner() | ||
| 2355 | { | ||
| 2356 | printf("PoolParticipantMiner: connecting to %s\n", strParticipantPool.c_str()); | ||
| 2357 | SetParticipantMiningStatus("resolving pool address"); | ||
| 2358 | |||
| 2359 | if (strParticipantPool.empty()) { | ||
| 2360 | printf("PoolParticipantMiner: no pool selected\n"); | ||
| 2361 | SetParticipantMiningStatus("no pool selected"); | ||
| 2362 | return false; | ||
| 2363 | } | ||
| 2364 | |||
| 2365 | unsigned char target_pubkey[32]; | ||
| 2366 | if (!btf::ParseAddress(strParticipantPool, target_pubkey)) { | ||
| 2367 | printf("PoolParticipantMiner: invalid .btf address %s\n", strParticipantPool.c_str()); | ||
| 2368 | SetParticipantMiningStatus("invalid pool address"); | ||
| 2369 | return false; | ||
| 2370 | } | ||
| 2371 | |||
| 2372 | std::string meetingHostPort; | ||
| 2373 | unsigned char service_enc_pub[32]; | ||
| 2374 | if (!BtfResolve(strParticipantPool, meetingHostPort, service_enc_pub)) { | ||
| 2375 | printf("PoolParticipantMiner: failed to resolve %s\n", strParticipantPool.c_str()); | ||
| 2376 | SetParticipantMiningStatus("failed to resolve pool"); | ||
| 2377 | return false; | ||
| 2378 | } | ||
| 2379 | |||
| 2380 | size_t colon = meetingHostPort.rfind(':'); | ||
| 2381 | if (colon == std::string::npos) { | ||
| 2382 | printf("PoolParticipantMiner: bad meeting node %s\n", meetingHostPort.c_str()); | ||
| 2383 | SetParticipantMiningStatus("bad pool endpoint"); | ||
| 2384 | return false; | ||
| 2385 | } | ||
| 2386 | std::string host = meetingHostPort.substr(0, colon); | ||
| 2387 | int port = atoi(meetingHostPort.substr(colon + 1).c_str()); | ||
| 2388 | if (port <= 0 || port > 65535) { | ||
| 2389 | printf("PoolParticipantMiner: bad meeting port %d\n", port); | ||
| 2390 | SetParticipantMiningStatus("bad pool port"); | ||
| 2391 | return false; | ||
| 2392 | } | ||
| 2393 | |||
| 2394 | btf_socket_t s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, target_pubkey, service_enc_pub); | ||
| 2395 | if (s == INVALID_SOCKET) { | ||
| 2396 | printf("PoolParticipantMiner: tunnel connect failed via %s\n", meetingHostPort.c_str()); | ||
| 2397 | SetParticipantMiningStatus("tunnel connect failed"); | ||
| 2398 | return false; | ||
| 2399 | } | ||
| 2400 | printf("PoolParticipantMiner: connected via %s\n", meetingHostPort.c_str()); | ||
| 2401 | SetParticipantMiningStatus("tunnel connected"); | ||
| 2402 | |||
| 2403 | // Use our wallet address as mining username so payouts go to our wallet | ||
| 2404 | std::string username; | ||
| 2405 | { | ||
| 2406 | std::vector<unsigned char> vchPubKey; | ||
| 2407 | if (CWalletDB("r").ReadDefaultKey(vchPubKey)) | ||
| 2408 | username = PubKeyToAddress(vchPubKey); | ||
| 2409 | } | ||
| 2410 | if (username.empty()) username = BtfLocalAddress(); | ||
| 2411 | if (username.empty()) username = "unknown"; | ||
| 2412 | |||
| 2413 | std::string buf; | ||
| 2414 | int msgId = 1; | ||
| 2415 | std::set<int> pendingSubmitIds; | ||
| 2416 | |||
| 2417 | // Subscribe | ||
| 2418 | SetParticipantMiningStatus("subscribing"); | ||
| 2419 | if (!StratumSendLine(s, json{{"id",msgId++},{"method","mining.subscribe"},{"params",json::array()}})) | ||
| 2420 | { SetParticipantMiningStatus("subscribe failed"); closesocket(s); return false; } | ||
| 2421 | |||
| 2422 | // Authorize | ||
| 2423 | SetParticipantMiningStatus("authorizing"); | ||
| 2424 | if (!StratumSendLine(s, json{{"id",msgId++},{"method","mining.authorize"}, | ||
| 2425 | {"params",json::array({username,"x"})}})) | ||
| 2426 | { SetParticipantMiningStatus("authorize failed"); closesocket(s); return false; } | ||
| 2427 | |||
| 2428 | // Job state | ||
| 2429 | std::string jobId; | ||
| 2430 | unsigned char header[80]; | ||
| 2431 | uint256 target; | ||
| 2432 | bool haveJob = false; | ||
| 2433 | bool gotSubscribeAck = false; | ||
| 2434 | bool gotAuthorizeAck = false; | ||
| 2435 | int64 authWaitStart = 0; | ||
| 2436 | unsigned int nNonce = 1; | ||
| 2437 | uint64 hashesSinceSample = 0; | ||
| 2438 | int64 sampleStart = GetTime(); | ||
| 2439 | |||
| 2440 | void* rxvm = RandomXCreateMinerVM(); | ||
| 2441 | if (!rxvm) { SetParticipantMiningStatus("failed to start RandomX miner"); closesocket(s); return false; } | ||
| 2442 | printf("PoolParticipantMiner: RandomX VM ready (%s)\n", | ||
| 2443 | RandomXFastReady() ? "fast 2GB" : "light 256MB"); | ||
| 2444 | SetParticipantMiningStatus("authorized; asking pool for work"); | ||
| 2445 | |||
| 2446 | while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) | ||
| 2447 | { | ||
| 2448 | // Non-blocking receive check | ||
| 2449 | { | ||
| 2450 | fd_set fds; FD_ZERO(&fds); FD_SET(s, &fds); | ||
| 2451 | struct timeval tv={0,50000}; // 50ms | ||
| 2452 | if (select((int)s+1, &fds, NULL, NULL, &tv) > 0) { | ||
| 2453 | std::string line; | ||
| 2454 | if (!StratumRecvLine(s, buf, line)) break; | ||
| 2455 | try { | ||
| 2456 | json msg = json::parse(line); | ||
| 2457 | if (msg.contains("id") && msg["id"].is_number_integer() && msg.contains("result")) { | ||
| 2458 | int id = msg["id"].get<int>(); | ||
| 2459 | if (id == 1 && msg["result"].is_boolean() && msg["result"].get<bool>()) { | ||
| 2460 | gotSubscribeAck = true; | ||
| 2461 | SetParticipantMiningStatus("subscribed; authorizing"); | ||
| 2462 | } else if (id == 2 && msg["result"].is_boolean()) { | ||
| 2463 | gotAuthorizeAck = msg["result"].get<bool>(); | ||
| 2464 | authWaitStart = GetTime(); | ||
| 2465 | SetParticipantMiningStatus(gotAuthorizeAck ? "authorized; asking pool for work" : "authorization rejected"); | ||
| 2466 | } | ||
| 2467 | if (pendingSubmitIds.erase(id) > 0) { | ||
| 2468 | if (msg["result"].is_boolean() && msg["result"].get<bool>()) { | ||
| 2469 | gParticipantSharesAccepted.fetch_add(1); | ||
| 2470 | SetParticipantMiningStatus("share accepted"); | ||
| 2471 | } else { | ||
| 2472 | SetParticipantMiningStatus("share rejected"); | ||
| 2473 | } | ||
| 2474 | } | ||
| 2475 | } | ||
| 2476 | std::string method = msg.value("method",""); | ||
| 2477 | if (method == "mining.notify") { | ||
| 2478 | auto& p = msg["params"]; | ||
| 2479 | jobId = p[0].get<std::string>(); | ||
| 2480 | // p[1] = full 80-byte header hex | ||
| 2481 | auto hdrBytes = FromHexStr(p[1].get<std::string>()); | ||
| 2482 | if (hdrBytes.size() >= 80) | ||
| 2483 | memcpy(header, hdrBytes.data(), 80); | ||
| 2484 | // p[3] = share target hex (little-endian uint256) | ||
| 2485 | auto tgtBytes = FromHexStr(p[3].get<std::string>()); | ||
| 2486 | if (tgtBytes.size() >= 32) | ||
| 2487 | memcpy(&target, tgtBytes.data(), 32); | ||
| 2488 | haveJob = true; | ||
| 2489 | nNonce = 1; | ||
| 2490 | SetParticipantMiningStatus("job received; hashing"); | ||
| 2491 | printf("PoolParticipantMiner: new job %s\n", jobId.c_str()); | ||
| 2492 | } else if (method == "mining.set_difficulty") { | ||
| 2493 | // acknowledged, we use the target from notify | ||
| 2494 | } | ||
| 2495 | } catch(...) {} | ||
| 2496 | } | ||
| 2497 | } | ||
| 2498 | |||
| 2499 | if (gotAuthorizeAck && !haveJob && authWaitStart > 0 && GetTime() - authWaitStart > 10) { | ||
| 2500 | SetParticipantMiningStatus("authorized; waiting for pool notify"); | ||
| 2501 | authWaitStart = 0; | ||
| 2502 | } | ||
| 2503 | |||
| 2504 | if (!haveJob) { Sleep(100); continue; } | ||
| 2505 | |||
| 2506 | // Hash one nonce | ||
| 2507 | memcpy(header+76, &nNonce, 4); | ||
| 2508 | uint256 hash = RandomXHashWithVM(rxvm, header, 80); | ||
| 2509 | hashesSinceSample++; | ||
| 2510 | gParticipantHashes.fetch_add(1); | ||
| 2511 | if ((hashesSinceSample & 0x3ff) == 0) | ||
| 2512 | UpdateParticipantHashRate(hashesSinceSample, sampleStart); | ||
| 2513 | |||
| 2514 | if (hash <= target) { | ||
| 2515 | printf("PoolParticipantMiner: share found! nonce=%u\n", nNonce); | ||
| 2516 | SetParticipantMiningStatus("share found; submitting"); | ||
| 2517 | int submitId = msgId++; | ||
| 2518 | pendingSubmitIds.insert(submitId); | ||
| 2519 | gParticipantSharesSent.fetch_add(1); | ||
| 2520 | json submit = {{"id",submitId},{"method","mining.submit"}, | ||
| 2521 | {"params",json::array({username, jobId, | ||
| 2522 | ToHexStr(&nNonce,4)})}}; | ||
| 2523 | if (!StratumSendLine(s, submit)) break; | ||
| 2524 | } | ||
| 2525 | |||
| 2526 | nNonce++; | ||
| 2527 | if (nNonce == 0) { haveJob = false; } // exhausted, wait for new job | ||
| 2528 | } | ||
| 2529 | |||
| 2530 | RandomXDestroyMinerVM(rxvm); | ||
| 2531 | closesocket(s); | ||
| 2532 | UpdateParticipantHashRate(hashesSinceSample, sampleStart); | ||
| 2533 | SetParticipantMiningStatus("participant miner stopped"); | ||
| 2534 | printf("PoolParticipantMiner: disconnected\n"); | ||
| 2535 | return true; | ||
| 2536 | } | ||
| 2537 | |||
| 2231 | bool BitcoinMiner() | 2538 | bool BitcoinMiner() |
| 2232 | { | 2539 | { |
| 2540 | // Participant mode: connect to external pool instead of mining solo/operator | ||
| 2541 | if (nMineMode == MINE_PARTICIPANT) | ||
| 2542 | { | ||
| 2543 | while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { | ||
| 2544 | PoolParticipantMiner(); | ||
| 2545 | if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { | ||
| 2546 | printf("PoolParticipantMiner: reconnecting in 10s\n"); | ||
| 2547 | for (int i = 0; i < 10 && !fShutdown; i++) Sleep(1000); | ||
| 2548 | } | ||
| 2549 | } | ||
| 2550 | return true; | ||
| 2551 | } | ||
| 2552 | |||
| 2553 | // Operator mode only serves the pool; it does not mine solo. | ||
| 2554 | if (nMineMode == MINE_OPERATOR) | ||
| 2555 | return true; | ||
| 2556 | |||
| 2233 | printf("BitcoinMiner started\n"); | 2557 | printf("BitcoinMiner started\n"); |
| 2234 | SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); | 2558 | SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); |
| 2235 | 2559 | ||
| @@ -2259,6 +2583,8 @@ bool BitcoinMiner() | |||
| 2259 | txNew.vin[0].prevout.SetNull(); | 2583 | txNew.vin[0].prevout.SetNull(); |
| 2260 | txNew.vin[0].scriptSig << nBits << ++bnExtraNonce; | 2584 | txNew.vin[0].scriptSig << nBits << ++bnExtraNonce; |
| 2261 | txNew.vout.resize(1); | 2585 | txNew.vout.resize(1); |
| 2586 | // Always pay coinbase to own wallet key. In operator mode, the pool | ||
| 2587 | // server distributes shares to miners via SendMoney() after each block. | ||
| 2262 | txNew.vout[0].scriptPubKey << key.GetPubKey() << OP_CHECKSIG; | 2588 | txNew.vout[0].scriptPubKey << key.GetPubKey() << OP_CHECKSIG; |
| 2263 | 2589 | ||
| 2264 | 2590 | ||
| @@ -2437,7 +2763,7 @@ int64 GetBalance() | |||
| 2437 | } | 2763 | } |
| 2438 | 2764 | ||
| 2439 | QueryPerformanceCounter((LARGE_INTEGER*)&nEnd); | 2765 | QueryPerformanceCounter((LARGE_INTEGER*)&nEnd); |
| 2440 | ///printf(" GetBalance() time = %16I64d\n", nEnd - nStart); | 2766 | ///printf(" GetBalance() time = %16lld\n", nEnd - nStart); |
| 2441 | return nTotal; | 2767 | return nTotal; |
| 2442 | } | 2768 | } |
| 2443 | 2769 | ||
| @@ -2670,12 +2996,10 @@ bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) | |||
| 2670 | strError = strprintf("Error: This is an oversized transaction that requires a transaction fee of %s ", FormatMoney(nFeeRequired).c_str()); | 2996 | strError = strprintf("Error: This is an oversized transaction that requires a transaction fee of %s ", FormatMoney(nFeeRequired).c_str()); |
| 2671 | else | 2997 | else |
| 2672 | strError = "Error: Transaction creation failed "; | 2998 | strError = "Error: Transaction creation failed "; |
| 2673 | wxMessageBox(strError, "Sending..."); | ||
| 2674 | return error("SendMoney() : %s\n", strError.c_str()); | 2999 | return error("SendMoney() : %s\n", strError.c_str()); |
| 2675 | } | 3000 | } |
| 2676 | if (!CommitTransactionSpent(wtxNew)) | 3001 | if (!CommitTransactionSpent(wtxNew)) |
| 2677 | { | 3002 | { |
| 2678 | wxMessageBox("Error finalizing transaction", "Sending..."); | ||
| 2679 | return error("SendMoney() : Error finalizing transaction"); | 3003 | return error("SendMoney() : Error finalizing transaction"); |
| 2680 | } | 3004 | } |
| 2681 | 3005 | ||
| @@ -2686,8 +3010,6 @@ bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) | |||
| 2686 | { | 3010 | { |
| 2687 | // This must not fail. The transaction has already been signed and recorded. | 3011 | // This must not fail. The transaction has already been signed and recorded. |
| 2688 | throw runtime_error("SendMoney() : wtxNew.AcceptTransaction() failed\n"); | 3012 | throw runtime_error("SendMoney() : wtxNew.AcceptTransaction() failed\n"); |
| 2689 | wxMessageBox("Error: Transaction not valid", "Sending..."); | ||
| 2690 | return error("SendMoney() : Error: Transaction not valid"); | ||
| 2691 | } | 3013 | } |
| 2692 | wtxNew.RelayWalletTransaction(); | 3014 | wtxNew.RelayWalletTransaction(); |
| 2693 | } | 3015 | } |