diff --git a/scripts/gen-lite-checkpoints.sh b/scripts/gen-lite-checkpoints.sh new file mode 100755 index 0000000..a32ed23 --- /dev/null +++ b/scripts/gen-lite-checkpoints.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Generate SDXL lite-wallet mainnet checkpoint entries from a fully-synced dragonxd. +# Each entry is (height,"blockhash","serialized_sapling_tree") in checkpoints.rs format. +# Fills the 1,770,000 -> tip gap so wallets reseed close to their birthday on rescan, +# bounding the (divergence-prone) compact-block replay span. Usage: +# scripts/gen-lite-checkpoints.sh [start] [step] > /tmp/new_checkpoints.txt +set -euo pipefail + +CLI=${DRAGONX_CLI:-/home/d/dragonx/src/dragonx-cli} +START=${1:-1770000} +STEP=${2:-10000} + +tip=$("$CLI" getblockcount) +end=$(( (tip / STEP) * STEP )) + +# Sanity: confirm the method reproduces a KNOWN checkpoint tree before trusting it. +ref_hash=$("$CLI" getblockhash 1760000 | tr -d '"[:space:]') +ref_tree=$("$CLI" getblockmerkletree 1760000 | tr -d '"[:space:]') +expect_hash="0000545a45b8d4ee4e4b423cb1ea74d67e3a04c320c6ea2f59ee06c08f91a117" +if [ "$ref_hash" != "$expect_hash" ]; then + echo "ABORT: getblockhash 1760000 = $ref_hash != known $expect_hash" >&2; exit 1 +fi +echo "# self-check: 1760000 hash matches; tree len=${#ref_tree}" >&2 + +n=0 +h=$START +while [ "$h" -le "$end" ]; do + hash=$("$CLI" getblockhash "$h" | tr -d '"[:space:]') + tree=$("$CLI" getblockmerkletree "$h" | tr -d '"[:space:]') + if [ -z "$hash" ] || [ -z "$tree" ]; then echo "ABORT: empty hash/tree at $h" >&2; exit 1; fi + printf '\t(%s,"%s",\n\t\t"%s"\n\t),\n' "$h" "$hash" "$tree" + n=$((n+1)) + h=$((h+STEP)) +done +echo "# generated $n checkpoints from $START to $end (tip=$tip)" >&2 diff --git a/tools/lite_send_smoke.cpp b/tools/lite_send_smoke.cpp new file mode 100644 index 0000000..05b5cb3 --- /dev/null +++ b/tools/lite_send_smoke.cpp @@ -0,0 +1,133 @@ +// DragonX Wallet - ImGui Edition +// Copyright 2024-2026 The Hush Developers +// Released under the GPLv3 +// +// Real-backend SEND smoke harness for the lite wallet. Links the actual SDXL litelib_* backend +// (same imported target the app uses) and drives the EXACT send path the GUI uses +// (LiteClientBridge::execute("send", [{address,amount,memo}])), so it surfaces the same errors a +// GUI send would. ALWAYS run with an isolated HOME — it reads/writes ~/.silentdragonxlite there. +// +// lite_send_smoke --newaddr [server] # create a new wallet, print receive addresses +// lite_send_smoke --status [server] # open + sync + print balance/addresses +// lite_send_smoke --send [server] # open + sync + self-send to own z-addr +// +// Receive addresses are PUBLIC (meant to be shared to receive funds) so they are printed; seeds and +// private keys are never touched/printed here. + +#include "wallet/lite_client_bridge.h" + +#include + +#include +#include +#include +#include +#include + +using namespace dragonx::wallet; +using nlohmann::json; + +// Recursively collect address-looking strings from any JSON shape the backend returns. +static void collectAddrs(const json& n, std::vector& zs, std::vector& ts) +{ + if (n.is_string()) { + const std::string s = n.get(); + if (s.rfind("zs", 0) == 0) zs.push_back(s); + else if (!s.empty() && (s[0] == 'R' || s[0] == 't')) ts.push_back(s); + } else if (n.is_array()) { + for (const auto& e : n) collectAddrs(e, zs, ts); + } else if (n.is_object()) { + for (const auto& kv : n.items()) collectAddrs(kv.value(), zs, ts); + } +} + +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"; + std::string mode; + double sendDrgx = 0.0; + bool doRescan = false; + for (int i = 1; i < argc; ++i) { + const std::string a = argv[i]; + if (a == "--newaddr" || a == "--status" || a == "--list" || a == "--tree") mode = a; + else if (a == "--send") { mode = a; if (i + 1 < argc) sendDrgx = std::stod(argv[++i]); } + else if (a == "--rescan") doRescan = true; // force witness rebuild from the closest checkpoint + else server = a; + } + if (mode.empty()) { std::printf("usage: lite_send_smoke --newaddr|--status|--send [server]\n"); return 2; } + + auto bridge = LiteClientBridge::linkedSdxl(); + std::printf("[send-smoke] server = %s\n", server.c_str()); + std::printf("[send-smoke] available() = %s\n", bridge.available() ? "true" : "false"); + if (!bridge.available()) { std::printf("[send-smoke] FAIL: backend not linked (%s)\n", + bridge.unavailableReason().c_str()); return 2; } + std::printf("[send-smoke] serverOnline = %s\n", bridge.checkServerOnline(server) ? "true" : "false"); + + if (mode == "--newaddr") { + auto r = bridge.initializeNew(false, server); + std::printf("[send-smoke] initializeNew ok=%d %s\n", r.ok, r.ok ? "" : r.error.c_str()); + if (!r.ok) return 1; + } else { + auto r = bridge.initializeExisting(false, server); + std::printf("[send-smoke] initializeExisting ok=%d %s\n", r.ok, r.ok ? "" : r.error.c_str()); + if (!r.ok) return 1; + if (doRescan) { + std::printf("[send-smoke] rescanning (rebuild witnesses from closest checkpoint)...\n"); + auto rs = bridge.execute("rescan", ""); + std::printf("[send-smoke] rescan ok=%d %.160s\n", rs.ok, rs.value.c_str()); + } else { + std::printf("[send-smoke] syncing (may take a while)...\n"); + auto s = bridge.execute("sync", ""); + std::printf("[send-smoke] sync ok=%d\n", s.ok); + } + auto b = bridge.execute("balance", ""); + std::printf("[send-smoke] balance = %.400s\n", b.value.c_str()); + } + + // Addresses (public — safe to print). + std::vector zs, ts; + { + auto a = bridge.execute("addresses", ""); + json j = json::parse(a.value, nullptr, /*allow_exceptions*/ false); + if (!j.is_discarded()) collectAddrs(j, zs, ts); + std::printf("[send-smoke] addresses: z=%zu t=%zu\n", zs.size(), ts.size()); + for (const auto& z : zs) std::printf("[send-smoke] Z (fund this for z2z): %s\n", z.c_str()); + for (const auto& t : ts) std::printf("[send-smoke] T (transparent) : %s\n", t.c_str()); + if (zs.empty() && ts.empty()) std::printf("[send-smoke] addresses RAW = %.200s\n", a.value.c_str()); + } + + if (mode == "--list") { + auto l = bridge.execute("list", ""); + std::printf("[send-smoke] list = %.3000s\n", l.value.c_str()); + } + + if (mode == "--tree") { + // Dump the wallet's latest Sapling commitment tree to diff against the node's + // `getblockmerkletree ` — a mismatch localizes a tree-build divergence. + auto t = bridge.execute("saplingtree", ""); + std::printf("[send-smoke] saplingtree = %s\n", t.value.c_str()); + } + + if (mode == "--newaddr") { + std::printf("[send-smoke] save ok=%d\n", bridge.execute("save", "").ok); + } else if (mode == "--send") { + if (zs.empty()) { std::printf("[send-smoke] FAIL: no z-address to self-send to\n"); bridge.shutdown(); return 1; } + const long long zat = static_cast(std::llround(sendDrgx * 1e8)); + json arr = json::array(); + json o; o["address"] = zs.front(); o["amount"] = zat; o["memo"] = "lite send smoke"; + arr.push_back(std::move(o)); + std::printf("[send-smoke] SEND %.8f DRGX (%lld zat) -> own z-addr ...\n", sendDrgx, zat); + std::printf("[send-smoke] send args = %s\n", arr.dump().c_str()); + auto res = bridge.execute("send", arr.dump()); + std::printf("[send-smoke] send bridge_ok=%d\n", res.ok); + std::printf("[send-smoke] send RESULT = %.600s\n", res.value.c_str()); + if (!res.error.empty()) std::printf("[send-smoke] send ERROR = %s\n", res.error.c_str()); + std::printf("[send-smoke] save ok=%d\n", bridge.execute("save", "").ok); + } + + bridge.shutdown(); + std::printf("[send-smoke] done\n"); + return 0; +}