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.cpp161
1 files changed, 139 insertions, 22 deletions
diff --git a/src/rpc.cpp b/src/rpc.cpp
index dbbf06d..310c0d4 100644
--- a/src/rpc.cpp
+++ b/src/rpc.cpp
@@ -1369,6 +1369,24 @@ static void MarkPoolRoundForfeited(const std::string& id,
1369 PublishPoolRounds(snap); 1369 PublishPoolRounds(snap);
1370} 1370}
1371 1371
1372// Does this round have a published round proof?
1373//
1374// Existence answers a temporal question the legacy fallback below needs:
1375// which builds could have paid this round. RecordPoolRound writes the proof
1376// when the round is created, and structured payout metadata (upstream
1377// cbdd06d) is an ancestor of round proofs (upstream 83ba822) -- no build
1378// able to write a proof has ever sent a payment that only a comment can
1379// identify. A round with a proof therefore cannot have a comment-only
1380// payment; one that matches it is another round's, colliding on the height.
1381static bool PoolRoundHasProof(const std::string& id)
1382{
1383 std::lock_guard<std::mutex> lk(gPoolRoundsMutex);
1384 for (const PoolRoundProof& r : gPoolRounds)
1385 if (r.id == id)
1386 return true;
1387 return false;
1388}
1389
1372// Which wallet transaction, if any, already paid this? 1390// Which wallet transaction, if any, already paid this?
1373// 1391//
1374// Two kinds of evidence, in order of quality. 1392// Two kinds of evidence, in order of quality.
@@ -1404,11 +1422,22 @@ static void MarkPoolRoundForfeited(const std::string& id,
1404// (round, address) pair -- from the ledger's paidTxids, which is durable, 1422// (round, address) pair -- from the ledger's paidTxids, which is durable,
1405// and from what this process has already claimed. One legacy transaction 1423// and from what this process has already claimed. One legacy transaction
1406// can therefore settle at most one debt, so N transactions can never 1424// can therefore settle at most one debt, so N transactions can never
1407// satisfy more than N obligations. 1425// satisfy more than N obligations;
1426//
1427// 4. it is not consulted at all for a round that has a round proof
1428// (fAllowLegacy, decided by the caller from PoolRoundHasProof). Proofs
1429// are written when a round is created, and only by builds whose every
1430// payment names its round exactly -- so a comment-only transaction
1431// cannot be a payment for such a round, only a same-height collision
1432// from an older one, and skipping the fallback cannot hide a real
1433// payment: the structured probe above already found any that exist.
1408// 1434//
1409// What is left unresolved by construction is two pre-upgrade rounds at the 1435// What is left unresolved by construction is a comment-only transaction and
1410// SAME height paying the same address the same amount, since the comment 1436// a PROOFLESS round at the same height paying the same address the same
1411// carries only the height. That needs a reorg. 1437// amount: two grandfathered zero-hash twins, or a twin pair from the builds
1438// that keyed the ledger by hash before any metadata reached the wallet. The
1439// comment carries only the height, so nothing ties the transaction to one
1440// twin rather than the other. That needs a reorg.
1412// 1441//
1413// This used to say the losing twin's ledger entry is voided by the orphan 1442// This used to say the losing twin's ledger entry is voided by the orphan
1414// check before it can be paid. That is wrong: PayoutBlockStillOnChain() 1443// check before it can be paid. That is wrong: PayoutBlockStillOnChain()
@@ -1445,6 +1474,7 @@ static PayoutProbeResult FindRecordedPoolPayout(const std::string& id,
1445 const uint256& blockHash, 1474 const uint256& blockHash,
1446 const std::string& address, 1475 const std::string& address,
1447 int64 amount, 1476 int64 amount,
1477 bool fAllowLegacy,
1448 const std::set<std::string>& excluded, 1478 const std::set<std::string>& excluded,
1449 std::string& txidRet) 1479 std::string& txidRet)
1450{ 1480{
@@ -1505,6 +1535,8 @@ static PayoutProbeResult FindRecordedPoolPayout(const std::string& id,
1505 return PAYOUT_PROBE_STRUCTURED; 1535 return PAYOUT_PROBE_STRUCTURED;
1506 } 1536 }
1507 1537
1538 if (!fAllowLegacy)
1539 continue; // comment-only evidence is inadmissible: rule 4
1508 if (!fHaveScript) 1540 if (!fHaveScript)
1509 continue; 1541 continue;
1510 // The comment is written only by the payout path, so its presence 1542 // The comment is written only by the payout path, so its presence
@@ -2142,10 +2174,15 @@ static void PayoutThreadFn(void*)
2142 std::set<std::string> excluded; 2174 std::set<std::string> excluded;
2143 CollectForeignPayoutTxids(r.blockHeight, r.blockHash, r.address, 2175 CollectForeignPayoutTxids(r.blockHeight, r.blockHash, r.address,
2144 claimKey, excluded); 2176 claimKey, excluded);
2177 // A round with a published proof was created by a build whose
2178 // every payment names its round exactly, so only structured
2179 // evidence can settle it -- a comment match would be another
2180 // round's payment colliding on the height.
2181 const bool fAllowLegacy = !PoolRoundHasProof(id);
2145 r.fProbeWallet = false; 2182 r.fProbeWallet = false;
2146 PayoutProbeResult probe = FindRecordedPoolPayout( 2183 PayoutProbeResult probe = FindRecordedPoolPayout(
2147 id, r.blockHeight, r.blockHash, r.address, r.amount, 2184 id, r.blockHeight, r.blockHash, r.address, r.amount,
2148 excluded, recovered); 2185 fAllowLegacy, excluded, recovered);
2149 if (probe != PAYOUT_PROBE_NONE) { 2186 if (probe != PAYOUT_PROBE_NONE) {
2150 if (probe == PAYOUT_PROBE_LEGACY) { 2187 if (probe == PAYOUT_PROBE_LEGACY) {
2151 // Loud on purpose: this is the upgrade path, where the 2188 // Loud on purpose: this is the upgrade path, where the
@@ -3880,31 +3917,34 @@ int RunPayoutLedgerSelfTest()
3880 const std::string comment = LegacyPayoutComment(height); 3917 const std::string comment = LegacyPayoutComment(height);
3881 std::set<std::string> none; 3918 std::set<std::string> none;
3882 std::string found; 3919 std::string found;
3920 // No round proof exists for these rounds, so legacy evidence is
3921 // admissible -- computed exactly as the payout thread computes it.
3922 const bool fAllowLegacy = !PoolRoundHasProof(id);
3883 3923
3884 check(FindRecordedPoolPayout(id, height, 0, addrA, amount, none, found) 3924 check(FindRecordedPoolPayout(id, height, 0, addrA, amount, fAllowLegacy, none, found)
3885 == PAYOUT_PROBE_NONE, 3925 == PAYOUT_PROBE_NONE,
3886 "an empty wallet is not evidence that anything was paid"); 3926 "an empty wallet is not evidence that anything was paid");
3887 3927
3888 const std::string legacyTxid = 3928 const std::string legacyTxid =
3889 PayoutTestAddWalletTx(comment, addrA, amount, 1, ""); 3929 PayoutTestAddWalletTx(comment, addrA, amount, 1, "");
3890 check(FindRecordedPoolPayout(id, height, 0, addrA, amount, none, found) 3930 check(FindRecordedPoolPayout(id, height, 0, addrA, amount, fAllowLegacy, none, found)
3891 == PAYOUT_PROBE_LEGACY && found == legacyTxid, 3931 == PAYOUT_PROBE_LEGACY && found == legacyTxid,
3892 "a payment carrying only a pre-upgrade comment is recognised as" 3932 "a payment carrying only a pre-upgrade comment is recognised as"
3893 " already paid"); 3933 " already paid");
3894 3934
3895 check(FindRecordedPoolPayout(id, height, 0, addrB, amount, none, found) 3935 check(FindRecordedPoolPayout(id, height, 0, addrB, amount, fAllowLegacy, none, found)
3896 == PAYOUT_PROBE_NONE, 3936 == PAYOUT_PROBE_NONE,
3897 "the legacy match requires the output to pay THIS recipient"); 3937 "the legacy match requires the output to pay THIS recipient");
3898 check(FindRecordedPoolPayout(id, height, 0, addrA, amount + COIN, none, found) 3938 check(FindRecordedPoolPayout(id, height, 0, addrA, amount + COIN, fAllowLegacy, none, found)
3899 == PAYOUT_PROBE_NONE, 3939 == PAYOUT_PROBE_NONE,
3900 "the legacy match requires the output to pay exactly this amount"); 3940 "the legacy match requires the output to pay exactly this amount");
3901 check(FindRecordedPoolPayout(id, height + 1, 0, addrA, amount, none, found) 3941 check(FindRecordedPoolPayout(id, height + 1, 0, addrA, amount, fAllowLegacy, none, found)
3902 == PAYOUT_PROBE_NONE, 3942 == PAYOUT_PROBE_NONE,
3903 "the legacy match requires the comment for this block height"); 3943 "the legacy match requires the comment for this block height");
3904 3944
3905 std::set<std::string> spent; 3945 std::set<std::string> spent;
3906 spent.insert(legacyTxid); 3946 spent.insert(legacyTxid);
3907 check(FindRecordedPoolPayout(id, height, 0, addrA, amount, spent, found) 3947 check(FindRecordedPoolPayout(id, height, 0, addrA, amount, fAllowLegacy, spent, found)
3908 == PAYOUT_PROBE_NONE, 3948 == PAYOUT_PROBE_NONE,
3909 "one legacy transaction cannot settle a second debt"); 3949 "one legacy transaction cannot settle a second debt");
3910 3950
@@ -3913,7 +3953,7 @@ int RunPayoutLedgerSelfTest()
3913 // exactly what the structured metadata exists to prevent. 3953 // exactly what the structured metadata exists to prevent.
3914 const std::string otherId = PayoutIdForBlock(height, 0) + "-other"; 3954 const std::string otherId = PayoutIdForBlock(height, 0) + "-other";
3915 PayoutTestAddWalletTx(comment, addrB, amount, 2, otherId); 3955 PayoutTestAddWalletTx(comment, addrB, amount, 2, otherId);
3916 check(FindRecordedPoolPayout(id, height, 0, addrB, amount, none, found) 3956 check(FindRecordedPoolPayout(id, height, 0, addrB, amount, fAllowLegacy, none, found)
3917 == PAYOUT_PROBE_NONE, 3957 == PAYOUT_PROBE_NONE,
3918 "another round's structured payment is not legacy evidence for" 3958 "another round's structured payment is not legacy evidence for"
3919 " this one"); 3959 " this one");
@@ -3921,7 +3961,7 @@ int RunPayoutLedgerSelfTest()
3921 // The structured path still wins where it applies. 3961 // The structured path still wins where it applies.
3922 const std::string structuredTxid = 3962 const std::string structuredTxid =
3923 PayoutTestAddWalletTx(comment, addrB, amount, 3, id); 3963 PayoutTestAddWalletTx(comment, addrB, amount, 3, id);
3924 check(FindRecordedPoolPayout(id, height, 0, addrB, amount, none, found) 3964 check(FindRecordedPoolPayout(id, height, 0, addrB, amount, fAllowLegacy, none, found)
3925 == PAYOUT_PROBE_STRUCTURED && found == structuredTxid, 3965 == PAYOUT_PROBE_STRUCTURED && found == structuredTxid,
3926 "structured payout metadata is matched in preference to a comment"); 3966 "structured payout metadata is matched in preference to a comment");
3927 3967
@@ -3935,7 +3975,7 @@ int RunPayoutLedgerSelfTest()
3935 PayoutTestClearBlockIndex(); 3975 PayoutTestClearBlockIndex();
3936 const std::string addrC = PayoutTestAddress(0x33); 3976 const std::string addrC = PayoutTestAddress(0x33);
3937 PayoutTestAddWalletTx(comment, addrC, amount, 4, "", false); 3977 PayoutTestAddWalletTx(comment, addrC, amount, 4, "", false);
3938 check(FindRecordedPoolPayout(id, height, 0, addrC, amount, none, found) 3978 check(FindRecordedPoolPayout(id, height, 0, addrC, amount, fAllowLegacy, none, found)
3939 == PAYOUT_PROBE_NONE, 3979 == PAYOUT_PROBE_NONE,
3940 "a pre-upgrade payout that never confirmed is not read as" 3980 "a pre-upgrade payout that never confirmed is not read as"
3941 " payment"); 3981 " payment");
@@ -3945,7 +3985,7 @@ int RunPayoutLedgerSelfTest()
3945 mapWallet.clear(); 3985 mapWallet.clear();
3946 const std::string confirmedTxid = 3986 const std::string confirmedTxid =
3947 PayoutTestAddWalletTx(comment, addrC, amount, 4, "", true); 3987 PayoutTestAddWalletTx(comment, addrC, amount, 4, "", true);
3948 check(FindRecordedPoolPayout(id, height, 0, addrC, amount, none, found) 3988 check(FindRecordedPoolPayout(id, height, 0, addrC, amount, fAllowLegacy, none, found)
3949 == PAYOUT_PROBE_LEGACY && found == confirmedTxid, 3989 == PAYOUT_PROBE_LEGACY && found == confirmedTxid,
3950 "the same payout, confirmed, is"); 3990 "the same payout, confirmed, is");
3951 3991
@@ -3956,7 +3996,7 @@ int RunPayoutLedgerSelfTest()
3956 PayoutTestAddWalletTx(comment, addrC, amount, 5, "", false); 3996 PayoutTestAddWalletTx(comment, addrC, amount, 5, "", false);
3957 std::set<std::string> spentFirst; 3997 std::set<std::string> spentFirst;
3958 spentFirst.insert(confirmedTxid); 3998 spentFirst.insert(confirmedTxid);
3959 check(FindRecordedPoolPayout(id, height, 0, addrC, amount, spentFirst, found) 3999 check(FindRecordedPoolPayout(id, height, 0, addrC, amount, fAllowLegacy, spentFirst, found)
3960 == PAYOUT_PROBE_LEGACY && found == alsoConfirmed, 4000 == PAYOUT_PROBE_LEGACY && found == alsoConfirmed,
3961 "an unconfirmed transaction does not hide a confirmed one behind" 4001 "an unconfirmed transaction does not hide a confirmed one behind"
3962 " it"); 4002 " it");
@@ -4511,11 +4551,14 @@ int RunPayoutLedgerSelfTest()
4511 const std::string idB = PayoutIdForBlock(height, hashB); 4551 const std::string idB = PayoutIdForBlock(height, hashB);
4512 std::set<std::string> none; 4552 std::set<std::string> none;
4513 std::string found; 4553 std::string found;
4554 // Neither twin has a round proof here (section 15 covers the gate),
4555 // and their ids collide, so one answer serves both.
4556 const bool fAllowLegacy = !PoolRoundHasProof(idA);
4514 PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount, 1, ""); 4557 PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount, 1, "");
4515 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount, none, found) 4558 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount, fAllowLegacy, none, found)
4516 == PAYOUT_PROBE_NONE, 4559 == PAYOUT_PROBE_NONE,
4517 "a new-format comment is never legacy evidence for a twin round"); 4560 "a new-format comment is never legacy evidence for a twin round");
4518 check(FindRecordedPoolPayout(idA, height, hashA, addrA, amount, none, found) 4561 check(FindRecordedPoolPayout(idA, height, hashA, addrA, amount, fAllowLegacy, none, found)
4519 == PAYOUT_PROBE_NONE, 4562 == PAYOUT_PROBE_NONE,
4520 "nor a match channel for its own round -- structured metadata is"); 4563 "nor a match channel for its own round -- structured metadata is");
4521 4564
@@ -4527,7 +4570,7 @@ int RunPayoutLedgerSelfTest()
4527 const std::string paidA = 4570 const std::string paidA =
4528 PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount, 4571 PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount,
4529 2, idA); 4572 2, idA);
4530 check(FindRecordedPoolPayout(idA, height, hashA, addrA, amount, none, found) 4573 check(FindRecordedPoolPayout(idA, height, hashA, addrA, amount, fAllowLegacy, none, found)
4531 == PAYOUT_PROBE_STRUCTURED && found == paidA, 4574 == PAYOUT_PROBE_STRUCTURED && found == paidA,
4532 "the winning twin's own payment is found by its structured id"); 4575 "the winning twin's own payment is found by its structured id");
4533 4576
@@ -4539,7 +4582,7 @@ int RunPayoutLedgerSelfTest()
4539 check(idA == idB, 4582 check(idA == idB,
4540 "twin hashes differing only past the sixteenth hex digit collide" 4583 "twin hashes differing only past the sixteenth hex digit collide"
4541 " in id -- the reason the full hash must decide"); 4584 " in id -- the reason the full hash must decide");
4542 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount, none, found) 4585 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount, fAllowLegacy, none, found)
4543 == PAYOUT_PROBE_STRUCTURED, 4586 == PAYOUT_PROBE_STRUCTURED,
4544 "a payment without the block key answers for a colliding twin --" 4587 "a payment without the block key answers for a colliding twin --"
4545 " the accepted residual for pre-key transactions"); 4588 " the accepted residual for pre-key transactions");
@@ -4552,10 +4595,10 @@ int RunPayoutLedgerSelfTest()
4552 const std::string keyedA = 4595 const std::string keyedA =
4553 PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount, 4596 PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount,
4554 3, idA, true, hashA.ToString()); 4597 3, idA, true, hashA.ToString());
4555 check(FindRecordedPoolPayout(idA, height, hashA, addrA, amount, none, found) 4598 check(FindRecordedPoolPayout(idA, height, hashA, addrA, amount, fAllowLegacy, none, found)
4556 == PAYOUT_PROBE_STRUCTURED && found == keyedA, 4599 == PAYOUT_PROBE_STRUCTURED && found == keyedA,
4557 "a payment carrying the full block hash settles its own round"); 4600 "a payment carrying the full block hash settles its own round");
4558 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount, none, found) 4601 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount, fAllowLegacy, none, found)
4559 == PAYOUT_PROBE_NONE, 4602 == PAYOUT_PROBE_NONE,
4560 "and is no evidence for the twin, so the re-won height is paid"); 4603 "and is no evidence for the twin, so the re-won height is paid");
4561 4604
@@ -4564,6 +4607,80 @@ int RunPayoutLedgerSelfTest()
4564 PayoutTestClearBlockIndex(); 4607 PayoutTestClearBlockIndex();
4565 } 4608 }
4566 4609
4610 // -- 15. A proof-bearing round rejects comment-only evidence -------------
4611 //
4612 // The surviving half of the same-height collision: a pre-upgrade
4613 // transaction carries only "Pool payout height H", so if H is won again
4614 // after a reorg, it matches the new round's (height, address, amount)
4615 // exactly and would settle a debt it never paid. The new round has a
4616 // round proof -- written at creation, and only by builds whose payments
4617 // all name their round -- so for it, comment-only evidence is
4618 // inadmissible and the miner is paid. A round without a proof keeps
4619 // today's bias: the evidence is admissible and doubt resolves to
4620 // "already paid".
4621 {
4622 PayoutTestResetState();
4623 CRITICAL_BLOCK(cs_mapWallet)
4624 mapWallet.clear();
4625 PayoutTestClearBlockIndex();
4626 const std::string roundsPath = dir + "/pool_rounds_gate_selftest.json";
4627 const std::string savedRoundsFile = strPoolRoundsFile;
4628 strPoolRoundsFile = roundsPath;
4629 remove(roundsPath.c_str());
4630
4631 const int height = 7171;
4632 const int64 amount = 4 * COIN;
4633 uint256 hashB = 0;
4634 hashB.SetHex("00000000000000000000000000000000000000000000000000000000000000cc");
4635 const std::string idB = PayoutIdForBlock(height, hashB);
4636 std::set<std::string> none;
4637 std::string found;
4638
4639 // The colliding pre-upgrade payment: confirmed, comment-only, same
4640 // height, same address, same amount -- the strongest wrong evidence
4641 // there is.
4642 PayoutTestAddWalletTx(LegacyPayoutComment(height), addrA, amount, 1, "");
4643
4644 check(!PoolRoundHasProof(idB),
4645 "a round with no recorded proof does not claim one");
4646 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount,
4647 !PoolRoundHasProof(idB), none, found)
4648 == PAYOUT_PROBE_LEGACY,
4649 "without a proof the doubt is genuine and still resolves to"
4650 " already-paid");
4651
4652 PoolRoundProof round;
4653 round.id = idB;
4654 round.blockHeight = height;
4655 round.matureAtHeight = height + COINBASE_SPENDABLE_DEPTH;
4656 round.payouts[addrA] = amount;
4657 RecordPoolRound(round);
4658 check(PoolRoundHasProof(idB),
4659 "recording the round proof marks the round as this lineage's");
4660 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount,
4661 !PoolRoundHasProof(idB), none, found)
4662 == PAYOUT_PROBE_NONE,
4663 "with a proof, a same-height comment-only transaction is not"
4664 " evidence, and the re-won height is paid");
4665
4666 // Structured evidence still is: the round's own payment, recovered.
4667 const std::string paidB =
4668 PayoutTestAddWalletTx(PayoutComment(height, hashB), addrA, amount,
4669 2, idB, true, hashB.ToString());
4670 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount,
4671 !PoolRoundHasProof(idB), none, found)
4672 == PAYOUT_PROBE_STRUCTURED && found == paidB,
4673 "the gate does not blind the probe to the round's own structured"
4674 " payment");
4675
4676 CRITICAL_BLOCK(cs_mapWallet)
4677 mapWallet.clear();
4678 PayoutTestClearBlockIndex();
4679 remove(roundsPath.c_str());
4680 remove((roundsPath + ".tmp").c_str());
4681 strPoolRoundsFile = savedRoundsFile;
4682 }
4683
4567 // -- clean up ------------------------------------------------------------ 4684 // -- clean up ------------------------------------------------------------
4568 PayoutTestResetState(); 4685 PayoutTestResetState();
4569 CRITICAL_BLOCK(cs_mapWallet) 4686 CRITICAL_BLOCK(cs_mapWallet)