diff options
Diffstat (limited to 'src/rpc.cpp')
| -rw-r--r-- | src/rpc.cpp | 164 |
1 files changed, 155 insertions, 9 deletions
diff --git a/src/rpc.cpp b/src/rpc.cpp index 06de7f2..b0436ba 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -1339,7 +1339,16 @@ static PayoutProbeResult FindRecordedPoolPayout(const std::string& id, | |||
| 1339 | want << OP_DUP << OP_HASH160 << h160 << OP_EQUALVERIFY << OP_CHECKSIG; | 1339 | want << OP_DUP << OP_HASH160 << h160 << OP_EQUALVERIFY << OP_CHECKSIG; |
| 1340 | const std::string comment = PayoutComment(blockHeight); | 1340 | const std::string comment = PayoutComment(blockHeight); |
| 1341 | 1341 | ||
| 1342 | std::string legacyTxid; | 1342 | // Candidate legacy matches, adjudicated after cs_mapWallet is released. |
| 1343 | // | ||
| 1344 | // The depth check cannot happen inside the scan: GetDepthInMainChain() | ||
| 1345 | // reads mapBlockIndex and pindexBest, which belong to cs_main, and cs_main | ||
| 1346 | // is taken BEFORE cs_mapWallet everywhere else in this tree | ||
| 1347 | // (CreateTransaction, ConnectBlock). Taking it the other way round here | ||
| 1348 | // would be a lock-order inversion in the payout thread. There is at most a | ||
| 1349 | // handful of these: a candidate has to carry this exact height's comment | ||
| 1350 | // and an output of exactly this amount to this address. | ||
| 1351 | std::vector<uint256> legacyCandidates; | ||
| 1343 | 1352 | ||
| 1344 | // One pass, not two: this scan runs under cs_mapWallet, which the whole | 1353 | // One pass, not two: this scan runs under cs_mapWallet, which the whole |
| 1345 | // node contends on, and the merge is already paying for it once per record. | 1354 | // node contends on, and the merge is already paying for it once per record. |
| @@ -1365,27 +1374,63 @@ static PayoutProbeResult FindRecordedPoolPayout(const std::string& id, | |||
| 1365 | return PAYOUT_PROBE_STRUCTURED; | 1374 | return PAYOUT_PROBE_STRUCTURED; |
| 1366 | } | 1375 | } |
| 1367 | 1376 | ||
| 1368 | if (!fHaveScript || !legacyTxid.empty()) | 1377 | if (!fHaveScript) |
| 1369 | continue; | 1378 | continue; |
| 1370 | // The comment is written only by the payout path, so its presence | 1379 | // The comment is written only by the payout path, so its presence |
| 1371 | // already identifies the transaction as one of ours. | 1380 | // already identifies the transaction as one of ours. |
| 1372 | map<string, string>::const_iterator ci = wtx.mapValue.find("comment"); | 1381 | map<string, string>::const_iterator ci = wtx.mapValue.find("comment"); |
| 1373 | if (ci == wtx.mapValue.end() || ci->second != comment) | 1382 | if (ci == wtx.mapValue.end() || ci->second != comment) |
| 1374 | continue; | 1383 | continue; |
| 1375 | std::string txid = wtx.GetHash().GetHex(); | 1384 | if (excluded.count(wtx.GetHash().GetHex())) |
| 1376 | if (excluded.count(txid)) | ||
| 1377 | continue; // already credited to a different debt | 1385 | continue; // already credited to a different debt |
| 1378 | foreach (const CTxOut& txout, wtx.vout) | 1386 | foreach (const CTxOut& txout, wtx.vout) |
| 1379 | if (txout.nValue == amount && txout.scriptPubKey == want) { | 1387 | if (txout.nValue == amount && txout.scriptPubKey == want) { |
| 1380 | legacyTxid = txid; | 1388 | legacyCandidates.push_back(it->first); |
| 1381 | break; | 1389 | break; |
| 1382 | } | 1390 | } |
| 1383 | } | 1391 | } |
| 1384 | } | 1392 | } |
| 1385 | 1393 | ||
| 1386 | if (!legacyTxid.empty()) { | 1394 | // A legacy match must have CONFIRMED. |
| 1387 | txidRet = legacyTxid; | 1395 | // |
| 1388 | return PAYOUT_PROBE_LEGACY; | 1396 | // The structured path does not need this and deliberately does not have |
| 1397 | // it: this process sent that transaction moments ago, and a send that | ||
| 1398 | // failed leaves nothing behind, because SendMoney calls | ||
| 1399 | // RollbackTransactionSpent, which calls EraseFromWallet. | ||
| 1400 | // | ||
| 1401 | // The legacy path is the opposite case in every respect. Those | ||
| 1402 | // transactions are days or weeks old, so whether they confirmed is a | ||
| 1403 | // settled fact rather than an open question, and a pre-upgrade payout that | ||
| 1404 | // never confirmed is evidence of NON-payment about to be read as payment. | ||
| 1405 | // This chain has had a 280-block reorg (see FlushMaturePayouts): a payout | ||
| 1406 | // invalidated by it is still sitting in mapWallet with its comment intact, | ||
| 1407 | // and the first boot after this upgrade is exactly when it gets matched. | ||
| 1408 | // The cost of being wrong here is a miner who is never paid and no record | ||
| 1409 | // that they were owed. | ||
| 1410 | CRITICAL_BLOCK(cs_main) | ||
| 1411 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 1412 | { | ||
| 1413 | for (const uint256& hash : legacyCandidates) { | ||
| 1414 | map<uint256, CWalletTx>::const_iterator it = mapWallet.find(hash); | ||
| 1415 | if (it == mapWallet.end()) | ||
| 1416 | continue; // erased under us; it is not evidence of anything | ||
| 1417 | int depth = it->second.GetDepthInMainChain(); | ||
| 1418 | if (depth < 1) { | ||
| 1419 | // Loud: this is the difference between a miner having been | ||
| 1420 | // paid and a miner about to be paid twice, decided on a fact | ||
| 1421 | // that is easy to check and impossible to recover afterwards. | ||
| 1422 | LogPrint("payout", "[payout] wallet tx %s carries the payout" | ||
| 1423 | " comment for height %d and an output of exactly %s to" | ||
| 1424 | " %s, but it is NOT in the main chain (depth %d) --" | ||
| 1425 | " refusing to read it as payment; this recipient will" | ||
| 1426 | " be paid\n", | ||
| 1427 | hash.GetHex().c_str(), blockHeight, | ||
| 1428 | FormatMoney(amount).c_str(), address.c_str(), depth); | ||
| 1429 | continue; | ||
| 1430 | } | ||
| 1431 | txidRet = hash.GetHex(); | ||
| 1432 | return PAYOUT_PROBE_LEGACY; | ||
| 1433 | } | ||
| 1389 | } | 1434 | } |
| 1390 | return PAYOUT_PROBE_NONE; | 1435 | return PAYOUT_PROBE_NONE; |
| 1391 | } | 1436 | } |
| @@ -2997,13 +3042,47 @@ static CScript PayoutTestScriptFor(const std::string& address) | |||
| 2997 | return sc; | 3042 | return sc; |
| 2998 | } | 3043 | } |
| 2999 | 3044 | ||
| 3045 | // Block index entries fabricated by PayoutTestAddWalletTx, so they can be | ||
| 3046 | // freed and unhooked when the test finishes. gPayoutTestChainTip is the end of | ||
| 3047 | // that chain: blocks are linked onto it rather than each replacing the tip, | ||
| 3048 | // because IsInMainChain() is "pnext is set, or I am the tip". | ||
| 3049 | static std::vector<CBlockIndex*> gPayoutTestBlockIndexes; | ||
| 3050 | static CBlockIndex* gPayoutTestChainTip = NULL; | ||
| 3051 | |||
| 3052 | static void PayoutTestClearBlockIndex() | ||
| 3053 | { | ||
| 3054 | CRITICAL_BLOCK(cs_main) | ||
| 3055 | { | ||
| 3056 | for (CBlockIndex* p : gPayoutTestBlockIndexes) { | ||
| 3057 | for (std::map<uint256, CBlockIndex*>::iterator it = mapBlockIndex.begin(); | ||
| 3058 | it != mapBlockIndex.end(); ) { | ||
| 3059 | if (it->second == p) mapBlockIndex.erase(it++); | ||
| 3060 | else ++it; | ||
| 3061 | } | ||
| 3062 | delete p; | ||
| 3063 | } | ||
| 3064 | gPayoutTestBlockIndexes.clear(); | ||
| 3065 | gPayoutTestChainTip = NULL; | ||
| 3066 | pindexBest = NULL; | ||
| 3067 | } | ||
| 3068 | } | ||
| 3069 | |||
| 3000 | // Put a transaction in the wallet map that looks exactly like one this pool | 3070 | // Put a transaction in the wallet map that looks exactly like one this pool |
| 3001 | // sent. `nonce` only exists to keep the hashes distinct. | 3071 | // sent. `nonce` only exists to keep the hashes distinct. |
| 3072 | // | ||
| 3073 | // fConfirmed decides what GetDepthInMainChain() will answer for it, which is | ||
| 3074 | // what the legacy probe now gates on. A confirmed one is given a block index | ||
| 3075 | // entry of its own: an empty merkle branch hashes to the transaction itself, | ||
| 3076 | // so a block whose merkle root IS that hash accepts it at index 0, and | ||
| 3077 | // pointing pindexBest at that block puts it in the main chain one deep. An | ||
| 3078 | // unconfirmed one keeps hashBlock 0, which is depth 0 -- the shape a payout | ||
| 3079 | // left behind by a reorg has. | ||
| 3002 | static std::string PayoutTestAddWalletTx(const std::string& comment, | 3080 | static std::string PayoutTestAddWalletTx(const std::string& comment, |
| 3003 | const std::string& address, | 3081 | const std::string& address, |
| 3004 | int64 amount, | 3082 | int64 amount, |
| 3005 | int nonce, | 3083 | int nonce, |
| 3006 | const std::string& structuredId) | 3084 | const std::string& structuredId, |
| 3085 | bool fConfirmed = true) | ||
| 3007 | { | 3086 | { |
| 3008 | CWalletTx wtx; | 3087 | CWalletTx wtx; |
| 3009 | wtx.vin.resize(1); | 3088 | wtx.vin.resize(1); |
| @@ -3020,6 +3099,32 @@ static std::string PayoutTestAddWalletTx(const std::string& comment, | |||
| 3020 | wtx.mapValue["pool_payout_amount"] = strprintf("%lld", amount); | 3099 | wtx.mapValue["pool_payout_amount"] = strprintf("%lld", amount); |
| 3021 | } | 3100 | } |
| 3022 | uint256 hash = wtx.GetHash(); | 3101 | uint256 hash = wtx.GetHash(); |
| 3102 | if (fConfirmed) { | ||
| 3103 | // A block hash distinct from the transaction's own. | ||
| 3104 | uint256 blockHash = hash; | ||
| 3105 | ((unsigned char*)&blockHash)[0] ^= 0xff; | ||
| 3106 | CRITICAL_BLOCK(cs_main) | ||
| 3107 | { | ||
| 3108 | CBlockIndex* pindex = new CBlockIndex(); | ||
| 3109 | pindex->hashMerkleRoot = hash; | ||
| 3110 | // Linked onto the end of the fabricated chain rather than simply | ||
| 3111 | // becoming the new tip: IsInMainChain() is "pnext is set, or I am | ||
| 3112 | // the tip", so a second confirmed transaction would otherwise take | ||
| 3113 | // the first one out of the chain again. | ||
| 3114 | pindex->pprev = gPayoutTestChainTip; | ||
| 3115 | pindex->nHeight = gPayoutTestChainTip ? gPayoutTestChainTip->nHeight + 1 : 1; | ||
| 3116 | if (gPayoutTestChainTip) | ||
| 3117 | gPayoutTestChainTip->pnext = pindex; | ||
| 3118 | gPayoutTestChainTip = pindex; | ||
| 3119 | std::map<uint256, CBlockIndex*>::iterator mi = | ||
| 3120 | mapBlockIndex.insert(std::make_pair(blockHash, pindex)).first; | ||
| 3121 | pindex->phashBlock = &mi->first; | ||
| 3122 | pindexBest = pindex; | ||
| 3123 | gPayoutTestBlockIndexes.push_back(pindex); | ||
| 3124 | } | ||
| 3125 | wtx.hashBlock = blockHash; | ||
| 3126 | wtx.nIndex = 0; | ||
| 3127 | } | ||
| 3023 | CRITICAL_BLOCK(cs_mapWallet) | 3128 | CRITICAL_BLOCK(cs_mapWallet) |
| 3024 | mapWallet[hash] = wtx; | 3129 | mapWallet[hash] = wtx; |
| 3025 | return hash.GetHex(); | 3130 | return hash.GetHex(); |
| @@ -3497,6 +3602,46 @@ int RunPayoutLedgerSelfTest() | |||
| 3497 | check(FindRecordedPoolPayout(id, height, addrB, amount, none, found) | 3602 | check(FindRecordedPoolPayout(id, height, addrB, amount, none, found) |
| 3498 | == PAYOUT_PROBE_STRUCTURED && found == structuredTxid, | 3603 | == PAYOUT_PROBE_STRUCTURED && found == structuredTxid, |
| 3499 | "structured payout metadata is matched in preference to a comment"); | 3604 | "structured payout metadata is matched in preference to a comment"); |
| 3605 | |||
| 3606 | // A comment-matching transaction that never made it into the chain is | ||
| 3607 | // evidence of NON-payment. This tree has seen a 280-block reorg; a | ||
| 3608 | // pre-upgrade payout invalidated by one is still in mapWallet with its | ||
| 3609 | // comment intact, and the first boot after the upgrade is exactly when | ||
| 3610 | // it would be matched. | ||
| 3611 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 3612 | mapWallet.clear(); | ||
| 3613 | PayoutTestClearBlockIndex(); | ||
| 3614 | const std::string addrC = PayoutTestAddress(0x33); | ||
| 3615 | PayoutTestAddWalletTx(comment, addrC, amount, 4, "", false); | ||
| 3616 | check(FindRecordedPoolPayout(id, height, addrC, amount, none, found) | ||
| 3617 | == PAYOUT_PROBE_NONE, | ||
| 3618 | "a pre-upgrade payout that never confirmed is not read as" | ||
| 3619 | " payment"); | ||
| 3620 | |||
| 3621 | // The same transaction, confirmed, is. | ||
| 3622 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 3623 | mapWallet.clear(); | ||
| 3624 | const std::string confirmedTxid = | ||
| 3625 | PayoutTestAddWalletTx(comment, addrC, amount, 4, "", true); | ||
| 3626 | check(FindRecordedPoolPayout(id, height, addrC, amount, none, found) | ||
| 3627 | == PAYOUT_PROBE_LEGACY && found == confirmedTxid, | ||
| 3628 | "the same payout, confirmed, is"); | ||
| 3629 | |||
| 3630 | // And an unconfirmed one does not shadow a confirmed one that would | ||
| 3631 | // have matched: the probe used to stop at the first comment match. | ||
| 3632 | const std::string alsoConfirmed = | ||
| 3633 | PayoutTestAddWalletTx(comment, addrC, amount, 6, "", true); | ||
| 3634 | PayoutTestAddWalletTx(comment, addrC, amount, 5, "", false); | ||
| 3635 | std::set<std::string> spentFirst; | ||
| 3636 | spentFirst.insert(confirmedTxid); | ||
| 3637 | check(FindRecordedPoolPayout(id, height, addrC, amount, spentFirst, found) | ||
| 3638 | == PAYOUT_PROBE_LEGACY && found == alsoConfirmed, | ||
| 3639 | "an unconfirmed transaction does not hide a confirmed one behind" | ||
| 3640 | " it"); | ||
| 3641 | |||
| 3642 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 3643 | mapWallet.clear(); | ||
| 3644 | PayoutTestClearBlockIndex(); | ||
| 3500 | } | 3645 | } |
| 3501 | 3646 | ||
| 3502 | // -- 9. On-disk compatibility with the live production ledger ------------ | 3647 | // -- 9. On-disk compatibility with the live production ledger ------------ |
| @@ -3908,6 +4053,7 @@ int RunPayoutLedgerSelfTest() | |||
| 3908 | PayoutTestResetState(); | 4053 | PayoutTestResetState(); |
| 3909 | CRITICAL_BLOCK(cs_mapWallet) | 4054 | CRITICAL_BLOCK(cs_mapWallet) |
| 3910 | mapWallet.clear(); | 4055 | mapWallet.clear(); |
| 4056 | PayoutTestClearBlockIndex(); | ||
| 3911 | // Out of the scratch directory before removing it, and out of the key maps | 4057 | // Out of the scratch directory before removing it, and out of the key maps |
| 3912 | // too: the keys this test generated are process-local, but leaving them in | 4058 | // too: the keys this test generated are process-local, but leaving them in |
| 3913 | // the keystore would let a later test think a script was its own. | 4059 | // the keystore would let a later test think a script was its own. |