upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.cpp1
-rw-r--r--src/main.h1
-rw-r--r--src/main_gui.cpp5
-rw-r--r--src/readme.txt4
-rw-r--r--src/rpc.cpp316
5 files changed, 323 insertions, 4 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 2be9c54..2eb0e3a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -105,6 +105,7 @@ string strParticipantPool; // participant mode: pool .btf address
105string strPoolName = "Bitflash Pool"; 105string strPoolName = "Bitflash Pool";
106string strPoolDashboardUrl; 106string strPoolDashboardUrl;
107string strPoolStatusFile; 107string strPoolStatusFile;
108string strPoolRoundsFile;
108double dPoolFeePercent = 0.0; 109double dPoolFeePercent = 0.0;
109bool fStratumBridge = false; 110bool fStratumBridge = false;
110int nStratumBridgePort = 3333; 111int nStratumBridgePort = 3333;
diff --git a/src/main.h b/src/main.h
index 2786a4a..eafbf16 100644
--- a/src/main.h
+++ b/src/main.h
@@ -87,6 +87,7 @@ extern string strParticipantPool; // participant: pool .btf address
87extern string strPoolName; // operator: announced pool name 87extern string strPoolName; // operator: announced pool name
88extern string strPoolDashboardUrl; // operator: optional dashboard URL 88extern string strPoolDashboardUrl; // operator: optional dashboard URL
89extern string strPoolStatusFile; // operator: optional JSON status output path 89extern string strPoolStatusFile; // operator: optional JSON status output path
90extern string strPoolRoundsFile; // operator: optional public round proof path
90extern double dPoolFeePercent; // operator: announced fee percent 91extern double dPoolFeePercent; // operator: announced fee percent
91extern bool fStratumBridge; // participant helper: local Stratum bridge 92extern bool fStratumBridge; // participant helper: local Stratum bridge
92extern int nStratumBridgePort; // local bridge listen port, default 3333 93extern int nStratumBridgePort; // local bridge listen port, default 3333
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index ce6d2b5..7ad2075 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -126,6 +126,7 @@ static void PrintUsage()
126 printf(" /poolfee=PCT\n"); 126 printf(" /poolfee=PCT\n");
127 printf(" /pooldashboard=URL (alias: /pooldash=URL)\n"); 127 printf(" /pooldashboard=URL (alias: /pooldash=URL)\n");
128 printf(" /poolstatusfile=PATH (write pool status JSON for dashboards)\n"); 128 printf(" /poolstatusfile=PATH (write pool status JSON for dashboards)\n");
129 printf(" /poolroundsfile=PATH (write public pool round proofs JSON)\n");
129 printf("\n"); 130 printf("\n");
130 printf(".btf and rendezvous:\n"); 131 printf(".btf and rendezvous:\n");
131 printf(" /connectbtf=PEER_BTF_ADDRESS\n"); 132 printf(" /connectbtf=PEER_BTF_ADDRESS\n");
@@ -262,6 +263,10 @@ static void ParseStartupArguments(int argc, char* argv[])
262 if (!poolStatusFile.empty()) 263 if (!poolStatusFile.empty())
263 strPoolStatusFile = poolStatusFile; 264 strPoolStatusFile = poolStatusFile;
264 265
266 string poolRoundsFile = argval2(argc, argv, "/poolroundsfile", "-poolroundsfile");
267 if (!poolRoundsFile.empty())
268 strPoolRoundsFile = poolRoundsFile;
269
265 string btfConnect = argval2(argc, argv, "/connectbtf", "-connectbtf"); 270 string btfConnect = argval2(argc, argv, "/connectbtf", "-connectbtf");
266 if (!btfConnect.empty()) 271 if (!btfConnect.empty())
267 strBtfConnect = btfConnect; 272 strBtfConnect = btfConnect;
diff --git a/src/readme.txt b/src/readme.txt
index fa58b6e..b8b89c1 100644
--- a/src/readme.txt
+++ b/src/readme.txt
@@ -55,6 +55,10 @@ Command-line options (in addition to the original ones)
55 /poolfee=PCT operator announcement fee percent (example: 0.75) 55 /poolfee=PCT operator announcement fee percent (example: 0.75)
56 /pooldashboard=URL 56 /pooldashboard=URL
57 operator announcement dashboard URL 57 operator announcement dashboard URL
58 /poolstatusfile=PATH
59 write pool status JSON for dashboards
60 /poolroundsfile=PATH
61 write public pool round proofs JSON
58 /connectbtf=ADDR 62 /connectbtf=ADDR
59 keep an outbound .btf connection to ADDR 63 keep an outbound .btf connection to ADDR
60 /rvrelay=HOST:PORT 64 /rvrelay=HOST:PORT
diff --git a/src/rpc.cpp b/src/rpc.cpp
index 1aa4f9b..813f1d1 100644
--- a/src/rpc.cpp
+++ b/src/rpc.cpp
@@ -263,11 +263,47 @@ struct PendingPayout {
263static std::vector<PendingPayout> gPendingPayouts; 263static std::vector<PendingPayout> gPendingPayouts;
264static std::mutex gPayoutLedgerMutex; 264static std::mutex gPayoutLedgerMutex;
265 265
266struct PoolRoundProof {
267 std::string id;
268 int blockHeight;
269 int matureAtHeight;
270 int64 createdAt;
271 std::string blockHash;
272 std::string coinbaseTxid;
273 std::string foundBy;
274 double feePercent;
275 uint64 totalShares;
276 int64 coinbaseValue;
277 int64 subsidy;
278 int64 minerReward;
279 int64 operatorReward;
280 int skippedDust;
281 int64 forfeitedBadAddress;
282 std::map<std::string, uint64> shares;
283 std::map<std::string, int64> payouts;
284 std::map<std::string, std::string> paidTxids;
285
286 PoolRoundProof()
287 : blockHeight(0), matureAtHeight(0), createdAt(0), feePercent(0.0),
288 totalShares(0), coinbaseValue(0), subsidy(0), minerReward(0), operatorReward(0),
289 skippedDust(0), forfeitedBadAddress(0) {}
290};
291static std::vector<PoolRoundProof> gPoolRounds;
292static std::mutex gPoolRoundsMutex;
293static const size_t MAX_POOL_ROUNDS = 200;
294
266static std::string PayoutFilePath() 295static std::string PayoutFilePath()
267{ 296{
268 return GetAppDir() + "/pending_payouts.json"; 297 return GetAppDir() + "/pending_payouts.json";
269} 298}
270 299
300static std::string PoolRoundsFilePath()
301{
302 if (!strPoolRoundsFile.empty())
303 return strPoolRoundsFile;
304 return GetAppDir() + "/pool_rounds.json";
305}
306
271static std::string PayoutIdForBlock(int blockHeight) 307static std::string PayoutIdForBlock(int blockHeight)
272{ 308{
273 return strprintf("block-%d", blockHeight); 309 return strprintf("block-%d", blockHeight);
@@ -362,6 +398,167 @@ static void LoadPendingPayouts()
362 } 398 }
363} 399}
364 400
401static nlohmann::json PoolRoundToJson(const PoolRoundProof& r)
402{
403 nlohmann::json j;
404 j["id"] = r.id;
405 j["blockHeight"] = r.blockHeight;
406 j["matureAtHeight"] = r.matureAtHeight;
407 j["createdAt"] = r.createdAt;
408 j["blockHash"] = r.blockHash;
409 j["coinbaseTxid"] = r.coinbaseTxid;
410 j["foundBy"] = r.foundBy;
411 j["feePercent"] = r.feePercent;
412 j["totalShares"] = r.totalShares;
413 j["coinbaseValueSatoshis"] = r.coinbaseValue;
414 j["coinbaseValueBtf"] = FormatMoney(r.coinbaseValue);
415 j["subsidySatoshis"] = r.subsidy;
416 j["subsidyBtf"] = FormatMoney(r.subsidy);
417 j["minerRewardSatoshis"] = r.minerReward;
418 j["minerRewardBtf"] = FormatMoney(r.minerReward);
419 j["operatorRewardSatoshis"] = r.operatorReward;
420 j["operatorRewardBtf"] = FormatMoney(r.operatorReward);
421 j["skippedDust"] = r.skippedDust;
422 j["forfeitedBadAddressSatoshis"] = r.forfeitedBadAddress;
423 j["forfeitedBadAddressBtf"] = FormatMoney(r.forfeitedBadAddress);
424
425 j["shares"] = nlohmann::json::object();
426 for (const auto& kv : r.shares)
427 j["shares"][kv.first] = kv.second;
428
429 j["payouts"] = nlohmann::json::object();
430 for (const auto& kv : r.payouts) {
431 nlohmann::json p;
432 p["amountSatoshis"] = kv.second;
433 p["amountBtf"] = FormatMoney(kv.second);
434 std::map<std::string, std::string>::const_iterator tx = r.paidTxids.find(kv.first);
435 if (tx != r.paidTxids.end())
436 p["txid"] = tx->second;
437 else
438 p["txid"] = nullptr;
439 j["payouts"][kv.first] = p;
440 }
441 return j;
442}
443
444static void SavePoolRoundsLocked()
445{
446 nlohmann::json root;
447 root["schema"] = "bitflash-pool-rounds-1";
448 root["updatedAt"] = GetTime();
449 root["rounds"] = nlohmann::json::array();
450 for (const PoolRoundProof& r : gPoolRounds)
451 root["rounds"].push_back(PoolRoundToJson(r));
452
453 std::string path = PoolRoundsFilePath();
454 std::string tmp = path + ".tmp";
455 FILE* f = fopen(tmp.c_str(), "w");
456 if (!f) {
457 LogPrint("payout", "[payout] WARNING: could not write %s\n", tmp.c_str());
458 return;
459 }
460 std::string s = root.dump(2);
461 fwrite(s.c_str(), 1, s.size(), f);
462 if (fclose(f) != 0) {
463 LogPrint("payout", "[payout] WARNING: could not close %s\n", tmp.c_str());
464 return;
465 }
466#ifdef _WIN32
467 remove(path.c_str());
468#endif
469 if (rename(tmp.c_str(), path.c_str()) != 0)
470 LogPrint("payout", "[payout] WARNING: could not replace %s\n", path.c_str());
471}
472
473static void LoadPoolRounds()
474{
475 std::string path = PoolRoundsFilePath();
476 FILE* f = fopen(path.c_str(), "r");
477 if (!f) return;
478 fseek(f, 0, SEEK_END);
479 long sz = ftell(f);
480 fseek(f, 0, SEEK_SET);
481 std::string s(sz, '\0');
482 fread(&s[0], 1, sz, f);
483 fclose(f);
484
485 try {
486 nlohmann::json root = nlohmann::json::parse(s);
487 nlohmann::json arr = root.is_array() ? root : root["rounds"];
488 std::lock_guard<std::mutex> lk(gPoolRoundsMutex);
489 for (const auto& obj : arr) {
490 PoolRoundProof r;
491 if (obj.contains("id") && obj["id"].is_string()) r.id = obj["id"].get<std::string>();
492 if (obj.contains("blockHeight")) r.blockHeight = obj["blockHeight"].get<int>();
493 if (obj.contains("matureAtHeight")) r.matureAtHeight = obj["matureAtHeight"].get<int>();
494 if (obj.contains("createdAt")) r.createdAt = obj["createdAt"].get<int64>();
495 if (obj.contains("blockHash") && obj["blockHash"].is_string()) r.blockHash = obj["blockHash"].get<std::string>();
496 if (obj.contains("coinbaseTxid") && obj["coinbaseTxid"].is_string()) r.coinbaseTxid = obj["coinbaseTxid"].get<std::string>();
497 if (obj.contains("foundBy") && obj["foundBy"].is_string()) r.foundBy = obj["foundBy"].get<std::string>();
498 if (obj.contains("feePercent")) r.feePercent = obj["feePercent"].get<double>();
499 if (obj.contains("totalShares")) r.totalShares = obj["totalShares"].get<uint64>();
500 if (obj.contains("coinbaseValueSatoshis")) r.coinbaseValue = obj["coinbaseValueSatoshis"].get<int64>();
501 if (obj.contains("subsidySatoshis")) r.subsidy = obj["subsidySatoshis"].get<int64>();
502 if (obj.contains("minerRewardSatoshis")) r.minerReward = obj["minerRewardSatoshis"].get<int64>();
503 if (obj.contains("operatorRewardSatoshis")) r.operatorReward = obj["operatorRewardSatoshis"].get<int64>();
504 if (obj.contains("skippedDust")) r.skippedDust = obj["skippedDust"].get<int>();
505 if (obj.contains("forfeitedBadAddressSatoshis")) r.forfeitedBadAddress = obj["forfeitedBadAddressSatoshis"].get<int64>();
506 if (obj.contains("shares") && obj["shares"].is_object())
507 for (auto it = obj["shares"].begin(); it != obj["shares"].end(); ++it)
508 r.shares[it.key()] = it.value().get<uint64>();
509 if (obj.contains("payouts") && obj["payouts"].is_object())
510 for (auto it = obj["payouts"].begin(); it != obj["payouts"].end(); ++it) {
511 if (it.value().is_object()) {
512 r.payouts[it.key()] = it.value().value("amountSatoshis", (int64)0);
513 if (it.value().contains("txid") && it.value()["txid"].is_string())
514 r.paidTxids[it.key()] = it.value()["txid"].get<std::string>();
515 } else if (it.value().is_number_integer()) {
516 r.payouts[it.key()] = it.value().get<int64>();
517 }
518 }
519 if (!r.id.empty())
520 gPoolRounds.push_back(r);
521 }
522 while (gPoolRounds.size() > MAX_POOL_ROUNDS)
523 gPoolRounds.erase(gPoolRounds.begin());
524 LogPrint("payout", "[payout] loaded %zu pool round proof(s) from disk\n",
525 gPoolRounds.size());
526 } catch (const std::exception& e) {
527 LogPrint("payout", "[payout] WARNING: failed to parse %s: %s\n",
528 path.c_str(), e.what());
529 }
530}
531
532static void RecordPoolRound(const PoolRoundProof& round)
533{
534 std::lock_guard<std::mutex> lk(gPoolRoundsMutex);
535 for (PoolRoundProof& existing : gPoolRounds) {
536 if (existing.id == round.id) {
537 existing = round;
538 SavePoolRoundsLocked();
539 return;
540 }
541 }
542 gPoolRounds.push_back(round);
543 while (gPoolRounds.size() > MAX_POOL_ROUNDS)
544 gPoolRounds.erase(gPoolRounds.begin());
545 SavePoolRoundsLocked();
546}
547
548static void MarkPoolRoundPaid(const std::string& id,
549 const std::string& address,
550 const std::string& txid)
551{
552 std::lock_guard<std::mutex> lk(gPoolRoundsMutex);
553 for (PoolRoundProof& r : gPoolRounds) {
554 if (r.id != id)
555 continue;
556 r.paidTxids[address] = txid;
557 SavePoolRoundsLocked();
558 return;
559 }
560}
561
365// Payouts ready to execute are moved here under gPayoutExecMutex. 562// Payouts ready to execute are moved here under gPayoutExecMutex.
366// A dedicated thread drains this list so SendMoney (which takes cs_main) 563// A dedicated thread drains this list so SendMoney (which takes cs_main)
367// never runs on the event loop thread and can't stall miner I/O. 564// never runs on the event loop thread and can't stall miner I/O.
@@ -399,10 +596,28 @@ static bool FindRecordedPoolPayout(const std::string& id,
399} 596}
400 597
401static void QueuePayouts(int blockHeight, 598static void QueuePayouts(int blockHeight,
599 const std::string& blockHash,
600 const std::string& coinbaseTxid,
601 int64 coinbaseValue,
602 const std::string& foundBy,
402 const std::map<std::string, uint64>& shareCount, 603 const std::map<std::string, uint64>& shareCount,
403 uint64 total) 604 uint64 total)
404{ 605{
606 PoolRoundProof round;
607 round.id = PayoutIdForBlock(blockHeight);
608 round.blockHeight = blockHeight;
609 round.matureAtHeight = blockHeight + COINBASE_MATURITY;
610 round.createdAt = GetTime();
611 round.blockHash = blockHash;
612 round.coinbaseTxid = coinbaseTxid;
613 round.coinbaseValue = coinbaseValue;
614 round.foundBy = foundBy;
615 round.feePercent = dPoolFeePercent;
616 round.totalShares = total;
617 round.shares = shareCount;
618
405 if (total == 0) { 619 if (total == 0) {
620 RecordPoolRound(round);
406 LogPrint("payout", "[payout] block %d: zero shares recorded -- nothing to pay out\n", 621 LogPrint("payout", "[payout] block %d: zero shares recorded -- nothing to pay out\n",
407 blockHeight); 622 blockHeight);
408 return; 623 return;
@@ -418,9 +633,13 @@ static void QueuePayouts(int blockHeight,
418 int64 nSubsidy = 50 * COIN; 633 int64 nSubsidy = 50 * COIN;
419 nSubsidy >>= (blockHeight / 210000); // halving schedule 634 nSubsidy >>= (blockHeight / 210000); // halving schedule
420 int64 minerReward = (int64)((double)nSubsidy * (1.0 - feeFrac)); 635 int64 minerReward = (int64)((double)nSubsidy * (1.0 - feeFrac));
636 int64 operatorCut = nSubsidy - minerReward;
637 round.subsidy = nSubsidy;
638 round.minerReward = minerReward;
639 round.operatorReward = operatorCut;
421 640
422 PendingPayout pp; 641 PendingPayout pp;
423 pp.id = PayoutIdForBlock(blockHeight); 642 pp.id = round.id;
424 pp.blockHeight = blockHeight; 643 pp.blockHeight = blockHeight;
425 pp.matureAtHeight = blockHeight + COINBASE_MATURITY; 644 pp.matureAtHeight = blockHeight + COINBASE_MATURITY;
426 645
@@ -443,15 +662,18 @@ static void QueuePayouts(int blockHeight,
443 continue; 662 continue;
444 } 663 }
445 pp.amounts[kv.first] = amount; 664 pp.amounts[kv.first] = amount;
665 round.payouts[kv.first] = amount;
446 } 666 }
667 round.skippedDust = skipped;
668 round.forfeitedBadAddress = burned;
447 669
448 { 670 {
449 std::lock_guard<std::mutex> lk(gPayoutLedgerMutex); 671 std::lock_guard<std::mutex> lk(gPayoutLedgerMutex);
450 gPendingPayouts.push_back(pp); 672 gPendingPayouts.push_back(pp);
451 SavePendingPayoutsLocked(); 673 SavePendingPayoutsLocked();
452 } 674 }
675 RecordPoolRound(round);
453 676
454 int64 operatorCut = nSubsidy - minerReward;
455 LogPrint("payout", 677 LogPrint("payout",
456 "[payout] block %d: subsidy %s BTF, miners get %s" 678 "[payout] block %d: subsidy %s BTF, miners get %s"
457 " (fee %.2f%% = %s to operator)," 679 " (fee %.2f%% = %s to operator),"
@@ -548,12 +770,14 @@ static void PayoutThreadFn(void*)
548 LogPrint("payout", "[payout] recovered recorded payout %s to %s" 770 LogPrint("payout", "[payout] recovered recorded payout %s to %s"
549 " from wallet tx %s\n", 771 " from wallet tx %s\n",
550 FormatMoney(kv.second).c_str(), kv.first.c_str(), txid.c_str()); 772 FormatMoney(kv.second).c_str(), kv.first.c_str(), txid.c_str());
773 MarkPoolRoundPaid(ppQueued.id, kv.first, txid);
551 } 774 }
552 else if (SendMoney(sc, kv.second, wtx)) 775 else if (SendMoney(sc, kv.second, wtx))
553 { 776 {
554 txid = wtx.GetHash().GetHex(); 777 txid = wtx.GetHash().GetHex();
555 LogPrint("payout", "[payout] paid %s to %s\n", 778 LogPrint("payout", "[payout] paid %s to %s\n",
556 FormatMoney(kv.second).c_str(), kv.first.c_str()); 779 FormatMoney(kv.second).c_str(), kv.first.c_str());
780 MarkPoolRoundPaid(ppQueued.id, kv.first, txid);
557 } 781 }
558 else 782 else
559 { 783 {
@@ -664,7 +888,8 @@ static void WritePoolStatusJsonLocked()
664 {"name", strPoolName}, 888 {"name", strPoolName},
665 {"btfAddress", BtfLocalAddress()}, 889 {"btfAddress", BtfLocalAddress()},
666 {"feePercent", dPoolFeePercent}, 890 {"feePercent", dPoolFeePercent},
667 {"dashboardUrl", strPoolDashboardUrl} 891 {"dashboardUrl", strPoolDashboardUrl},
892 {"roundsFile", "pool_rounds.json"}
668 }; 893 };
669 894
670 j["node"] = { 895 j["node"] = {
@@ -702,6 +927,30 @@ static void WritePoolStatusJsonLocked()
702 }); 927 });
703 } 928 }
704 929
930 j["recentRounds"] = nlohmann::json::array();
931 {
932 std::lock_guard<std::mutex> lk(gPoolRoundsMutex);
933 size_t start = gPoolRounds.size() > 10 ? gPoolRounds.size() - 10 : 0;
934 for (size_t i = start; i < gPoolRounds.size(); i++) {
935 const PoolRoundProof& r = gPoolRounds[i];
936 int unpaid = 0;
937 for (const auto& kv : r.payouts)
938 if (r.paidTxids.count(kv.first) == 0)
939 unpaid++;
940 j["recentRounds"].push_back({
941 {"id", r.id},
942 {"blockHeight", r.blockHeight},
943 {"blockHash", r.blockHash},
944 {"coinbaseTxid", r.coinbaseTxid},
945 {"totalShares", r.totalShares},
946 {"payouts", (int)r.payouts.size()},
947 {"unpaidPayouts", unpaid},
948 {"coinbaseValueBtf", FormatMoney(r.coinbaseValue)},
949 {"minerRewardBtf", FormatMoney(r.minerReward)}
950 });
951 }
952 }
953
705 std::string path = PoolStatusFilePath(); 954 std::string path = PoolStatusFilePath();
706 std::string tmp = path + ".tmp"; 955 std::string tmp = path + ".tmp";
707 FILE* f = fopen(tmp.c_str(), "w"); 956 FILE* f = fopen(tmp.c_str(), "w");
@@ -1231,7 +1480,9 @@ static bool HandleLine(Miner* m, const std::string& rawLine,
1231 if (accepted) { 1480 if (accepted) {
1232 // Snapshot height before RebuildJob() (which increments it) 1481 // Snapshot height before RebuildJob() (which increments it)
1233 int foundHeight = gCurrentJob.height; 1482 int foundHeight = gCurrentJob.height;
1234 QueuePayouts(foundHeight, roundShareCount, roundShareTotal); 1483 QueuePayouts(foundHeight, b.GetHash().GetHex(), b.vtx[0].GetHash().GetHex(),
1484 b.vtx[0].vout[0].nValue, m->address,
1485 roundShareCount, roundShareTotal);
1235 // Reset round shares 1486 // Reset round shares
1236 roundShareCount.clear(); 1487 roundShareCount.clear();
1237 roundShareTotal = 0; 1488 roundShareTotal = 0;
@@ -1333,6 +1584,62 @@ int RunPoolStratumSelfTest()
1333 HandleLine(&miner, stale.dump(), roundShareCount, roundShareTotal, blocksFound); 1584 HandleLine(&miner, stale.dump(), roundShareCount, roundShareTotal, blocksFound);
1334 check(roundShareTotal == 1, "stale submit does not increment shares"); 1585 check(roundShareTotal == 1, "stale submit does not increment shares");
1335 1586
1587 std::vector<PoolRoundProof> savedRounds = gPoolRounds;
1588 std::string savedRoundsFile = strPoolRoundsFile;
1589 std::string proofPath = "pool_rounds_selftest.json";
1590 remove(proofPath.c_str());
1591 remove((proofPath + ".tmp").c_str());
1592 gPoolRounds.clear();
1593 strPoolRoundsFile = proofPath;
1594
1595 PoolRoundProof proof;
1596 proof.id = "selftest-round";
1597 proof.blockHeight = 42;
1598 proof.matureAtHeight = 42 + COINBASE_MATURITY;
1599 proof.createdAt = GetTime();
1600 proof.blockHash = block.GetHash().GetHex();
1601 proof.coinbaseTxid = coinbase.GetHash().GetHex();
1602 proof.foundBy = miner.address;
1603 proof.feePercent = 1.25;
1604 proof.totalShares = 3;
1605 proof.coinbaseValue = 50 * COIN;
1606 proof.subsidy = 50 * COIN;
1607 proof.minerReward = 49 * COIN;
1608 proof.operatorReward = 1 * COIN;
1609 proof.shares[miner.address] = 3;
1610 proof.payouts[miner.address] = 49 * COIN;
1611 RecordPoolRound(proof);
1612 MarkPoolRoundPaid(proof.id, miner.address, "selftest-txid");
1613
1614 bool proofOk = false;
1615 FILE* pf = fopen(proofPath.c_str(), "r");
1616 if (pf) {
1617 fseek(pf, 0, SEEK_END);
1618 long sz = ftell(pf);
1619 fseek(pf, 0, SEEK_SET);
1620 std::string s(sz, '\0');
1621 fread(&s[0], 1, sz, pf);
1622 fclose(pf);
1623 try {
1624 json root = json::parse(s);
1625 proofOk =
1626 root.value("schema", "") == "bitflash-pool-rounds-1" &&
1627 root["rounds"].is_array() &&
1628 root["rounds"].size() == 1 &&
1629 root["rounds"][0].value("id", "") == proof.id &&
1630 root["rounds"][0]["shares"].value(miner.address, (uint64)0) == 3 &&
1631 root["rounds"][0]["payouts"][miner.address].value("txid", "") == "selftest-txid";
1632 } catch (...) {
1633 proofOk = false;
1634 }
1635 }
1636 check(proofOk, "pool round proof ledger is written and paid txid is recorded");
1637
1638 remove(proofPath.c_str());
1639 remove((proofPath + ".tmp").c_str());
1640 strPoolRoundsFile = savedRoundsFile;
1641 gPoolRounds = savedRounds;
1642
1336 gCurrentJob = savedJob; 1643 gCurrentJob = savedJob;
1337 gHaveJob = savedHaveJob; 1644 gHaveJob = savedHaveJob;
1338 1645
@@ -1754,6 +2061,7 @@ void ThreadRPCServer(void*)
1754 } 2061 }
1755 2062
1756 LoadPendingPayouts(); 2063 LoadPendingPayouts();
2064 LoadPoolRounds();
1757 2065
1758 _beginthread(AcceptLoopFn, 0, NULL); 2066 _beginthread(AcceptLoopFn, 0, NULL);
1759 _beginthread(ThreadBtfPoolAnnouncer, 0, NULL); 2067 _beginthread(ThreadBtfPoolAnnouncer, 0, NULL);