// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 // // Pure search for the lowest block height whose data the node can still read (a node restored from a // bootstrap snapshot lacks everything below the snapshot base). No I/O: the caller supplies the probe. // Header-only so the test target needs no new source. #pragma once #include "../rpc/rpc_failure.h" #include #include #include #include namespace dragonx { namespace services { enum class BlockProbe { Present, Missing, Unknown }; struct LowestBlockResult { bool ok = false; // false: could not tell (never read as "missing") int lowest = 0; // lowest readable height (valid when ok) bool fullHistory = false; // block 1 readable: not a bootstrapped / pruned node }; // What a failed getblockhash / getblock means. Only "the daemon says it cannot read the block data" // (RPC_INTERNAL_ERROR -32603: "Can't read block from disk" / "Block not available (pruned data)", // external/dragonx/src/rpc/blockchain.cpp) is Missing. Every transport problem (timeout, busy gate // "RPC deferred", queue full, abort, not listening), warmup (-28), out of range (-8), not found (-5) and // anything unrecognised is Unknown, which stops the search. inline BlockProbe classifyBlockProbeError(const std::exception& e) { if (const auto* r = dynamic_cast(&e)) { if (r->code == -32603) return BlockProbe::Missing; const std::string m = r->what(); if (m.find("from disk") != std::string::npos || m.find("pruned") != std::string::npos) return BlockProbe::Missing; return BlockProbe::Unknown; } return BlockProbe::Unknown; // RpcTransportError, runtime_error("Not connected"), json errors } // call(method, params) -> json, throwing like RPCClient::call. Present = both calls answered. template BlockProbe probeBlockPresence(Call&& call, int height) { try { nlohmann::json h = call("getblockhash", nlohmann::json::array({height})); if (!h.is_string()) return BlockProbe::Unknown; nlohmann::json b = call("getblock", nlohmann::json::array({h.get()})); return b.is_null() ? BlockProbe::Unknown : BlockProbe::Present; } catch (const std::exception& e) { return classifyBlockProbeError(e); } } // Assumes block data is present from some base height upward (as the old search did). tip = a height known // to exist (the caller's synced tip). Probe order: 1 (a normal full-history node answers in ONE probe), then // the tip (the search's premise), then a binary search over (1, tip]. Any Unknown ends it with ok=false: // nothing is guessed. inline LowestBlockResult findLowestAvailableBlock(int tip, const std::function& probe) { LowestBlockResult out; if (tip <= 1) return out; switch (probe(1)) { case BlockProbe::Present: out.ok = true; out.lowest = 1; out.fullHistory = true; return out; case BlockProbe::Unknown: return out; case BlockProbe::Missing: break; } if (probe(tip) != BlockProbe::Present) return out; // an unreadable tip is as unusable as Unknown int lo = 1, hi = tip; // invariant: lo Missing, hi Present while (hi - lo > 1) { const int mid = lo + (hi - lo) / 2; switch (probe(mid)) { case BlockProbe::Present: hi = mid; break; case BlockProbe::Missing: lo = mid; break; case BlockProbe::Unknown: return out; } } out.ok = true; out.lowest = hi; out.fullHistory = false; return out; } } // namespace services } // namespace dragonx