diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/rpc.cpp | 96 |
1 files changed, 76 insertions, 20 deletions
diff --git a/src/rpc.cpp b/src/rpc.cpp index f9471c2..dbbf06d 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -1373,10 +1373,15 @@ static void MarkPoolRoundForfeited(const std::string& id, | |||
| 1373 | // | 1373 | // |
| 1374 | // Two kinds of evidence, in order of quality. | 1374 | // Two kinds of evidence, in order of quality. |
| 1375 | // | 1375 | // |
| 1376 | // Structured (pool_payout_id / pool_payout_address / pool_payout_amount) is | 1376 | // Structured (pool_payout_id / pool_payout_address / pool_payout_amount, |
| 1377 | // upstream's, and it is exact: the id is derived from the block hash, so it | 1377 | // plus pool_payout_block since this series) is upstream's, extended. The id |
| 1378 | // names one round and one round only. Every transaction this build sends | 1378 | // is derived from the block hash, but only from the FIRST sixteen hex digits |
| 1379 | // carries it. | 1379 | // of it -- and proof-of-work concentrates zeros exactly there, so the id can |
| 1380 | // name two same-height rounds identically. New sends therefore record the | ||
| 1381 | // full hash as pool_payout_block, and a structured match must agree with it | ||
| 1382 | // wherever both sides have one; a transaction without the key was sent | ||
| 1383 | // before it existed, and for those the id is the best evidence there is. | ||
| 1384 | // Every transaction this build sends carries all four keys. | ||
| 1380 | // | 1385 | // |
| 1381 | // But NO transaction this pool has ever sent carries it. Every payout in the | 1386 | // But NO transaction this pool has ever sent carries it. Every payout in the |
| 1382 | // existing wallet has only a "comment". So on the first boot after this | 1387 | // existing wallet has only a "comment". So on the first boot after this |
| @@ -1437,6 +1442,7 @@ enum PayoutProbeResult { | |||
| 1437 | 1442 | ||
| 1438 | static PayoutProbeResult FindRecordedPoolPayout(const std::string& id, | 1443 | static PayoutProbeResult FindRecordedPoolPayout(const std::string& id, |
| 1439 | int blockHeight, | 1444 | int blockHeight, |
| 1445 | const uint256& blockHash, | ||
| 1440 | const std::string& address, | 1446 | const std::string& address, |
| 1441 | int64 amount, | 1447 | int64 amount, |
| 1442 | const std::set<std::string>& excluded, | 1448 | const std::set<std::string>& excluded, |
| @@ -1482,6 +1488,19 @@ static PayoutProbeResult FindRecordedPoolPayout(const std::string& id, | |||
| 1482 | map<string, string>::const_iterator amtIt = wtx.mapValue.find("pool_payout_amount"); | 1488 | map<string, string>::const_iterator amtIt = wtx.mapValue.find("pool_payout_amount"); |
| 1483 | if (amtIt == wtx.mapValue.end() || amtIt->second != strprintf("%lld", amount)) | 1489 | if (amtIt == wtx.mapValue.end() || amtIt->second != strprintf("%lld", amount)) |
| 1484 | continue; | 1490 | continue; |
| 1491 | // The id alone cannot tell twin rounds at one height apart | ||
| 1492 | // (see above); the full hash can, where the transaction | ||
| 1493 | // recorded one. Where it did not, the id decides, as it | ||
| 1494 | // always has -- refusing those transactions would make every | ||
| 1495 | // payment sent before the key existed look unpaid and pay it | ||
| 1496 | // again. | ||
| 1497 | if (blockHash != 0) { | ||
| 1498 | map<string, string>::const_iterator blkIt = | ||
| 1499 | wtx.mapValue.find("pool_payout_block"); | ||
| 1500 | if (blkIt != wtx.mapValue.end() | ||
| 1501 | && blkIt->second != blockHash.ToString()) | ||
| 1502 | continue; // a twin round's payment, not this round's | ||
| 1503 | } | ||
| 1485 | txidRet = wtx.GetHash().GetHex(); | 1504 | txidRet = wtx.GetHash().GetHex(); |
| 1486 | return PAYOUT_PROBE_STRUCTURED; | 1505 | return PAYOUT_PROBE_STRUCTURED; |
| 1487 | } | 1506 | } |
| @@ -2125,7 +2144,8 @@ static void PayoutThreadFn(void*) | |||
| 2125 | claimKey, excluded); | 2144 | claimKey, excluded); |
| 2126 | r.fProbeWallet = false; | 2145 | r.fProbeWallet = false; |
| 2127 | PayoutProbeResult probe = FindRecordedPoolPayout( | 2146 | PayoutProbeResult probe = FindRecordedPoolPayout( |
| 2128 | id, r.blockHeight, r.address, r.amount, excluded, recovered); | 2147 | id, r.blockHeight, r.blockHash, r.address, r.amount, |
| 2148 | excluded, recovered); | ||
| 2129 | if (probe != PAYOUT_PROBE_NONE) { | 2149 | if (probe != PAYOUT_PROBE_NONE) { |
| 2130 | if (probe == PAYOUT_PROBE_LEGACY) { | 2150 | if (probe == PAYOUT_PROBE_LEGACY) { |
| 2131 | // Loud on purpose: this is the upgrade path, where the | 2151 | // Loud on purpose: this is the upgrade path, where the |
| @@ -2219,6 +2239,11 @@ static void PayoutThreadFn(void*) | |||
| 2219 | wtx.mapValue["pool_payout_id"] = id; | 2239 | wtx.mapValue["pool_payout_id"] = id; |
| 2220 | wtx.mapValue["pool_payout_address"] = r.address; | 2240 | wtx.mapValue["pool_payout_address"] = r.address; |
| 2221 | wtx.mapValue["pool_payout_amount"] = strprintf("%lld", r.amount); | 2241 | wtx.mapValue["pool_payout_amount"] = strprintf("%lld", r.amount); |
| 2242 | // The full hash, not only the id's sixteen-digit prefix: the | ||
| 2243 | // prefix of a proof-of-work hash is where the zeros live, and | ||
| 2244 | // the probe needs to tell twin rounds at one height apart. | ||
| 2245 | if (r.blockHash != 0) | ||
| 2246 | wtx.mapValue["pool_payout_block"] = r.blockHash.ToString(); | ||
| 2222 | 2247 | ||
| 2223 | SendMoneyOutcome outcome; | 2248 | SendMoneyOutcome outcome; |
| 2224 | if (SendMoney(sc, r.amount, wtx, outcome)) { | 2249 | if (SendMoney(sc, r.amount, wtx, outcome)) { |
| @@ -3285,7 +3310,8 @@ static std::string PayoutTestAddWalletTx(const std::string& comment, | |||
| 3285 | int64 amount, | 3310 | int64 amount, |
| 3286 | int nonce, | 3311 | int nonce, |
| 3287 | const std::string& structuredId, | 3312 | const std::string& structuredId, |
| 3288 | bool fConfirmed = true) | 3313 | bool fConfirmed = true, |
| 3314 | const std::string& structuredBlock = "") | ||
| 3289 | { | 3315 | { |
| 3290 | CWalletTx wtx; | 3316 | CWalletTx wtx; |
| 3291 | wtx.vin.resize(1); | 3317 | wtx.vin.resize(1); |
| @@ -3300,6 +3326,8 @@ static std::string PayoutTestAddWalletTx(const std::string& comment, | |||
| 3300 | wtx.mapValue["pool_payout_id"] = structuredId; | 3326 | wtx.mapValue["pool_payout_id"] = structuredId; |
| 3301 | wtx.mapValue["pool_payout_address"] = address; | 3327 | wtx.mapValue["pool_payout_address"] = address; |
| 3302 | wtx.mapValue["pool_payout_amount"] = strprintf("%lld", amount); | 3328 | wtx.mapValue["pool_payout_amount"] = strprintf("%lld", amount); |
| 3329 | if (!structuredBlock.empty()) | ||
| 3330 | wtx.mapValue["pool_payout_block"] = structuredBlock; | ||
| 3303 | } | 3331 | } |
| 3304 | uint256 hash = wtx.GetHash(); | 3332 | uint256 hash = wtx.GetHash(); |
| 3305 | if (fConfirmed) { | 3333 | if (fConfirmed) { |
| @@ -3853,30 +3881,30 @@ int RunPayoutLedgerSelfTest() | |||
| 3853 | std::set<std::string> none; | 3881 | std::set<std::string> none; |
| 3854 | std::string found; | 3882 | std::string found; |
| 3855 | 3883 | ||
| 3856 | check(FindRecordedPoolPayout(id, height, addrA, amount, none, found) | 3884 | check(FindRecordedPoolPayout(id, height, 0, addrA, amount, none, found) |
| 3857 | == PAYOUT_PROBE_NONE, | 3885 | == PAYOUT_PROBE_NONE, |
| 3858 | "an empty wallet is not evidence that anything was paid"); | 3886 | "an empty wallet is not evidence that anything was paid"); |
| 3859 | 3887 | ||
| 3860 | const std::string legacyTxid = | 3888 | const std::string legacyTxid = |
| 3861 | PayoutTestAddWalletTx(comment, addrA, amount, 1, ""); | 3889 | PayoutTestAddWalletTx(comment, addrA, amount, 1, ""); |
| 3862 | check(FindRecordedPoolPayout(id, height, addrA, amount, none, found) | 3890 | check(FindRecordedPoolPayout(id, height, 0, addrA, amount, none, found) |
| 3863 | == PAYOUT_PROBE_LEGACY && found == legacyTxid, | 3891 | == PAYOUT_PROBE_LEGACY && found == legacyTxid, |
| 3864 | "a payment carrying only a pre-upgrade comment is recognised as" | 3892 | "a payment carrying only a pre-upgrade comment is recognised as" |
| 3865 | " already paid"); | 3893 | " already paid"); |
| 3866 | 3894 | ||
| 3867 | check(FindRecordedPoolPayout(id, height, addrB, amount, none, found) | 3895 | check(FindRecordedPoolPayout(id, height, 0, addrB, amount, none, found) |
| 3868 | == PAYOUT_PROBE_NONE, | 3896 | == PAYOUT_PROBE_NONE, |
| 3869 | "the legacy match requires the output to pay THIS recipient"); | 3897 | "the legacy match requires the output to pay THIS recipient"); |
| 3870 | check(FindRecordedPoolPayout(id, height, addrA, amount + COIN, none, found) | 3898 | check(FindRecordedPoolPayout(id, height, 0, addrA, amount + COIN, none, found) |
| 3871 | == PAYOUT_PROBE_NONE, | 3899 | == PAYOUT_PROBE_NONE, |
| 3872 | "the legacy match requires the output to pay exactly this amount"); | 3900 | "the legacy match requires the output to pay exactly this amount"); |
| 3873 | check(FindRecordedPoolPayout(id, height + 1, addrA, amount, none, found) | 3901 | check(FindRecordedPoolPayout(id, height + 1, 0, addrA, amount, none, found) |
| 3874 | == PAYOUT_PROBE_NONE, | 3902 | == PAYOUT_PROBE_NONE, |
| 3875 | "the legacy match requires the comment for this block height"); | 3903 | "the legacy match requires the comment for this block height"); |
| 3876 | 3904 | ||
| 3877 | std::set<std::string> spent; | 3905 | std::set<std::string> spent; |
| 3878 | spent.insert(legacyTxid); | 3906 | spent.insert(legacyTxid); |
| 3879 | check(FindRecordedPoolPayout(id, height, addrA, amount, spent, found) | 3907 | check(FindRecordedPoolPayout(id, height, 0, addrA, amount, spent, found) |
| 3880 | == PAYOUT_PROBE_NONE, | 3908 | == PAYOUT_PROBE_NONE, |
| 3881 | "one legacy transaction cannot settle a second debt"); | 3909 | "one legacy transaction cannot settle a second debt"); |
| 3882 | 3910 | ||
| @@ -3885,7 +3913,7 @@ int RunPayoutLedgerSelfTest() | |||
| 3885 | // exactly what the structured metadata exists to prevent. | 3913 | // exactly what the structured metadata exists to prevent. |
| 3886 | const std::string otherId = PayoutIdForBlock(height, 0) + "-other"; | 3914 | const std::string otherId = PayoutIdForBlock(height, 0) + "-other"; |
| 3887 | PayoutTestAddWalletTx(comment, addrB, amount, 2, otherId); | 3915 | PayoutTestAddWalletTx(comment, addrB, amount, 2, otherId); |
| 3888 | check(FindRecordedPoolPayout(id, height, addrB, amount, none, found) | 3916 | check(FindRecordedPoolPayout(id, height, 0, addrB, amount, none, found) |
| 3889 | == PAYOUT_PROBE_NONE, | 3917 | == PAYOUT_PROBE_NONE, |
| 3890 | "another round's structured payment is not legacy evidence for" | 3918 | "another round's structured payment is not legacy evidence for" |
| 3891 | " this one"); | 3919 | " this one"); |
| @@ -3893,7 +3921,7 @@ int RunPayoutLedgerSelfTest() | |||
| 3893 | // The structured path still wins where it applies. | 3921 | // The structured path still wins where it applies. |
| 3894 | const std::string structuredTxid = | 3922 | const std::string structuredTxid = |
| 3895 | PayoutTestAddWalletTx(comment, addrB, amount, 3, id); | 3923 | PayoutTestAddWalletTx(comment, addrB, amount, 3, id); |
| 3896 | check(FindRecordedPoolPayout(id, height, addrB, amount, none, found) | 3924 | check(FindRecordedPoolPayout(id, height, 0, addrB, amount, none, found) |
| 3897 | == PAYOUT_PROBE_STRUCTURED && found == structuredTxid, | 3925 | == PAYOUT_PROBE_STRUCTURED && found == structuredTxid, |
| 3898 | "structured payout metadata is matched in preference to a comment"); | 3926 | "structured payout metadata is matched in preference to a comment"); |
| 3899 | 3927 | ||
| @@ -3907,7 +3935,7 @@ int RunPayoutLedgerSelfTest() | |||
| 3907 | PayoutTestClearBlockIndex(); | 3935 | PayoutTestClearBlockIndex(); |
| 3908 | const std::string addrC = PayoutTestAddress(0x33); | 3936 | const std::string addrC = PayoutTestAddress(0x33); |
| 3909 | PayoutTestAddWalletTx(comment, addrC, amount, 4, "", false); | 3937 | PayoutTestAddWalletTx(comment, addrC, amount, 4, "", false); |
| 3910 | check(FindRecordedPoolPayout(id, height, addrC, amount, none, found) | 3938 | check(FindRecordedPoolPayout(id, height, 0, addrC, amount, none, found) |
| 3911 | == PAYOUT_PROBE_NONE, | 3939 | == PAYOUT_PROBE_NONE, |
| 3912 | "a pre-upgrade payout that never confirmed is not read as" | 3940 | "a pre-upgrade payout that never confirmed is not read as" |
| 3913 | " payment"); | 3941 | " payment"); |
| @@ -3917,7 +3945,7 @@ int RunPayoutLedgerSelfTest() | |||
| 3917 | mapWallet.clear(); | 3945 | mapWallet.clear(); |
| 3918 | const std::string confirmedTxid = | 3946 | const std::string confirmedTxid = |
| 3919 | PayoutTestAddWalletTx(comment, addrC, amount, 4, "", true); | 3947 | PayoutTestAddWalletTx(comment, addrC, amount, 4, "", true); |
| 3920 | check(FindRecordedPoolPayout(id, height, addrC, amount, none, found) | 3948 | check(FindRecordedPoolPayout(id, height, 0, addrC, amount, none, found) |
| 3921 | == PAYOUT_PROBE_LEGACY && found == confirmedTxid, | 3949 | == PAYOUT_PROBE_LEGACY && found == confirmedTxid, |
| 3922 | "the same payout, confirmed, is"); | 3950 | "the same payout, confirmed, is"); |
| 3923 | 3951 | ||
| @@ -3928,7 +3956,7 @@ int RunPayoutLedgerSelfTest() | |||
| 3928 | PayoutTestAddWalletTx(comment, addrC, amount, 5, "", false); | 3956 | PayoutTestAddWalletTx(comment, addrC, amount, 5, "", false); |
| 3929 | std::set<std::string> spentFirst; | 3957 | std::set<std::string> spentFirst; |
| 3930 | spentFirst.insert(confirmedTxid); | 3958 | spentFirst.insert(confirmedTxid); |
| 3931 | check(FindRecordedPoolPayout(id, height, addrC, amount, spentFirst, found) | 3959 | check(FindRecordedPoolPayout(id, height, 0, addrC, amount, spentFirst, found) |
| 3932 | == PAYOUT_PROBE_LEGACY && found == alsoConfirmed, | 3960 | == PAYOUT_PROBE_LEGACY && found == alsoConfirmed, |
| 3933 | "an unconfirmed transaction does not hide a confirmed one behind" | 3961 | "an unconfirmed transaction does not hide a confirmed one behind" |
| 3934 | " it"); | 3962 | " it"); |
| @@ -4484,10 +4512,10 @@ int RunPayoutLedgerSelfTest() | |||
| 4484 | std::set<std::string> none; | 4512 | std::set<std::string> none; |
| 4485 | std::string found; | 4513 | std::string found; |
| 4486 | PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount, 1, ""); | 4514 | PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount, 1, ""); |
| 4487 | check(FindRecordedPoolPayout(idB, height, addrA, amount, none, found) | 4515 | check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount, none, found) |
| 4488 | == PAYOUT_PROBE_NONE, | 4516 | == PAYOUT_PROBE_NONE, |
| 4489 | "a new-format comment is never legacy evidence for a twin round"); | 4517 | "a new-format comment is never legacy evidence for a twin round"); |
| 4490 | check(FindRecordedPoolPayout(idA, height, addrA, amount, none, found) | 4518 | check(FindRecordedPoolPayout(idA, height, hashA, addrA, amount, none, found) |
| 4491 | == PAYOUT_PROBE_NONE, | 4519 | == PAYOUT_PROBE_NONE, |
| 4492 | "nor a match channel for its own round -- structured metadata is"); | 4520 | "nor a match channel for its own round -- structured metadata is"); |
| 4493 | 4521 | ||
| @@ -4499,10 +4527,38 @@ int RunPayoutLedgerSelfTest() | |||
| 4499 | const std::string paidA = | 4527 | const std::string paidA = |
| 4500 | PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount, | 4528 | PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount, |
| 4501 | 2, idA); | 4529 | 2, idA); |
| 4502 | check(FindRecordedPoolPayout(idA, height, addrA, amount, none, found) | 4530 | check(FindRecordedPoolPayout(idA, height, hashA, addrA, amount, none, found) |
| 4503 | == PAYOUT_PROBE_STRUCTURED && found == paidA, | 4531 | == PAYOUT_PROBE_STRUCTURED && found == paidA, |
| 4504 | "the winning twin's own payment is found by its structured id"); | 4532 | "the winning twin's own payment is found by its structured id"); |
| 4505 | 4533 | ||
| 4534 | // The id is derived from the FIRST sixteen hex digits of the hash -- | ||
| 4535 | // the end proof-of-work fills with zeros -- so twin hashes that agree | ||
| 4536 | // there collide in id, and a payment carrying no pool_payout_block | ||
| 4537 | // answers for either twin. That is the accepted trade for payments | ||
| 4538 | // sent before the key existed, pinned here so it stays a decision. | ||
| 4539 | check(idA == idB, | ||
| 4540 | "twin hashes differing only past the sixteenth hex digit collide" | ||
| 4541 | " in id -- the reason the full hash must decide"); | ||
| 4542 | check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount, none, found) | ||
| 4543 | == PAYOUT_PROBE_STRUCTURED, | ||
| 4544 | "a payment without the block key answers for a colliding twin --" | ||
| 4545 | " the accepted residual for pre-key transactions"); | ||
| 4546 | |||
| 4547 | // With the full hash recorded -- as every send now records it -- the | ||
| 4548 | // twins are told apart and the re-won height is paid. | ||
| 4549 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 4550 | mapWallet.clear(); | ||
| 4551 | PayoutTestClearBlockIndex(); | ||
| 4552 | const std::string keyedA = | ||
| 4553 | PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount, | ||
| 4554 | 3, idA, true, hashA.ToString()); | ||
| 4555 | check(FindRecordedPoolPayout(idA, height, hashA, addrA, amount, none, found) | ||
| 4556 | == PAYOUT_PROBE_STRUCTURED && found == keyedA, | ||
| 4557 | "a payment carrying the full block hash settles its own round"); | ||
| 4558 | check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount, none, found) | ||
| 4559 | == PAYOUT_PROBE_NONE, | ||
| 4560 | "and is no evidence for the twin, so the re-won height is paid"); | ||
| 4561 | |||
| 4506 | CRITICAL_BLOCK(cs_mapWallet) | 4562 | CRITICAL_BLOCK(cs_mapWallet) |
| 4507 | mapWallet.clear(); | 4563 | mapWallet.clear(); |
| 4508 | PayoutTestClearBlockIndex(); | 4564 | PayoutTestClearBlockIndex(); |