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/rpc.cpp97
1 files changed, 93 insertions, 4 deletions
diff --git a/src/rpc.cpp b/src/rpc.cpp
index a4b53ca..f9471c2 100644
--- a/src/rpc.cpp
+++ b/src/rpc.cpp
@@ -1186,11 +1186,35 @@ static bool PayoutBlockStillOnChain(const uint256& blockHash)
1186 return false; 1186 return false;
1187} 1187}
1188 1188
1189static std::string PayoutComment(int blockHeight) 1189// The comment every pre-upgrade payout transaction carries, and therefore the
1190// ONLY needle the legacy fallback in FindRecordedPoolPayout may search with.
1191// This format is frozen: change it and every payment those builds made stops
1192// matching, every one of those debts looks unpaid, and reconciliation pays
1193// them all a second time.
1194static std::string LegacyPayoutComment(int blockHeight)
1190{ 1195{
1191 return strprintf("Pool payout height %d", blockHeight); 1196 return strprintf("Pool payout height %d", blockHeight);
1192} 1197}
1193 1198
1199// The comment written on new sends. It names the funding block, because the
1200// height alone cannot: a height that is won, lost to a reorg, and won again
1201// yields two rounds whose height-only comments are identical -- and identical
1202// amounts too, whenever the same miner held both rounds. The structured
1203// pool_payout_* metadata already keys the payment by hash for the code; this
1204// puts the same fact where an operator reading the wallet can see it, and
1205// makes a new transaction unmatchable by the legacy needle even if the
1206// structured keys are ever lost.
1207//
1208// A zero hash keeps the legacy form: those records predate hash keying, and
1209// there is no hash to name.
1210static std::string PayoutComment(int blockHeight, const uint256& blockHash)
1211{
1212 if (blockHash == 0)
1213 return LegacyPayoutComment(blockHeight);
1214 return strprintf("Pool payout height %d block %s", blockHeight,
1215 blockHash.ToString().c_str());
1216}
1217
1194// Startup reconciliation. 1218// Startup reconciliation.
1195// 1219//
1196// This used to be the crash-recovery mechanism: it scanned the wallet with 1220// This used to be the crash-recovery mechanism: it scanned the wallet with
@@ -1425,7 +1449,7 @@ static PayoutProbeResult FindRecordedPoolPayout(const std::string& id,
1425 CScript want; 1449 CScript want;
1426 if (fHaveScript) 1450 if (fHaveScript)
1427 want << OP_DUP << OP_HASH160 << h160 << OP_EQUALVERIFY << OP_CHECKSIG; 1451 want << OP_DUP << OP_HASH160 << h160 << OP_EQUALVERIFY << OP_CHECKSIG;
1428 const std::string comment = PayoutComment(blockHeight); 1452 const std::string comment = LegacyPayoutComment(blockHeight);
1429 1453
1430 // Candidate legacy matches, adjudicated after cs_mapWallet is released. 1454 // Candidate legacy matches, adjudicated after cs_mapWallet is released.
1431 // 1455 //
@@ -2186,7 +2210,7 @@ static void PayoutThreadFn(void*)
2186 } 2210 }
2187 2211
2188 CWalletTx wtx; 2212 CWalletTx wtx;
2189 wtx.mapValue["comment"] = PayoutComment(r.blockHeight); 2213 wtx.mapValue["comment"] = PayoutComment(r.blockHeight, r.blockHash);
2190 // Upstream's structured metadata. This is what makes the wallet 2214 // Upstream's structured metadata. This is what makes the wallet
2191 // probe above possible at all -- the old comment-only form could 2215 // probe above possible at all -- the old comment-only form could
2192 // not distinguish two rounds paying the same address the same 2216 // not distinguish two rounds paying the same address the same
@@ -3825,7 +3849,7 @@ int RunPayoutLedgerSelfTest()
3825 const int height = 8080; 3849 const int height = 8080;
3826 const int64 amount = 7 * COIN; 3850 const int64 amount = 7 * COIN;
3827 const std::string id = PayoutIdForBlock(height, 0); 3851 const std::string id = PayoutIdForBlock(height, 0);
3828 const std::string comment = PayoutComment(height); 3852 const std::string comment = LegacyPayoutComment(height);
3829 std::set<std::string> none; 3853 std::set<std::string> none;
3830 std::string found; 3854 std::string found;
3831 3855
@@ -4419,6 +4443,71 @@ int RunPayoutLedgerSelfTest()
4419 } 4443 }
4420#endif 4444#endif
4421 4445
4446 // -- 14. The payout comment names the funding block ----------------------
4447 //
4448 // Same height, two hashes: the shape a reorg leaves when the pool wins a
4449 // height, loses the block, and wins the height again. The legacy needle is
4450 // frozen at the pre-upgrade format, so pre-upgrade payments keep matching;
4451 // a new-format comment never matches it, so a new send can never be read
4452 // as a pre-upgrade payment for a twin round.
4453 {
4454 PayoutTestResetState();
4455 CRITICAL_BLOCK(cs_mapWallet)
4456 mapWallet.clear();
4457 PayoutTestClearBlockIndex();
4458
4459 const int height = 9090;
4460 const int64 amount = 3 * COIN;
4461 uint256 hashA = 0, hashB = 0;
4462 hashA.SetHex("00000000000000000000000000000000000000000000000000000000000000aa");
4463 hashB.SetHex("00000000000000000000000000000000000000000000000000000000000000bb");
4464
4465 check(LegacyPayoutComment(height) ==
4466 strprintf("Pool payout height %d", height),
4467 "the legacy needle is frozen at the format pre-upgrade builds"
4468 " wrote");
4469 check(PayoutComment(height, 0) == LegacyPayoutComment(height),
4470 "a grandfathered zero-hash record still writes the legacy form");
4471 check(PayoutComment(height, hashA) ==
4472 strprintf("Pool payout height %d block %s", height,
4473 hashA.ToString().c_str()),
4474 "a hash-keyed record's comment names its funding block");
4475 check(PayoutComment(height, hashA) != PayoutComment(height, hashB),
4476 "twin rounds at one height carry distinguishable comments");
4477
4478 // A transaction wearing the NEW comment format and -- hypothetically
4479 // -- no structured metadata must not be matched by the legacy needle:
4480 // the fallback exists for pre-upgrade transactions, and the new format
4481 // proves this is not one.
4482 const std::string idA = PayoutIdForBlock(height, hashA);
4483 const std::string idB = PayoutIdForBlock(height, hashB);
4484 std::set<std::string> none;
4485 std::string found;
4486 PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount, 1, "");
4487 check(FindRecordedPoolPayout(idB, height, addrA, amount, none, found)
4488 == PAYOUT_PROBE_NONE,
4489 "a new-format comment is never legacy evidence for a twin round");
4490 check(FindRecordedPoolPayout(idA, height, addrA, amount, none, found)
4491 == PAYOUT_PROBE_NONE,
4492 "nor a match channel for its own round -- structured metadata is");
4493
4494 // Round A's own payment, sent by this build, is found by its
4495 // structured id.
4496 CRITICAL_BLOCK(cs_mapWallet)
4497 mapWallet.clear();
4498 PayoutTestClearBlockIndex();
4499 const std::string paidA =
4500 PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount,
4501 2, idA);
4502 check(FindRecordedPoolPayout(idA, height, addrA, amount, none, found)
4503 == PAYOUT_PROBE_STRUCTURED && found == paidA,
4504 "the winning twin's own payment is found by its structured id");
4505
4506 CRITICAL_BLOCK(cs_mapWallet)
4507 mapWallet.clear();
4508 PayoutTestClearBlockIndex();
4509 }
4510
4422 // -- clean up ------------------------------------------------------------ 4511 // -- clean up ------------------------------------------------------------
4423 PayoutTestResetState(); 4512 PayoutTestResetState();
4424 CRITICAL_BLOCK(cs_mapWallet) 4513 CRITICAL_BLOCK(cs_mapWallet)