fix: macOS block index corruption, dbcache auto-sizing, import key rescan height

- Shutdown: 3-phase stop (wait for RPC stop → SIGTERM → SIGKILL) prevents
  LevelDB flush interruption on macOS/APFS that caused full re-sync on restart
- dbcache: auto-detect RAM and set -dbcache to 12.5% (clamped 450-4096 MB)
  on macOS (sysctl), Linux (sysconf), and Windows (GlobalMemoryStatusEx)
- Import key: pass user-entered start height to z_importkey and trigger
  rescanblockchain from that height for t-key imports
- Bump version to 1.1.1
This commit is contained in:
2026-03-25 11:00:14 -05:00
parent 9ed4fbc476
commit e0bfeb2f29
6 changed files with 109 additions and 25 deletions

View File

@@ -34,6 +34,9 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef __APPLE__
#include <sys/sysctl.h>
#endif
#endif
namespace fs = std::filesystem;
@@ -152,6 +155,41 @@ std::vector<std::string> EmbeddedDaemon::getChainParams()
// DragonX chain parameters.
// On Windows, omit -printtoconsole: we tail debug.log instead of piping stdout.
// On Linux, -printtoconsole is used for pipe-based output capture.
// Auto-detect a reasonable -dbcache based on available physical RAM.
// Default LevelDB cache is small (~450MB); larger caches improve sync
// performance and reduce disk I/O — especially on macOS with APFS.
std::string dbcache_arg = "-dbcache=450";
{
#ifdef __APPLE__
// sysctl hw.memsize returns total physical RAM in bytes
int64_t memsize = 0;
size_t len = sizeof(memsize);
if (sysctlbyname("hw.memsize", &memsize, &len, nullptr, 0) == 0 && memsize > 0) {
int totalMB = static_cast<int>(memsize / (1024 * 1024));
// Use ~12.5% of RAM for dbcache, clamped to [450, 4096]
int cache = std::max(450, std::min(4096, totalMB / 8));
dbcache_arg = "-dbcache=" + std::to_string(cache);
}
#elif defined(__linux__)
long pages = sysconf(_SC_PHYS_PAGES);
long page_size = sysconf(_SC_PAGE_SIZE);
if (pages > 0 && page_size > 0) {
int totalMB = static_cast<int>((static_cast<int64_t>(pages) * page_size) / (1024 * 1024));
int cache = std::max(450, std::min(4096, totalMB / 8));
dbcache_arg = "-dbcache=" + std::to_string(cache);
}
#elif defined(_WIN32)
MEMORYSTATUSEX memInfo;
memInfo.dwLength = sizeof(memInfo);
if (GlobalMemoryStatusEx(&memInfo)) {
int totalMB = static_cast<int>(memInfo.ullTotalPhys / (1024 * 1024));
int cache = std::max(450, std::min(4096, totalMB / 8));
dbcache_arg = "-dbcache=" + std::to_string(cache);
}
#endif
DEBUG_LOGF("[INFO] Using %s\n", dbcache_arg.c_str());
}
return {
"-tls=only",
#ifndef _WIN32
@@ -166,7 +204,8 @@ std::vector<std::string> EmbeddedDaemon::getChainParams()
"-ac_private=1",
"-addnode=176.126.87.241",
"-experimentalfeatures",
"-developerencryptwallet"
"-developerencryptwallet",
dbcache_arg
};
}
@@ -1045,14 +1084,12 @@ void EmbeddedDaemon::stop(int wait_ms)
if (process_pid_ > 0) {
setState(State::Stopping, "Stopping dragonxd...");
// Send SIGTERM to the entire process group (negative PID).
// This ensures that if dragonxd is a shell script wrapper,
// both bash AND the actual dragonxd child receive the signal.
// Without this, only bash is killed and dragonxd is orphaned.
DEBUG_LOGF("Sending SIGTERM to process group -%d\n", process_pid_);
kill(-process_pid_, SIGTERM);
// Wait for process to exit, draining stdout each iteration
// Phase 1: Wait for the daemon to exit naturally.
// The caller (stopEmbeddedDaemon) already sent an RPC "stop" which
// tells the daemon to flush LevelDB, close sockets, and exit cleanly.
// On macOS/APFS the LevelDB flush can take several seconds — we must
// NOT send SIGTERM until the daemon has had enough time to finish.
DEBUG_LOGF("Waiting up to %d ms for daemon to exit after RPC stop...\n", wait_ms);
auto start = std::chrono::steady_clock::now();
while (isRunning()) {
drainOutput();
@@ -1061,15 +1098,34 @@ void EmbeddedDaemon::stop(int wait_ms)
std::chrono::steady_clock::now() - start).count();
if (elapsed >= wait_ms) {
// Force kill the entire process group
DEBUG_LOGF("Forcing dragonxd termination with SIGKILL (group -%d)...\n", process_pid_);
kill(-process_pid_, SIGKILL);
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// Phase 2: If still running, send SIGTERM and wait a further 10s.
if (isRunning()) {
DEBUG_LOGF("Daemon did not exit gracefully — sending SIGTERM to process group -%d\n", process_pid_);
kill(-process_pid_, SIGTERM);
auto sigterm_start = std::chrono::steady_clock::now();
while (isRunning()) {
drainOutput();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - sigterm_start).count();
if (elapsed >= 10000) {
// Phase 3: Force kill
DEBUG_LOGF("Forcing dragonxd termination with SIGKILL (group -%d)...\n", process_pid_);
kill(-process_pid_, SIGKILL);
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
} else {
DEBUG_LOGF("Daemon exited cleanly after RPC stop\n");
}
drainOutput(); // read any remaining output
// Reap the child process