mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-25 03:11:21 +00:00
feat(indexer): expose events over FFI
This commit is contained in:
@@ -5,11 +5,17 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* Largest block span a single `query_events` range request may cover.
|
||||
*/
|
||||
#define MAX_EVENT_QUERY_BLOCK_SPAN 1000
|
||||
|
||||
typedef enum OperationStatus {
|
||||
Ok = 0,
|
||||
NullPointer = 1,
|
||||
InitializationError = 2,
|
||||
ClientError = 3,
|
||||
InvalidArgument = 4,
|
||||
} OperationStatus;
|
||||
|
||||
typedef enum FfiTransactionKind {
|
||||
@@ -394,6 +400,41 @@ typedef struct PointerResult_FfiVec_FfiTransaction_____OperationStatus {
|
||||
enum OperationStatus error;
|
||||
} PointerResult_FfiVec_FfiTransaction_____OperationStatus;
|
||||
|
||||
/**
|
||||
* 8-byte array type for event selectors.
|
||||
*/
|
||||
typedef struct FfiBytes8 {
|
||||
uint8_t data[8];
|
||||
} FfiBytes8;
|
||||
|
||||
typedef struct FfiBytes8 FfiSelector;
|
||||
|
||||
typedef struct FfiEventRecord {
|
||||
FfiBlockId block_id;
|
||||
uint32_t tx_index;
|
||||
FfiHashType tx_hash;
|
||||
struct FfiProgramId program_id;
|
||||
FfiSelector selector;
|
||||
FfiVecU8 data;
|
||||
} FfiEventRecord;
|
||||
|
||||
typedef struct FfiVec_FfiEventRecord {
|
||||
struct FfiEventRecord *entries;
|
||||
uintptr_t len;
|
||||
uintptr_t capacity;
|
||||
} FfiVec_FfiEventRecord;
|
||||
|
||||
/**
|
||||
* Simple wrapper around a pointer to a value or an error.
|
||||
*
|
||||
* Pointer is not guaranteed. You should check the error field before
|
||||
* dereferencing the pointer.
|
||||
*/
|
||||
typedef struct PointerResult_FfiVec_FfiEventRecord_____OperationStatus {
|
||||
struct FfiVec_FfiEventRecord *value;
|
||||
enum OperationStatus error;
|
||||
} PointerResult_FfiVec_FfiEventRecord_____OperationStatus;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
@@ -639,6 +680,45 @@ struct PointerResult_FfiVec_FfiTransaction_____OperationStatus query_transaction
|
||||
uint64_t offset,
|
||||
uint64_t limit);
|
||||
|
||||
/**
|
||||
* Query events emitted by programs, optionally filtered.
|
||||
*
|
||||
* Resolution mirrors the `getEvents` RPC: a non-null `tx_hash` makes this a point
|
||||
* lookup and the block range is ignored; otherwise the range from `from_block` to
|
||||
* `to_block` (defaulting to the current tip when none) is read, capped at
|
||||
* `MAX_EVENT_QUERY_BLOCK_SPAN` blocks — `InvalidArgument` when exceeded. `program_id`
|
||||
* and `selector` are exact-match filters applied to the result.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* - `indexer`: A pointer to the [`IndexerServiceFFI`] instance to be queried.
|
||||
* - `from_block`: Inclusive range start, ignored when `tx_hash` is non-null.
|
||||
* - `to_block`: `FfiOption<u64>` - inclusive range end; none means the current tip. Ignored when
|
||||
* `tx_hash` is non-null.
|
||||
* - `tx_hash`: Optional transaction hash; null means absent.
|
||||
* - `program_id`: Optional emitting-program filter; null means absent.
|
||||
* - `selector`: Optional event-selector filter; null means absent.
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* A [`PointerResult`] holding an `FfiVec<FfiEventRecord>` that the caller MUST free
|
||||
* with `free_ffi_event_record_vec`, or an error status.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* The caller must ensure that:
|
||||
* - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance.
|
||||
* - if `to_block.is_some`, its `value` points to a valid `u64`.
|
||||
* - each of `tx_hash`, `program_id` and `selector` is either null or a valid pointer to its
|
||||
* respective type.
|
||||
*/
|
||||
struct PointerResult_FfiVec_FfiEventRecord_____OperationStatus query_events(const struct IndexerServiceFFI *indexer,
|
||||
uint64_t from_block,
|
||||
struct FfiOption_u64 to_block,
|
||||
const FfiHashType *tx_hash,
|
||||
const struct FfiProgramId *program_id,
|
||||
const FfiSelector *selector);
|
||||
|
||||
/**
|
||||
* Frees the resources associated with the given ffi account.
|
||||
*
|
||||
@@ -730,6 +810,28 @@ void free_ffi_block_opt(FfiBlockOpt *val);
|
||||
*/
|
||||
void free_ffi_block_vec(struct FfiVec_FfiBlock *val);
|
||||
|
||||
/**
|
||||
* Frees the resources associated with the given vector of ffi event records.
|
||||
*
|
||||
* Takes ownership of the whole allocation produced by `query_events`: the outer
|
||||
* `Box<FfiVec<FfiEventRecord>>` (the `PointerResult.value` pointer), the vector's
|
||||
* backing buffer, and every record's payload within it.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* - `val`: The `*mut FfiVec<FfiEventRecord>` returned in `PointerResult.value`.
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* void.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* The caller must ensure that:
|
||||
* - `val` is a pointer to an `FfiVec<FfiEventRecord>` produced by this library and not yet freed.
|
||||
*/
|
||||
void free_ffi_event_record_vec(struct FfiVec_FfiEventRecord *val);
|
||||
|
||||
/**
|
||||
* Frees the resources associated with the given ffi transaction.
|
||||
*
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use std::ffi::{CString, c_char};
|
||||
|
||||
use indexer_service_protocol::AccountId;
|
||||
use indexer_service_protocol::{AccountId, EventRecord};
|
||||
|
||||
use crate::{
|
||||
IndexerServiceFFI,
|
||||
IndexerServiceFFI, MAX_EVENT_QUERY_BLOCK_SPAN,
|
||||
api::{
|
||||
PointerResult,
|
||||
types::{
|
||||
FfiAccountId, FfiBlockId, FfiHashType, FfiOption, FfiVec,
|
||||
FfiAccountId, FfiBlockId, FfiHashType, FfiOption, FfiProgramId, FfiSelector, FfiVec,
|
||||
account::FfiAccount,
|
||||
block::{FfiBlock, FfiBlockOpt},
|
||||
event::FfiEventRecord,
|
||||
transaction::FfiTransaction,
|
||||
},
|
||||
},
|
||||
@@ -434,3 +435,131 @@ pub unsafe extern "C" fn query_transactions_by_account(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Query events emitted by programs, optionally filtered.
|
||||
///
|
||||
/// Resolution mirrors the `getEvents` RPC: a non-null `tx_hash` makes this a point
|
||||
/// lookup and the block range is ignored; otherwise the range from `from_block` to
|
||||
/// `to_block` (defaulting to the current tip when none) is read, capped at
|
||||
/// `MAX_EVENT_QUERY_BLOCK_SPAN` blocks — `InvalidArgument` when exceeded. `program_id`
|
||||
/// and `selector` are exact-match filters applied to the result.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `indexer`: A pointer to the [`IndexerServiceFFI`] instance to be queried.
|
||||
/// - `from_block`: Inclusive range start, ignored when `tx_hash` is non-null.
|
||||
/// - `to_block`: `FfiOption<u64>` - inclusive range end; none means the current tip. Ignored when
|
||||
/// `tx_hash` is non-null.
|
||||
/// - `tx_hash`: Optional transaction hash; null means absent.
|
||||
/// - `program_id`: Optional emitting-program filter; null means absent.
|
||||
/// - `selector`: Optional event-selector filter; null means absent.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A [`PointerResult`] holding an `FfiVec<FfiEventRecord>` that the caller MUST free
|
||||
/// with `free_ffi_event_record_vec`, or an error status.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that:
|
||||
/// - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance.
|
||||
/// - if `to_block.is_some`, its `value` points to a valid `u64`.
|
||||
/// - each of `tx_hash`, `program_id` and `selector` is either null or a valid pointer to its
|
||||
/// respective type.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn query_events(
|
||||
indexer: *const IndexerServiceFFI,
|
||||
from_block: u64,
|
||||
to_block: FfiOption<u64>,
|
||||
tx_hash: *const FfiHashType,
|
||||
program_id: *const FfiProgramId,
|
||||
selector: *const FfiSelector,
|
||||
) -> PointerResult<FfiVec<FfiEventRecord>, OperationStatus> {
|
||||
if indexer.is_null() {
|
||||
log::error!("Attempted to query a null indexer pointer. This is a bug. Aborting.");
|
||||
return PointerResult::from_error(OperationStatus::NullPointer);
|
||||
}
|
||||
|
||||
let indexer = unsafe { &*indexer };
|
||||
let program_id =
|
||||
unsafe { program_id.as_ref() }.map(|id| indexer_service_protocol::ProgramId(id.data));
|
||||
let selector = unsafe { selector.as_ref() }.map(|s| indexer_service_protocol::Selector(s.data));
|
||||
|
||||
let records = if let Some(tx_hash) = unsafe { tx_hash.as_ref() } {
|
||||
indexer
|
||||
.core()
|
||||
.store
|
||||
.block_id_by_tx_hash(tx_hash.data)
|
||||
.and_then(|resolved| {
|
||||
resolved.map_or(Ok(Vec::new()), |block_id| {
|
||||
indexer
|
||||
.core()
|
||||
.store
|
||||
.get_events_for_block(block_id)
|
||||
.map(|row| {
|
||||
row.and_then(|groups| {
|
||||
groups
|
||||
.into_iter()
|
||||
.find(|group| group.tx_hash.0 == tx_hash.data)
|
||||
})
|
||||
.map(|group| EventRecord::from_tx_events(block_id, group))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
})
|
||||
})
|
||||
} else {
|
||||
if to_block.is_some && to_block.value.is_null() {
|
||||
log::error!("query_events to_block is flagged present but its value pointer is null");
|
||||
return PointerResult::from_error(OperationStatus::InvalidArgument);
|
||||
}
|
||||
let to_block = match to_block.is_some.then(|| unsafe { *to_block.value }) {
|
||||
Some(to_block) => to_block,
|
||||
None => match indexer.core().store.get_last_block_id() {
|
||||
Ok(tip) => tip.unwrap_or(0),
|
||||
Err(e) => {
|
||||
log::error!("Failed to query events: {e:#}");
|
||||
return PointerResult::from_error(OperationStatus::ClientError);
|
||||
}
|
||||
},
|
||||
};
|
||||
// Same bound as the RPC surface, from the shared constant.
|
||||
let span = to_block.saturating_sub(from_block).saturating_add(1);
|
||||
if span > MAX_EVENT_QUERY_BLOCK_SPAN {
|
||||
log::error!(
|
||||
"query_events block span {span} exceeds the maximum of {MAX_EVENT_QUERY_BLOCK_SPAN}"
|
||||
);
|
||||
return PointerResult::from_error(OperationStatus::InvalidArgument);
|
||||
}
|
||||
indexer
|
||||
.core()
|
||||
.store
|
||||
.get_events_range(from_block, to_block)
|
||||
.map(|groups| {
|
||||
groups
|
||||
.into_iter()
|
||||
.flat_map(|(block_id, groups)| {
|
||||
groups
|
||||
.into_iter()
|
||||
.flat_map(move |group| EventRecord::from_tx_events(block_id, group))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
};
|
||||
|
||||
records.map_or_else(
|
||||
|e| {
|
||||
log::error!("Failed to query events: {e:#}");
|
||||
PointerResult::from_error(OperationStatus::ClientError)
|
||||
},
|
||||
|records| {
|
||||
PointerResult::from_value(
|
||||
records
|
||||
.into_iter()
|
||||
.filter(|record| record.matches_fields(program_id, selector))
|
||||
.map(Into::into)
|
||||
.collect::<Vec<FfiEventRecord>>()
|
||||
.into(),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
use indexer_service_protocol::EventRecord;
|
||||
|
||||
use crate::api::types::{
|
||||
FfiBlockId, FfiHashType, FfiProgramId, FfiSelector, FfiVec, vectors::FfiVecU8,
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FfiEventRecord {
|
||||
pub block_id: FfiBlockId,
|
||||
pub tx_index: u32,
|
||||
pub tx_hash: FfiHashType,
|
||||
pub program_id: FfiProgramId,
|
||||
pub selector: FfiSelector,
|
||||
pub data: FfiVecU8,
|
||||
}
|
||||
|
||||
impl From<EventRecord> for FfiEventRecord {
|
||||
fn from(value: EventRecord) -> Self {
|
||||
let EventRecord {
|
||||
block_id,
|
||||
tx_index,
|
||||
tx_hash,
|
||||
program_id,
|
||||
selector,
|
||||
data,
|
||||
} = value;
|
||||
|
||||
Self {
|
||||
block_id,
|
||||
tx_index,
|
||||
tx_hash: tx_hash.into(),
|
||||
program_id: program_id.into(),
|
||||
selector: selector.into(),
|
||||
data: data.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees the resources associated with the given vector of ffi event records.
|
||||
///
|
||||
/// Takes ownership of the whole allocation produced by `query_events`: the outer
|
||||
/// `Box<FfiVec<FfiEventRecord>>` (the `PointerResult.value` pointer), the vector's
|
||||
/// backing buffer, and every record's payload within it.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `val`: The `*mut FfiVec<FfiEventRecord>` returned in `PointerResult.value`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// void.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that:
|
||||
/// - `val` is a pointer to an `FfiVec<FfiEventRecord>` produced by this library and not yet freed.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn free_ffi_event_record_vec(val: *mut FfiVec<FfiEventRecord>) {
|
||||
if val.is_null() {
|
||||
log::error!("Trying to free a null pointer. Exiting");
|
||||
return;
|
||||
}
|
||||
let ffi_vec = unsafe { Box::from_raw(val) };
|
||||
let records: Vec<FfiEventRecord> = (*ffi_vec).into();
|
||||
for record in records {
|
||||
drop(Vec::from(record.data));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
use indexer_service_protocol::{AccountId, HashType, ProgramId, PublicKey, Signature};
|
||||
use indexer_service_protocol::{AccountId, HashType, ProgramId, PublicKey, Selector, Signature};
|
||||
|
||||
pub mod account;
|
||||
pub mod block;
|
||||
pub mod event;
|
||||
pub mod transaction;
|
||||
pub mod vectors;
|
||||
|
||||
@@ -12,6 +13,13 @@ pub struct FfiBytes32 {
|
||||
pub data: [u8; 32],
|
||||
}
|
||||
|
||||
/// 8-byte array type for event selectors.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct FfiBytes8 {
|
||||
pub data: [u8; 8],
|
||||
}
|
||||
|
||||
/// 64-byte array type for signatures, etc.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -74,6 +82,7 @@ pub type FfiSignature = FfiBytes64;
|
||||
pub type FfiAccountId = FfiBytes32;
|
||||
pub type FfiNonce = FfiU128;
|
||||
pub type FfiPublicKey = FfiBytes32;
|
||||
pub type FfiSelector = FfiBytes8;
|
||||
|
||||
impl From<HashType> for FfiHashType {
|
||||
fn from(value: HashType) -> Self {
|
||||
@@ -93,6 +102,12 @@ impl From<AccountId> for FfiAccountId {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Selector> for FfiSelector {
|
||||
fn from(value: Selector) -> Self {
|
||||
Self { data: value.0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PublicKey> for FfiPublicKey {
|
||||
fn from(value: PublicKey) -> Self {
|
||||
Self { data: value.0 }
|
||||
|
||||
@@ -6,6 +6,7 @@ pub enum OperationStatus {
|
||||
NullPointer = 0x1,
|
||||
InitializationError = 0x2,
|
||||
ClientError = 0x3,
|
||||
InvalidArgument = 0x4,
|
||||
}
|
||||
|
||||
impl OperationStatus {
|
||||
|
||||
@@ -8,3 +8,12 @@ pub mod api;
|
||||
mod errors;
|
||||
mod indexer;
|
||||
mod runtime;
|
||||
|
||||
/// Largest block span a single `query_events` range request may cover.
|
||||
// Spelled as a literal so cbindgen can emit it into the header for C callers; the
|
||||
// assertion below makes any drift from the protocol crate's value a compile error.
|
||||
pub const MAX_EVENT_QUERY_BLOCK_SPAN: u64 = 1000;
|
||||
const _: () = assert!(
|
||||
MAX_EVENT_QUERY_BLOCK_SPAN == indexer_service_protocol::MAX_EVENT_QUERY_BLOCK_SPAN,
|
||||
"FFI event-query span cap must match the protocol crate's"
|
||||
);
|
||||
|
||||
@@ -48,7 +48,8 @@ mod base64 {
|
||||
}
|
||||
}
|
||||
|
||||
// Bounds the server work one range query can request.
|
||||
// Largest block span a single events range query may cover. Lives here so every surface
|
||||
// that serves the query (RPC service, FFI) enforces the identical bound.
|
||||
pub const MAX_EVENT_QUERY_BLOCK_SPAN: u64 = 1000;
|
||||
|
||||
pub type Nonce = u128;
|
||||
|
||||
Reference in New Issue
Block a user