The Mining tab's earnings card walked all N wallet transactions on every frame to total Today / Yesterday / All time and pick the 4 most recent rows: four string compares per transaction plus a memo search on receives. It also ran localtime and mktime every frame to find local midnight. The pass is now a pure function behind a one-slot cache keyed on WalletState::tx_revision, the transaction count, the filter pill, the Solo/Pool mode, solo support and local midnight, with a 2 s maximum age as a safety net. It reruns only when one of those changes or the 2 s age runs out, instead of every frame. The totals are bit-identical (same loop, same summation order), and crossing local midnight still moves Today into Yesterday at once. Local midnight is recomputed only when the clock second changes, through the shared util::localTime; a failed localtime now yields a zeroed struct instead of an uninitialised one. The "x ago" labels and Est. Daily are still computed per frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
161 lines
5.5 KiB
C++
161 lines
5.5 KiB
C++
#include "mining_tab_helpers.h"
|
|
|
|
#include "../../util/local_time.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <ctime>
|
|
#include <thread>
|
|
|
|
namespace dragonx {
|
|
namespace ui {
|
|
|
|
int GetMaxMiningThreads()
|
|
{
|
|
int hardwareThreads = static_cast<int>(std::thread::hardware_concurrency());
|
|
return std::max(1, hardwareThreads);
|
|
}
|
|
|
|
int ClampMiningThreads(int requestedThreads, int maxThreads)
|
|
{
|
|
int boundedMax = std::max(1, maxThreads);
|
|
return std::clamp(requestedThreads, 1, boundedMax);
|
|
}
|
|
|
|
bool IsPoolMiningActive(bool poolMode, bool xmrigRunning, bool soloMiningRunning)
|
|
{
|
|
return poolMode ? xmrigRunning : (soloMiningRunning || xmrigRunning);
|
|
}
|
|
|
|
std::string FormatHashrate(double hashrate)
|
|
{
|
|
char buffer[64];
|
|
if (hashrate >= 1e12) {
|
|
snprintf(buffer, sizeof(buffer), "%.2f TH/s", hashrate / 1e12);
|
|
} else if (hashrate >= 1e9) {
|
|
snprintf(buffer, sizeof(buffer), "%.2f GH/s", hashrate / 1e9);
|
|
} else if (hashrate >= 1e6) {
|
|
snprintf(buffer, sizeof(buffer), "%.2f MH/s", hashrate / 1e6);
|
|
} else if (hashrate >= 1e3) {
|
|
snprintf(buffer, sizeof(buffer), "%.2f KH/s", hashrate / 1e3);
|
|
} else {
|
|
snprintf(buffer, sizeof(buffer), "%.2f H/s", hashrate);
|
|
}
|
|
return std::string(buffer);
|
|
}
|
|
|
|
std::string FormatFeePercent(double feePercent)
|
|
{
|
|
// Whole fees read "1"; fractional ones keep only their significant decimals
|
|
// ("1.5", "0.9", "1.25") with no trailing zeros. Capped at 2 dp — finer than
|
|
// any pool advertises, and the caller appends the "%".
|
|
char buffer[32];
|
|
snprintf(buffer, sizeof(buffer), "%.2f", feePercent);
|
|
std::string s(buffer);
|
|
if (s.find('.') != std::string::npos) {
|
|
s.erase(s.find_last_not_of('0') + 1);
|
|
if (!s.empty() && s.back() == '.') s.pop_back();
|
|
}
|
|
return s;
|
|
}
|
|
|
|
double EstimateHoursToBlock(double localHashrate, double networkHashrate, double difficulty)
|
|
{
|
|
(void)difficulty;
|
|
if (localHashrate <= 0.0 || networkHashrate <= 0.0) return 0.0;
|
|
double blocksPerHour = 3600.0 / 150.0; // DragonX mainnet target spacing is 150s (chainparams) (L-05)
|
|
double share = localHashrate / networkHashrate;
|
|
if (share <= 0.0) return 0.0;
|
|
return 1.0 / (blocksPerHour * share);
|
|
}
|
|
|
|
std::string FormatEstTime(double estimatedHours)
|
|
{
|
|
char buffer[64];
|
|
if (estimatedHours <= 0.0) {
|
|
return "N/A";
|
|
} else if (estimatedHours < 1.0) {
|
|
snprintf(buffer, sizeof(buffer), "~%.0f min", estimatedHours * 60.0);
|
|
} else if (estimatedHours < 24.0) {
|
|
snprintf(buffer, sizeof(buffer), "~%.1f hrs", estimatedHours);
|
|
} else if (estimatedHours < 168.0) {
|
|
snprintf(buffer, sizeof(buffer), "~%.1f days", estimatedHours / 24.0);
|
|
} else {
|
|
snprintf(buffer, sizeof(buffer), "~%.1f weeks", estimatedHours / 168.0);
|
|
}
|
|
return std::string(buffer);
|
|
}
|
|
|
|
MiningEarningsTotals ComputeMiningEarnings(const std::vector<TransactionInfo>& txs, int filter,
|
|
bool soloSupported, bool poolMode,
|
|
std::int64_t todayStart, std::int64_t yesterdayStart)
|
|
{
|
|
MiningEarningsTotals t;
|
|
for (const auto& tx : txs) {
|
|
bool isSoloMined = (tx.type == "generate" || tx.type == "immature" || tx.type == "mined");
|
|
bool isPoolPayout = (tx.type == "receive"
|
|
&& !tx.memo.empty()
|
|
&& tx.memo.find("Mining Pool payout") != std::string::npos);
|
|
if (!(isSoloMined || isPoolPayout)) continue;
|
|
// Apply earnings filter
|
|
if (!soloSupported) {
|
|
if (filter == 1 && !isPoolPayout) continue;
|
|
} else {
|
|
if (filter == 1 && !isSoloMined) continue;
|
|
if (filter == 2 && !isPoolPayout) continue;
|
|
}
|
|
double amt = std::abs(tx.amount);
|
|
t.allTime += amt;
|
|
t.allTimeCount++;
|
|
if (tx.timestamp >= todayStart) {
|
|
t.today += amt;
|
|
t.todayCount++;
|
|
} else if (tx.timestamp >= yesterdayStart) {
|
|
t.yesterday += amt;
|
|
t.yesterdayCount++;
|
|
}
|
|
// Separate solo blocks from pool payouts based on current mode
|
|
bool showInCurrentMode = poolMode ? isPoolPayout : isSoloMined;
|
|
if (showInCurrentMode && t.recent.size() < 4)
|
|
t.recent.push_back({tx.timestamp, amt, tx.confirmations, tx.confirmations >= 100, tx.txid, isPoolPayout});
|
|
}
|
|
return t;
|
|
}
|
|
|
|
const MiningEarningsTotals& MiningEarningsCache::get(const MiningEarningsKey& key,
|
|
const std::vector<TransactionInfo>& txs,
|
|
std::int64_t nowMs)
|
|
{
|
|
if (!valid_ || !(key == key_) || IntervalElapsed(nowMs, computedAtMs_, kMaxAgeMs)) {
|
|
totals_ = ComputeMiningEarnings(txs, key.filter, key.soloSupported, key.poolMode,
|
|
key.todayStart, key.todayStart - 86400); // 86400: as before
|
|
key_ = key;
|
|
computedAtMs_ = nowMs;
|
|
valid_ = true;
|
|
++computes_;
|
|
}
|
|
return totals_;
|
|
}
|
|
|
|
bool IntervalElapsed(std::int64_t nowMs, std::int64_t lastMs, std::int64_t minMs)
|
|
{
|
|
return nowMs < lastMs || nowMs - lastMs >= minMs;
|
|
}
|
|
|
|
std::int64_t LocalDayStartMemo::get(std::int64_t now)
|
|
{
|
|
if (now == now_) return start_;
|
|
std::tm local{};
|
|
util::localTime(static_cast<std::time_t>(now), local); // same localtime_r / localtime_s as before
|
|
local.tm_hour = 0;
|
|
local.tm_min = 0;
|
|
local.tm_sec = 0;
|
|
start_ = (std::int64_t)mktime(&local);
|
|
now_ = now;
|
|
return start_;
|
|
}
|
|
|
|
} // namespace ui
|
|
} // namespace dragonx
|