Merge pull request #16 from logos-blockchain/erhant/bump-lez-and-fix-logging

fix: better logging + bumped flakes
This commit is contained in:
erhant 2026-07-02 17:48:39 +03:00 committed by GitHub
commit 62c1af660f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 142 additions and 82 deletions

View File

@ -40,11 +40,11 @@ On success it returns `0`; a non-zero return is the FFI `OperationStatus` (e.g.
> [!TIP]
>
> By default the indexer is silent. Call `init_logger(level)` once (e.g.
> `init_logger("info")`; accepts `off`/`error`/`warn`/`info`/`debug`/`trace`) to
> surface the indexer's own `log` output — useful since a failed call otherwise
> only reports a numeric `OperationStatus`. Logging is scoped to the indexer
> crates, and the first call wins.
> The module logs its lifecycle (start/stop, the resolved config + storage paths,
> and failures with their `OperationStatus`) to stderr, which the `logos_host`
> captures and surfaces through its own logger. For ongoing indexer health — sync
> state, and a parked/stalled tip with its reason — poll `getStatus()`; that runs
> inside the indexer and reports what the C++ lifecycle logs can't see.
### Query methods (the Logos-protocol API)

View File

@ -7,47 +7,5 @@
"max_retries": 5
}
},
"channel_id": "0101010101010101010101010101010101010101010101010101010101010101",
"initial_accounts": [
{
"account_id": "CbgR6tj5kWx5oziiFptM7jMvrQeYY3Mzaao6ciuhSr2r",
"balance": 10000
},
{
"account_id": "2RHZhw9h534Zr3eq2RGhQete2Hh667foECzXPmSkGni2",
"balance": 20000
}
],
"initial_commitments": [
{
"npk": [
139, 19, 158, 11, 155, 231, 85, 206, 132, 228, 220, 114, 145, 89, 113,
156, 238, 142, 242, 74, 182, 91, 43, 100, 6, 190, 31, 15, 31, 88, 96,
204
],
"account": {
"program_owner": [0, 0, 0, 0, 0, 0, 0, 0],
"balance": 10000,
"data": [],
"nonce": 0
}
},
{
"npk": [
173, 134, 33, 223, 54, 226, 10, 71, 215, 254, 143, 172, 24, 244, 243,
208, 65, 112, 118, 70, 217, 240, 69, 100, 129, 3, 121, 25, 213, 132, 42,
45
],
"account": {
"program_owner": [0, 0, 0, 0, 0, 0, 0, 0],
"balance": 20000,
"data": [],
"nonce": 0
}
}
],
"signing_key": [
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37
]
"channel_id": "0101010101010101010101010101010101010101010101010101010101010101"
}

12
flake.lock generated
View File

@ -1574,16 +1574,16 @@
"rust-rapidsnark": "rust-rapidsnark"
},
"locked": {
"lastModified": 1782820738,
"narHash": "sha256-OnXBx3nD/r7vCzZwh/RXmoqbtNF9rG+ZZsWXPsXoOzk=",
"ref": "refs/tags/v0.2.0",
"rev": "a58fbce2ff48c58b7bb5001b1a27e64b9596ee3a",
"revCount": 2828,
"lastModified": 1782941647,
"narHash": "sha256-H7T0mBWY75tZjCf9zcQUAFteurZhqnZ+2srtdPeQTRQ=",
"ref": "main",
"rev": "d1637b65e2479e80bb617b900ec5d4024216457d",
"revCount": 2830,
"type": "git",
"url": "https://github.com/logos-blockchain/logos-execution-zone"
},
"original": {
"ref": "refs/tags/v0.2.0",
"ref": "main",
"type": "git",
"url": "https://github.com/logos-blockchain/logos-execution-zone"
}

View File

@ -3,11 +3,7 @@
inputs = {
logos-module-builder.url = "github:logos-co/logos-module-builder";
# The LEZ indexer Rust FFI lib + header come from this flake's `indexer`
# package output (/lib/libindexer_ffi.* + /include/indexer_ffi.h). It is a
# prebuilt Nix derivation, so mkExternalLib consumes it directly (no build).
logos-execution-zone.url = "git+https://github.com/logos-blockchain/logos-execution-zone?ref=refs/tags/v0.2.0";
logos-execution-zone.url = "git+https://github.com/logos-blockchain/logos-execution-zone?ref=main";
};
outputs =

View File

