test(lite): smoke-check M4/M5 command shapes against the real backend
Add `lite_smoke --keys`: create a fresh wallet and exercise the M4/M5 spend/backup commands (new-address, export, seed, save) against the real linked SDXL backend, verifying each response's JSON shape with nlohmann. SECRET-SAFE: seed and private-key VALUES are never printed — only field presence/shape and counts (no send/shield, which would broadcast). Verified live (isolated HOME, throwaway wallet shredded after): new z shape_ok=1 new t shape_ok=1 seed has_seed=1 has_birthday=1 (REDACTED) export is_array=1 count=4 has_private_key=1 (REDACTED) save result_success=1 Confirms the controller's newAddress / exportSeed / exportPrivateKeys / save parsing matches real backend output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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 <nlohmann/json.hpp>
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
@@ -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<std::string>().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<std::string>().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<std::string>().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);
|
||||
|
||||
Reference in New Issue
Block a user