upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/rpc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc.cpp')
-rw-r--r--src/rpc.cpp43
1 files changed, 27 insertions, 16 deletions
diff --git a/src/rpc.cpp b/src/rpc.cpp
index 6f6d101..d655ad5 100644
--- a/src/rpc.cpp
+++ b/src/rpc.cpp
@@ -352,11 +352,13 @@ struct PendingPayout {
352 // lets an entry stay on disk until every address in `amounts` is paid, 352 // lets an entry stay on disk until every address in `amounts` is paid,
353 // instead of being erased the moment the batch is handed off. 353 // instead of being erased the moment the batch is handed off.
354 std::map<std::string, std::string> paidTxids; // address -> txid 354 std::map<std::string, std::string> paidTxids; // address -> txid
355 int retries = 0; // exec-thread only: SendMoney attempts so far 355 // Retry and backoff state does NOT live here. It used to be declared here
356 int deferrals = 0; // exec-thread only: waits for spendable coin 356 // as well as on PayingRecord, and the copy on this side was never written:
357 // Upstream's `nextAttempt` is this field under another name; ours is kept 357 // `retryAfter` was declared, serialised, loaded and tested at the flush
358 // so the retry/deferral policy above stays coherent with it. 358 // gate, and the only assignment to it anywhere was `= 0`, so the gate could
359 int64 retryAfter = 0; // exec-thread only: unix time, 0 = ready now 359 // not fire. `retries` and `deferrals` were the same story. The real state
360 // is per (round, recipient) -- which is the granularity a payment can fail
361 // at -- and it lives on PayingRecord, which is now what persists it.
360}; 362};
361static std::vector<PendingPayout> gPendingPayouts; 363static std::vector<PendingPayout> gPendingPayouts;
362static std::mutex gPayoutLedgerMutex; 364static std::mutex gPayoutLedgerMutex;
@@ -597,7 +599,11 @@ static VersionedBlob SerializePendingPayoutsLocked()
597 obj["blockHeight"] = pp.blockHeight; 599 obj["blockHeight"] = pp.blockHeight;
598 obj["blockHash"] = pp.blockHash.ToString(); 600 obj["blockHash"] = pp.blockHash.ToString();
599 obj["matureAtHeight"] = pp.matureAtHeight; 601 obj["matureAtHeight"] = pp.matureAtHeight;
600 obj["retryAfter"] = pp.retryAfter; 602 // No retryAfter: it was never written with anything but zero, and the
603 // retry clock that is actually used is on PayingRecord. Files that
604 // still carry the key load fine (it is ignored), and a build that
605 // expects it defaults an absent one to 0, which means "ready now" --
606 // the same answer it always got.
601 nlohmann::json amounts = nlohmann::json::object(); 607 nlohmann::json amounts = nlohmann::json::object();
602 for (const auto& kv : pp.amounts) 608 for (const auto& kv : pp.amounts)
603 amounts[kv.first] = kv.second; 609 amounts[kv.first] = kv.second;
@@ -655,10 +661,9 @@ static void LoadPendingPayouts()
655 // still COINBASE_MATURITY. 661 // still COINBASE_MATURITY.
656 if (pp.blockHeight == 0 && pp.matureAtHeight > COINBASE_MATURITY) 662 if (pp.blockHeight == 0 && pp.matureAtHeight > COINBASE_MATURITY)
657 pp.blockHeight = pp.matureAtHeight - COINBASE_MATURITY; 663 pp.blockHeight = pp.matureAtHeight - COINBASE_MATURITY;
658 // Absent in every file written before this merge; default 0 means 664 // "retryAfter" is deliberately not read. Files written by the
659 // "ready now", which is the right answer for a restored entry. 665 // merged build carry it, always as 0, and there is nowhere left to
660 if (obj.contains("retryAfter") && obj["retryAfter"].is_number()) 666 // put it -- the retry clock is PayingRecord's.
661 pp.retryAfter = obj["retryAfter"].get<int64>();
662 for (auto it = obj["amounts"].begin(); it != obj["amounts"].end(); ++it) 667 for (auto it = obj["amounts"].begin(); it != obj["amounts"].end(); ++it)
663 pp.amounts[it.key()] = it.value().get<int64>(); 668 pp.amounts[it.key()] = it.value().get<int64>();
664 if (obj.contains("paidTxids") && obj["paidTxids"].is_object()) 669 if (obj.contains("paidTxids") && obj["paidTxids"].is_object())
@@ -777,6 +782,11 @@ static VersionedBlob SerializePayingLocked()
777 obj["amount"] = r.amount; 782 obj["amount"] = r.amount;
778 obj["retries"] = r.retries; 783 obj["retries"] = r.retries;
779 obj["deferrals"] = r.deferrals; 784 obj["deferrals"] = r.deferrals;
785 // The live retry clock, which used not to be written at all: every
786 // restart handed every backing-off payment a retryAfter of 0 and put
787 // the whole set into the very next 500 ms batch, so a node restarted
788 // in a loop retried continuously instead of once a minute.
789 obj["retryAfter"] = r.retryAfter;
780 arr.push_back(obj); 790 arr.push_back(obj);
781 } 791 }
782 VersionedBlob v; 792 VersionedBlob v;
@@ -818,6 +828,10 @@ static void LoadPaying()
818 r.retries = obj["retries"].get<int>(); 828 r.retries = obj["retries"].get<int>();
819 if (obj.contains("deferrals") && obj["deferrals"].is_number()) 829 if (obj.contains("deferrals") && obj["deferrals"].is_number())
820 r.deferrals = obj["deferrals"].get<int>(); 830 r.deferrals = obj["deferrals"].get<int>();
831 // Absent in files written before this change; 0 means ready now,
832 // which is exactly the behaviour those files used to get.
833 if (obj.contains("retryAfter") && obj["retryAfter"].is_number())
834 r.retryAfter = obj["retryAfter"].get<int64>();
821 // Collapse duplicates as they are read. A build without the 835 // Collapse duplicates as they are read. A build without the
822 // ReconcilePayouts seeding below wrote a second (and after another 836 // ReconcilePayouts seeding below wrote a second (and after another
823 // restart, a third) byte-identical record for every unpaid 837 // restart, a third) byte-identical record for every unpaid
@@ -834,8 +848,9 @@ static void LoadPaying()
834 break; 848 break;
835 } 849 }
836 if (dup) { 850 if (dup) {
837 dup->retries = std::max(dup->retries, r.retries); 851 dup->retries = std::max(dup->retries, r.retries);
838 dup->deferrals = std::max(dup->deferrals, r.deferrals); 852 dup->deferrals = std::max(dup->deferrals, r.deferrals);
853 dup->retryAfter = std::max(dup->retryAfter, r.retryAfter);
839 LogPrint("payout", "[payout] collapsing duplicate in-flight" 854 LogPrint("payout", "[payout] collapsing duplicate in-flight"
840 " record for %s to %s -- one payment, one record\n", 855 " record for %s to %s -- one payment, one record\n",
841 FormatMoney(r.amount).c_str(), r.address.c_str()); 856 FormatMoney(r.amount).c_str(), r.address.c_str());
@@ -1505,12 +1520,9 @@ static void FlushMaturePayouts()
1505 } 1520 }
1506 if (voided) ledgerSnap = SerializePendingPayoutsLocked(); 1521 if (voided) ledgerSnap = SerializePendingPayoutsLocked();
1507 1522
1508 int64 now = GetTime();
1509 for (const PendingPayout& pp : gPendingPayouts) { 1523 for (const PendingPayout& pp : gPendingPayouts) {
1510 if (nBestHeight < pp.matureAtHeight) 1524 if (nBestHeight < pp.matureAtHeight)
1511 continue; 1525 continue;
1512 if (pp.retryAfter > now)
1513 continue;
1514 if (PayoutFullyPaid(pp)) 1526 if (PayoutFullyPaid(pp))
1515 continue; 1527 continue;
1516 // An entry with neither a hash nor a height cannot be told apart 1528 // An entry with neither a hash nor a height cannot be told apart
@@ -1658,7 +1670,6 @@ static void RecordPayoutTxid(const PayingRecord& r, const std::string& txid)
1658 r.blockHeight, r.blockHash)) 1670 r.blockHeight, r.blockHash))
1659 continue; 1671 continue;
1660 it->paidTxids[r.address] = txid; 1672 it->paidTxids[r.address] = txid;
1661 it->retryAfter = 0;
1662 if (PayoutFullyPaid(*it)) { 1673 if (PayoutFullyPaid(*it)) {
1663 LogPrint("payout", "[payout] payout for block %d complete --" 1674 LogPrint("payout", "[payout] payout for block %d complete --"
1664 " removing from ledger\n", it->blockHeight); 1675 " removing from ledger\n", it->blockHeight);