improve diagnostics, security UX, and network tab refresh

Diagnostics & logging:
- add verbose logging system (VERBOSE_LOGF) with toggle in Settings
- forward app-level log messages to Console tab for in-UI visibility
- add detailed connection attempt logging (attempt #, daemon state,
  config paths, auth failures, port owner identification)
- detect HTTP 401 auth failures and show actionable error messages
- identify port owner process (PID + name) on both Linux and Windows
- demote noisy acrylic/shader traces from DEBUG_LOGF to VERBOSE_LOGF
- persist verbose_logging preference in settings.json
- link iphlpapi on Windows for GetExtendedTcpTable

Security & encryption:
- update local encryption state immediately after encryptwallet RPC
  so Settings reflects the change before daemon restarts
- show notifications for encrypt success/failure and PIN skip
- use dedicated RPC client for z_importwallet during decrypt flow
  to avoid blocking main rpc_ curl_mutex (which starved peer/tx refresh)
- force full state refresh (addresses, transactions, peers) after
  successful wallet import

Network tab:
- redesign peers refresh button as glass-panel with icon + label,
  matching the mining button style
- add spinning arc animation while peer data is loading
  (peer_refresh_in_progress_ atomic flag set/cleared in refreshPeerInfo)
- prevent double-click spam during refresh
- add refresh-button size to ui.toml

Other:
- use fast_rpc_ for rescan polling to avoid blocking on main rpc_
- enable DRAGONX_DEBUG in all build configs (was debug-only)
- setup.sh: pull latest xmrig-hac when repo already exists
This commit is contained in:
2026-03-05 05:26:04 -06:00
parent 9368b945e0
commit e2265b0bdf
19 changed files with 461 additions and 52 deletions

View File

