fix: Windows identity, async address creation, mining UI, and chart artifacts

Windows identity:
- Add VERSIONINFO resource (.rc) with ObsidianDragon file description
- Embed application manifest for DPI awareness and shell identity
- Patch libwinpthread/libpthread to remove competing VERSIONINFO
- Set AppUserModelID and HWND property store to override Task Manager cache
- Link patched pthread libs to eliminate "POSIX WinThreads" description

Address creation (+New button):
- Move z_getnewaddress/getnewaddress off UI thread to async worker
- Inject new address into state immediately for instant UI selection
- Trigger background refresh for balance updates

Mining tab:
- Add pool mining dropdown with saved URLs/workers and bookmarks
- Add solo mining log panel from daemon output with chart/log toggle
- Fix toggle button cursor (render after InputTextMultiline)
- Auto-restart miner on pool config change
- Migrate default pool URL to include stratum port

Transactions:
- Sort pending (0-conf) transactions to top of history
- Fall back to timereceived when timestamp is missing

Shutdown:
- Replace blocking sleep_for calls with 100ms polling loops
- Check shutting_down_ flag throughout daemon restart/bootstrap flows
- Reduce daemon stop timeout from 30s to 10s

Other:
- Fix market chart fill artifact (single concave polygon vs per-segment quads)
- Add bootstrap checksum verification state display
- Rename daemon client identifier to ObsidianDragon
This commit is contained in:
dan_s
2026-03-05 22:43:27 -06:00
parent 4b16a2a2c4
commit 653a90de62
20 changed files with 842 additions and 116 deletions

View File

@@ -21,6 +21,7 @@
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <unordered_set>
namespace dragonx {
namespace ui {
@@ -45,6 +46,22 @@ static std::string ExtractIP(const std::string& addr)
return ip;
}
// Known seed/addnode IPs for the DragonX network.
// These are the official seed nodes that the daemon connects to on startup.
static bool IsSeedNode(const std::string& addr) {
static const std::unordered_set<std::string> seeds = {
"176.126.87.241", // embedded daemon -addnode
"94.72.112.24", // node1.hush.is
"37.60.252.160", // node2.hush.is
"176.57.70.185", // node3.hush.is / node6.hush.is
"185.213.209.89", // node4.hush.is
"137.74.4.198", // node5.hush.is
"18.193.113.121", // node7.hush.is
"38.60.224.94", // node8.hush.is
};
return seeds.count(ExtractIP(addr)) > 0;
}
void RenderPeersTab(App* app)
{
auto& S = schema::UI();
@@ -149,10 +166,28 @@ void RenderPeersTab(App* app)
// Blocks
float cx = cardMin.x + pad;
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Blocks");
int blocks = mining.blocks > 0 ? mining.blocks : state.sync.blocks;
int blocks = state.sync.blocks;
if (blocks > 0) {
snprintf(buf, sizeof(buf), "%d", blocks);
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, ry + capFont->LegacySize + Layout::spacingXs()), OnSurface(), buf);
int blocksLeft = state.sync.headers - blocks;
if (blocksLeft < 0) blocksLeft = 0;
if (blocksLeft > 0) {
snprintf(buf, sizeof(buf), "%d (%d left)", blocks, blocksLeft);
float valY = ry + capFont->LegacySize + Layout::spacingXs();
// Draw block number in normal color
char blockStr[32];
snprintf(blockStr, sizeof(blockStr), "%d ", blocks);
ImVec2 numSz = sub1->CalcTextSizeA(sub1->LegacySize, FLT_MAX, 0, blockStr);
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, valY), OnSurface(), blockStr);
// Draw "(X left)" in warning color
char leftStr[32];
snprintf(leftStr, sizeof(leftStr), "(%d left)", blocksLeft);
dl->AddText(capFont, capFont->LegacySize,
ImVec2(cx + numSz.x, valY + (sub1->LegacySize - capFont->LegacySize) * 0.5f),
Warning(), leftStr);
} else {
snprintf(buf, sizeof(buf), "%d", blocks);
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, ry + capFont->LegacySize + Layout::spacingXs()), OnSurface(), buf);
}
} else {
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, ry + capFont->LegacySize + Layout::spacingXs()), OnSurfaceDisabled(), "\xE2\x80\x94");
}
@@ -680,7 +715,16 @@ void RenderPeersTab(App* app)
float pingDotR = S.drawElement("tabs.peers", "ping-dot-radius-base").size + S.drawElement("tabs.peers", "ping-dot-radius-scale").size * hs;
dl->AddCircleFilled(ImVec2(cx + S.drawElement("tabs.peers", "ping-dot-x-offset").size, cy + body2->LegacySize * 0.5f), pingDotR, dotCol);
dl->AddText(body2, body2->LegacySize, ImVec2(cx + S.drawElement("tabs.peers", "address-x-offset").size, cy), OnSurface(), peer.addr.c_str());
float addrX = cx + S.drawElement("tabs.peers", "address-x-offset").size;
dl->AddText(body2, body2->LegacySize, ImVec2(addrX, cy), OnSurface(), peer.addr.c_str());
// Seed node icon — rendered right after the IP address
if (IsSeedNode(peer.addr)) {
ImVec2 addrSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, peer.addr.c_str());
ImFont* iconFont = Type().iconSmall();
float iconY = cy + (body2->LegacySize - iconFont->LegacySize) * 0.5f;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(addrX + addrSz.x + Layout::spacingSm(), iconY), WithAlpha(Success(), 200), ICON_MD_GRASS);
}
{
const char* dirLabel = peer.inbound ? "In" : "Out";