diff options
| -rw-r--r-- | src/rpc.cpp | 97 |
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 | ||
| 1189 | static 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. | ||
| 1194 | static 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. | ||
| 1210 | static 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) |