@@ -17,12 +17,13 @@
#include "../util/logger.h"
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <psapi.h>
#include <tlhelp32.h>
#include <shlobj.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#else
#include <unistd.h>
#include <signal.h>
@@ -224,6 +225,113 @@ std::vector<std::string> EmbeddedDaemon::getRecentLines(int maxLines) const
return lines;
}
// Identify what process owns a given TCP port.
// Returns a string like "PID 1234 (dragonxd.exe)" or "unknown" on failure.
static std::string getPortOwnerInfo(int port)
{
#ifdef _WIN32
DWORD size = 0;
// First call to get required buffer size
GetExtendedTcpTable(nullptr, &size, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0);
if (size == 0) return "unknown (GetExtendedTcpTable size query failed)";
std::vector<BYTE> buf(size);
DWORD ret = GetExtendedTcpTable(buf.data(), &size, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0);
if (ret != NO_ERROR) return "unknown (GetExtendedTcpTable failed, error " + std::to_string(ret) + ")";
auto* table = reinterpret_cast<MIB_TCPTABLE_OWNER_PID*>(buf.data());
DWORD ownerPid = 0;
for (DWORD i = 0; i < table->dwNumEntries; i++) {
auto& row = table->table[i];
int rowPort = ntohs(static_cast<u_short>(row.dwLocalPort));
// Match port in LISTEN state (MIB_TCP_STATE_LISTEN = 2)
if (rowPort == port && row.dwState == MIB_TCP_STATE_LISTEN) {
ownerPid = row.dwOwningPid;
break;
}
}
if (ownerPid == 0) {
// Maybe it's in ESTABLISHED or another state from our connect probe — try any state
for (DWORD i = 0; i < table->dwNumEntries; i++) {
auto& row = table->table[i];
int rowPort = ntohs(static_cast<u_short>(row.dwLocalPort));
if (rowPort == port && row.dwOwningPid != 0) {
ownerPid = row.dwOwningPid;
break;
}
}
}
if (ownerPid == 0) return "unknown (no PID found for port " + std::to_string(port) + ")";
// Resolve PID to process name
std::string procName = "<unknown>";
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap != INVALID_HANDLE_VALUE) {
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
if (Process32First(snap, &pe)) {
do {
if (pe.th32ProcessID == ownerPid) {
procName = pe.szExeFile;
break;
}
} while (Process32Next(snap, &pe));
}
CloseHandle(snap);
}
return "PID " + std::to_string(ownerPid) + " (" + procName + ")";
#else
// Linux: parse /proc/net/tcp to find the inode, then scan /proc/*/fd
FILE* fp = fopen("/proc/net/tcp", "r");
if (!fp) return "unknown (cannot read /proc/net/tcp)";
char line[512];
unsigned long inode = 0;
while (fgets(line, sizeof(line), fp)) {
unsigned int localPort, state;
unsigned long lineInode;
if (sscanf(line, " %*d: %*X:%X %*X:%*X %X %*X:%*X %*X:%*X %*X %*u %*u %lu",
&localPort, &state, &lineInode) == 3) {
if (static_cast<int>(localPort) == port && state == 0x0A) { // 0x0A = LISTEN
inode = lineInode;
break;
}
}
}
fclose(fp);
if (inode == 0) return "unknown (no listener found for port " + std::to_string(port) + ")";
// Scan /proc/*/fd/* for the matching inode
for (const auto& entry : fs::directory_iterator("/proc")) {
if (!entry.is_directory()) continue;
std::string pidStr = entry.path().filename().string();
if (pidStr.empty() || !std::isdigit(pidStr[0])) continue;
std::string fdDir = "/proc/" + pidStr + "/fd";
try {
for (const auto& fdEntry : fs::directory_iterator(fdDir)) {
char target[512];
ssize_t len = readlink(fdEntry.path().c_str(), target, sizeof(target) - 1);
if (len > 0) {
target[len] = '\0';
std::string t(target);
if (t.find("socket:[" + std::to_string(inode) + "]") != std::string::npos) {
// Found the PID, now get the process name
std::string commPath = "/proc/" + pidStr + "/comm";
FILE* cf = fopen(commPath.c_str(), "r");
std::string procName = "<unknown>";
if (cf) {
char name[256];
if (fgets(name, sizeof(name), cf)) {
procName = name;
while (!procName.empty() && procName.back() == '\n') procName.pop_back();
}
fclose(cf);
}
return "PID " + pidStr + " (" + procName + ")";
}
}
}
} catch (...) { /* permission denied — skip */ }
}
return "unknown (inode " + std::to_string(inode) + " found but no matching PID)";
#endif
}
// Check if a TCP port is already in use (something is LISTENING)
static bool isPortInUse(int port)
{
@@ -279,10 +387,11 @@ bool EmbeddedDaemon::start(const std::string& binary_path)
// Check if something is already listening on the RPC port
int rpc_port = std::atoi(DRAGONX_DEFAULT_RPC_PORT);
if (isPortInUse(rpc_port)) {
DEBUG_LOGF("[INFO] Port %d is already in use — external daemon detected, will connect to it.\\n", rpc_port);
std::string owner = getPortOwnerInfo(rpc_port);
VERBOSE_LOGF("[INFO] Port %d is already in use by %s — external daemon detected, will connect to it.\\n", rpc_port, owner.c_str());
external_daemon_detected_ = true;
// Don't set Error — the wallet will connect to the running daemon.
setState(State::Stopped, "External daemon detected on port " + std::string(DRAGONX_DEFAULT_RPC_PORT));
setState(State::Stopped, "External daemon detected on port " + std::string(DRAGONX_DEFAULT_RPC_PORT) + " (owned by " + owner + ")");
return false;
}
external_daemon_detected_ = false;
@@ -519,7 +628,7 @@ void EmbeddedDaemon::drainOutput()
std::lock_guard<std::mutex> lk(output_mutex_);
appendOutput(buffer, bytes_read);
}
DEBUG_LOGF("[dragonxd] %s", buffer);
VERBOSE_LOGF("[dragonxd] %s", buffer);
debug_log_offset_ += bytes_read;
}
@@ -895,7 +1004,7 @@ void EmbeddedDaemon::drainOutput()
std::lock_guard<std::mutex> lk(output_mutex_);
appendOutput(buffer, static_cast<size_t>(n));
}
DEBUG_LOGF("[dragonxd] %s", buffer);
VERBOSE_LOGF("[dragonxd] %s", buffer);
}
}
@@ -987,7 +1096,7 @@ void EmbeddedDaemon::monitorProcess()
std::lock_guard<std::mutex> lk(output_mutex_);
appendOutput(buffer, static_cast<size_t>(bytes_read));
}
DEBUG_LOGF("[dragonxd] %s", buffer);
VERBOSE_LOGF("[dragonxd] %s", buffer);
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));