lez-indexer-module/src/lez_indexer_module_impl.cpp

276 lines
9.0 KiB
C++

#include "lez_indexer_module_impl.h"
#include "lez_ffi_marshalling.h"
#include "lez_indexer_ffi.h"
#include <cstdint>
#include <cstdio>
using namespace marshalling;
namespace {
// The impl header keeps the handle opaque (void*) so the universal codegen
// never needs the FFI types; recover the real type here.
inline IndexerServiceFFI* handle(void* p) {
return static_cast<IndexerServiceFFI*>(p);
}
void warn(const char* method, const char* msg) {
std::fprintf(stderr, "lez_indexer_module: %s: %s\n", method, msg);
}
} // namespace
LezIndexerModuleImpl::~LezIndexerModuleImpl() {
if (indexer_service_ffi) {
OperationStatus operation_result = stop_indexer(handle(indexer_service_ffi));
if (is_error(&operation_result)) {
warn("destructor", "indexer FFI error on stop");
}
indexer_service_ffi = nullptr;
}
}
// === Indexer Lifecycle ===
int64_t LezIndexerModuleImpl::start_indexer(const std::string& config_path, const std::string& port) {
if (!indexer_service_ffi) {
char* end = nullptr;
const unsigned long parsed = std::strtoul(port.c_str(), &end, 10);
if (end == port.c_str() || *end != '\0' || parsed > 0xFFFF) {
warn("start_indexer", "invalid port");
return -1;
}
const uint16_t port_num = static_cast<uint16_t>(parsed);
InitializedIndexerServiceFFIResult res = ::start_indexer(config_path.c_str(), port_num);
if (is_error(&res.error)) {
warn("start_indexer", "indexer FFI error");
return static_cast<int64_t>(res.error);
}
indexer_service_ffi = res.value;
}
return 0;
}
// === Indexer Queries ===
//
// Each method calls the matching query_* FFI function on the handle we already
// hold (no Runtime needed — the pinned FFI carries its own runtime inside the
// handle), marshals the returned C struct into compact JSON, then frees the
// FFI's deep allocations with the matching free_ffi_* function.
//
// Known leak: query_* returns its payload in a `Box::into_raw` *outer* box
// (PointerResult.value), and the free_ffi_* functions free only the inner
// boxes/vectors (they take the struct by value). The pinned FFI provides no
// free for the outer box, and libc free() would be UB if Rust's global
// allocator differs from the system one, so we deliberately leak the small
// (~8-32 byte) outer wrapper per query. Tracked for an upstream fix.
std::string LezIndexerModuleImpl::getAccount(const std::string& account_id) {
if (!indexer_service_ffi) {
warn("getAccount", "indexer not started");
return {};
}
FfiAccountId id;
if (!hexToBytes32(account_id, &id)) {
warn("getAccount", "invalid account id (need 32-byte hex)");
return {};
}
PointerResult_FfiAccount__OperationStatus res = ::query_account(handle(indexer_service_ffi), id);
if (is_error(&res.error) || !res.value) {
warn("getAccount", "indexer FFI error");
return {};
}
const std::string out = jsonToCompactString(ffiAccountToJson(*res.value));
::free_ffi_account(*res.value);
return out;
}
std::string LezIndexerModuleImpl::getBlockById(const std::string& block_id) {
if (!indexer_service_ffi) {
warn("getBlockById", "indexer not started");
return {};
}
char* end = nullptr;
const uint64_t id = std::strtoull(block_id.c_str(), &end, 10);
if (end == block_id.c_str() || *end != '\0') {
warn("getBlockById", "invalid block id");
return {};
}
PointerResult_FfiBlockOpt__OperationStatus res = ::query_block(handle(indexer_service_ffi), id);
if (is_error(&res.error) || !res.value) {
warn("getBlockById", "indexer FFI error");
return {};
}
std::string out;
if (res.value->is_some && res.value->value) {
out = jsonToCompactString(ffiBlockToJson(*res.value->value));
}
::free_ffi_block_opt(*res.value);
return out;
}
std::string LezIndexerModuleImpl::getBlockByHash(const std::string& hash) {
if (!indexer_service_ffi) {
warn("getBlockByHash", "indexer not started");
return {};
}
FfiHashType h;
if (!hexToBytes32(hash, &h)) {
warn("getBlockByHash", "invalid hash (need 32-byte hex)");
return {};
}
PointerResult_FfiBlockOpt__OperationStatus res = ::query_block_by_hash(handle(indexer_service_ffi), h);
if (is_error(&res.error) || !res.value) {
warn("getBlockByHash", "indexer FFI error");
return {};
}
std::string out;
if (res.value->is_some && res.value->value) {
out = jsonToCompactString(ffiBlockToJson(*res.value->value));
}
::free_ffi_block_opt(*res.value);
return out;
}
std::string LezIndexerModuleImpl::getTransaction(const std::string& hash) {
if (!indexer_service_ffi) {
warn("getTransaction", "indexer not started");
return {};
}
FfiHashType h;
if (!hexToBytes32(hash, &h)) {
warn("getTransaction", "invalid hash (need 32-byte hex)");
return {};
}
PointerResult_FfiOption_FfiTransaction_____OperationStatus res =
::query_transaction(handle(indexer_service_ffi), h);
if (is_error(&res.error) || !res.value) {
warn("getTransaction", "indexer FFI error");
return {};
}
std::string out;
if (res.value->is_some && res.value->value) {
out = jsonToCompactString(ffiTransactionToJson(*res.value->value));
}
::free_ffi_transaction_opt(*res.value);
return out;
}
std::string LezIndexerModuleImpl::getBlocks(const std::string& before, const std::string& limit) {
if (!indexer_service_ffi) {
warn("getBlocks", "indexer not started");
return {};
}
char* end = nullptr;
const uint64_t limitNum = std::strtoull(limit.c_str(), &end, 10);
if (end == limit.c_str() || *end != '\0') {
warn("getBlocks", "invalid limit");
return {};
}
// `before` is the optional pagination cursor: an empty string means "from
// the tip", a non-empty value that fails to parse is an error. `beforeVal`
// must outlive the call since FfiOption_u64 borrows its address.
uint64_t beforeVal = 0;
bool hasBefore = false;
if (!before.empty()) {
char* bend = nullptr;
beforeVal = std::strtoull(before.c_str(), &bend, 10);
if (bend == before.c_str() || *bend != '\0') {
warn("getBlocks", "invalid before");
return {};
}
hasBefore = true;
}
FfiOption_u64 beforeOpt;
beforeOpt.is_some = hasBefore;
beforeOpt.value = hasBefore ? &beforeVal : nullptr;
PointerResult_FfiVec_FfiBlock_____OperationStatus res =
::query_block_vec(handle(indexer_service_ffi), beforeOpt, limitNum);
if (is_error(&res.error) || !res.value) {
warn("getBlocks", "indexer FFI error");
return {};
}
nlohmann::json arr = nlohmann::json::array();
for (uintptr_t i = 0; i < res.value->len; ++i) {
arr.push_back(ffiBlockToJson(res.value->entries[i]));
}
::free_ffi_block_vec(*res.value);
return jsonToCompactString(arr);
}
std::string LezIndexerModuleImpl::getLastFinalizedBlockId() {
if (!indexer_service_ffi) {
warn("getLastFinalizedBlockId", "indexer not started");
return {};
}
PointerResult_u64__OperationStatus res = ::query_last_block(handle(indexer_service_ffi));
if (is_error(&res.error) || !res.value) {
warn("getLastFinalizedBlockId", "indexer FFI error");
return {};
}
// Bare decimal string; the FFI provides no free for the boxed u64 (leaked,
// see the note above).
return u64ToString(*res.value);
}
std::string LezIndexerModuleImpl::getTransactionsByAccount(
const std::string& account_id,
const std::string& offset,
const std::string& limit
) {
if (!indexer_service_ffi) {
warn("getTransactionsByAccount", "indexer not started");
return {};
}
FfiAccountId id;
if (!hexToBytes32(account_id, &id)) {
warn("getTransactionsByAccount", "invalid account id (need 32-byte hex)");
return {};
}
char* offEnd = nullptr;
char* limEnd = nullptr;
const uint64_t offsetNum = std::strtoull(offset.c_str(), &offEnd, 10);
const uint64_t limitNum = std::strtoull(limit.c_str(), &limEnd, 10);
if (offEnd == offset.c_str() || *offEnd != '\0' || limEnd == limit.c_str() || *limEnd != '\0') {
warn("getTransactionsByAccount", "invalid offset/limit");
return {};
}
PointerResult_FfiVec_FfiTransaction_____OperationStatus res =
::query_transactions_by_account(handle(indexer_service_ffi), id, offsetNum, limitNum);
if (is_error(&res.error) || !res.value) {
warn("getTransactionsByAccount", "indexer FFI error");
return {};
}
nlohmann::json arr = nlohmann::json::array();
for (uintptr_t i = 0; i < res.value->len; ++i) {
arr.push_back(ffiTransactionToJson(res.value->entries[i]));
}
::free_ffi_transaction_vec(*res.value);
return jsonToCompactString(arr);
}