perf(mining): one save, at most one restart per thread-count gesture

Every thread-count change on the Mining tab saved settings.json and,
with a miner running, stopped and restarted it: xmrig re-inits its
RandomX dataset and reconnects to the pool, seconds of lost hashing
each time. A drag across the grid did this once per tile crossed (up to
~24), with a "Starting pool miner" toast each; every -/+ click or Enter
did it again.

The tab now hands each pick to the App, which puts the newest in
settings at once and applies it when the input settles: on drag
release, or 0.5 s after the last click, even if the Mining tab was left
in the meantime. The header, RAM estimate and tile fill still follow
the drag live.

- Pool: the restart is decided on the mining-control thread, after every
  earlier start or stop has run. It never starts a miner the user
  stopped, is a no-op when xmrig already runs at that count, and is no
  longer silently skipped when the pick lands in another restart's stop
  window (which left xmrig on the old count).
- Solo: one setgenerate per gesture. A pick made while one is in flight
  now waits for it instead of being dropped (another stale count).
- Clicking the tile that is already selected does nothing; it used to
  save settings and restart the miner.
- With idle thread scaling on, the grid no longer snaps back to the old
  count while a pick is on its way to the miner. A pool auto-balance
  restart keeps a count still queued.
- Idle thread scaling changes the pool count through the same retarget.
  Its correction after a start compares with the count last requested,
  not the xmrig API's lagging report, so it comes about 1 s in instead
  of after the first API report (3-5 s).
- A retarget whose pool config no longer validates shows the same error
  and stops the miner, as the old stop-then-start did.
- While a benchmark runs a pick is saved, but the miner change is
  dropped: the benchmark owns the count until it ends.