@ -1,5 +1,6 @@
{
"name": "lez_indexer_module",
"display_name": "LEZ Indexer",
"version": "1.0.0",
"type": "core",
"interface": "universal",

View File

@ -152,6 +152,19 @@ namespace marshalling {
return true;
}
// True iff `s` is exactly `num_chars` hex digits (no prefix/whitespace).
bool isHex(const std::string& s, const size_t num_chars) {
if (s.size() != num_chars) {
return false;
}
for (const char c : s) {
if (hexNibble(c) < 0) {
return false;
}
}
return true;
}
// Base58-encode `length` raw bytes (plain Bitcoin alphabet). Big-integer
// base 256 -> 58, leading zero bytes map to leading '1's. Mirrors
// Base58.js::encode so account ids match the wallet UI and canonical LEZ.

View File

@ -40,6 +40,11 @@ namespace marshalling {
// Returns false unless it decodes to exactly 32 bytes.
bool hexToBytes32(const std::string& hex, FfiBytes32* out);
// True iff `s` is exactly `num_chars` hex digits (no 0x prefix, no whitespace).
// For validating fixed-width hex identifiers (e.g. a 64-char channel id) before
// trusting them as-is, such as when composing a filesystem path.
bool isHex(const std::string& s, size_t num_chars);
// Base58 (plain Bitcoin alphabet, no checksum) of `length` raw bytes. This is
// the canonical string form of an LEZ *account id* — it matches lee::AccountId
// Display, wallet_ffi's account_id_to_base58, and the wallet UI's Base58.js.

View File

@ -5,6 +5,9 @@
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <string>
using namespace marshalling;
@ -15,34 +18,62 @@ namespace {
return static_cast<IndexerServiceFFI*>(p);
}
void warn(const char* method, const char* msg) {
std::fprintf(stderr, "lez_indexer_module: %s: %s\n", method, msg);
// Module diagnostics go to stderr; logos_host captures it and routes each line
// through its own logger, classifying by an "Error:"/"Warning:" token found
// anywhere in the line (plain lines are treated as info). Prefix accordingly.
void info(const char* method, const std::string& msg) {
std::fprintf(stderr, "lez_indexer_module: %s: %s\n", method, msg.c_str());
}
void warn(const char* method, const std::string& msg) {
std::fprintf(stderr, "lez_indexer_module: Warning: %s: %s\n", method, msg.c_str());
}
void error(const char* method, const std::string& msg) {
std::fprintf(stderr, "lez_indexer_module: Error: %s: %s\n", method, msg.c_str());
}
} // namespace
LezIndexerModuleImpl::~LezIndexerModuleImpl() {
if (stop_indexer() != 0) {
warn("destructor", "indexer FFI error on stop");
error("destructor", "indexer FFI error on stop");
}
}
// === Indexer Lifecycle ===
int64_t LezIndexerModuleImpl::start_indexer(const std::string& config_path) {
if (!indexer_service_ffi) {
// Null runtime: the FFI creates and owns its own tokio runtime. Storage
// goes under this module's instance persistence path (host-owned, stable
// per Basecamp --user-dir) so RocksDB never lands in the process CWD.
InitializedIndexerServiceFFIResult res =
::start_indexer(nullptr, config_path.c_str(), instancePersistencePath().c_str());
if (is_error(&res.error)) {
warn("start_indexer", "indexer FFI error");
return static_cast<int64_t>(res.error);
}
std::string LezIndexerModuleImpl::resolveStorageDir(const char* method) const {
// The host owns where state lives (its instance persistence path). When that
// isn't provisioned (e.g. running outside Basecamp), fall back to the process
// working directory — the same "." the FFI's start_indexer uses — so start and
// reset always agree on the store location.
const std::string& path = instancePersistencePath();
if (!path.empty()) {
return path;
}
warn(method, "no instance persistence path; using the working directory");
return ".";
}
indexer_service_ffi = res.value;
int64_t LezIndexerModuleImpl::start_indexer(const std::string& config_path) {
if (indexer_service_ffi) {
info("start_indexer", "indexer already running; ignoring start request");
return 0;
}
// Null runtime: the FFI creates and owns its own tokio runtime.
const std::string storage = resolveStorageDir("start_indexer");
info("start_indexer", "starting indexer (config=" + config_path + ", storage=" + storage + ")");
InitializedIndexerServiceFFIResult res = ::start_indexer(nullptr, config_path.c_str(), storage.c_str());
if (is_error(&res.error)) {
error(
"start_indexer",
"FFI failed to start indexer (OperationStatus " + std::to_string(static_cast<int64_t>(res.error)) + ")"
);
return static_cast<int64_t>(res.error);
}
indexer_service_ffi = res.value;
info("start_indexer", "indexer started");
return 0;
}
@ -55,14 +86,60 @@ int64_t LezIndexerModuleImpl::stop_indexer() {
OperationStatus operation_result = ::stop_indexer(handle(indexer_service_ffi));
indexer_service_ffi = nullptr;
if (is_error(&operation_result)) {
warn("stop_indexer", "indexer FFI error on stop");
error(
"stop_indexer",
"FFI error on stop (OperationStatus " + std::to_string(static_cast<int64_t>(operation_result)) + ")"
);
return static_cast<int64_t>(operation_result);
}
info("stop_indexer", "indexer stopped");
return 0;
}
void LezIndexerModuleImpl::init_logger(const std::string& level) {
::init_logger(level.c_str());
int64_t LezIndexerModuleImpl::reset_storage(const std::string& config_path) {
// Stop first so RocksDB is closed before its files are deleted
const int64_t stop_code = stop_indexer();
if (stop_code != 0) {
error("reset_storage", "could not stop indexer before wiping storage");
return stop_code;
}
// FFI stores RocksDB at <storage>/rocksdb-{channel_id}
const std::filesystem::path storage = resolveStorageDir("reset_storage");
std::string channel_id;
try {
std::ifstream in(config_path);
if (!in) {
error("reset_storage", "could not open config " + config_path);
return -1;
}
channel_id = nlohmann::json::parse(in).at("channel_id").get<std::string>();
} catch (const std::exception& e) {
error("reset_storage", std::string("could not read channel_id from config: ") + e.what());
return -1;
}
// A channel id is a 32-byte hex string. Validate before building a path so a
// malformed/edited config can't inject separators ("../", absolute paths) and
// make remove_all escape the storage dir.
if (!isHex(channel_id, 64)) {
error("reset_storage", "config channel_id is not a 64-char hex string; refusing to build a wipe path");
return -1;
}
const std::filesystem::path store = storage / ("rocksdb-" + channel_id);
std::error_code ec;
// remove_all returns the number of entries removed and only sets `ec` on a real
// error; a missing store removes 0 with no error. No exists() pre-check — that
// would mask an IO/permission error as a successful "nothing to wipe".
const std::uintmax_t removed = std::filesystem::remove_all(store, ec);
if (ec) {
error("reset_storage", "failed to remove " + store.string() + ": " + ec.message());
return -1;
}
info("reset_storage",
removed == 0 ? "no store at " + store.string() + "; nothing to wipe"
: "wiped rocksdb store " + store.string());
return 0;
}
// === Indexer Queries ===

View File

@ -41,6 +41,16 @@ public:
/// own). Returns 0 on success, else the FFI OperationStatus code.
int64_t stop_indexer();
/// Stop the indexer (if running) and delete the RocksDB store for the config's
/// channel — `<storage>/rocksdb-<channel_id>`, where `<storage>` is resolved the
/// same way start_indexer resolves it (the host's instance persistence path, or
/// the process working directory when unset) — so the next start_indexer
/// re-indexes from scratch. The recovery path when the store is stale against a
/// different/reset chain. Pass the same `config_path` given to start_indexer;
/// does NOT restart. Returns 0 on success, else non-zero. int64_t for the same
/// codegen reason as start_indexer.
int64_t reset_storage(const std::string& config_path);
/// Account by id, accepting Base58 (canonical) or 32-byte hex. The returned
/// JSON omits the id; callers inject the queried id themselves.
std::string getAccount(const std::string& account_id);
@ -71,12 +81,12 @@ public:
std::string getTransactionsByAccount(const std::string& account_id, const std::string& offset, const std::string& limit);
// clang-format on
/// Enable the indexer's logging at `level` (off/error/warn/info/debug/trace;
/// null or unparseable falls back to info). Scoped to the indexer crates
/// only; the first call wins (subsequent calls are no-ops).
void init_logger(const std::string& level);
private:
// Storage dir handed to the FFI, either:
// - the host's instance persistence path
// - the process working directory (".") when the host didn't provision one
std::string resolveStorageDir(const char* method) const;
// IndexerServiceFFI* — opaque here (see class comment); cast in the .cpp.
void* indexer_service_ffi = nullptr;
};