mirror of
https://github.com/logos-blockchain/lez-indexer-module.git
synced 2026-07-29 14:45:57 +00:00
Merge pull request #16 from logos-blockchain/erhant/bump-lez-and-fix-logging
fix: better logging + bumped flakes
This commit is contained in:
commit
62c1af660f
10
README.md
10
README.md
@ -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)
|
||||
|
||||
|
||||
@ -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
12
flake.lock
generated
@ -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"
|
||||
}
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "lez_indexer_module",
|
||||
"display_name": "LEZ Indexer",
|
||||
"version": "1.0.0",
|
||||
"type": "core",
|
||||
"interface": "universal",
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 ===
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user