A crash within 0.5 s of a pick, or mid-drag, loses that pick; every
clean exit saves it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 21:09:48 -05:00
co-authored by Claude Opus 5.5
parent c9cb50f4dd
commit 6c02e67c6d
9 changed files with 296 additions and 36 deletions
+1
View File
@@ -623,6 +623,7 @@ set(APP_HEADERS
src/services/connect_scan_gate.h
src/services/scan_policy.h
src/services/chat_scan_policy.h
src/services/mining_thread_commit.h
src/services/wallet_security_controller.h
src/services/wallet_security_workflow.h
src/services/wallet_security_workflow_executor.h
+7 -1
View File
@@ -264,7 +264,9 @@ void App::applyPoolAutoBalance(const util::PoolStatsService::Snapshot& snap)
// Restart so the new pool (and its per-pool algo) takes effect immediately.
if (xmrig_manager_ && xmrig_manager_->isRunning()) {
const int threads = xmrig_manager_->getRequestedThreads();
// A thread-count retarget may still be queued on the control thread: keep its count, not the old one.
const int queued = pool_threads_target_.load(std::memory_order_relaxed);
const int threads = queued > 0 ? queued : xmrig_manager_->getRequestedThreads();
stopPoolMining();
startPoolMining(threads);
}
@@ -994,6 +996,9 @@ void App::update()
checkAutoLock();
}
// Apply a settled mining thread-count pick: one settings save + at most one miner change per gesture.
tickMiningThreadCommit();
// Mine-when-idle check (cheap on most frames: it gates itself to 1 Hz)
checkIdleMining();
@@ -1131,6 +1136,7 @@ void App::update()
state_.pool_mining.hashrate_60s = 0.0;
state_.pool_mining.hashrate_15m = 0.0;
pool_starting_.store(false, std::memory_order_relaxed);
pool_threads_target_.store(0, std::memory_order_relaxed); // that miner is gone
const std::string err = xmrig_manager_->getLastError();
ui::Notifications::instance().error(err.empty() ? TR("appx_miner_stopped_unexpectedly")
: (std::string(TR("appx_miner_stopped_prefix")) + err));
+34
View File
@@ -32,6 +32,7 @@
#include "services/wallet_scan_governor.h"
#include "services/chat_scan_policy.h"
#include "services/note_consolidation.h"
#include "services/mining_thread_commit.h"
#include "services/network_refresh_service.h"
#include "services/wallet_security_controller.h"
#include "services/wallet_security_workflow.h"
@@ -347,9 +348,23 @@ public:
// Pool mining (xmrig)
void startPoolMining(int threads);
void stopPoolMining();
// Change the thread count of the pool miner that is running (or queued to start). Never starts one.
void retargetPoolMining(int threads);
int getXmrigRequestedThreads() const {
return xmrig_manager_ ? xmrig_manager_->getRequestedThreads() : 0;
}
// Thread count of the newest pool start/retarget posted and not stopped since (0 = none). Leads
// getXmrigRequestedThreads(), which only changes once the mining-control thread runs that job.
int poolThreadsTarget() const { return pool_threads_target_.load(std::memory_order_relaxed); }
// A thread-count pick from the Mining tab (grid tile, -/+, Enter). The settings value changes at once;
// the save and the miner change happen ONCE, when the input settles (drag released, or ~0.5 s after
// the last click). Latest wins.
void selectMiningThreads(int threads);
void setMiningThreadDragActive(bool active) { mining_thread_drag_ = active; }
// A pick has not reached the miner yet (the grid must not snap back to the old count meanwhile).
bool miningThreadCommitPending() const {
return mining_thread_commit_.pending() || mining_solo_threads_pending_ > 0;
}
// True while the pool miner process is live — used to refuse replacing the binary under it.
bool isPoolMinerRunning() const {
return xmrig_manager_ && xmrig_manager_->isRunning();
@@ -1107,6 +1122,15 @@ private:
bool mining_ctl_stop_ = false;
void postMiningControl(std::function<void()> job); // enqueue a blocking xmrig op onto the FIFO thread
void stopMiningControlThread(); // signal + join the control thread (shutdown)
// Builds the pool config on the main thread and posts stop(if running)+start to the control thread.
// retargetOnly: a thread-count change, decided on the control thread (never starts a miner).
void postPoolMinerJob(int threads, bool retargetOnly);
// Coalesced thread-count picks (main thread only; see services/mining_thread_commit.h).
services::MiningThreadCommit mining_thread_commit_;
bool mining_thread_drag_ = false; // the thread grid is mid-drag (set by the Mining tab)
bool mining_threads_save_pending_ = false; // settings hold a picked count that is not on disk yet
int mining_solo_threads_pending_ = 0; // settled pick waiting for an in-flight setgenerate
void tickMiningThreadCommit(); // per frame, from update()
// Auto-balance runtime state (pool mining, full-node only). The service fetches
// pool hashrates off-thread; the RNG drives the weighted-random pick.
util::PoolStatsService pool_stats_service_;
@@ -1589,6 +1613,16 @@ private:
// poll — drives the "connecting…" → "connected" feedback for pool mining (which has a connect delay).
std::atomic<bool> pool_starting_{false};
// Thread count of the newest pool start/retarget job posted (0 = none / stopped). A hint for the main
// thread: set when a job is posted, cleared by stopPoolMining(), by a start that fails and by a retarget
// that finds no miner. The control thread re-checks the real state, so a stale value can only skip a
// job the newest posted one already covers, or post a no-op one.
std::atomic<int> pool_threads_target_{0};
void clearPoolThreadsTarget(int mine) { // only if it still holds this job's value
int expected = mine;
pool_threads_target_.compare_exchange_strong(expected, 0, std::memory_order_relaxed);
}
// Auto-shield guard (prevents concurrent auto-shield operations)
std::atomic<bool> auto_shield_pending_{false};
// v1.3.0+ daemons auto-shield coinbase themselves; probe z_autoshieldstatus once per connection and
+36 -1
View File
@@ -3369,6 +3369,28 @@ void App::startPoolMining(int threads)
if (state_.mining.generate) stopMining();
// (the "stop the already-running miner first" step is done inside the control job below, in FIFO order)
postPoolMinerJob(threads, /*retargetOnly=*/false);
}
// Change the thread count of the pool miner that is running, or queued to start (Mining tab picks, idle
// thread scaling). Never starts a miner: the job decides on the mining-control thread (postPoolMinerJob).
// Nothing is posted when there is no miner, or when the newest start/retarget already asks for `threads`.
void App::retargetPoolMining(int threads)
{
if (!supportsPoolMining() || !xmrig_manager_) return;
threads = std::clamp(threads, 1, std::max(1, (int)std::thread::hardware_concurrency()));
const int target = pool_threads_target_.load(std::memory_order_relaxed);
if (target <= 0 && !xmrig_manager_->isRunning()) return; // no miner, none queued
if (target == threads) return; // the newest queued job already does this
postPoolMinerJob(threads, /*retargetOnly=*/true);
}
// Builds the xmrig config from settings + wallet state on this (main) thread and posts the blocking
// stop(if running)+start to the mining-control thread. retargetOnly (a thread-count change): the job does
// nothing unless a miner is running when it runs, at another count; a config that no longer validates
// stops the miner, as the old stop-then-start did.
void App::postPoolMinerJob(int threads, bool retargetOnly)
{
daemon::XmrigManager::Config cfg;
cfg.pool_url = settings_->getPoolUrl();
cfg.worker_name = settings_->getPoolWorker();
@@ -3381,6 +3403,7 @@ void App::startPoolMining(int threads)
ui::Notifications::instance().error(util::isTransparentAddress(cfg.worker_name)
? TR("mining_payout_needs_shielded")
: "Pool payout address is not a valid DragonX address — mining not started.");
if (retargetOnly) stopPoolMining(); // never keep mining on a config that no longer validates
return;
}
// The algo follows the pool: official pools use their own algo (pool.dragonx.cc
@@ -3411,6 +3434,7 @@ void App::startPoolMining(int threads)
if (cfg.wallet_address.empty()) {
DEBUG_LOGF("[ERROR] Pool mining: No wallet address available\n");
ui::Notifications::instance().error("No wallet address available — generate a Z address in the Receive tab");
if (retargetOnly) stopPoolMining();
return;
}
@@ -3418,10 +3442,20 @@ void App::startPoolMining(int threads)
// never blocks on stop()'s SIGTERM->SIGKILL->join; marshal the spawn result back to the UI. (M-03/L-06/
// L-08/L-09/L-13). cfg was fully built above on this (main) thread.
daemon::XmrigManager::Config cfgCopy = cfg;
postMiningControl([this, cfgCopy]() {
const int target = cfg.threads;
pool_threads_target_.store(target, std::memory_order_relaxed);
postMiningControl([this, cfgCopy, target, retargetOnly]() {
// A retarget is decided HERE, after every earlier start/stop ran: nothing happens unless a miner is
// running now and at another count. So it never starts a miner the user stopped, and a restart's
// stop window (isRunning() false on the UI thread) can't hide the running one.
if (retargetOnly) {
if (!xmrig_manager_->isRunning()) { clearPoolThreadsTarget(target); return; }
if (xmrig_manager_->getRequestedThreads() == target) return;
}
if (xmrig_manager_->isRunning()) xmrig_manager_->stop(3000);
const bool ok = xmrig_manager_->start(cfgCopy);
const std::string err = ok ? std::string() : xmrig_manager_->getLastError();
if (!ok) clearPoolThreadsTarget(target);
if (!worker_) return;
worker_->post([this, ok, err]() -> rpc::RPCWorker::MainCb {
return [this, ok, err]() {
@@ -3450,6 +3484,7 @@ void App::startPoolMining(int threads)
void App::stopPoolMining()
{
if (!xmrig_manager_) return;
pool_threads_target_.store(0, std::memory_order_relaxed); // no start/retarget is current any more
// Off the render thread — stop()'s SIGTERM->SIGKILL->join can block up to ~3s. (M-03/L-06/L-08/L-09)
postMiningControl([this]() {
if (xmrig_manager_->isRunning()) xmrig_manager_->stop(3000);
+70 -7
View File
@@ -819,8 +819,7 @@ void App::checkIdleMining() {
if (!idle_scaled_to_idle_) {
idle_scaled_to_idle_ = true;
if (isPool) {
stopPoolMining();
startPoolMining(idleThreads);
retargetPoolMining(idleThreads);
} else {
startMining(idleThreads);
}
@@ -831,8 +830,7 @@ void App::checkIdleMining() {
if (idle_scaled_to_idle_) {
idle_scaled_to_idle_ = false;
if (isPool) {
stopPoolMining();
startPoolMining(activeThreads);
retargetPoolMining(activeThreads);
} else {
startMining(activeThreads);
}
@@ -840,13 +838,16 @@ void App::checkIdleMining() {
} else {
// Mining just started while user is active — ensure active
// thread count is applied (grid selection may differ).
// Pool: compare with the count last REQUESTED (a restart still queued on the control thread
// included), not the xmrig API's last report, which lags a restart by seconds.
int currentThreads = isPool
? xmrig_manager_->getStats().threads_active
? services::effectivePoolThreads(pool_threads_target_.load(std::memory_order_relaxed),
xmrig_manager_->getRequestedThreads(),
xmrig_manager_->getStats().threads_active)
: state_.mining.genproclimit;
if (currentThreads > 0 && currentThreads != activeThreads) {
if (isPool) {
stopPoolMining();
startPoolMining(activeThreads);
retargetPoolMining(activeThreads);
} else {
startMining(activeThreads);
}
@@ -889,6 +890,68 @@ void App::checkIdleMining() {
}
}
// ===========================================================================
// Mining thread-count picks — coalesced (one save + at most one miner change per gesture)
// ===========================================================================
namespace {
double miningSteadySeconds()
{
return std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
}
} // namespace
void App::selectMiningThreads(int threads)
{
if (!settings_) return;
threads = std::clamp(threads, 1, std::max(1, (int)std::thread::hardware_concurrency()));
if (settings_->getPoolThreads() != threads) { // in memory now; saved once when the pick settles
settings_->setPoolThreads(threads);
mining_threads_save_pending_ = true;
}
mining_thread_commit_.select(threads, miningSteadySeconds());
}
void App::tickMiningThreadCommit()
{
if (shutting_down_ || !settings_) return; // beginShutdown() saves settings itself
const double now = miningSteadySeconds();
// The grid holds the commit while the button is down. The release is noticed HERE, not by the tab, so
// a drag that ends while the Mining tab isn't drawn (page switch, focus loss) still commits.
bool held = false;
if (mining_thread_drag_) {
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) held = true;
else { mining_thread_drag_ = false; mining_thread_commit_.release(now); }
}
// The benchmark owns the miner's thread count while it runs (its cancel/finish restore it): keep the
// pick in settings, drop the miner change.
if (ui::IsMiningBenchmarkActive()) {
mining_thread_commit_.cancel();
mining_solo_threads_pending_ = 0;
if (mining_threads_save_pending_) { mining_threads_save_pending_ = false; settings_->save(); }
return;
}
int threads = 0;
if (mining_thread_commit_.take(now, held, threads)) {
if (mining_threads_save_pending_) { mining_threads_save_pending_ = false; settings_->save(); }
retargetPoolMining(threads); // restarts xmrig once, only if it is running
mining_solo_threads_pending_ = threads; // setgenerate, once no other toggle is in flight
} else if (mining_thread_commit_.pending() && !held) {
ui::material::RequestProgressFrame(); // keep frames coming until the debounce deadline
}
// Solo: startMining() drops a call while another setgenerate is in flight, so wait for it (its result
// wakes the loop). Applies only while solo mining is on, as before.
if (mining_solo_threads_pending_ > 0 && !mining_toggle_in_progress_.load(std::memory_order_relaxed)) {
const int t = mining_solo_threads_pending_;
mining_solo_threads_pending_ = 0;
if (state_.mining.generate) startMining(t);
}
}
// ===========================================================================
// Restart Setup Wizard (from Settings)
// ===========================================================================
+63
View File
@@ -0,0 +1,63 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
// Coalesces mining thread-count picks. The Mining tab reports every intermediate value of a gesture (a tile
// drag crosses up to ~24 tiles; the -/+ stepper is clicked repeatedly); only the LAST one should reach the
// settings file and the miner, once, when the input settles: on drag release, or kDebounceSec after the
// last click. Pure: time is passed in; nothing here touches settings, the miner or ImGui.
namespace dragonx {
namespace services {
class MiningThreadCommit {
public:
static constexpr double kDebounceSec = 0.5;
// The user picked `threads` at `now` (seconds, any monotonic clock). Latest wins.
void select(int threads, double now)
{
target_ = threads;
pending_ = true;
due_ = now + kDebounceSec;
}
// The drag that produced the pending pick ended: it is due now, not after the debounce.
void release(double now)
{
if (pending_ && due_ > now) due_ = now;
}
void cancel() { pending_ = false; }
bool pending() const { return pending_; }
// Once per frame. `held`: a drag is still in progress (nothing commits until it ends). Returns true
// exactly once per settled pick and sets `out` to it.
bool take(double now, bool held, int& out)
{
if (!pending_ || held || now < due_) return false;
pending_ = false;
out = target_;
return true;
}
private:
int target_ = 0;
bool pending_ = false;
double due_ = 0.0;
};
// The pool thread count a wanted count is compared with: the newest start/retarget posted (it includes a
// restart still queued on the mining-control thread), else the count the running xmrig was started with,
// else what its HTTP API last reported (that lags a restart by seconds, so it is only a last resort).
inline int effectivePoolThreads(int queuedTarget, int requested, int apiActive)
{
if (queuedTarget > 0) return queuedTarget;
if (requested > 0) return requested;
return apiActive;
}
} // namespace services
} // namespace dragonx
+13 -22
View File
@@ -143,14 +143,8 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
auto applyThreads = [&](int tc) {
tc = std::clamp(tc, 1, max_threads);
if (tc == s_selected_threads) return;
s_selected_threads = tc;
app->settings()->setPoolThreads(tc);
app->settings()->save();
if (mining.generate) app->startMining(tc);
if (s_pool_mode && state.pool_mining.xmrig_running) {
app->stopPoolMining();
app->startPoolMining(tc);
}
s_selected_threads = tc; // shown at once; the App applies it ~0.5 s after the last change
app->selectMiningThreads(tc);
};
ImFont* stepFont = Type().iconSmall();
// Match the -/+ button height to the InputInt's actual frame height (GetFrameHeight ==
@@ -707,11 +701,14 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
// Drag-to-select logic (disabled during benchmark; ignored while a dropdown popup is open)
if (!benchActive && !anyPopupOpen && ImGui::IsMouseClicked(0) && hovered_thread > 0) {
// Begin drag
// Begin drag. A click on the tile that is already selected changes nothing (it used to save
// settings and restart the miner).
s_drag_active = true;
s_drag_anchor_thread = hovered_thread;
s_selected_threads = hovered_thread;
threads_changed = true;
if (hovered_thread != s_selected_threads) {
s_selected_threads = hovered_thread;
threads_changed = true;
}
}
if (s_drag_active) {
if (ImGui::IsMouseDown(0)) {
@@ -783,17 +780,11 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
dl->AddText(capFont, capFont->LegacySize, txtPos, txtCol, buf);
}
if (threads_changed) {
app->settings()->setPoolThreads(s_selected_threads);
app->settings()->save();
if (mining.generate) {
app->startMining(s_selected_threads);
}
if (s_pool_mode && state.pool_mining.xmrig_running) {
app->stopPoolMining();
app->startPoolMining(s_selected_threads);
}
}
// Report every change (the header, RAM estimate and tile fill follow s_selected_threads live), but
// let the App apply it: it holds the commit while the mouse is down, so a drag across 16 tiles is
// ONE settings save and at most ONE miner change, on release.
if (threads_changed) app->selectMiningThreads(s_selected_threads);
app->setMiningThreadDragActive(s_drag_active);
curY += gridH + secGap;
}
+8 -5
View File
@@ -140,16 +140,19 @@ static void RenderMiningTabContent(App* app)
// Sync thread grid with actual count when idle thread scaling adjusts threads
// Skip during benchmark — the benchmark controls thread counts directly
if (app->settings()->getMineWhenIdle() && app->settings()->getIdleThreadScaling() && !s_drag_active && !IsMiningBenchmarkActive()) {
// ...nor while a thread-count pick is still on its way to the miner (the grid would snap back to the old count).
if (app->settings()->getMineWhenIdle() && app->settings()->getIdleThreadScaling() && !s_drag_active &&
!app->miningThreadCommitPending() && !IsMiningBenchmarkActive()) {
if (s_pool_mode && state.pool_mining.xmrig_running) {
// Use the requested thread count (available immediately) rather
// than threads_active from the xmrig API which lags during restarts.
int reqThreads = app->getXmrigRequestedThreads();
// Use the requested thread count (available immediately) rather than threads_active from the
// xmrig API, which lags during restarts; the App's target also covers a restart still queued.
const int queued = app->poolThreadsTarget();
int reqThreads = queued > 0 ? queued : app->getXmrigRequestedThreads();
if (reqThreads > 0)
s_selected_threads = ClampMiningThreads(reqThreads, max_threads);
else if (state.pool_mining.threads_active > 0)
s_selected_threads = ClampMiningThreads(state.pool_mining.threads_active, max_threads);
} else if (mining.generate && mining.genproclimit > 0) {
} else if (mining.generate && mining.genproclimit > 0 && !app->isMiningToggleInProgress()) {
s_selected_threads = ClampMiningThreads(mining.genproclimit, max_threads);
}
}
+64
View File
@@ -33,6 +33,7 @@
#include "services/scan_policy.h"
#include "services/connect_scan_gate.h"
#include "services/chat_scan_policy.h"
#include "services/mining_thread_commit.h"
#include "services/network_refresh_service.h"
#include "services/refresh_scheduler.h"
#include "services/note_consolidation.h"
@@ -5686,6 +5687,68 @@ void testConsoleInputModel()
EXPECT_EQ(errorLines[0].text, std::string("Error: bad rpc"));
}
void testMiningThreadCommit()
{
using dragonx::services::MiningThreadCommit;
using dragonx::services::effectivePoolThreads;
int out = 0;
// Stepper: three clicks inside the debounce window -> one commit, with the last value.
{
MiningThreadCommit c;
c.select(5, 10.0); c.select(6, 10.1); c.select(7, 10.2);
EXPECT_TRUE(c.pending());
EXPECT_FALSE(c.take(10.5, false, out)); // 0.3 s after the last click: not yet
EXPECT_TRUE(c.take(10.75, false, out));
EXPECT_EQ(out, 7);
EXPECT_FALSE(c.pending());
EXPECT_FALSE(c.take(11.0, false, out)); // consumed exactly once
}
// Drag: nothing commits while held, however long; the release commits the last tile at once.
{
MiningThreadCommit c;
for (int i = 1; i <= 16; ++i) {
c.select(i, 20.0 + i * 0.1);
EXPECT_FALSE(c.take(20.0 + i * 0.1 + 0.9, true, out));
}
EXPECT_FALSE(c.take(40.0, true, out)); // still held 20 s later
c.release(40.1);
EXPECT_TRUE(c.take(40.1, false, out));
EXPECT_EQ(out, 16);
}
// Plain click in real frame order: the tab's render selects (button down); on the next frame the App's
// tick sees the button up, calls release() and then take(): it commits on that frame, not 0.5 s later.
{
MiningThreadCommit c;
c.select(4, 30.0);
c.release(30.016);
EXPECT_TRUE(c.take(30.016, false, out));
EXPECT_EQ(out, 4);
}
// release() with nothing pending changes nothing.
{
MiningThreadCommit c;
c.release(1.0);
EXPECT_FALSE(c.pending());
EXPECT_FALSE(c.take(2.0, false, out));
}
// A new pick during the wait pushes the deadline out; cancel() drops it.
{
MiningThreadCommit c;
c.select(2, 50.0);
c.select(9, 50.4);
EXPECT_FALSE(c.take(50.6, false, out));
c.cancel();
EXPECT_FALSE(c.pending());
EXPECT_FALSE(c.take(60.0, false, out));
}
// Which pool count idle scaling compares with.
EXPECT_EQ(effectivePoolThreads(8, 4, 2), 8); // a queued restart's target beats the stale values
EXPECT_EQ(effectivePoolThreads(0, 4, 2), 4);
EXPECT_EQ(effectivePoolThreads(0, 0, 2), 2);
EXPECT_EQ(effectivePoolThreads(0, 0, 0), 0);
}
void testMiningBenchmarkModel()
{
dragonx::ui::ThreadBenchmark benchmark;
@@ -12660,6 +12723,7 @@ int main()
testRendererHelpers();
testConsoleInputModel();
testMiningBenchmarkModel();
testMiningThreadCommit();
testBalanceAddressListModel();
testExplorerBlockCache();
testTransactionHistoryCache();