mirror of
https://github.com/logos-blockchain/lez-indexer-module.git
synced 2026-07-30 07:03:31 +00:00
Merge pull request #8 from logos-blockchain/erhant/prep-for-logos-protocol
feat: add methods to indexer module to favor logos-protocol
This commit is contained in:
commit
ffa4e2e8b7
@ -177,6 +177,7 @@ set_target_properties(${PLUGIN_TARGET} PROPERTIES
|
|||||||
|
|
||||||
target_sources(${PLUGIN_TARGET} PRIVATE
|
target_sources(${PLUGIN_TARGET} PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/lez_indexer_module.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/lez_indexer_module.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/lez_ffi_marshalling.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET ${PLUGIN_TARGET} PROPERTY PUBLIC_HEADER
|
set_property(TARGET ${PLUGIN_TARGET} PROPERTY PUBLIC_HEADER
|
||||||
|
|||||||
@ -17,9 +17,34 @@ public:
|
|||||||
// ModuleProxy invokes by exact meta-type match and delivers caller arguments
|
// ModuleProxy invokes by exact meta-type match and delivers caller arguments
|
||||||
// as QString (the module-viewer reads every parameter as text); a uint16_t
|
// 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.
|
// parameter would never match. It is parsed to a port number internally.
|
||||||
virtual int start_indexer(
|
virtual int start_indexer(const QString& config_path, const QString& port) = 0;
|
||||||
const QString& config_path,
|
|
||||||
const QString& port
|
// Indexer Queries
|
||||||
|
//
|
||||||
|
// Every parameter is a QString for the same QtRO exact-type-match reason as
|
||||||
|
// `start_indexer` above: callers (consumer modules, the module-viewer) deliver
|
||||||
|
// arguments as QString, so numeric/hash params are passed as text and parsed
|
||||||
|
// internally. Each method returns a compact JSON string (the Logos protocol
|
||||||
|
// data model is JSON-in-strings); an empty string means "not found" or a
|
||||||
|
// failed query (the FFI does not log, so the body qWarns the status code).
|
||||||
|
//
|
||||||
|
// The JSON shape is tailored to what the LEZ explorer's Block/Transaction/
|
||||||
|
// Account models consume (counts/sizes rather than raw arrays). Large 64-bit
|
||||||
|
// values (ids, timestamps, validity windows) are emitted as decimal STRINGS
|
||||||
|
// to avoid the double-precision loss of JSON numbers; counts/sizes are numbers.
|
||||||
|
virtual QString getAccount(const QString& account_id) = 0;
|
||||||
|
virtual QString getBlockById(const QString& block_id) = 0;
|
||||||
|
virtual QString getBlockByHash(const QString& hash) = 0;
|
||||||
|
virtual QString getTransaction(const QString& hash) = 0;
|
||||||
|
// `before` is the optional pagination cursor: a block id to page back from,
|
||||||
|
// or an empty string to start from the tip. `limit` caps the result count.
|
||||||
|
virtual QString getBlocks(const QString& before, const QString& limit) = 0;
|
||||||
|
virtual QString getLastFinalizedBlockId() = 0;
|
||||||
|
// `offset` and `limit` are both required (the paging window).
|
||||||
|
virtual QString getTransactionsByAccount(
|
||||||
|
const QString& account_id,
|
||||||
|
const QString& offset,
|
||||||
|
const QString& limit
|
||||||
) = 0;
|
) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
190
src/lez_ffi_marshalling.cpp
Normal file
190
src/lez_ffi_marshalling.cpp
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
#include "lez_ffi_marshalling.h"
|
||||||
|
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace marshalling {
|
||||||
|
|
||||||
|
// Lower-case hex of `length` raw bytes (used for 32-byte hashes/ids/keys and
|
||||||
|
// 64-byte signatures). Qt's toHex() avoids a hand-rolled nibble loop.
|
||||||
|
QString bytesToHex(const uint8_t* data, const size_t length) {
|
||||||
|
return QString::fromLatin1(QByteArray(reinterpret_cast<const char*>(data), static_cast<int>(length)).toHex());
|
||||||
|
}
|
||||||
|
|
||||||
|
// FfiU128 is a 16-byte little-endian integer (balances, nonces). C++ has no
|
||||||
|
// native u128, so build the decimal string via __uint128_t (GCC/Clang, 64-bit).
|
||||||
|
QString u128LeToDecimal(const uint8_t data[16]) {
|
||||||
|
#if defined(__SIZEOF_INT128__) && __SIZEOF_INT128__ >= 16
|
||||||
|
__uint128_t v = 0;
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
v |= static_cast<__uint128_t>(data[i]) << (i * 8);
|
||||||
|
}
|
||||||
|
if (v == 0) {
|
||||||
|
return QStringLiteral("0");
|
||||||
|
}
|
||||||
|
char buf[40];
|
||||||
|
int n = 0;
|
||||||
|
while (v) {
|
||||||
|
buf[n++] = static_cast<char>('0' + static_cast<int>(v % 10));
|
||||||
|
v /= 10;
|
||||||
|
}
|
||||||
|
std::reverse(buf, buf + n);
|
||||||
|
return QString::fromLatin1(buf, n);
|
||||||
|
#else
|
||||||
|
#error "u128LeToDecimal requires __uint128_t; build with GCC or Clang on 64-bit"
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// 64-bit values are emitted as decimal STRINGS, not JSON numbers: QJsonValue
|
||||||
|
// stores numbers as double, which silently loses precision above 2^53.
|
||||||
|
QString u64ToString(uint64_t v) {
|
||||||
|
return QString::number(static_cast<qulonglong>(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a hex string (optionally 0x-prefixed) into a fixed 32-byte FfiBytes32.
|
||||||
|
// Returns false unless it decodes to exactly 32 bytes.
|
||||||
|
bool hexToBytes32(const QString& hex, FfiBytes32* out) {
|
||||||
|
QString trimmed = hex.trimmed();
|
||||||
|
if (trimmed.startsWith(QLatin1String("0x")) || trimmed.startsWith(QLatin1String("0X"))) {
|
||||||
|
trimmed = trimmed.mid(2);
|
||||||
|
}
|
||||||
|
const QByteArray raw = QByteArray::fromHex(trimmed.toLatin1());
|
||||||
|
if (raw.size() != 32) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::memcpy(out->data, raw.constData(), 32);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject ffiAccountToJson(const FfiAccount& account) {
|
||||||
|
QJsonObject obj;
|
||||||
|
obj[QStringLiteral("program_owner")] =
|
||||||
|
bytesToHex(reinterpret_cast<const uint8_t*>(account.program_owner.data), 32);
|
||||||
|
obj[QStringLiteral("balance")] = u128LeToDecimal(account.balance.data);
|
||||||
|
obj[QStringLiteral("nonce")] = u128LeToDecimal(account.nonce.data);
|
||||||
|
obj[QStringLiteral("data_size")] = static_cast<int>(account.data_len);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject ffiTransactionToJson(const FfiTransaction& tx) {
|
||||||
|
QJsonObject obj;
|
||||||
|
|
||||||
|
switch (tx.kind) {
|
||||||
|
case Public: {
|
||||||
|
const FfiPublicTransactionBody* body = tx.body.public_body;
|
||||||
|
if (!body) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
obj[QStringLiteral("type")] = QStringLiteral("Public");
|
||||||
|
obj[QStringLiteral("hash")] = bytesToHex(body->hash.data, 32);
|
||||||
|
obj[QStringLiteral("program_id")] =
|
||||||
|
bytesToHex(reinterpret_cast<const uint8_t*>(body->message.program_id.data), 32);
|
||||||
|
|
||||||
|
QJsonArray accounts;
|
||||||
|
const FfiAccountIdList& ids = body->message.account_ids;
|
||||||
|
const FfiNonceList& nonces = body->message.nonces;
|
||||||
|
for (uintptr_t i = 0; i < ids.len; ++i) {
|
||||||
|
QJsonObject ref;
|
||||||
|
ref[QStringLiteral("account_id")] = bytesToHex(ids.entries[i].data, 32);
|
||||||
|
ref[QStringLiteral("nonce")] =
|
||||||
|
i < nonces.len ? u128LeToDecimal(nonces.entries[i].data) : QStringLiteral("0");
|
||||||
|
accounts.append(ref);
|
||||||
|
}
|
||||||
|
obj[QStringLiteral("accounts")] = accounts;
|
||||||
|
|
||||||
|
QJsonArray instructionData;
|
||||||
|
const FfiInstructionDataList& instr = body->message.instruction_data;
|
||||||
|
for (uintptr_t i = 0; i < instr.len; ++i) {
|
||||||
|
instructionData.append(static_cast<double>(instr.entries[i]));
|
||||||
|
}
|
||||||
|
obj[QStringLiteral("instruction_data")] = instructionData;
|
||||||
|
obj[QStringLiteral("signature_count")] = static_cast<int>(body->witness_set.len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Private: {
|
||||||
|
const FfiPrivateTransactionBody* body = tx.body.private_body;
|
||||||
|
if (!body) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
obj[QStringLiteral("type")] = QStringLiteral("PrivacyPreserving");
|
||||||
|
obj[QStringLiteral("hash")] = bytesToHex(body->hash.data, 32);
|
||||||
|
|
||||||
|
QJsonArray accounts;
|
||||||
|
const FfiAccountIdList& ids = body->message.public_account_ids;
|
||||||
|
const FfiNonceList& nonces = body->message.nonces;
|
||||||
|
for (uintptr_t i = 0; i < ids.len; ++i) {
|
||||||
|
QJsonObject ref;
|
||||||
|
ref[QStringLiteral("account_id")] = bytesToHex(ids.entries[i].data, 32);
|
||||||
|
ref[QStringLiteral("nonce")] =
|
||||||
|
i < nonces.len ? u128LeToDecimal(nonces.entries[i].data) : QStringLiteral("0");
|
||||||
|
accounts.append(ref);
|
||||||
|
}
|
||||||
|
obj[QStringLiteral("accounts")] = accounts;
|
||||||
|
|
||||||
|
obj[QStringLiteral("new_commitments_count")] = static_cast<int>(body->message.new_commitments.len);
|
||||||
|
obj[QStringLiteral("nullifiers_count")] = static_cast<int>(body->message.new_nullifiers.len);
|
||||||
|
obj[QStringLiteral("encrypted_states_count")] =
|
||||||
|
static_cast<int>(body->message.encrypted_private_post_states.len);
|
||||||
|
obj[QStringLiteral("validity_window_start")] = u64ToString(body->message.block_validity_window[0]);
|
||||||
|
obj[QStringLiteral("validity_window_end")] = u64ToString(body->message.block_validity_window[1]);
|
||||||
|
obj[QStringLiteral("signature_count")] = static_cast<int>(body->witness_set.len);
|
||||||
|
obj[QStringLiteral("proof_size")] = static_cast<int>(body->proof.len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ProgramDeploy: {
|
||||||
|
const FfiProgramDeploymentTransactionBody* body = tx.body.program_deployment_body;
|
||||||
|
if (!body) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
obj[QStringLiteral("type")] = QStringLiteral("ProgramDeployment");
|
||||||
|
obj[QStringLiteral("hash")] = bytesToHex(body->hash.data, 32);
|
||||||
|
obj[QStringLiteral("bytecode_size")] = static_cast<int>(body->message.len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QString bedrockStatusToString(FfiBedrockStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case Safe:
|
||||||
|
return QStringLiteral("Safe");
|
||||||
|
case Finalized:
|
||||||
|
return QStringLiteral("Finalized");
|
||||||
|
case Pending:
|
||||||
|
default:
|
||||||
|
return QStringLiteral("Pending");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
QJsonObject ffiBlockToJson(const FfiBlock& block) {
|
||||||
|
QJsonObject obj;
|
||||||
|
obj[QStringLiteral("block_id")] = u64ToString(block.header.block_id);
|
||||||
|
obj[QStringLiteral("hash")] = bytesToHex(block.header.hash.data, 32);
|
||||||
|
obj[QStringLiteral("prev_block_hash")] = bytesToHex(block.header.prev_block_hash.data, 32);
|
||||||
|
obj[QStringLiteral("timestamp")] = u64ToString(block.header.timestamp);
|
||||||
|
obj[QStringLiteral("signature")] = bytesToHex(block.header.signature.data, 64);
|
||||||
|
obj[QStringLiteral("bedrock_status")] = bedrockStatusToString(block.bedrock_status);
|
||||||
|
|
||||||
|
QJsonArray transactions;
|
||||||
|
for (uintptr_t i = 0; i < block.body.len; ++i) {
|
||||||
|
transactions.append(ffiTransactionToJson(block.body.entries[i]));
|
||||||
|
}
|
||||||
|
obj[QStringLiteral("transactions")] = transactions;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString jsonToCompactString(const QJsonObject& obj) {
|
||||||
|
return QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString jsonToCompactString(const QJsonArray& arr) {
|
||||||
|
return QString::fromUtf8(QJsonDocument(arr).toJson(QJsonDocument::Compact));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace marshalling
|
||||||
48
src/lez_ffi_marshalling.h
Normal file
48
src/lez_ffi_marshalling.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lez_indexer_ffi.h"
|
||||||
|
|
||||||
|
#include <QtCore/QJsonArray>
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
|
||||||
|
// FFI marshalling helpers
|
||||||
|
//
|
||||||
|
// Bridge the raw C structs returned by the indexer FFI (indexer_ffi.h) to the
|
||||||
|
// compact JSON the LEZ explorer's Block/Transaction/Account models consume, and
|
||||||
|
// parse the QString inputs back into FFI types. Kept in their own translation
|
||||||
|
// unit so lez_indexer_module.cpp stays focused on the query flow and the JSON
|
||||||
|
// data model lives in one place — which is also what makes it easy to share
|
||||||
|
// with other modules later.
|
||||||
|
//
|
||||||
|
// These never take ownership of FFI memory: the caller frees it with the
|
||||||
|
// matching free_ffi_* function AFTER marshalling.
|
||||||
|
//
|
||||||
|
// Large 64-bit values (ids, timestamps, validity windows) and 128-bit values
|
||||||
|
// (balances, nonces) are emitted as decimal STRINGS to avoid the double-
|
||||||
|
// precision loss of JSON numbers; counts/sizes are emitted as numbers.
|
||||||
|
namespace marshalling {
|
||||||
|
|
||||||
|
// Lower-case hex of `length` raw bytes (32-byte hashes/ids/keys, 64-byte
|
||||||
|
// signatures).
|
||||||
|
QString bytesToHex(const uint8_t* data, size_t length);
|
||||||
|
|
||||||
|
// FfiU128 is a 16-byte little-endian integer (balances, nonces); rendered
|
||||||
|
// as a decimal string.
|
||||||
|
QString u128LeToDecimal(const uint8_t data[16]);
|
||||||
|
|
||||||
|
// 64-bit value as a decimal string.
|
||||||
|
QString u64ToString(uint64_t v);
|
||||||
|
|
||||||
|
// Parse a hex string (optionally 0x-prefixed) into a fixed 32-byte buffer.
|
||||||
|
// Returns false unless it decodes to exactly 32 bytes.
|
||||||
|
bool hexToBytes32(const QString& hex, FfiBytes32* out);
|
||||||
|
|
||||||
|
QJsonObject ffiAccountToJson(const FfiAccount& account);
|
||||||
|
QJsonObject ffiTransactionToJson(const FfiTransaction& tx);
|
||||||
|
QJsonObject ffiBlockToJson(const FfiBlock& block);
|
||||||
|
|
||||||
|
QString jsonToCompactString(const QJsonObject& obj);
|
||||||
|
QString jsonToCompactString(const QJsonArray& arr);
|
||||||
|
|
||||||
|
} // namespace marshalling
|
||||||
14
src/lez_indexer_ffi.h
Normal file
14
src/lez_indexer_ffi.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// The generated indexer_ffi.h carries no include guard, so including it from
|
||||||
|
// more than one header in the same translation unit redefines every type.
|
||||||
|
// Wrap it once here (behind #pragma once) and include THIS header instead of
|
||||||
|
// <indexer_ffi.h> directly, so the FFI types can be shared by both
|
||||||
|
// lez_indexer_module.h and lez_ffi_marshalling.h.
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
#include <indexer_ffi.h>
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -1,12 +1,15 @@
|
|||||||
#include "lez_indexer_module.h"
|
#include "lez_indexer_module.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include "lez_ffi_marshalling.h"
|
||||||
|
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
#include <QtCore/QJsonArray>
|
#include <QtCore/QJsonArray>
|
||||||
#include <QtCore/QJsonDocument>
|
#include <QtCore/QString>
|
||||||
#include <QtCore/QJsonObject>
|
|
||||||
#include <QtCore/QVariantMap>
|
#include <QtCore/QVariantMap>
|
||||||
|
|
||||||
|
using namespace marshalling;
|
||||||
|
|
||||||
LezIndexerModule::LezIndexerModule() = default;
|
LezIndexerModule::LezIndexerModule() = default;
|
||||||
|
|
||||||
LezIndexerModule::~LezIndexerModule() {
|
LezIndexerModule::~LezIndexerModule() {
|
||||||
@ -66,3 +69,220 @@ int LezIndexerModule::start_indexer(const QString& config_path, const QString& p
|
|||||||
|
|
||||||
return 0;
|
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 (add a
|
||||||
|
// free_*_result, or switch the query API to out-params).
|
||||||
|
|
||||||
|
QString LezIndexerModule::getAccount(const QString& account_id) {
|
||||||
|
if (!indexer_service_ffi) {
|
||||||
|
qWarning() << "getAccount: indexer not started";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
FfiAccountId id;
|
||||||
|
if (!hexToBytes32(account_id, &id)) {
|
||||||
|
qWarning() << "getAccount: invalid account id (need 32-byte hex):" << account_id;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerResult_FfiAccount__OperationStatus res = ::query_account(indexer_service_ffi, id);
|
||||||
|
if (is_error(&res.error) || !res.value) {
|
||||||
|
qWarning() << "getAccount: indexer FFI error:" << res.error;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString out = jsonToCompactString(ffiAccountToJson(*res.value));
|
||||||
|
::free_ffi_account(*res.value);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LezIndexerModule::getBlockById(const QString& block_id) {
|
||||||
|
if (!indexer_service_ffi) {
|
||||||
|
qWarning() << "getBlockById: indexer not started";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
const uint64_t id = block_id.toULongLong(&ok);
|
||||||
|
if (!ok) {
|
||||||
|
qWarning() << "getBlockById: invalid block id:" << block_id;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerResult_FfiBlockOpt__OperationStatus res = ::query_block(indexer_service_ffi, id);
|
||||||
|
if (is_error(&res.error) || !res.value) {
|
||||||
|
qWarning() << "getBlockById: indexer FFI error:" << res.error;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString out;
|
||||||
|
if (res.value->is_some && res.value->value) {
|
||||||
|
out = jsonToCompactString(ffiBlockToJson(*res.value->value));
|
||||||
|
}
|
||||||
|
::free_ffi_block_opt(*res.value);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LezIndexerModule::getBlockByHash(const QString& hash) {
|
||||||
|
if (!indexer_service_ffi) {
|
||||||
|
qWarning() << "getBlockByHash: indexer not started";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
FfiHashType h;
|
||||||
|
if (!hexToBytes32(hash, &h)) {
|
||||||
|
qWarning() << "getBlockByHash: invalid hash (need 32-byte hex):" << hash;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerResult_FfiBlockOpt__OperationStatus res = ::query_block_by_hash(indexer_service_ffi, h);
|
||||||
|
if (is_error(&res.error) || !res.value) {
|
||||||
|
qWarning() << "getBlockByHash: indexer FFI error:" << res.error;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString out;
|
||||||
|
if (res.value->is_some && res.value->value) {
|
||||||
|
out = jsonToCompactString(ffiBlockToJson(*res.value->value));
|
||||||
|
}
|
||||||
|
::free_ffi_block_opt(*res.value);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LezIndexerModule::getTransaction(const QString& hash) {
|
||||||
|
if (!indexer_service_ffi) {
|
||||||
|
qWarning() << "getTransaction: indexer not started";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
FfiHashType h;
|
||||||
|
if (!hexToBytes32(hash, &h)) {
|
||||||
|
qWarning() << "getTransaction: invalid hash (need 32-byte hex):" << hash;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerResult_FfiOption_FfiTransaction_____OperationStatus res = ::query_transaction(indexer_service_ffi, h);
|
||||||
|
if (is_error(&res.error) || !res.value) {
|
||||||
|
qWarning() << "getTransaction: indexer FFI error:" << res.error;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString out;
|
||||||
|
if (res.value->is_some && res.value->value) {
|
||||||
|
out = jsonToCompactString(ffiTransactionToJson(*res.value->value));
|
||||||
|
}
|
||||||
|
::free_ffi_transaction_opt(*res.value);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LezIndexerModule::getBlocks(const QString& before, const QString& limit) {
|
||||||
|
if (!indexer_service_ffi) {
|
||||||
|
qWarning() << "getBlocks: indexer not started";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool limitOk = false;
|
||||||
|
const uint64_t limitNum = limit.toULongLong(&limitOk);
|
||||||
|
if (!limitOk) {
|
||||||
|
qWarning() << "getBlocks: invalid limit:" << 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.isEmpty()) {
|
||||||
|
beforeVal = before.toULongLong(&hasBefore);
|
||||||
|
if (!hasBefore) {
|
||||||
|
qWarning() << "getBlocks: invalid before:" << before;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FfiOption_u64 beforeOpt;
|
||||||
|
beforeOpt.is_some = hasBefore;
|
||||||
|
beforeOpt.value = hasBefore ? &beforeVal : nullptr;
|
||||||
|
|
||||||
|
PointerResult_FfiVec_FfiBlock_____OperationStatus res = ::query_block_vec(indexer_service_ffi, beforeOpt, limitNum);
|
||||||
|
if (is_error(&res.error) || !res.value) {
|
||||||
|
qWarning() << "getBlocks: indexer FFI error:" << res.error;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray arr;
|
||||||
|
for (uintptr_t i = 0; i < res.value->len; ++i) {
|
||||||
|
arr.append(ffiBlockToJson(res.value->entries[i]));
|
||||||
|
}
|
||||||
|
::free_ffi_block_vec(*res.value);
|
||||||
|
return jsonToCompactString(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LezIndexerModule::getLastFinalizedBlockId() {
|
||||||
|
if (!indexer_service_ffi) {
|
||||||
|
qWarning() << "getLastFinalizedBlockId: indexer not started";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerResult_u64__OperationStatus res = ::query_last_block(indexer_service_ffi);
|
||||||
|
if (is_error(&res.error) || !res.value) {
|
||||||
|
qWarning() << "getLastFinalizedBlockId: indexer FFI error:" << res.error;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bare decimal string; the FFI provides no free for the boxed u64 (leaked,
|
||||||
|
// see the note above).
|
||||||
|
return u64ToString(*res.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LezIndexerModule::getTransactionsByAccount(
|
||||||
|
const QString& account_id,
|
||||||
|
const QString& offset,
|
||||||
|
const QString& limit
|
||||||
|
) {
|
||||||
|
if (!indexer_service_ffi) {
|
||||||
|
qWarning() << "getTransactionsByAccount: indexer not started";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
FfiAccountId id;
|
||||||
|
if (!hexToBytes32(account_id, &id)) {
|
||||||
|
qWarning() << "getTransactionsByAccount: invalid account id (need 32-byte hex):" << account_id;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool offsetOk = false;
|
||||||
|
bool limitOk = false;
|
||||||
|
const uint64_t offsetNum = offset.toULongLong(&offsetOk);
|
||||||
|
const uint64_t limitNum = limit.toULongLong(&limitOk);
|
||||||
|
if (!offsetOk || !limitOk) {
|
||||||
|
qWarning() << "getTransactionsByAccount: invalid offset/limit:" << offset << limit;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerResult_FfiVec_FfiTransaction_____OperationStatus res =
|
||||||
|
::query_transactions_by_account(indexer_service_ffi, id, offsetNum, limitNum);
|
||||||
|
if (is_error(&res.error) || !res.value) {
|
||||||
|
qWarning() << "getTransactionsByAccount: indexer FFI error:" << res.error;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray arr;
|
||||||
|
for (uintptr_t i = 0; i < res.value->len; ++i) {
|
||||||
|
arr.append(ffiTransactionToJson(res.value->entries[i]));
|
||||||
|
}
|
||||||
|
::free_ffi_transaction_vec(*res.value);
|
||||||
|
return jsonToCompactString(arr);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,14 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "i_lez_indexer_module.h"
|
#include "i_lez_indexer_module.h"
|
||||||
|
#include "lez_indexer_ffi.h"
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
#include <indexer_ffi.h>
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@ -41,6 +34,29 @@ public:
|
|||||||
// Indexer Lifecycle
|
// Indexer Lifecycle
|
||||||
Q_INVOKABLE int start_indexer(const QString& config_path, const QString& port) override;
|
Q_INVOKABLE int start_indexer(const QString& config_path, const QString& port) override;
|
||||||
|
|
||||||
|
// Indexer Queries (return compact JSON strings; see i_lez_indexer_module.h)
|
||||||
|
Q_INVOKABLE QString getAccount(const QString& account_id) override;
|
||||||
|
Q_INVOKABLE QString getBlockById(const QString& block_id) override;
|
||||||
|
Q_INVOKABLE QString getBlockByHash(const QString& hash) override;
|
||||||
|
Q_INVOKABLE QString getTransaction(const QString& hash) override;
|
||||||
|
Q_INVOKABLE QString getBlocks(const QString& before, const QString& limit) override;
|
||||||
|
Q_INVOKABLE QString getLastFinalizedBlockId() override;
|
||||||
|
Q_INVOKABLE QString
|
||||||
|
getTransactionsByAccount(const QString& account_id, const QString& offset, const QString& limit) override;
|
||||||
|
|
||||||
|
// Indexer Logging (opt-in)
|
||||||
|
//
|
||||||
|
// Installs the indexer FFI's logger (env_logger) so the indexer's Rust `log`
|
||||||
|
// output surfaces in the host process.
|
||||||
|
//
|
||||||
|
// Commented out because the underlying `::init_logger()` export does not yet
|
||||||
|
// exist in the pinned logos-execution-zone FFI
|
||||||
|
//
|
||||||
|
// Uncomment this single block once the export lands upstream
|
||||||
|
// and the pin is bumped to a rev that includes it.
|
||||||
|
//
|
||||||
|
// Q_INVOKABLE void init_logger() { ::init_logger(); }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void eventResponse(const QString& eventName, const QVariantList& data);
|
void eventResponse(const QString& eventName, const QVariantList& data);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user