fix(wallet): connect the isolated seed node over plaintext RPC + add smoke

The seed-wallet creator forced TLS on its RPC client, but the DRAGONX RPC is
plaintext HTTP on localhost (-tls=only applies to P2P, not the RPC; the main
GUI's conf has no rpctls key and connects over http too). Forcing https made
every connect fail with "SSL connect error", so the isolated create never
completed. Connect without TLS, matching the main daemon path.

Also add an env-gated (DRAGONX_SEED_CREATE_SMOKE=1) headless smoke in main()
that runs SeedWalletCreator::create() before GUI init and prints
ok/word-count/address (never the seed words) — used to validate the full C++
creator path without the GUI. Harmless in normal runs.

Validated end-to-end against the hd-transparent-keys (v1.0.3-dc45e7d9)
daemon: ok=1, words=24, a shielded z-address returned, temp datadir cleaned up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-07 14:17:00 -05:00
parent 718cb82c13
commit 7ee20bb497
2 changed files with 22 additions and 3 deletions

View File

@@ -64,8 +64,9 @@ SeedWalletResult SeedWalletCreator::create(bool keepDatadir,
const std::string user = randomToken(16);
const std::string pass = randomToken(32);
// 3. Minimal conf for the isolated node (own creds/port; -tls=only comes from the chain args,
// so the RPC client below connects with TLS just like the main daemon does).
// 3. Minimal conf for the isolated node (own creds/port). The DRAGONX RPC is plaintext HTTP on
// localhost — `-tls=only` applies to P2P, not the RPC — so the client below connects without
// TLS, exactly as the main GUI does (its conf has no rpctls key either).
const std::string conf = "rpcuser=" + user + "\n"
"rpcpassword=" + pass + "\n"
"rpcport=" + std::to_string(port) + "\n"
@@ -94,7 +95,7 @@ SeedWalletResult SeedWalletCreator::create(bool keepDatadir,
rpc::RPCClient cli;
bool ready = false;
for (int i = 0; i < 90 && !ready; ++i) {
if (cli.connect("127.0.0.1", std::to_string(port), user, pass, /*useTls=*/true)) {
if (cli.connect("127.0.0.1", std::to_string(port), user, pass, /*useTls=*/false)) {
try { cli.call("getinfo"); ready = true; } // succeeds only once past warmup (-28)
catch (...) { cli.disconnect(); }
}

View File

@@ -25,7 +25,9 @@
#include "ui/effects/theme_effects.h"
#include "util/texture_loader.h"
#include "util/platform.h"
#include "daemon/seed_wallet_creator.h"
#include "resources/embedded_resources.h"
#include <sodium.h>
// Backend-specific headers
#ifdef DRAGONX_USE_DX11
@@ -652,6 +654,22 @@ int main(int argc, char* argv[])
DEBUG_LOGF("Built with Dear ImGui %s\n", IMGUI_VERSION);
fflush(stdout);
// Env-gated headless smoke for the isolated seed-wallet creator (migrate-to-seed Phase 1).
// Exercises SeedWalletCreator::create() end-to-end without the GUI, then exits. Secret-safe:
// it never prints the seed words — only ok/word-count/address/error. Run with an isolated HOME.
if (const char* smk = std::getenv("DRAGONX_SEED_CREATE_SMOKE"); smk && std::string(smk) == "1") {
sodium_init();
printf("[seed-create-smoke] starting isolated create...\n"); fflush(stdout);
auto r = dragonx::daemon::SeedWalletCreator::create(/*keepDatadir=*/false,
[](const std::string& m) { printf("[seed-create-smoke] %s\n", m.c_str()); fflush(stdout); });
int words = 0; bool inWord = false;
for (char c : r.seedPhrase) { bool sp = (c == ' '); if (!sp && !inWord) { inWord = true; ++words; } else if (sp) inWord = false; }
printf("[seed-create-smoke] ok=%d words=%d addr=%s err=%s\n",
r.ok ? 1 : 0, words, r.destAddress.c_str(), r.error.c_str());
if (!r.seedPhrase.empty()) sodium_memzero(&r.seedPhrase[0], r.seedPhrase.size());
return r.ok ? 0 : 1;
}
// Check for existing instance
if (!g_single_instance.tryLock()) {
fprintf(stderr, "Another instance of ObsidianDragon is already running.\n");