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.cpp164
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".
3049static std::vector<CBlockIndex*> gPayoutTestBlockIndexes;
3050static CBlockIndex* gPayoutTestChainTip = NULL;
3051
3052static 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.
3002static std::string PayoutTestAddWalletTx(const std::string& comment, 3080static 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.