diff --git a/.gitignore b/.gitignore index cabf2a7..a2898d6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,9 @@ result # Node resources/ + +# RocksDB +rocksdb + +# Other +.DS_Store \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index b878dfe..4b609f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.20) -project(logos_execution_zone_indexer_module LANGUAGES CXX) +project(LEZIndexerModule LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -151,7 +151,7 @@ if(WIN32) endif() add_library(logos_core STATIC IMPORTED - src/i_logos_execution_zone_indexer_module.h) + src/i_lez_indexer_module.h) set_target_properties(logos_core PROPERTIES IMPORTED_LOCATION "${SDK_LIB}" ) @@ -160,17 +160,28 @@ add_library(logos_cpp_sdk INTERFACE) target_include_directories(logos_cpp_sdk INTERFACE "${SDK_INC}") # ---- Plugin ---- -set(PLUGIN_TARGET logos_execution_zone_indexer_module) +set(PLUGIN_TARGET lez_indexer_module) -qt_add_plugin(${PLUGIN_TARGET} CLASS_NAME LogosExecutionZoneIndexerModule) +qt_add_plugin(${PLUGIN_TARGET} CLASS_NAME LezIndexerModule) + +# A consumer (module-viewer / basecamp) derives the QtRO object name it INVOKES +# from the produced library's FILENAME, whereas logos_host REGISTERS the plugin +# under metadata.json `name`. They must be identical, and short — macOS caps the +# QtRO `local:` socket path (sun_path) at 104 bytes, so a long name makes the host +# fail to listen. `PREFIX ""` drops the `lib` prefix so the file is exactly +# `lez_indexer_module.` == metadata `name` == `main` == name(). +set_target_properties(${PLUGIN_TARGET} PROPERTIES + OUTPUT_NAME lez_indexer_module + PREFIX "" +) target_sources(${PLUGIN_TARGET} PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src/logos_execution_zone_indexer_module.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/lez_indexer_module.cpp ) set_property(TARGET ${PLUGIN_TARGET} PROPERTY PUBLIC_HEADER - ${CMAKE_CURRENT_SOURCE_DIR}/src/i_logos_execution_zone_indexer_module.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/logos_execution_zone_indexer_module.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/i_lez_indexer_module.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/lez_indexer_module.h ) target_include_directories(${PLUGIN_TARGET} PRIVATE @@ -188,7 +199,7 @@ target_link_libraries(${PLUGIN_TARGET} PRIVATE ) target_compile_definitions(${PLUGIN_TARGET} PRIVATE - LOGOS_EXECUTION_ZONE_INDEXER_MODULE_METADATA_FILE="${CMAKE_CURRENT_SOURCE_DIR}/metadata.json" + LEZ_INDEXER_MODULE_METADATA_FILE="${CMAKE_CURRENT_SOURCE_DIR}/metadata.json" ) add_dependencies(${PLUGIN_TARGET} logos_execution_zone_libs) diff --git a/README.md b/README.md index 33b6715..7e0e0e2 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,56 @@ -# Logos Blockchain Indexer Module +# Logos Execution Zone Indexer Module -### Setup +A Logos Core **service module** (`type: core`) that runs the Logos Execution Zone (L2) indexer and exposes it to the Logos ecosystem. It is a thin Qt plugin around the `indexer_ffi` library from +[`logos-execution-zone`](https://github.com/logos-blockchain/logos-execution-zone): it starts the indexer, which connects to an L1/bedrock node, indexes the zone's channel, and serves queries over an RPC (WebSocket) server. -#### IDE +Registered module name: **`lez_indexer_module`**. It pairs with the +[`lez-explorer-ui`](https://github.com/logos-co/lez-explorer-ui) block explorer, which connects to the indexer's RPC endpoint. + +> [!TIP] +> +> **Keep the module name short.** `logos_host` derives a Qt Remote Objects `local:` socket from it (`local:logos__`), and Unix socket path names are limited in at most 108 bytes (see [man unix](https://man7.org/linux/man-pages/man7/unix.7.html)). The build emits the library with no `lib` prefix so its filename equals the registered name (`lez_indexer_module`) - consumers derive the QtRO invoke target from the filename, so the two must stay identical. + +## Setup + +### IDE If you're using an IDE with CMake integration make sure it points to the same cmake directory as the `justfile`, which defaults to `build`. This will reduce friction when working on the project. -#### Nix +### Nix -* Use `nix flake update` to bring all nix context and packages -* Use `nix build` to build the package -* Use `nix run` to launch the module-viewer and check your module loads properly -* Use `nix develop` to setup your IDE +- Use `nix flake update` to bring all nix context and packages +- Use `nix build` to build the package (produces `lez_indexer_module.`) +- Use `nix run` to launch the module-viewer and check your module loads properly +- Use `nix develop` to setup your IDE + +## Usage + +The module does **not** start the indexer on load - something must invoke its `start_indexer` method (via the module-viewer's invoke panel, or another module / basecamp over the Logos API): + +```c +start_indexer(config_path, port) +``` + +- `config_path` — **absolute** path to a JSON config (see + [`config/indexer_config.json`](config/indexer_config.json)). It must be absolute: the module runs inside the `logos_host` subprocess, whose working directory is not your shell's. +- `port` — TCP port for the RPC server, e.g. `8779`. Passed as a **string** (see the macOS / Qt notes below). + +On success it returns `0` and the RPC server listens on `ws://localhost:`; point the explorer (or any client) there. + +> [!CAUTION] +> +> A non-zero return is the FFI `OperationStatus` (e.g. `2 = InitializationError`) — note the FFI does not log, so the numeric code is all you get. + +### Configuration + +`config/indexer_config.json` is deserialized into the indexer's `IndexerConfig`. Key fields: + +| Field | Meaning | Default | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `bedrock_config.addr` | L1/bedrock node URL the indexer reads from; it must be reachable. | `http://localhost:8080` | +| `home` | Directory for the indexer's RocksDB state, relative to the host's working directory. Use an absolute path for a predictable location. | `"."` | +| `channel_id` | The zone channel the indexer consumes; must match what the sequencer inscribes. | | + +The config keys must match the `IndexerConfig` schema of the `logos-execution-zone` rev pinned in `flake.nix`/`flake.lock`; bumping that rev may require re-syncing this file. Unknown keys are ignored. diff --git a/config/indexer_config.json b/config/indexer_config.json index e4dd8f9..a8ff5cb 100644 --- a/config/indexer_config.json +++ b/config/indexer_config.json @@ -1,7 +1,7 @@ { "home": ".", "consensus_info_polling_interval": "1s", - "bedrock_client_config": { + "bedrock_config": { "addr": "http://localhost:8080", "backoff": { "start_delay": "100ms", diff --git a/flake.nix b/flake.nix index 7888a70..8554cba 100644 --- a/flake.nix +++ b/flake.nix @@ -45,8 +45,8 @@ logosCore = logos-core.packages.${system}.default; logosExecutionZoneIndexerPackage = logos-execution-zone.packages.${system}.indexer; - logosExecutionZoneIndexerModulePackage = pkgs.stdenv.mkDerivation { - pname = "logos-execution-zone-module"; + lezIndexerModulePackage = pkgs.stdenv.mkDerivation { + pname = "lez-indexer-module"; version = "dev"; src = ./.; @@ -83,8 +83,8 @@ }; in { - lib = logosExecutionZoneIndexerModulePackage; - default = logosExecutionZoneIndexerModulePackage; + lib = lezIndexerModulePackage; + default = lezIndexerModulePackage; } ); @@ -92,7 +92,7 @@ system: let pkgs = mkPkgs system; - logosExecutionZoneIndexerModuleLib = self.packages.${system}.lib; + lezIndexerModuleLib = self.packages.${system}.lib; logosModuleViewerPackage = logos-module-viewer.packages.${system}.default; extension = if pkgs.stdenv.isDarwin then "dylib" else if pkgs.stdenv.hostPlatform.isWindows then "dll" @@ -102,7 +102,7 @@ program = "${pkgs.writeShellScriptBin "inspect-module" '' exec ${logosModuleViewerPackage}/bin/logos-module-viewer \ - --module ${logosExecutionZoneIndexerModuleLib}/lib/liblogos_execution_zone_indexer_module.${extension} + --module ${lezIndexerModuleLib}/lib/lez_indexer_module.${extension} ''}/bin/inspect-module"; }; in diff --git a/justfile b/justfile index 07c1165..536d3dc 100644 --- a/justfile +++ b/justfile @@ -7,7 +7,7 @@ configure: ${LOGOS_EXECUTION_ZONE_INDEXER_INCLUDE:+-DLOGOS_EXECUTION_ZONE_INDEXER_INCLUDE="$LOGOS_EXECUTION_ZONE_INDEXER_INCLUDE"} build: configure - cmake --build build --parallel --target logos_execution_zone_indexer_module + cmake --build build --parallel --target lez_indexer_module clean: rm -rf build result diff --git a/metadata.json b/metadata.json index a63b7c6..239530f 100644 --- a/metadata.json +++ b/metadata.json @@ -5,7 +5,7 @@ "author": "Logos Blockchain Team", "type": "core", "category": "blockchain", - "main": "liblogos_execution_zone_indexer_module", + "main": "lez_indexer_module", "dependencies": [], "capabilities": [], "include": ["libnomos.dylib"] diff --git a/src/i_lez_indexer_module.h b/src/i_lez_indexer_module.h new file mode 100644 index 0000000..eb716fc --- /dev/null +++ b/src/i_lez_indexer_module.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +class ILezIndexerModule { +public: + virtual ~ILezIndexerModule() = default; + + // === Logos Core === + + virtual void initLogos(LogosAPI* logosApiInstance) = 0; + + // === Logos Execution Zone Indexer === + + // Indexer Lifecycle + // `port` is taken as a QString (not uint16_t) because the Qt Remote Objects + // ModuleProxy invokes by exact meta-type match and delivers caller arguments + // as QString (the module-viewer reads every parameter as text); a uint16_t + // parameter would never match. It is parsed to a port number internally. + virtual int start_indexer( + const QString& config_path, + const QString& port + ) = 0; +}; + +#define ILezIndexerModule_iid "org.logos.ilezindexermodule" +Q_DECLARE_INTERFACE(ILezIndexerModule, ILezIndexerModule_iid) diff --git a/src/i_logos_execution_zone_indexer_module.h b/src/i_logos_execution_zone_indexer_module.h deleted file mode 100644 index a75ea59..0000000 --- a/src/i_logos_execution_zone_indexer_module.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - -class ILogosExecutionZoneIndexerModule { -public: - virtual ~ILogosExecutionZoneIndexerModule() = default; - - // === Logos Core === - - virtual void initLogos(LogosAPI* logosApiInstance) = 0; - - // === Logos Execution Zone Indexer === - - // Indexer Lifecycle - virtual int start_indexer( - const QString& config_path, - uint16_t port - ) = 0; -}; - -#define ILogosExecutionZoneIndexerModule_iid "org.logos.ilogosexecutionzoneindexermodule" -Q_DECLARE_INTERFACE(ILogosExecutionZoneIndexerModule, ILogosExecutionZoneIndexerModule_iid) diff --git a/src/logos_execution_zone_indexer_module.cpp b/src/lez_indexer_module.cpp similarity index 66% rename from src/logos_execution_zone_indexer_module.cpp rename to src/lez_indexer_module.cpp index 8fa6ae3..1a5a33c 100644 --- a/src/logos_execution_zone_indexer_module.cpp +++ b/src/lez_indexer_module.cpp @@ -1,4 +1,4 @@ -#include "logos_execution_zone_indexer_module.h" +#include "lez_indexer_module.h" #include #include @@ -7,9 +7,9 @@ #include #include -LogosExecutionZoneIndexerModule::LogosExecutionZoneIndexerModule() = default; +LezIndexerModule::LezIndexerModule() = default; -LogosExecutionZoneIndexerModule::~LogosExecutionZoneIndexerModule() { +LezIndexerModule::~LezIndexerModule() { if (indexer_service_ffi) { OperationStatus operation_result = stop_indexer(indexer_service_ffi); @@ -24,28 +24,35 @@ LogosExecutionZoneIndexerModule::~LogosExecutionZoneIndexerModule() { // === Plugin Interface === -QString LogosExecutionZoneIndexerModule::name() const { +QString LezIndexerModule::name() const { return "lez_indexer_module"; } -QString LogosExecutionZoneIndexerModule::version() const { +QString LezIndexerModule::version() const { return "1.0.0"; } // === Logos Core === -void LogosExecutionZoneIndexerModule::initLogos(LogosAPI* logosApiInstance) { +void LezIndexerModule::initLogos(LogosAPI* logosApiInstance) { logosAPI = logosApiInstance; } // === Indexer Lifecycle === -int LogosExecutionZoneIndexerModule::start_indexer(const QString& config_path, uint16_t port) { +int LezIndexerModule::start_indexer(const QString& config_path, const QString& port) { if (!indexer_service_ffi) { + bool ok = false; + const uint16_t port_num = port.toUShort(&ok); + if (!ok) { + qWarning() << "start_indexer: invalid port:" << port; + return -1; + } + QByteArray utf8 = config_path.toUtf8(); const char* c_path = utf8.constData(); - InitializedIndexerServiceFFIResult indexer_service_ffi_res = ::start_indexer(c_path, port); + InitializedIndexerServiceFFIResult indexer_service_ffi_res = ::start_indexer(c_path, port_num); if (is_error(&indexer_service_ffi_res.error)) { int signal = indexer_service_ffi_res.error; diff --git a/src/logos_execution_zone_indexer_module.h b/src/lez_indexer_module.h similarity index 56% rename from src/logos_execution_zone_indexer_module.h rename to src/lez_indexer_module.h index 44f252f..23dde79 100644 --- a/src/logos_execution_zone_indexer_module.h +++ b/src/lez_indexer_module.h @@ -1,6 +1,6 @@ #pragma once -#include "i_logos_execution_zone_indexer_module.h" +#include "i_lez_indexer_module.h" #ifdef __cplusplus extern "C" { @@ -15,18 +15,18 @@ extern "C" { #include #include -class LogosExecutionZoneIndexerModule : public QObject, public PluginInterface, public ILogosExecutionZoneIndexerModule { +class LezIndexerModule : public QObject, public PluginInterface, public ILezIndexerModule { Q_OBJECT - Q_PLUGIN_METADATA(IID ILogosExecutionZoneIndexerModule_iid FILE LOGOS_EXECUTION_ZONE_INDEXER_MODULE_METADATA_FILE) - Q_INTERFACES(PluginInterface ILogosExecutionZoneIndexerModule) + Q_PLUGIN_METADATA(IID ILezIndexerModule_iid FILE LEZ_INDEXER_MODULE_METADATA_FILE) + Q_INTERFACES(PluginInterface ILezIndexerModule) private: LogosAPI* logosApi = nullptr; IndexerServiceFFI* indexer_service_ffi = nullptr; public: - LogosExecutionZoneIndexerModule(); - ~LogosExecutionZoneIndexerModule() override; + LezIndexerModule(); + ~LezIndexerModule() override; // === Plugin Interface === [[nodiscard]] QString name() const override; @@ -39,8 +39,8 @@ public: // === Logos Execution Zone Indexer === // Indexer Lifecycle - Q_INVOKABLE int start_indexer(const QString& config_path, uint16_t port) override; - + Q_INVOKABLE int start_indexer(const QString& config_path, const QString& port) override; + signals: void eventResponse(const QString& eventName, const QVariantList& data); };