diff --git a/tools/lite_smoke.cpp b/tools/lite_smoke.cpp index 41ddbfa..ff0e8b4 100644 --- a/tools/lite_smoke.cpp +++ b/tools/lite_smoke.cpp @@ -7,18 +7,22 @@ // against a real lightwalletd server. The "real backend smoke test" the plan gates behind // passing fake-backend tests. // -// lite_smoke [server-url] [--create] [--refresh] [--full] [--restore-recent] +// lite_smoke [server-url] [--create] [--refresh] [--full] [--restore-recent] [--keys] // // Read-only by default. --create initializes a new wallet. --refresh runs the refresh // commands through the parsers (shape check). --full also does a (slow) full sync first. // --restore-recent restores a throwaway wallet with a birthday near the tip so the sync is // minimal (seconds), then runs the data-shape checks — fast verification of balance/addresses/ -// list shapes without a multi-minute full scan. Always run with an isolated HOME. +// list shapes without a multi-minute full scan. --keys creates a fresh wallet and checks the +// M4/M5 spend/backup command shapes (new-address / export / seed / save) — fast, no sync, and +// SECRET-SAFE (seed/key values are never printed). Always run with an isolated HOME. #include "wallet/lite_client_bridge.h" #include "wallet/lite_connection_service.h" #include "wallet/lite_result_parsers.h" +#include + #include #include @@ -68,18 +72,73 @@ static void runDataShapeChecks(LiteClientBridge& bridge) } } +// Exercise the M4/M5 spend/backup command shapes against the real backend: new-address, +// export (private keys), seed, and save. SECRET-SAFE — seed/private-key VALUES are never +// printed; only their presence and JSON shape are reported. (No send/shield here: those would +// attempt a real broadcast.) +static void runLiteKeysShapeChecks(LiteClientBridge& bridge) +{ + auto parse = [](const std::string& s) { return nlohmann::json::parse(s, nullptr, false); }; + + { + auto r = bridge.execute("new", "zs"); + auto j = parse(r.value); + const bool shape = j.is_array() && !j.empty() && j[0].is_string() && + j[0].get().rfind("zs", 0) == 0; + std::printf("[lite-smoke] new z bridge_ok=%d shape_ok=%d (addr redacted)\n", r.ok, shape); + if (!shape) std::printf("[lite-smoke] new z RAW = %.80s\n", r.value.c_str()); + } + { + auto r = bridge.execute("new", "R"); + auto j = parse(r.value); + const bool shape = j.is_array() && !j.empty() && j[0].is_string() && + j[0].get().rfind("R", 0) == 0; + std::printf("[lite-smoke] new t bridge_ok=%d shape_ok=%d (addr redacted)\n", r.ok, shape); + if (!shape) std::printf("[lite-smoke] new t RAW = %.80s\n", r.value.c_str()); + } + { + // seed: {"seed": "...", "birthday": N} — verify shape ONLY; never print the seed. + auto r = bridge.execute("seed", ""); + auto j = parse(r.value); + const bool hasSeed = j.is_object() && j.contains("seed") && j["seed"].is_string() && + !j["seed"].get().empty(); + const bool hasBday = j.is_object() && j.contains("birthday"); + std::printf("[lite-smoke] seed bridge_ok=%d has_seed=%d has_birthday=%d (seed REDACTED)\n", + r.ok, hasSeed, hasBday); + if (!hasSeed) std::printf("[lite-smoke] seed shape unexpected (len=%zu)\n", r.value.size()); + } + { + // export: [{"address","private_key","viewing_key"}, ...] — shape ONLY; never print keys. + auto r = bridge.execute("export", ""); + auto j = parse(r.value); + const bool isArr = j.is_array(); + const bool hasPk = isArr && !j.empty() && j[0].is_object() && j[0].contains("private_key"); + std::printf("[lite-smoke] export bridge_ok=%d is_array=%d count=%zu has_private_key=%d (keys REDACTED)\n", + r.ok, isArr, isArr ? j.size() : 0, hasPk); + if (!isArr) std::printf("[lite-smoke] export shape unexpected (len=%zu)\n", r.value.size()); + } + { + auto r = bridge.execute("save", ""); + auto j = parse(r.value); + const bool ok = j.is_object() && j.value("result", std::string()) == "success"; + std::printf("[lite-smoke] save bridge_ok=%d result_success=%d\n", r.ok, ok); + if (!ok) std::printf("[lite-smoke] save RAW = %.80s\n", r.value.c_str()); + } +} + int main(int argc, char** argv) { std::setvbuf(stdout, nullptr, _IONBF, 0); // unbuffered so output survives a timeout kill std::string server = "https://lite.dragonx.is"; - bool doCreate = false, doRefresh = false, doFull = false, doRestoreRecent = false; + bool doCreate = false, doRefresh = false, doFull = false, doRestoreRecent = false, doKeys = false; for (int i = 1; i < argc; ++i) { const std::string arg = argv[i]; if (arg == "--create") doCreate = true; else if (arg == "--refresh") doRefresh = true; else if (arg == "--full") doFull = true; else if (arg == "--restore-recent") doRestoreRecent = true; + else if (arg == "--keys") doKeys = true; else server = arg; } @@ -124,6 +183,24 @@ int main(int argc, char** argv) return 0; } + if (doKeys) { + // Fast M4/M5 path check: create a fresh wallet (no sync needed for local key ops) and + // exercise new-address / export / seed / save shapes. Run with an isolated HOME. + std::printf("[lite-smoke] initializeNew() for M4/M5 keys check...\n"); + auto cr = bridge.initializeNew(false, server); + std::printf("[lite-smoke] initializeNew ok=%d\n", cr.ok); + if (!cr.ok) { + std::printf("[lite-smoke] error = %s\n", cr.error.c_str()); + bridge.shutdown(); + return 1; + } + std::printf("[lite-smoke] --- M4/M5 shape check (new/export/seed/save) ---\n"); + runLiteKeysShapeChecks(bridge); + bridge.shutdown(); + std::printf("[lite-smoke] done\n"); + return 0; + } + if (doCreate) { std::printf("[lite-smoke] initializeNew() ... (real network + writes wallet state)\n"); auto result = bridge.initializeNew(false, server);