Changeset View
Changeset View
Standalone View
Standalone View
src/validation.cpp
Show First 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | |||||
#include <boost/algorithm/string/join.hpp> | #include <boost/algorithm/string/join.hpp> | ||||
#include <boost/algorithm/string/replace.hpp> | #include <boost/algorithm/string/replace.hpp> | ||||
#include <boost/thread.hpp> | #include <boost/thread.hpp> | ||||
#if defined(NDEBUG) | #if defined(NDEBUG) | ||||
#error "Bitcoin cannot be compiled without assertions." | #error "Bitcoin cannot be compiled without assertions." | ||||
#endif | #endif | ||||
#define MICRO 0.000001 | |||||
#define MILLI 0.001 | |||||
/** | /** | ||||
* Global state | * Global state | ||||
*/ | */ | ||||
CCriticalSection cs_main; | CCriticalSection cs_main; | ||||
BlockMap mapBlockIndex; | BlockMap mapBlockIndex; | ||||
CChain chainActive; | CChain chainActive; | ||||
CBlockIndex *pindexBestHeader = nullptr; | CBlockIndex *pindexBestHeader = nullptr; | ||||
▲ Show 20 Lines • Show All 1,516 Lines • ▼ Show 20 Lines | |||||
static int64_t nTimeCheck = 0; | static int64_t nTimeCheck = 0; | ||||
static int64_t nTimeForks = 0; | static int64_t nTimeForks = 0; | ||||
static int64_t nTimeVerify = 0; | static int64_t nTimeVerify = 0; | ||||
static int64_t nTimeConnect = 0; | static int64_t nTimeConnect = 0; | ||||
static int64_t nTimeIndex = 0; | static int64_t nTimeIndex = 0; | ||||
static int64_t nTimeCallbacks = 0; | static int64_t nTimeCallbacks = 0; | ||||
static int64_t nTimeTotal = 0; | static int64_t nTimeTotal = 0; | ||||
static int64_t nBlocksTotal = 0; | |||||
/** | /** | ||||
* Apply the effects of this block (with given index) on the UTXO set | * Apply the effects of this block (with given index) on the UTXO set | ||||
* represented by coins. Validity checks that depend on the UTXO set are also | * represented by coins. Validity checks that depend on the UTXO set are also | ||||
* done; ConnectBlock() can fail if those validity checks fail (among other | * done; ConnectBlock() can fail if those validity checks fail (among other | ||||
* reasons). | * reasons). | ||||
*/ | */ | ||||
static bool ConnectBlock(const Config &config, const CBlock &block, | static bool ConnectBlock(const Config &config, const CBlock &block, | ||||
Show All 23 Lines | static bool ConnectBlock(const Config &config, const CBlock &block, | ||||
if (block.GetHash() == consensusParams.hashGenesisBlock) { | if (block.GetHash() == consensusParams.hashGenesisBlock) { | ||||
if (!fJustCheck) { | if (!fJustCheck) { | ||||
view.SetBestBlock(pindex->GetBlockHash()); | view.SetBestBlock(pindex->GetBlockHash()); | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
nBlocksTotal++; | |||||
bool fScriptChecks = true; | bool fScriptChecks = true; | ||||
if (!hashAssumeValid.IsNull()) { | if (!hashAssumeValid.IsNull()) { | ||||
// We've been configured with the hash of a block which has been | // We've been configured with the hash of a block which has been | ||||
// externally verified to have a valid history. A suitable default value | // externally verified to have a valid history. A suitable default value | ||||
// is included with the software and updated from time to time. Because | // is included with the software and updated from time to time. Because | ||||
// validity relative to a piece of software is an objective fact these | // validity relative to a piece of software is an objective fact these | ||||
// defaults can be easily reviewed. This setting doesn't force the | // defaults can be easily reviewed. This setting doesn't force the | ||||
// selection of any particular chain but makes validating some faster by | // selection of any particular chain but makes validating some faster by | ||||
Show All 21 Lines | if (!hashAssumeValid.IsNull()) { | ||||
*pindexBestHeader, *pindex, *pindexBestHeader, | *pindexBestHeader, *pindex, *pindexBestHeader, | ||||
consensusParams) <= 60 * 60 * 24 * 7 * 2); | consensusParams) <= 60 * 60 * 24 * 7 * 2); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
int64_t nTime1 = GetTimeMicros(); | int64_t nTime1 = GetTimeMicros(); | ||||
nTimeCheck += nTime1 - nTimeStart; | nTimeCheck += nTime1 - nTimeStart; | ||||
LogPrint(BCLog::BENCH, " - Sanity checks: %.2fms [%.2fs]\n", | LogPrint(BCLog::BENCH, " - Sanity checks: %.2fms [%.2fs (%.2fms/blk)]\n", | ||||
0.001 * (nTime1 - nTimeStart), nTimeCheck * 0.000001); | MILLI * (nTime1 - nTimeStart), nTimeCheck * MICRO, | ||||
nTimeCheck * MILLI / nBlocksTotal); | |||||
// Do not allow blocks that contain transactions which 'overwrite' older | // Do not allow blocks that contain transactions which 'overwrite' older | ||||
// transactions, unless those are already completely spent. If such | // transactions, unless those are already completely spent. If such | ||||
// overwrites are allowed, coinbases and transactions depending upon those | // overwrites are allowed, coinbases and transactions depending upon those | ||||
// can be duplicated to remove the ability to spend the first instance -- | // can be duplicated to remove the ability to spend the first instance -- | ||||
// even after being sent to another address. See BIP30 and | // even after being sent to another address. See BIP30 and | ||||
// http://r6.ca/blog/20120206T005236Z.html for more information. This logic | // http://r6.ca/blog/20120206T005236Z.html for more information. This logic | ||||
// is not necessary for memory pool transactions, as AcceptToMemoryPool | // is not necessary for memory pool transactions, as AcceptToMemoryPool | ||||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | static bool ConnectBlock(const Config &config, const CBlock &block, | ||||
if (pindex->nHeight >= consensusParams.CSVHeight) { | if (pindex->nHeight >= consensusParams.CSVHeight) { | ||||
nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE; | nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE; | ||||
} | } | ||||
const uint32_t flags = GetBlockScriptFlags(config, pindex->pprev); | const uint32_t flags = GetBlockScriptFlags(config, pindex->pprev); | ||||
int64_t nTime2 = GetTimeMicros(); | int64_t nTime2 = GetTimeMicros(); | ||||
nTimeForks += nTime2 - nTime1; | nTimeForks += nTime2 - nTime1; | ||||
LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs]\n", | LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n", | ||||
0.001 * (nTime2 - nTime1), nTimeForks * 0.000001); | MILLI * (nTime2 - nTime1), nTimeForks * MICRO, | ||||
nTimeForks * MILLI / nBlocksTotal); | |||||
CBlockUndo blockundo; | CBlockUndo blockundo; | ||||
CCheckQueueControl<CScriptCheck> control(fScriptChecks ? &scriptcheckqueue | CCheckQueueControl<CScriptCheck> control(fScriptChecks ? &scriptcheckqueue | ||||
: nullptr); | : nullptr); | ||||
std::vector<int> prevheights; | std::vector<int> prevheights; | ||||
Amount nFees = Amount::zero(); | Amount nFees = Amount::zero(); | ||||
▲ Show 20 Lines • Show All 83 Lines • ▼ Show 20 Lines | for (const auto &ptx : block.vtx) { | ||||
blockundo.vtxundo.push_back(CTxUndo()); | blockundo.vtxundo.push_back(CTxUndo()); | ||||
SpendCoins(view, tx, blockundo.vtxundo.back(), pindex->nHeight); | SpendCoins(view, tx, blockundo.vtxundo.back(), pindex->nHeight); | ||||
} | } | ||||
int64_t nTime3 = GetTimeMicros(); | int64_t nTime3 = GetTimeMicros(); | ||||
nTimeConnect += nTime3 - nTime2; | nTimeConnect += nTime3 - nTime2; | ||||
LogPrint(BCLog::BENCH, | LogPrint(BCLog::BENCH, | ||||
" - Connect %u transactions: %.2fms (%.3fms/tx, " | " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) " | ||||
"%.3fms/txin) [%.2fs]\n", | "[%.2fs (%.2fms/blk)]\n", | ||||
(unsigned)block.vtx.size(), 0.001 * (nTime3 - nTime2), | (unsigned)block.vtx.size(), MILLI * (nTime3 - nTime2), | ||||
0.001 * (nTime3 - nTime2) / block.vtx.size(), | MILLI * (nTime3 - nTime2) / block.vtx.size(), | ||||
nInputs <= 1 ? 0 : 0.001 * (nTime3 - nTime2) / (nInputs - 1), | nInputs <= 1 ? 0 : MILLI * (nTime3 - nTime2) / (nInputs - 1), | ||||
nTimeConnect * 0.000001); | nTimeConnect * MICRO, nTimeConnect * MILLI / nBlocksTotal); | ||||
Amount blockReward = | Amount blockReward = | ||||
nFees + GetBlockSubsidy(pindex->nHeight, consensusParams); | nFees + GetBlockSubsidy(pindex->nHeight, consensusParams); | ||||
if (block.vtx[0]->GetValueOut() > blockReward) { | if (block.vtx[0]->GetValueOut() > blockReward) { | ||||
return state.DoS(100, | return state.DoS(100, | ||||
error("ConnectBlock(): coinbase pays too much " | error("ConnectBlock(): coinbase pays too much " | ||||
"(actual=%d vs limit=%d)", | "(actual=%d vs limit=%d)", | ||||
block.vtx[0]->GetValueOut(), blockReward), | block.vtx[0]->GetValueOut(), blockReward), | ||||
REJECT_INVALID, "bad-cb-amount"); | REJECT_INVALID, "bad-cb-amount"); | ||||
} | } | ||||
if (!control.Wait()) { | if (!control.Wait()) { | ||||
return state.DoS(100, false, REJECT_INVALID, "blk-bad-inputs", false, | return state.DoS(100, false, REJECT_INVALID, "blk-bad-inputs", false, | ||||
"parallel script check failed"); | "parallel script check failed"); | ||||
} | } | ||||
int64_t nTime4 = GetTimeMicros(); | int64_t nTime4 = GetTimeMicros(); | ||||
nTimeVerify += nTime4 - nTime2; | nTimeVerify += nTime4 - nTime2; | ||||
LogPrint(BCLog::BENCH, | LogPrint( | ||||
" - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs]\n", | BCLog::BENCH, | ||||
nInputs - 1, 0.001 * (nTime4 - nTime2), | " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", | ||||
nInputs <= 1 ? 0 : 0.001 * (nTime4 - nTime2) / (nInputs - 1), | nInputs - 1, MILLI * (nTime4 - nTime2), | ||||
nTimeVerify * 0.000001); | nInputs <= 1 ? 0 : MILLI * (nTime4 - nTime2) / (nInputs - 1), | ||||
nTimeVerify * MICRO, nTimeVerify * MILLI / nBlocksTotal); | |||||
if (fJustCheck) { | if (fJustCheck) { | ||||
return true; | return true; | ||||
} | } | ||||
if (!WriteUndoDataForBlock(blockundo, state, pindex, | if (!WriteUndoDataForBlock(blockundo, state, pindex, | ||||
config.GetChainParams())) { | config.GetChainParams())) { | ||||
return false; | return false; | ||||
} | } | ||||
if (!pindex->IsValid(BlockValidity::SCRIPTS)) { | if (!pindex->IsValid(BlockValidity::SCRIPTS)) { | ||||
pindex->RaiseValidity(BlockValidity::SCRIPTS); | pindex->RaiseValidity(BlockValidity::SCRIPTS); | ||||
setDirtyBlockIndex.insert(pindex); | setDirtyBlockIndex.insert(pindex); | ||||
} | } | ||||
if (!WriteTxIndexDataForBlock(block, state, pindex)) { | if (!WriteTxIndexDataForBlock(block, state, pindex)) { | ||||
return false; | return false; | ||||
} | } | ||||
// add this block to the view's block chain | // add this block to the view's block chain | ||||
view.SetBestBlock(pindex->GetBlockHash()); | view.SetBestBlock(pindex->GetBlockHash()); | ||||
int64_t nTime5 = GetTimeMicros(); | int64_t nTime5 = GetTimeMicros(); | ||||
nTimeIndex += nTime5 - nTime4; | nTimeIndex += nTime5 - nTime4; | ||||
LogPrint(BCLog::BENCH, " - Index writing: %.2fms [%.2fs]\n", | LogPrint(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n", | ||||
0.001 * (nTime5 - nTime4), nTimeIndex * 0.000001); | MILLI * (nTime5 - nTime4), nTimeIndex * MICRO, | ||||
nTimeIndex * MILLI / nBlocksTotal); | |||||
int64_t nTime6 = GetTimeMicros(); | int64_t nTime6 = GetTimeMicros(); | ||||
nTimeCallbacks += nTime6 - nTime5; | nTimeCallbacks += nTime6 - nTime5; | ||||
LogPrint(BCLog::BENCH, " - Callbacks: %.2fms [%.2fs]\n", | LogPrint(BCLog::BENCH, " - Callbacks: %.2fms [%.2fs (%.2fms/blk)]\n", | ||||
0.001 * (nTime6 - nTime5), nTimeCallbacks * 0.000001); | MILLI * (nTime6 - nTime5), nTimeCallbacks * MICRO, | ||||
nTimeCallbacks * MILLI / nBlocksTotal); | |||||
// If we just activated the replay protection with that block, it means | // If we just activated the replay protection with that block, it means | ||||
// transaction in the mempool are now invalid. As a result, we need to clear | // transaction in the mempool are now invalid. As a result, we need to clear | ||||
// the mempool. | // the mempool. | ||||
if (IsReplayProtectionEnabled(config, pindex) && | if (IsReplayProtectionEnabled(config, pindex) && | ||||
!IsReplayProtectionEnabled(config, pindex->pprev)) { | !IsReplayProtectionEnabled(config, pindex->pprev)) { | ||||
g_mempool.clear(); | g_mempool.clear(); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 275 Lines • ▼ Show 20 Lines | int64_t nStart = GetTimeMicros(); | ||||
pindexDelete->GetBlockHash().ToString()); | pindexDelete->GetBlockHash().ToString()); | ||||
} | } | ||||
bool flushed = view.Flush(); | bool flushed = view.Flush(); | ||||
assert(flushed); | assert(flushed); | ||||
} | } | ||||
LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", | LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", | ||||
(GetTimeMicros() - nStart) * 0.001); | (GetTimeMicros() - nStart) * MILLI); | ||||
// Write the chain state to disk, if necessary. | // Write the chain state to disk, if necessary. | ||||
if (!FlushStateToDisk(config.GetChainParams(), state, | if (!FlushStateToDisk(config.GetChainParams(), state, | ||||
FLUSH_STATE_IF_NEEDED)) { | FLUSH_STATE_IF_NEEDED)) { | ||||
return false; | return false; | ||||
} | } | ||||
// If this block was deactivating the replay protection, then we need to | // If this block was deactivating the replay protection, then we need to | ||||
▲ Show 20 Lines • Show All 215 Lines • ▼ Show 20 Lines | static bool ConnectTip(const Config &config, CValidationState &state, | ||||
const CBlock &blockConnecting = *pthisBlock; | const CBlock &blockConnecting = *pthisBlock; | ||||
// Apply the block atomically to the chain state. | // Apply the block atomically to the chain state. | ||||
int64_t nTime2 = GetTimeMicros(); | int64_t nTime2 = GetTimeMicros(); | ||||
nTimeReadFromDisk += nTime2 - nTime1; | nTimeReadFromDisk += nTime2 - nTime1; | ||||
int64_t nTime3; | int64_t nTime3; | ||||
LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs]\n", | LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs]\n", | ||||
(nTime2 - nTime1) * 0.001, nTimeReadFromDisk * 0.000001); | (nTime2 - nTime1) * MILLI, nTimeReadFromDisk * MICRO); | ||||
{ | { | ||||
CCoinsViewCache view(pcoinsTip.get()); | CCoinsViewCache view(pcoinsTip.get()); | ||||
bool rv = ConnectBlock(config, blockConnecting, state, pindexNew, view); | bool rv = ConnectBlock(config, blockConnecting, state, pindexNew, view); | ||||
GetMainSignals().BlockChecked(blockConnecting, state); | GetMainSignals().BlockChecked(blockConnecting, state); | ||||
if (!rv) { | if (!rv) { | ||||
if (state.IsInvalid()) { | if (state.IsInvalid()) { | ||||
InvalidBlockFound(pindexNew, state); | InvalidBlockFound(pindexNew, state); | ||||
} | } | ||||
Show All 11 Lines | LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs]\n", | ||||
state.SetCorruptionPossible(); | state.SetCorruptionPossible(); | ||||
return error("ConnectTip(): FinalizeBlock %s failed (%s)", | return error("ConnectTip(): FinalizeBlock %s failed (%s)", | ||||
pindexNew->GetBlockHash().ToString(), | pindexNew->GetBlockHash().ToString(), | ||||
FormatStateMessage(state)); | FormatStateMessage(state)); | ||||
} | } | ||||
nTime3 = GetTimeMicros(); | nTime3 = GetTimeMicros(); | ||||
nTimeConnectTotal += nTime3 - nTime2; | nTimeConnectTotal += nTime3 - nTime2; | ||||
LogPrint(BCLog::BENCH, " - Connect total: %.2fms [%.2fs]\n", | LogPrint(BCLog::BENCH, | ||||
(nTime3 - nTime2) * 0.001, nTimeConnectTotal * 0.000001); | " - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n", | ||||
(nTime3 - nTime2) * MILLI, nTimeConnectTotal * MICRO, | |||||
nTimeConnectTotal * MILLI / nBlocksTotal); | |||||
bool flushed = view.Flush(); | bool flushed = view.Flush(); | ||||
assert(flushed); | assert(flushed); | ||||
} | } | ||||
int64_t nTime4 = GetTimeMicros(); | int64_t nTime4 = GetTimeMicros(); | ||||
nTimeFlush += nTime4 - nTime3; | nTimeFlush += nTime4 - nTime3; | ||||
LogPrint(BCLog::BENCH, " - Flush: %.2fms [%.2fs]\n", | LogPrint(BCLog::BENCH, " - Flush: %.2fms [%.2fs (%.2fms/blk)]\n", | ||||
(nTime4 - nTime3) * 0.001, nTimeFlush * 0.000001); | (nTime4 - nTime3) * MILLI, nTimeFlush * MICRO, | ||||
nTimeFlush * MILLI / nBlocksTotal); | |||||
// Write the chain state to disk, if necessary. | // Write the chain state to disk, if necessary. | ||||
if (!FlushStateToDisk(config.GetChainParams(), state, | if (!FlushStateToDisk(config.GetChainParams(), state, | ||||
FLUSH_STATE_IF_NEEDED)) { | FLUSH_STATE_IF_NEEDED)) { | ||||
return false; | return false; | ||||
} | } | ||||
int64_t nTime5 = GetTimeMicros(); | int64_t nTime5 = GetTimeMicros(); | ||||
nTimeChainState += nTime5 - nTime4; | nTimeChainState += nTime5 - nTime4; | ||||
LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs]\n", | LogPrint(BCLog::BENCH, | ||||
(nTime5 - nTime4) * 0.001, nTimeChainState * 0.000001); | " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n", | ||||
(nTime5 - nTime4) * MILLI, nTimeChainState * MICRO, | |||||
nTimeChainState * MILLI / nBlocksTotal); | |||||
// Remove conflicting transactions from the mempool.; | // Remove conflicting transactions from the mempool.; | ||||
g_mempool.removeForBlock(blockConnecting.vtx, pindexNew->nHeight); | g_mempool.removeForBlock(blockConnecting.vtx, pindexNew->nHeight); | ||||
disconnectpool.removeForBlock(blockConnecting.vtx); | disconnectpool.removeForBlock(blockConnecting.vtx); | ||||
// Update chainActive & related variables. | // Update chainActive & related variables. | ||||
UpdateTip(config, pindexNew); | UpdateTip(config, pindexNew); | ||||
int64_t nTime6 = GetTimeMicros(); | int64_t nTime6 = GetTimeMicros(); | ||||
nTimePostConnect += nTime6 - nTime5; | nTimePostConnect += nTime6 - nTime5; | ||||
nTimeTotal += nTime6 - nTime1; | nTimeTotal += nTime6 - nTime1; | ||||
LogPrint(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs]\n", | LogPrint(BCLog::BENCH, | ||||
(nTime6 - nTime5) * 0.001, nTimePostConnect * 0.000001); | " - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n", | ||||
LogPrint(BCLog::BENCH, "- Connect block: %.2fms [%.2fs]\n", | (nTime6 - nTime5) * MILLI, nTimePostConnect * MICRO, | ||||
(nTime6 - nTime1) * 0.001, nTimeTotal * 0.000001); | nTimePostConnect * MILLI / nBlocksTotal); | ||||
LogPrint(BCLog::BENCH, "- Connect block: %.2fms [%.2fs (%.2fms/blk)]\n", | |||||
(nTime6 - nTime1) * MILLI, nTimeTotal * MICRO, | |||||
nTimeTotal * MILLI / nBlocksTotal); | |||||
connectTrace.BlockConnected(pindexNew, std::move(pthisBlock)); | connectTrace.BlockConnected(pindexNew, std::move(pthisBlock)); | ||||
return true; | return true; | ||||
} | } | ||||
/** | /** | ||||
* Return the tip of the chain with the most work in it, that isn't known to be | * Return the tip of the chain with the most work in it, that isn't known to be | ||||
* invalid (it's however far from certain to be valid). | * invalid (it's however far from certain to be valid). | ||||
▲ Show 20 Lines • Show All 2,978 Lines • ▼ Show 20 Lines | try { | ||||
file << mapDeltas; | file << mapDeltas; | ||||
FileCommit(file.Get()); | FileCommit(file.Get()); | ||||
file.fclose(); | file.fclose(); | ||||
RenameOver(GetDataDir() / "mempool.dat.new", | RenameOver(GetDataDir() / "mempool.dat.new", | ||||
GetDataDir() / "mempool.dat"); | GetDataDir() / "mempool.dat"); | ||||
int64_t last = GetTimeMicros(); | int64_t last = GetTimeMicros(); | ||||
LogPrintf("Dumped mempool: %gs to copy, %gs to dump\n", | LogPrintf("Dumped mempool: %gs to copy, %gs to dump\n", | ||||
(mid - start) * 0.000001, (last - mid) * 0.000001); | (mid - start) * MICRO, (last - mid) * MICRO); | ||||
} catch (const std::exception &e) { | } catch (const std::exception &e) { | ||||
LogPrintf("Failed to dump mempool: %s. Continuing anyway.\n", e.what()); | LogPrintf("Failed to dump mempool: %s. Continuing anyway.\n", e.what()); | ||||
} | } | ||||
} | } | ||||
//! Guess how far we are in the verification process at the given block index | //! Guess how far we are in the verification process at the given block index | ||||
double GuessVerificationProgress(const ChainTxData &data, CBlockIndex *pindex) { | double GuessVerificationProgress(const ChainTxData &data, CBlockIndex *pindex) { | ||||
if (pindex == nullptr) { | if (pindex == nullptr) { | ||||
Show All 28 Lines |