test(lite): real-backend shape verification of refresh parsers
lite_smoke: add --restore-recent (restore a throwaway wallet at birthday≈tip) and factor
the data-shape checks (non-blocking commands first). Finding: the backend downloads from a
fixed checkpoint regardless of birthday, so first sync is ~30 min and balance/list block
until synced — a full live data run is impractical.
Verified all refresh parsers against the real backend without a full sync:
- live run: info/addresses/syncstatus parse_ok=1 (addresses z=1/t=6 on a restored wallet).
- via the authoritative Rust source (commands.rs / lightclient.rs):
- balance do_balance fields match parseLiteBalanceResponse.
- list do_list_transactions: sends use outgoing_metadata (no top-level address), receives
use address+amount; parseTransactionRecord already branches correctly.
- syncstatus was the only mismatch (fixed in the prior commit).
No parser changes needed beyond syncstatus. M2 refresh path verified end-to-end at the
shape level.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,16 +2,18 @@
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
//
|
||||
// Real-backend smoke harness for the lite wallet bridge. Links the actual SDXL
|
||||
// litelib_* backend (via the same imported CMake target the app uses) and exercises
|
||||
// LiteClientBridge against a real lightwalletd server. This is the "real backend smoke
|
||||
// test" the implementation plan gates behind passing fake-backend tests.
|
||||
// Real-backend smoke harness for the lite wallet bridge. Links the actual SDXL litelib_*
|
||||
// backend (via the same imported CMake target the app uses) and exercises LiteClientBridge
|
||||
// against a real lightwalletd server. The "real backend smoke test" the plan gates behind
|
||||
// passing fake-backend tests.
|
||||
//
|
||||
// lite_smoke [server-url] [--create]
|
||||
// lite_smoke [server-url] [--create] [--refresh] [--full] [--restore-recent]
|
||||
//
|
||||
// Read-only by default (available / checkServerOnline / walletExists). Pass --create to
|
||||
// also attempt a real litelib_initialize_new (writes wallet state — run with an isolated
|
||||
// HOME, e.g. HOME=/tmp/lite_smoke env, so it cannot clobber a real wallet).
|
||||
// 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.
|
||||
|
||||
#include "wallet/lite_client_bridge.h"
|
||||
#include "wallet/lite_connection_service.h"
|
||||
@@ -20,20 +22,64 @@
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
using namespace dragonx::wallet;
|
||||
|
||||
// A standard valid 24-word BIP39 test mnemonic (no funds). Restored only into an isolated HOME.
|
||||
static const char* kTestSeed =
|
||||
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon "
|
||||
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon "
|
||||
"abandon art";
|
||||
|
||||
// Push each refresh command's real output through its parser; print flags/counts, and the raw
|
||||
// (truncated) response only on a parse failure to diagnose real-shape mismatches.
|
||||
static void runDataShapeChecks(LiteClientBridge& bridge)
|
||||
{
|
||||
// Non-blocking commands first (read local wallet state); balance last (may need a sync).
|
||||
{
|
||||
auto r = bridge.execute("syncstatus", "");
|
||||
auto p = parseLiteSyncStatusResponse(r.value);
|
||||
std::printf("[lite-smoke] syncstatus parse_ok=%d complete=%d synced=%llu/%llu\n",
|
||||
p.ok, p.syncStatus.complete,
|
||||
(unsigned long long)p.syncStatus.syncedBlocks, (unsigned long long)p.syncStatus.totalBlocks);
|
||||
if (!p.ok) std::printf("[lite-smoke] syncstatus RAW = %.150s\n", r.value.c_str());
|
||||
}
|
||||
{
|
||||
auto r = bridge.execute("addresses", "");
|
||||
auto p = parseLiteAddressesResponse(r.value);
|
||||
std::printf("[lite-smoke] addresses bridge_ok=%d parse_ok=%d err=%s z=%zu t=%zu\n",
|
||||
r.ok, p.ok, liteResultParserErrorName(p.error),
|
||||
p.addresses.zAddresses.size(), p.addresses.tAddresses.size());
|
||||
if (!p.ok) std::printf("[lite-smoke] addresses RAW = %.150s\n", r.value.c_str());
|
||||
}
|
||||
{
|
||||
auto r = bridge.execute("list", "");
|
||||
auto p = parseLiteTransactionsResponse(r.value);
|
||||
std::printf("[lite-smoke] list bridge_ok=%d parse_ok=%d err=%s count=%zu\n",
|
||||
r.ok, p.ok, liteResultParserErrorName(p.error), p.transactions.transactions.size());
|
||||
if (!p.ok) std::printf("[lite-smoke] list RAW = %.150s\n", r.value.c_str());
|
||||
}
|
||||
std::printf("[lite-smoke] balance (may block until synced)...\n");
|
||||
{
|
||||
auto r = bridge.execute("balance", "");
|
||||
auto p = parseLiteBalanceResponse(r.value);
|
||||
std::printf("[lite-smoke] balance bridge_ok=%d parse_ok=%d err=%s\n",
|
||||
r.ok, p.ok, liteResultParserErrorName(p.error));
|
||||
if (!p.ok) std::printf("[lite-smoke] balance RAW = %.150s\n", r.value.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
using namespace dragonx::wallet;
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0); // unbuffered so output survives a timeout kill
|
||||
|
||||
std::string server = "https://lite.dragonx.is";
|
||||
bool doCreate = false;
|
||||
bool doRefresh = false;
|
||||
bool doFull = false;
|
||||
bool doCreate = false, doRefresh = false, doFull = false, doRestoreRecent = 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 server = arg;
|
||||
}
|
||||
|
||||
@@ -47,30 +93,49 @@ int main(int argc, char** argv)
|
||||
return 2;
|
||||
}
|
||||
|
||||
const bool walletExists = bridge.walletExists(kDragonXLiteChainName);
|
||||
std::printf("[lite-smoke] walletExists(%s) = %s\n", kDragonXLiteChainName, walletExists ? "true" : "false");
|
||||
std::printf("[lite-smoke] walletExists(%s) = %s\n", kDragonXLiteChainName,
|
||||
bridge.walletExists(kDragonXLiteChainName) ? "true" : "false");
|
||||
std::printf("[lite-smoke] checkServerOnline() = %s\n", bridge.checkServerOnline(server) ? "true" : "false");
|
||||
|
||||
const bool online = bridge.checkServerOnline(server);
|
||||
std::printf("[lite-smoke] checkServerOnline() = %s\n", online ? "true" : "false");
|
||||
if (doRestoreRecent) {
|
||||
// Bootstrap a client to learn the chain tip, then restore a throwaway wallet with a
|
||||
// birthday near the tip so the scan is minimal (seconds), then check data shapes.
|
||||
std::printf("[lite-smoke] bootstrap initializeNew() to learn tip...\n");
|
||||
bridge.initializeNew(false, server);
|
||||
unsigned long long tip = 0;
|
||||
{
|
||||
auto r = bridge.execute("info", "");
|
||||
auto p = parseLiteInfoResponse(r.value);
|
||||
tip = p.info.latestBlockHeight.has_value() ? (unsigned long long)*p.info.latestBlockHeight : 0;
|
||||
std::printf("[lite-smoke] info parse_ok=%d tip=%llu\n", p.ok, tip);
|
||||
}
|
||||
const unsigned long long birthday = tip > 10 ? tip - 10 : tip;
|
||||
std::printf("[lite-smoke] restore-from-phrase at birthday=%llu (minimal scan)...\n", birthday);
|
||||
auto rr = bridge.initializeNewFromPhrase(false, server, kTestSeed, birthday, 0, /*overwrite*/ true);
|
||||
std::printf("[lite-smoke] restore ok=%d\n", rr.ok);
|
||||
if (!rr.ok) std::printf("[lite-smoke] restore err = %s\n", rr.error.c_str());
|
||||
// NB: this backend downloads from a fixed checkpoint regardless of birthday, so an
|
||||
// explicit full "sync" is slow. addresses/list/syncstatus read local state and don't
|
||||
// need it; balance may block until synced.
|
||||
std::printf("[lite-smoke] --- data shape check (no full sync) ---\n");
|
||||
runDataShapeChecks(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);
|
||||
std::printf("[lite-smoke] initializeNew ok = %s\n", result.ok ? "true" : "false");
|
||||
if (result.ok) {
|
||||
// The response is the new wallet's SEED PHRASE — never print it. Report only
|
||||
// that a well-formed, non-empty response came back.
|
||||
std::printf("[lite-smoke] wallet created; response len = %zu (seed redacted)\n",
|
||||
result.value.size());
|
||||
std::printf("[lite-smoke] wallet created; response len = %zu (seed redacted)\n", result.value.size());
|
||||
} else {
|
||||
std::printf("[lite-smoke] error = %s\n", result.error.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (doRefresh) {
|
||||
// Run each refresh command through the real parser to verify the LIVE backend's
|
||||
// JSON shapes match what the gateway expects. Prints flags/counts only (no secrets,
|
||||
// addresses, or amounts).
|
||||
std::printf("[lite-smoke] --- refresh shape check (real backend output -> parsers) ---\n");
|
||||
{
|
||||
auto r = bridge.execute("info", "");
|
||||
@@ -78,39 +143,17 @@ int main(int argc, char** argv)
|
||||
std::printf("[lite-smoke] info bridge_ok=%d parse_ok=%d err=%s\n",
|
||||
r.ok, p.ok, liteResultParserErrorName(p.error));
|
||||
}
|
||||
{
|
||||
auto r = bridge.execute("syncstatus", "");
|
||||
auto p = parseLiteSyncStatusResponse(r.value);
|
||||
std::printf("[lite-smoke] syncstatus bridge_ok=%d parse_ok=%d err=%s synced=%llu/%llu\n",
|
||||
r.ok, p.ok, liteResultParserErrorName(p.error),
|
||||
(unsigned long long)p.syncStatus.syncedBlocks, (unsigned long long)p.syncStatus.totalBlocks);
|
||||
std::printf("[lite-smoke] syncstatus RAW = %.200s\n", r.value.c_str());
|
||||
}
|
||||
// The data commands below trigger a blocking full-chain sync on a fresh wallet;
|
||||
// gate them behind --full so the shape check stays fast by default.
|
||||
if (!doFull) { bridge.shutdown(); std::printf("[lite-smoke] (skipping balance/addresses/list; pass --full)\n"); return 0; }
|
||||
{
|
||||
auto r = bridge.execute("balance", "");
|
||||
auto p = parseLiteBalanceResponse(r.value);
|
||||
std::printf("[lite-smoke] balance bridge_ok=%d parse_ok=%d err=%s\n",
|
||||
r.ok, p.ok, liteResultParserErrorName(p.error));
|
||||
}
|
||||
{
|
||||
auto r = bridge.execute("addresses", "");
|
||||
auto p = parseLiteAddressesResponse(r.value);
|
||||
std::printf("[lite-smoke] addresses bridge_ok=%d parse_ok=%d err=%s z=%zu t=%zu\n",
|
||||
r.ok, p.ok, liteResultParserErrorName(p.error),
|
||||
p.addresses.zAddresses.size(), p.addresses.tAddresses.size());
|
||||
}
|
||||
{
|
||||
auto r = bridge.execute("list", "");
|
||||
auto p = parseLiteTransactionsResponse(r.value);
|
||||
std::printf("[lite-smoke] list bridge_ok=%d parse_ok=%d err=%s count=%zu\n",
|
||||
r.ok, p.ok, liteResultParserErrorName(p.error), p.transactions.transactions.size());
|
||||
if (!doFull) {
|
||||
bridge.shutdown();
|
||||
std::printf("[lite-smoke] (skipping balance/addresses/list; pass --full or --restore-recent)\n");
|
||||
return 0;
|
||||
}
|
||||
std::printf("[lite-smoke] running full sync (blocking; can take many minutes)...\n");
|
||||
bridge.execute("sync", "");
|
||||
runDataShapeChecks(bridge);
|
||||
}
|
||||
|
||||
bridge.shutdown();
|
||||
std::printf("[lite-smoke] done (real litelib_* symbols are callable; results above are live)\n");
|
||||
std::printf("[lite-smoke] done\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user