perf: stop per-frame SQLite writes and console spam, make perf.log opt-in
- Explorer: the tip check runs every frame the Explorer tab is open, and it read both tip rows and rewrote them each time (two autocommit WAL writes per frame). A steady tip is now kept in memory and skips SQLite. A new tip writes both rows in one savepoint, so a crash between them can't pair a new height with the old hash. A test checks that 100 steady checks write nothing. - Console: while the daemon was in the Error state the Console tab added its error line on every poll, i.e. every frame, re-laying out the whole buffer each time. It prints once per error now: on entering Error, when the message changes, or after another crash (EmbeddedDaemon::getUnexpectedExitCount(), never reset). setState writes the message before the state, so a reader that sees Error sees that error's message. The stray " ===" suffix is gone. - perf.log: the profiler appended a block every 300 frames on every run and never rotated the file (tens of MB a day for a wallet left open). It is now off unless the app starts with DRAGONX_PERFLOG=1; otherwise its zone hooks are near no-ops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -256,11 +256,13 @@ std::vector<std::string> EmbeddedDaemon::getChainParams()
|
|||||||
|
|
||||||
void EmbeddedDaemon::setState(State s, const std::string& message)
|
void EmbeddedDaemon::setState(State s, const std::string& message)
|
||||||
{
|
{
|
||||||
state_ = s;
|
// Message first, state second: a reader that sees Error must also see that error's message, not the
|
||||||
|
// previous one (the console prints the message when it sees the state change).
|
||||||
if (!message.empty() && s == State::Error) {
|
if (!message.empty() && s == State::Error) {
|
||||||
std::lock_guard<std::mutex> lk(error_mutex_); // dedicated mutex — never taken with output_mutex_ held
|
std::lock_guard<std::mutex> lk(error_mutex_); // dedicated mutex — never taken with output_mutex_ held
|
||||||
last_error_ = message;
|
last_error_ = message;
|
||||||
}
|
}
|
||||||
|
state_ = s;
|
||||||
|
|
||||||
if (state_callback_) {
|
if (state_callback_) {
|
||||||
state_callback_(s, message);
|
state_callback_(s, message);
|
||||||
@@ -1098,6 +1100,7 @@ void EmbeddedDaemon::monitorProcess()
|
|||||||
}
|
}
|
||||||
|
|
||||||
crash_count_++;
|
crash_count_++;
|
||||||
|
unexpected_exits_++; // before setState(Error): a reader that sees Error sees the new count
|
||||||
|
|
||||||
// Build informative error message with full diagnostic details
|
// Build informative error message with full diagnostic details
|
||||||
std::string error_msg = "dragonxd exited unexpectedly (" + translateWindowsExitCode(exit_code) + ")";
|
std::string error_msg = "dragonxd exited unexpectedly (" + translateWindowsExitCode(exit_code) + ")";
|
||||||
@@ -1671,6 +1674,7 @@ void EmbeddedDaemon::monitorProcess()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
crash_count_++;
|
crash_count_++;
|
||||||
|
unexpected_exits_++; // before setState(Error): a reader that sees Error sees the new count
|
||||||
setState(State::Error, "dragonxd exited unexpectedly (" + exitInfo + ")");
|
setState(State::Error, "dragonxd exited unexpectedly (" + exitInfo + ")");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -446,6 +446,9 @@ public:
|
|||||||
int getCrashCount() const { return crash_count_.load(); }
|
int getCrashCount() const { return crash_count_.load(); }
|
||||||
/** Reset crash counter (call on successful connection or manual restart) */
|
/** Reset crash counter (call on successful connection or manual restart) */
|
||||||
void resetCrashCount() { crash_count_ = 0; }
|
void resetCrashCount() { crash_count_ = 0; }
|
||||||
|
/** Unexpected exits since this object was created; never reset (unlike the crash count), so a
|
||||||
|
* reader can tell a new crash from an error it has already reported. */
|
||||||
|
std::uint64_t getUnexpectedExitCount() const { return unexpected_exits_.load(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setState(State s, const std::string& message = "");
|
void setState(State s, const std::string& message = "");
|
||||||
@@ -494,6 +497,7 @@ private:
|
|||||||
std::string stratum_allowip_; // -stratumallowip subnet (empty = daemon default: loopback only)
|
std::string stratum_allowip_; // -stratumallowip subnet (empty = daemon default: loopback only)
|
||||||
std::string wallet_file_; // -wallet=<name> for the active wallet; empty/"wallet.dat" = default
|
std::string wallet_file_; // -wallet=<name> for the active wallet; empty/"wallet.dat" = default
|
||||||
std::atomic<int> crash_count_{0}; // consecutive crash counter
|
std::atomic<int> crash_count_{0}; // consecutive crash counter
|
||||||
|
std::atomic<std::uint64_t> unexpected_exits_{0}; // all unexpected exits (see getUnexpectedExitCount)
|
||||||
std::atomic<bool> rescan_on_next_start_{false}; // -rescan flag for next start
|
std::atomic<bool> rescan_on_next_start_{false}; // -rescan flag for next start
|
||||||
std::atomic<bool> zap_on_next_start_{false}; // -zapwallettxes=2 flag for next start
|
std::atomic<bool> zap_on_next_start_{false}; // -zapwallettxes=2 flag for next start
|
||||||
std::atomic<bool> salvage_on_next_start_{false}; // -salvagewallet flag for next start
|
std::atomic<bool> salvage_on_next_start_{false}; // -salvagewallet flag for next start
|
||||||
|
|||||||
+5
-2
@@ -77,6 +77,7 @@ extern "C" HRESULT __stdcall SHGetPropertyStoreForWindow(HWND hwnd, REFIID riid,
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -1447,8 +1448,10 @@ int main(int argc, char* argv[])
|
|||||||
dragonx::ui::schema::UISchema::instance().reapplyColorsToImGui();
|
dragonx::ui::schema::UISchema::instance().reapplyColorsToImGui();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize performance profiler — writes to <ObsidianDragonDir>/perf.log
|
// Performance profiler — writes to <ObsidianDragonDir>/perf.log. Opt-in (DRAGONX_PERFLOG=1): always
|
||||||
{
|
// on, it appended a block every 300 frames and was never rotated (tens of MB a day for any wallet
|
||||||
|
// left open).
|
||||||
|
if (const char* perfEnv = std::getenv("DRAGONX_PERFLOG"); perfEnv && *perfEnv && std::strcmp(perfEnv, "0") != 0) {
|
||||||
std::string perfPath = (std::filesystem::path(
|
std::string perfPath = (std::filesystem::path(
|
||||||
dragonx::util::Platform::getObsidianDragonDir()) / "perf.log").string();
|
dragonx::util::Platform::getObsidianDragonDir()) / "perf.log").string();
|
||||||
dragonx::util::PerfLog::instance().init(perfPath);
|
dragonx::util::PerfLog::instance().init(perfPath);
|
||||||
|
|||||||
@@ -229,7 +229,16 @@ ExplorerBlockCache::SavedTipValidation ExplorerBlockCache::prepareValidation(
|
|||||||
int currentHeight, const std::string& currentBestHash)
|
int currentHeight, const std::string& currentBestHash)
|
||||||
{
|
{
|
||||||
SavedTipValidation validation;
|
SavedTipValidation validation;
|
||||||
if (currentHeight <= 0 || !ensureOpen()) return validation;
|
if (currentHeight <= 0) return validation;
|
||||||
|
|
||||||
|
// Steady state: the tip hasn't moved since it was saved. This runs every frame the Explorer is
|
||||||
|
// open, and used to read both meta rows and rewrite them each time (two autocommit WAL writes per
|
||||||
|
// frame). Only an exact match takes the shortcut; anything else goes through the checks below.
|
||||||
|
if (tip_known_ && !currentBestHash.empty() &&
|
||||||
|
currentHeight == tip_height_ && currentBestHash == tip_hash_) {
|
||||||
|
return validation;
|
||||||
|
}
|
||||||
|
if (!ensureOpen()) return validation;
|
||||||
|
|
||||||
int savedHeight = getMetaInt("tip_height", 0);
|
int savedHeight = getMetaInt("tip_height", 0);
|
||||||
std::string savedHash = getMetaValue("tip_hash");
|
std::string savedHash = getMetaValue("tip_hash");
|
||||||
@@ -248,8 +257,15 @@ ExplorerBlockCache::SavedTipValidation ExplorerBlockCache::prepareValidation(
|
|||||||
|
|
||||||
if (currentHeight == savedHeight) {
|
if (currentHeight == savedHeight) {
|
||||||
if (currentBestHash.empty()) return validation;
|
if (currentBestHash.empty()) return validation;
|
||||||
if (currentBestHash != savedHash) clearBlocks();
|
if (currentBestHash != savedHash) {
|
||||||
updateTip(currentHeight, currentBestHash);
|
clearBlocks(); // same height, different block: a reorg
|
||||||
|
updateTip(currentHeight, currentBestHash);
|
||||||
|
} else {
|
||||||
|
// Already saved: remember it so later frames skip SQLite (nothing to write).
|
||||||
|
tip_known_ = true;
|
||||||
|
tip_height_ = savedHeight;
|
||||||
|
tip_hash_ = savedHash;
|
||||||
|
}
|
||||||
return validation;
|
return validation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,9 +296,26 @@ void ExplorerBlockCache::applySavedTipValidation(const SavedTipValidation& valid
|
|||||||
|
|
||||||
void ExplorerBlockCache::updateTip(int height, const std::string& hash)
|
void ExplorerBlockCache::updateTip(int height, const std::string& hash)
|
||||||
{
|
{
|
||||||
|
tip_known_ = false;
|
||||||
if (!ensureOpen()) return;
|
if (!ensureOpen()) return;
|
||||||
setMetaValue("tip_height", std::to_string(std::max(0, height)));
|
// Both rows in one transaction, so a crash between them can't save a new height with the old hash
|
||||||
setMetaValue("tip_hash", hash);
|
// (which would wipe the cache for nothing on the next run). A savepoint, not BEGIN, so it nests
|
||||||
|
// inside a caller's transaction instead of failing.
|
||||||
|
if (!exec("SAVEPOINT explorer_tip")) return;
|
||||||
|
const bool written = setMetaValue("tip_height", std::to_string(std::max(0, height))) &&
|
||||||
|
setMetaValue("tip_hash", hash);
|
||||||
|
if (!written) exec("ROLLBACK TO explorer_tip");
|
||||||
|
const bool released = exec("RELEASE explorer_tip");
|
||||||
|
if (written && released && height > 0 && !hash.empty()) {
|
||||||
|
tip_known_ = true;
|
||||||
|
tip_height_ = height;
|
||||||
|
tip_hash_ = hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ExplorerBlockCache::totalChanges() const
|
||||||
|
{
|
||||||
|
return db_ ? sqlite3_total_changes(db_) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExplorerBlockCache::exec(const char* sql)
|
bool ExplorerBlockCache::exec(const char* sql)
|
||||||
@@ -322,16 +355,16 @@ int ExplorerBlockCache::getMetaInt(const std::string& key, int fallback)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExplorerBlockCache::setMetaValue(const std::string& key, const std::string& value)
|
bool ExplorerBlockCache::setMetaValue(const std::string& key, const std::string& value)
|
||||||
{
|
{
|
||||||
if (!ensureOpen()) return;
|
if (!ensureOpen()) return false;
|
||||||
|
|
||||||
Statement statement(db_,
|
Statement statement(db_,
|
||||||
"INSERT OR REPLACE INTO explorer_cache_meta (key, value) VALUES (?, ?)");
|
"INSERT OR REPLACE INTO explorer_cache_meta (key, value) VALUES (?, ?)");
|
||||||
if (!statement.handle) return;
|
if (!statement.handle) return false;
|
||||||
if (!bindText(statement.handle, 1, key)) return;
|
if (!bindText(statement.handle, 1, key)) return false;
|
||||||
if (!bindText(statement.handle, 2, value)) return;
|
if (!bindText(statement.handle, 2, value)) return false;
|
||||||
sqlite3_step(statement.handle);
|
return sqlite3_step(statement.handle) == SQLITE_DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExplorerBlockCache::createSchema()
|
bool ExplorerBlockCache::createSchema()
|
||||||
@@ -392,6 +425,7 @@ void ExplorerBlockCache::migrateLegacyJsonIfNeeded()
|
|||||||
|
|
||||||
void ExplorerBlockCache::close()
|
void ExplorerBlockCache::close()
|
||||||
{
|
{
|
||||||
|
tip_known_ = false;
|
||||||
if (!db_) return;
|
if (!db_) return;
|
||||||
sqlite3_close(db_);
|
sqlite3_close(db_);
|
||||||
db_ = nullptr;
|
db_ = nullptr;
|
||||||
|
|||||||
@@ -56,11 +56,15 @@ public:
|
|||||||
const std::string& currentBestHash);
|
const std::string& currentBestHash);
|
||||||
void updateTip(int height, const std::string& hash);
|
void updateTip(int height, const std::string& hash);
|
||||||
|
|
||||||
|
// Rows changed through this connection since it opened (sqlite3_total_changes); 0 when closed.
|
||||||
|
// Lets tests check that a steady tip writes nothing.
|
||||||
|
int totalChanges() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool exec(const char* sql);
|
bool exec(const char* sql);
|
||||||
std::string getMetaValue(const std::string& key);
|
std::string getMetaValue(const std::string& key);
|
||||||
int getMetaInt(const std::string& key, int fallback);
|
int getMetaInt(const std::string& key, int fallback);
|
||||||
void setMetaValue(const std::string& key, const std::string& value);
|
bool setMetaValue(const std::string& key, const std::string& value);
|
||||||
bool createSchema();
|
bool createSchema();
|
||||||
void migrateLegacyJsonIfNeeded();
|
void migrateLegacyJsonIfNeeded();
|
||||||
void close();
|
void close();
|
||||||
@@ -68,6 +72,12 @@ private:
|
|||||||
sqlite3* db_ = nullptr;
|
sqlite3* db_ = nullptr;
|
||||||
std::string database_path_;
|
std::string database_path_;
|
||||||
std::string legacy_json_path_;
|
std::string legacy_json_path_;
|
||||||
|
|
||||||
|
// The saved tip as last written or read, so the per-frame check can skip SQLite while the chain
|
||||||
|
// tip hasn't moved. The database stays the source of truth; this is only a copy of it.
|
||||||
|
bool tip_known_ = false;
|
||||||
|
int tip_height_ = 0;
|
||||||
|
std::string tip_hash_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "console_channel.h"
|
#include "console_channel.h"
|
||||||
#include "console_command_reference.h" // consoleCommandCategories / liteConsoleCommandCategories
|
#include "console_command_reference.h" // consoleCommandCategories / liteConsoleCommandCategories
|
||||||
#include "console_input_model.h" // BuildConsoleRpcCall
|
#include "console_input_model.h" // BuildConsoleRpcCall
|
||||||
|
#include "console_tab_helpers.h" // ShouldPrintDaemonErrorLine
|
||||||
|
|
||||||
#include "../../app.h"
|
#include "../../app.h"
|
||||||
#include "../../data/wallet_state.h"
|
#include "../../data/wallet_state.h"
|
||||||
@@ -126,7 +127,16 @@ void FullNodeConsoleExecutor::pollLogLines(const ConsoleAddLineFn& add)
|
|||||||
add("", ConsoleChannel::None);
|
add("", ConsoleChannel::None);
|
||||||
add(TR("console_daemon_stopped"), ConsoleChannel::Info);
|
add(TR("console_daemon_stopped"), ConsoleChannel::Info);
|
||||||
} else if (cur == DState::Error) {
|
} else if (cur == DState::Error) {
|
||||||
add(std::string(TR("console_daemon_error")) + d->getLastError() + " ===", ConsoleChannel::Error);
|
// Once per error (see ShouldPrintDaemonErrorLine): printing on every poll added a line per
|
||||||
|
// frame and made the console re-lay-out its whole buffer each time.
|
||||||
|
std::string err = d->getLastError();
|
||||||
|
const std::uint64_t exits = d->getUnexpectedExitCount();
|
||||||
|
if (ShouldPrintDaemonErrorLine(prev == static_cast<int>(DState::Error), true, err, last_daemon_error_,
|
||||||
|
exits != last_printed_exit_count_)) {
|
||||||
|
add(std::string(TR("console_daemon_error")) + " " + err, ConsoleChannel::Error);
|
||||||
|
last_daemon_error_ = std::move(err);
|
||||||
|
last_printed_exit_count_ = exits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
last_daemon_state_ = static_cast<int>(cur);
|
last_daemon_state_ = static_cast<int>(cur);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ private:
|
|||||||
size_t last_daemon_output_size_ = 0;
|
size_t last_daemon_output_size_ = 0;
|
||||||
size_t last_xmrig_output_size_ = 0;
|
size_t last_xmrig_output_size_ = 0;
|
||||||
int last_daemon_state_ = -1; // daemon::EmbeddedDaemon::State as int
|
int last_daemon_state_ = -1; // daemon::EmbeddedDaemon::State as int
|
||||||
|
std::string last_daemon_error_; // error text last printed (main thread only)
|
||||||
|
std::uint64_t last_printed_exit_count_ = 0; // daemon unexpected-exit count at that print
|
||||||
bool last_rpc_connected_ = false;
|
bool last_rpc_connected_ = false;
|
||||||
std::deque<std::pair<std::string, bool>> results_; // {text, isError}
|
std::deque<std::pair<std::string, bool>> results_; // {text, isError}
|
||||||
std::mutex results_mutex_;
|
std::mutex results_mutex_;
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ float ClampConsoleWrapWidth(float contentWidth, float paddingX)
|
|||||||
return std::max(50.0f, contentWidth - paddingX * 2.0f);
|
return std::max(50.0f, contentWidth - paddingX * 2.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ShouldPrintDaemonErrorLine(bool wasError, bool isError,
|
||||||
|
const std::string& error, const std::string& lastPrinted,
|
||||||
|
bool newUnexpectedExit)
|
||||||
|
{
|
||||||
|
return isError && (!wasError || error != lastPrinted || newUnexpectedExit);
|
||||||
|
}
|
||||||
|
|
||||||
bool ConsoleCommandCarriesSecret(const std::string& cmd)
|
bool ConsoleCommandCarriesSecret(const std::string& cmd)
|
||||||
{
|
{
|
||||||
size_t end = 0;
|
size_t end = 0;
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ float ComputeConsoleOutputHeight(float availableHeight,
|
|||||||
float minHeightRatio);
|
float minHeightRatio);
|
||||||
float ClampConsoleWrapWidth(float contentWidth, float paddingX);
|
float ClampConsoleWrapWidth(float contentWidth, float paddingX);
|
||||||
|
|
||||||
|
// The console's daemon-error line prints once per error: on entering the Error state, when the
|
||||||
|
// reason changes while still in it, or when the daemon crashed again meanwhile (the console only polls
|
||||||
|
// while its tab is shown, so it can miss the Running state between two identical crashes). It used to
|
||||||
|
// print on every poll, i.e. every frame, while in Error.
|
||||||
|
bool ShouldPrintDaemonErrorLine(bool wasError, bool isError,
|
||||||
|
const std::string& error, const std::string& lastPrinted,
|
||||||
|
bool newUnexpectedExit = false);
|
||||||
|
|
||||||
// True if `cmd`'s first token names a console/RPC command that carries a SECRET on its command line
|
// True if `cmd`'s first token names a console/RPC command that carries a SECRET on its command line
|
||||||
// (passphrase, private/spending/viewing key, mnemonic). Output-secret commands (dumpprivkey,
|
// (passphrase, private/spending/viewing key, mnemonic). Output-secret commands (dumpprivkey,
|
||||||
// z_exportkey, z_exportmnemonic) are NOT covered — their secret is in the result, a separate concern.
|
// z_exportkey, z_exportmnemonic) are NOT covered — their secret is in the result, a separate concern.
|
||||||
|
|||||||
+3
-1
@@ -31,6 +31,8 @@ namespace util {
|
|||||||
*
|
*
|
||||||
* Every N frames (default 300 ≈ 5s at 60fps) it appends a summary block
|
* Every N frames (default 300 ≈ 5s at 60fps) it appends a summary block
|
||||||
* to <ObsidianDragonDir>/perf.log with min/avg/max/p95 for each zone.
|
* to <ObsidianDragonDir>/perf.log with min/avg/max/p95 for each zone.
|
||||||
|
* Off unless the app is started with DRAGONX_PERFLOG=1 (main.cpp calls init() only then); every
|
||||||
|
* call below is a near no-op until init() succeeds.
|
||||||
*/
|
*/
|
||||||
class PerfLog {
|
class PerfLog {
|
||||||
public:
|
public:
|
||||||
@@ -78,7 +80,7 @@ public:
|
|||||||
|
|
||||||
/// Begin a named zone. Returns an opaque time point.
|
/// Begin a named zone. Returns an opaque time point.
|
||||||
std::chrono::steady_clock::time_point beginZone() {
|
std::chrono::steady_clock::time_point beginZone() {
|
||||||
return Clock::now();
|
return ready_ ? Clock::now() : Clock::time_point{};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// End a named zone. Pass the time point from beginZone().
|
/// End a named zone. Pass the time point from beginZone().
|
||||||
|
|||||||
@@ -5468,6 +5468,15 @@ void testRendererHelpers()
|
|||||||
EXPECT_NEAR(dragonx::ui::ClampConsoleWrapWidth(40.0f, 8.0f), 50.0, 0.0001);
|
EXPECT_NEAR(dragonx::ui::ClampConsoleWrapWidth(40.0f, 8.0f), 50.0, 0.0001);
|
||||||
EXPECT_NEAR(dragonx::ui::ClampConsoleWrapWidth(200.0f, 12.0f), 176.0, 0.0001);
|
EXPECT_NEAR(dragonx::ui::ClampConsoleWrapWidth(200.0f, 12.0f), 176.0, 0.0001);
|
||||||
|
|
||||||
|
// The daemon-error line prints once per error, not on every poll (it flooded one line per frame).
|
||||||
|
using dragonx::ui::ShouldPrintDaemonErrorLine;
|
||||||
|
EXPECT_TRUE(ShouldPrintDaemonErrorLine(false, true, "bind failed", "")); // entering Error
|
||||||
|
EXPECT_FALSE(ShouldPrintDaemonErrorLine(true, true, "bind failed", "bind failed")); // still the same error
|
||||||
|
EXPECT_TRUE(ShouldPrintDaemonErrorLine(true, true, "binary missing", "bind failed")); // reason changed
|
||||||
|
EXPECT_TRUE(ShouldPrintDaemonErrorLine(false, true, "bind failed", "bind failed")); // a new episode
|
||||||
|
EXPECT_FALSE(ShouldPrintDaemonErrorLine(true, false, "bind failed", "")); // not in Error
|
||||||
|
EXPECT_TRUE(ShouldPrintDaemonErrorLine(true, true, "exit code 1", "exit code 1", true)); // crashed again
|
||||||
|
|
||||||
EXPECT_EQ(dragonx::ui::ClampMiningThreads(0, 8), 1);
|
EXPECT_EQ(dragonx::ui::ClampMiningThreads(0, 8), 1);
|
||||||
EXPECT_EQ(dragonx::ui::ClampMiningThreads(12, 8), 8);
|
EXPECT_EQ(dragonx::ui::ClampMiningThreads(12, 8), 8);
|
||||||
EXPECT_EQ(dragonx::ui::ClampMiningThreads(4, 8), 4);
|
EXPECT_EQ(dragonx::ui::ClampMiningThreads(4, 8), 4);
|
||||||
@@ -5783,6 +5792,12 @@ void testExplorerBlockCache()
|
|||||||
auto sameTipValidation = cache.prepareValidation(10, "hash-10");
|
auto sameTipValidation = cache.prepareValidation(10, "hash-10");
|
||||||
EXPECT_FALSE(sameTipValidation.needed);
|
EXPECT_FALSE(sameTipValidation.needed);
|
||||||
|
|
||||||
|
// A steady tip writes nothing: this check runs every frame the Explorer is open (it used to
|
||||||
|
// rewrite both tip rows each time).
|
||||||
|
const int changesBeforeSteadyTip = cache.totalChanges();
|
||||||
|
for (int i = 0; i < 100; ++i) EXPECT_FALSE(cache.prepareValidation(10, "hash-10").needed);
|
||||||
|
EXPECT_EQ(cache.totalChanges(), changesBeforeSteadyTip);
|
||||||
|
|
||||||
auto advancedValidation = cache.prepareValidation(12, "hash-12");
|
auto advancedValidation = cache.prepareValidation(12, "hash-12");
|
||||||
EXPECT_TRUE(advancedValidation.needed);
|
EXPECT_TRUE(advancedValidation.needed);
|
||||||
EXPECT_EQ(advancedValidation.height, 10);
|
EXPECT_EQ(advancedValidation.height, 10);
|
||||||
|
|||||||
Reference in New Issue
Block a user