diff --git a/.gitignore b/.gitignore index 5e9d4b5e6e..807436234b 100644 --- a/.gitignore +++ b/.gitignore @@ -89,6 +89,7 @@ src/univalue/gen Makefile !depends/Makefile background.tiff* +configure~ # Qt Creator Makefile.am.user diff --git a/src/init.cpp b/src/init.cpp index 8a2d037067..6750ab1983 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -375,7 +375,7 @@ void SetupServerArgs() // Hidden Options std::vector hidden_args = { - "-dbcrashratio", "-forcecompactdb", + "-dbcrashratio", "-forcecompactdb", "-interrupt-block=", "-stop-block=", // GUI args. These will be overwritten by SetupUIArgs for the GUI "-choosedatadir", "-lang=", "-min", "-resetguisettings", "-splash"}; @@ -809,6 +809,8 @@ static bool InitSanityCheck() static bool AppInitServers() { + if (!gArgs.GetBoolArg("-rpcstats", DEFAULT_RPC_STATS)) + statsRPC.setActive(false); RPCServer::OnStarted(&OnRPCStarted); RPCServer::OnStopped(&OnRPCStopped); if (!InitHTTPServer()) @@ -1285,6 +1287,34 @@ void SetupAnchorSPVDatabases(bool resync) { } } +bool SetupInterruptArg(const std::string &argName, std::string &hashStore, int &heightStore) { + // Experimental: Block height or hash to invalidate on and stop sync + auto val = gArgs.GetArg(argName, ""); + auto flagName = argName.substr(1); + if (val.empty()) + return false; + if (val.size() == 64) { + hashStore = val; + LogPrintf("flag: %s hash: %s\n", flagName, hashStore); + } else { + std::stringstream ss(val); + ss >> heightStore; + if (heightStore) { + LogPrintf("flag: %s height: %d\n", flagName, heightStore); + } else { + LogPrintf("%s: invalid hash or height provided: %s\n", flagName, val); + } + } + return true; +} + +void SetupInterrupts() { + auto isSet = false; + isSet = SetupInterruptArg("-interrupt-block", fInterruptBlockHash, fInterruptBlockHeight) || isSet; + isSet = SetupInterruptArg("-stop-block", fStopBlockHash, fStopBlockHeight) || isSet; + fStopOrInterrupt = isSet; +} + bool AppInitMain(InitInterfaces& interfaces) { const CChainParams& chainparams = Params(); @@ -1500,6 +1530,9 @@ bool AppInitMain(InitInterfaces& interfaces) nMaxOutboundLimit = gArgs.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET)*1024*1024; } + // Setup interrupts + SetupInterrupts(); + // ********************************************************* Step 7: load block chain fReindex = gArgs.GetBoolArg("-reindex", false); @@ -2069,7 +2102,5 @@ bool AppInitMain(InitInterfaces& interfaces) )); } - if (!gArgs.GetBoolArg("-rpcstats", DEFAULT_RPC_STATS)) statsRPC.setActive(false); - return true; } diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index dc61433305..8c2470e3bb 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -4239,21 +4239,38 @@ Res SwapToDFIOverUSD(CCustomCSView & mnview, DCT_ID tokenId, CAmount amount, CS bool IsVaultPriceValid(CCustomCSView& mnview, const CVaultId& vaultId, uint32_t height) { if (auto collaterals = mnview.GetVaultCollaterals(vaultId)) - for (const auto& collateral : collaterals->balances) - if (auto collateralToken = mnview.HasLoanCollateralToken({collateral.first, height})) - if (auto fixedIntervalPrice = mnview.GetFixedIntervalPrice(collateralToken->fixedIntervalPriceId)) - if (!fixedIntervalPrice.val->isLive(mnview.GetPriceDeviation())) + for (const auto& collateral : collaterals->balances) { + if (auto collateralToken = mnview.HasLoanCollateralToken({collateral.first, height})) { + if (auto fixedIntervalPrice = mnview.GetFixedIntervalPrice(collateralToken->fixedIntervalPriceId)) { + if (!fixedIntervalPrice.val->isLive(mnview.GetPriceDeviation())) { return false; + } + } else { + // No fixed interval prices available. Should not have happened. + return false; + } + } else { + // Not a collateral token. Should not have happened. + return false; + } + } if (auto loans = mnview.GetLoanTokens(vaultId)) - for (const auto& loan : loans->balances) - if (auto loanToken = mnview.GetLoanTokenByID(loan.first)) + for (const auto& loan : loans->balances) { + if (auto loanToken = mnview.GetLoanTokenByID(loan.first)) { if (auto fixedIntervalPrice = mnview.GetFixedIntervalPrice(loanToken->fixedIntervalPriceId)) { - if (!fixedIntervalPrice.val->isLive(mnview.GetPriceDeviation())) + if (!fixedIntervalPrice.val->isLive(mnview.GetPriceDeviation())) { return false; + } } else { + // No fixed interval prices available. Should not have happened. return false; } + } else { + // Not a loan token. Should not have happened. + return false; + } + } return true; } diff --git a/src/rpc/stats.cpp b/src/rpc/stats.cpp index 9cf237264f..df26c878c4 100644 --- a/src/rpc/stats.cpp +++ b/src/rpc/stats.cpp @@ -1,8 +1,54 @@ #include - #include #include +bool CRPCStats::isActive() { return active.load(); } +void CRPCStats::setActive(bool isActive) { active.store(isActive); } + +std::optional CRPCStats::get(const std::string& name) { + CLockFreeGuard lock(lock_stats); + + auto it = map.find(name); + if (it == map.end()) { + return {}; + } + return it->second; +} + +std::map CRPCStats::getMap() { + CLockFreeGuard lock(lock_stats); + return map; +} + +void CRPCStats::save() { + fs::path statsPath = GetDataDir() / DEFAULT_STATSFILE; + fsbridge::ofstream file(statsPath); + + file << toJSON().write() << '\n'; + file.close(); +} + +void CRPCStats::load() { + fs::path statsPath = GetDataDir() / DEFAULT_STATSFILE; + fsbridge::ifstream file(statsPath); + if (!file.is_open()) return; + + std::string line; + file >> line; + + if (!line.size()) return; + + UniValue arr(UniValue::VARR); + arr.read((const std::string)line); + + CLockFreeGuard lock(lock_stats); + for (const auto &val : arr.getValues()) { + auto name = val["name"].get_str(); + map[name] = RPCStats::fromJSON(val); + } + file.close(); +} + UniValue RPCStats::toJSON() { UniValue stats(UniValue::VOBJ), latencyObj(UniValue::VOBJ), diff --git a/src/rpc/stats.h b/src/rpc/stats.h index 0a5afecc2c..6a4327c78f 100644 --- a/src/rpc/stats.h +++ b/src/rpc/stats.h @@ -6,7 +6,7 @@ #include #include #include - +#include #include const char * const DEFAULT_STATSFILE = "stats.log"; @@ -39,9 +39,10 @@ struct RPCStats { RPCStats() : history(RPC_STATS_HISTORY_SIZE) {} - RPCStats(const std::string& name, int64_t latency, int64_t payload) : name(name), latency(latency), payload(payload), history(RPC_STATS_HISTORY_SIZE) { - lastUsedTime = GetSystemTimeInSeconds(); - count = 1; + RPCStats(const std::string& name, int64_t latency, int64_t payload) : + name(name), latency(latency), payload(payload), history(RPC_STATS_HISTORY_SIZE) { + lastUsedTime = GetSystemTimeInSeconds(); + count = 1; }; UniValue toJSON(); @@ -59,58 +60,14 @@ class CRPCStats std::atomic_bool active{DEFAULT_RPC_STATS}; public: - bool isActive() { - return active.load(); - } - void setActive(bool isActive) { - active.store(isActive); - } - + bool isActive(); + void setActive(bool isActive); void add(const std::string& name, const int64_t latency, const int64_t payload); - - std::optional get(const std::string& name) { - CLockFreeGuard lock(lock_stats); - - auto it = map.find(name); - if (it == map.end()) { - return {}; - } - return it->second; - }; - std::map getMap() { - CLockFreeGuard lock(lock_stats); - return map; - }; + std::optional get(const std::string& name); + std::map getMap(); UniValue toJSON(); - - void save() { - fs::path statsPath = GetDataDir() / DEFAULT_STATSFILE; - fsbridge::ofstream file(statsPath); - - file << toJSON().write() << '\n'; - file.close(); - }; - - void load() { - fs::path statsPath = GetDataDir() / DEFAULT_STATSFILE; - fsbridge::ifstream file(statsPath); - if (!file.is_open()) return; - - std::string line; - file >> line; - - if (!line.size()) return; - - UniValue arr(UniValue::VARR); - arr.read((const std::string)line); - - CLockFreeGuard lock(lock_stats); - for (const auto &val : arr.getValues()) { - auto name = val["name"].get_str(); - map[name] = RPCStats::fromJSON(val); - } - file.close(); - }; + void save(); + void load(); }; extern CRPCStats statsRPC; diff --git a/src/validation.cpp b/src/validation.cpp index df8c71e439..b5a26d79c5 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -136,6 +136,13 @@ bool fHavePruned = false; bool fPruneMode = false; bool fRequireStandard = true; bool fCheckBlockIndex = false; + +bool fStopOrInterrupt = false; +std::string fInterruptBlockHash = ""; +int fInterruptBlockHeight = 0; +std::string fStopBlockHash = ""; +int fStopBlockHeight = 0; + size_t nCoinCacheUsage = 5000 * 300; size_t nCustomMemUsage = nDefaultDbCache << 10; uint64_t nPruneTarget = 0; @@ -2304,6 +2311,33 @@ bool ApplyGovVars(CCustomCSView& cache, const CBlockIndex& pindex, const std::ma return false; } +bool StopOrInterruptConnect(const CBlockIndex *pIndex, CValidationState& state) { + if (!fStopOrInterrupt) + return false; + + const auto checkMatch = [](const CBlockIndex *index, const int height, const std::string& hash) { + return height == index->nHeight || (!hash.empty() && hash == index->phashBlock->ToString()); + }; + + // Stop is processed first. So, if a block has both stop and interrupt + // stop will take priority. + if (checkMatch(pIndex, fStopBlockHeight, fStopBlockHash)) { + StartShutdown(); + return true; + } + + if (checkMatch(pIndex, fInterruptBlockHeight, fInterruptBlockHash)) { + state.Invalid( + ValidationInvalidReason::CONSENSUS, + error("ConnectBlock(): user interrupt"), + REJECT_INVALID, + "user-interrupt-request"); + return true; + } + + return false; +} + /** 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 done; ConnectBlock() * can fail if those validity checks fail (among other reasons). */ @@ -2315,6 +2349,10 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl assert(*pindex->phashBlock == block.GetHash()); int64_t nTimeStart = GetTimeMicros(); + // Interrupt on hash or height requested. Invalidate the block. + if (StopOrInterruptConnect(pindex, state)) + return false; + // Reset phanton TX to block TX count nPhantomBurnTx = block.vtx.size(); @@ -4218,7 +4256,6 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin } view.SetVariable(*attributes); - view.Flush(); } } diff --git a/src/validation.h b/src/validation.h index 68cc102a9e..a913de819e 100644 --- a/src/validation.h +++ b/src/validation.h @@ -161,6 +161,13 @@ extern int nScriptCheckThreads; extern bool fRequireStandard; extern bool fCheckBlockIndex; extern bool fCheckpointsEnabled; + +extern bool fStopOrInterrupt; +extern std::string fInterruptBlockHash; +extern int fInterruptBlockHeight; +extern std::string fStopBlockHash; +extern int fStopBlockHeight; + extern size_t nCoinCacheUsage; extern size_t nCustomMemUsage; /** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */ diff --git a/test/lint/check-doc.py b/test/lint/check-doc.py index 86ff76f8ea..9788a10ed3 100755 --- a/test/lint/check-doc.py +++ b/test/lint/check-doc.py @@ -23,7 +23,7 @@ CMD_GREP_WALLET_HIDDEN_ARGS = r"git grep --function-context 'void DummyWalletInit::AddWalletOptions' -- {}".format(CMD_ROOT_DIR) CMD_GREP_DOCS = r"git grep --perl-regexp '{}' {}".format(REGEX_DOC, CMD_ROOT_DIR) # list unsupported, deprecated and duplicate args as they need no documentation -SET_DOC_OPTIONAL = set(['-h', '-help', '-dbcrashratio', '-forcecompactdb']) +SET_DOC_OPTIONAL = set(['-h', '-help', '-dbcrashratio', '-forcecompactdb', '-interrupt-block', '-stop-block']) def lint_missing_argument_documentation():