From 9fc2e39c1092eb3f3f88a97f406a0e2ce1b2ad6d Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Thu, 23 Apr 2026 17:07:19 +0300 Subject: [PATCH] feat: first query api --- Cargo.lock | 1 + indexer_ffi/Cargo.toml | 1 + indexer_ffi/indexer_ffi.h | 32 ++++++++++++++++++ indexer_ffi/src/api/mod.rs | 1 + indexer_ffi/src/api/query.rs | 41 +++++++++++++++++++++++ indexer_ffi/src/errors.rs | 1 + indexer_ffi/src/indexer.rs | 16 +++++++++ integration_tests/src/test_context_ffi.rs | 5 +++ integration_tests/tests/indexer_ffi.rs | 16 +++++++++ 9 files changed, 114 insertions(+) create mode 100644 indexer_ffi/src/api/query.rs diff --git a/Cargo.lock b/Cargo.lock index 0d997442..9b68aba9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3484,6 +3484,7 @@ version = "0.1.0" dependencies = [ "cbindgen", "indexer_service", + "indexer_service_rpc", "log", "sequencer_core", "tokio", diff --git a/indexer_ffi/Cargo.toml b/indexer_ffi/Cargo.toml index ed3de7ae..8099ad80 100644 --- a/indexer_ffi/Cargo.toml +++ b/indexer_ffi/Cargo.toml @@ -7,6 +7,7 @@ version = "0.1.0" [dependencies] indexer_service.workspace = true sequencer_core.workspace = true +indexer_service_rpc.workspace = true url.workspace = true log = { workspace = true } diff --git a/indexer_ffi/indexer_ffi.h b/indexer_ffi/indexer_ffi.h index d764d116..1db1784a 100644 --- a/indexer_ffi/indexer_ffi.h +++ b/indexer_ffi/indexer_ffi.h @@ -7,6 +7,7 @@ typedef enum OperationStatus { Ok = 0, NullPointer = 1, InitializationError = 2, + ClientError = 3, } OperationStatus; typedef struct IndexerServiceFFI { @@ -28,6 +29,17 @@ typedef struct PointerResult_IndexerServiceFFI__OperationStatus { typedef struct PointerResult_IndexerServiceFFI__OperationStatus InitializedIndexerServiceFFIResult; +/** + * 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_u64__OperationStatus { + uint64_t *value; + enum OperationStatus error; +} PointerResult_u64__OperationStatus; + /** * Creates and starts an indexer based on the provided * configuration file path. @@ -72,6 +84,26 @@ enum OperationStatus stop_indexer(struct IndexerServiceFFI *indexer); */ void free_cstring(char *block); +/** + * Stops and frees the resources associated with the given indexer service. + * + * # Arguments + * + * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be stopped. + * + * # Returns + * + * An `OperationStatus` indicating success or failure. + * + * # Safety + * + * The caller must ensure that: + * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance + * - The `IndexerServiceFFI` instance was created by this library + * - The pointer will not be used after this function returns + */ +struct PointerResult_u64__OperationStatus query_last_block(const struct IndexerServiceFFI *indexer); + bool is_ok(const enum OperationStatus *self); bool is_error(const enum OperationStatus *self); diff --git a/indexer_ffi/src/api/mod.rs b/indexer_ffi/src/api/mod.rs index a20cb6a5..43284dc8 100644 --- a/indexer_ffi/src/api/mod.rs +++ b/indexer_ffi/src/api/mod.rs @@ -3,4 +3,5 @@ pub use result::PointerResult; pub mod client; pub mod lifecycle; pub mod memory; +pub mod query; pub mod result; diff --git a/indexer_ffi/src/api/query.rs b/indexer_ffi/src/api/query.rs new file mode 100644 index 00000000..9f9523cf --- /dev/null +++ b/indexer_ffi/src/api/query.rs @@ -0,0 +1,41 @@ +use indexer_service_rpc::RpcClient; + +use crate::{IndexerServiceFFI, api::PointerResult, errors::OperationStatus}; + +/// Stops and frees the resources associated with the given indexer service. +/// +/// # Arguments +/// +/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be stopped. +/// +/// # Returns +/// +/// An `OperationStatus` indicating success or failure. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance +/// - The `IndexerServiceFFI` instance was created by this library +/// - The pointer will not be used after this function returns +#[unsafe(no_mangle)] +pub unsafe extern "C" fn query_last_block( + indexer: *const IndexerServiceFFI, +) -> PointerResult { + 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 client = unsafe { indexer.client() }; + let runtime = unsafe { indexer.runtime() }; + + runtime + .block_on(client.get_last_finalized_block_id()) + .map_or_else( + |_| PointerResult::from_error(OperationStatus::ClientError), + PointerResult::from_value, + ) +} diff --git a/indexer_ffi/src/errors.rs b/indexer_ffi/src/errors.rs index 46aa0f9f..4572474c 100644 --- a/indexer_ffi/src/errors.rs +++ b/indexer_ffi/src/errors.rs @@ -5,6 +5,7 @@ pub enum OperationStatus { Ok = 0x0, NullPointer = 0x1, InitializationError = 0x2, + ClientError = 0x3, } impl OperationStatus { diff --git a/indexer_ffi/src/indexer.rs b/indexer_ffi/src/indexer.rs index be01f7f9..1bc6fb3b 100644 --- a/indexer_ffi/src/indexer.rs +++ b/indexer_ffi/src/indexer.rs @@ -88,6 +88,22 @@ impl IndexerServiceFFI { .expect("Indexer Client must be non-null pointer") } } + + /// Helper to get indexer runtime ref. + /// + /// # Safety + /// + /// The caller must ensure that: + /// - `self` is a valid object(contains valid pointers in all fields) + #[must_use] + pub const unsafe fn runtime(&self) -> &Runtime { + unsafe { + self.runtime + .cast::() + .as_ref() + .expect("Indexer Runtime must be non-null pointer") + } + } } // Implement Drop to prevent memory leaks diff --git a/integration_tests/src/test_context_ffi.rs b/integration_tests/src/test_context_ffi.rs index 7d21aa28..1bda9e2c 100644 --- a/integration_tests/src/test_context_ffi.rs +++ b/integration_tests/src/test_context_ffi.rs @@ -268,6 +268,11 @@ impl BlockingTestContextFFI { pub fn runtime_clone(&self) -> Arc { Arc::::clone(&self.runtime) } + + #[must_use] + pub const fn indexer_ffi(&self) -> *const IndexerServiceFFI { + &(self.indexer_ffi) + } } impl Drop for BlockingTestContextFFI { diff --git a/integration_tests/tests/indexer_ffi.rs b/integration_tests/tests/indexer_ffi.rs index 5495e6c6..b75f1b2b 100644 --- a/integration_tests/tests/indexer_ffi.rs +++ b/integration_tests/tests/indexer_ffi.rs @@ -5,6 +5,7 @@ )] use anyhow::{Context as _, Result}; +use indexer_ffi::{IndexerServiceFFI, OperationStatus, api::PointerResult}; use indexer_service_rpc::RpcClient as _; use integration_tests::{ TIME_TO_WAIT_FOR_BLOCK_SECONDS, format_private_account_id, format_public_account_id, @@ -17,6 +18,12 @@ use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcomma /// Maximum time to wait for the indexer to catch up to the sequencer. const L2_TO_L1_TIMEOUT_MILLIS: u64 = 180_000; +unsafe extern "C" { + unsafe fn query_last_block( + indexer: *const IndexerServiceFFI, + ) -> PointerResult; +} + #[test] fn indexer_test_run_ffi() -> Result<()> { let blocking_ctx = BlockingTestContextFFI::new()?; @@ -28,10 +35,19 @@ fn indexer_test_run_ffi() -> Result<()> { }); let last_block_indexer = blocking_ctx.ctx().get_last_block_indexer(runtime_wrapped)?; + let last_block_indexer_ffi_res = unsafe { query_last_block(blocking_ctx.indexer_ffi()) }; + + assert!(last_block_indexer_ffi_res.error.is_ok()); + + let last_block_indexer_ffi = unsafe { *last_block_indexer_ffi_res.value }; info!("Last block on ind now is {last_block_indexer}"); + info!("Last block on ind ffi now is {last_block_indexer_ffi}"); assert!(last_block_indexer > 1); + assert!(last_block_indexer_ffi > 1); + + assert_eq!(last_block_indexer, last_block_indexer_ffi); Ok(()) }