upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp332
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
55int fGenerateBitcoins; 73int fGenerateBitcoins;
56int64 nTransactionFee = 0; 74int64 nTransactionFee = 0;
57CAddress addrIncoming; 75CAddress addrIncoming;
76int nMineMode = MINE_SOLO;
77string strParticipantPool; // participant mode: pool .btf address
78string strPoolName = "Bitflash Pool";
79string strPoolDashboardUrl;
80double dPoolFeePercent = 0.0;
81
82static std::atomic<uint64> gParticipantSharesSent{0};
83static std::atomic<uint64> gParticipantSharesAccepted{0};
84static std::atomic<uint64> gParticipantHashes{0};
85static std::atomic<uint64> gParticipantHashRateX1000{0};
86static std::mutex gParticipantStatusMutex;
87static std::string gParticipantStatus;
88
89void 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
98std::string GetParticipantMiningStatus()
99{
100 std::lock_guard<std::mutex> lk(gParticipantStatusMutex);
101 return gParticipantStatus;
102}
103
104void 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
2288using json = nlohmann::json;
2289
2290static 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
2299static 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
2314static 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
2324static 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
2336static 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
2354static 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
2231bool BitcoinMiner() 2538bool 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 }