mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-08 09:09:31 +00:00
feat: indexer ffi added
This commit is contained in:
parent
3e24ae2736
commit
cf420291e3
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -3458,6 +3458,17 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexer_ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cbindgen",
|
||||
"indexer_service",
|
||||
"log",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexer_service"
|
||||
version = "0.1.0"
|
||||
|
||||
@ -37,6 +37,7 @@ members = [
|
||||
"examples/program_deployment/methods/guest",
|
||||
"bedrock_client",
|
||||
"testnet_initial_state",
|
||||
"indexer_ffi",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
|
||||
26
indexer_ffi/Cargo.toml
Normal file
26
indexer_ffi/Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[package]
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
name = "indexer_ffi"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
indexer_service.workspace = true
|
||||
log = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { features = ["rt-multi-thread"], workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.29"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
name = "indexer_ffi"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = [
|
||||
"cbindgen",
|
||||
] # machete does not recognize this for build dep and complains.
|
||||
12
indexer_ffi/build.rs
Normal file
12
indexer_ffi/build.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
println!("cargo:rerun-if-changed=src/");
|
||||
cbindgen::Builder::new()
|
||||
.with_crate(crate_dir)
|
||||
.with_language(cbindgen::Language::C)
|
||||
.generate()
|
||||
.expect("Unable to generate bindings")
|
||||
.write_to_file("indexer_ffi.h");
|
||||
}
|
||||
2
indexer_ffi/cbindgen.toml
Normal file
2
indexer_ffi/cbindgen.toml
Normal file
@ -0,0 +1,2 @@
|
||||
language = "C" # For increased compatibility
|
||||
no_includes = true
|
||||
76
indexer_ffi/indexer_ffi.h
Normal file
76
indexer_ffi/indexer_ffi.h
Normal file
@ -0,0 +1,76 @@
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef enum OperationStatus {
|
||||
Ok = 0,
|
||||
NullPointer = 1,
|
||||
InitializationError = 2,
|
||||
} OperationStatus;
|
||||
|
||||
typedef struct IndexerServiceFFI {
|
||||
void *indexer_handle;
|
||||
void *runtime;
|
||||
} IndexerServiceFFI;
|
||||
|
||||
/**
|
||||
* 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_IndexerServiceFFI__OperationStatus {
|
||||
struct IndexerServiceFFI *value;
|
||||
enum OperationStatus error;
|
||||
} PointerResult_IndexerServiceFFI__OperationStatus;
|
||||
|
||||
typedef struct PointerResult_IndexerServiceFFI__OperationStatus InitializedIndexerServiceFFIResult;
|
||||
|
||||
/**
|
||||
* Creates and starts an indexer based on the provided
|
||||
* configuration file path.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* - `config_path`: A pointer to a string representing the path to the configuration file.
|
||||
* - `port`: Number representing a port, on which indexers RPC will start.
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* An `InitializedIndexerServiceFFIResult` containing either a pointer to the
|
||||
* initialized `IndexerServiceFFI` or an error code.
|
||||
*/
|
||||
InitializedIndexerServiceFFIResult start_indexer(const char *config_path, uint16_t port);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
enum OperationStatus stop_indexer(struct IndexerServiceFFI *indexer);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* It's up to the caller to pass a proper pointer, if somehow from c/c++ side
|
||||
* this is called with a type which doesn't come from a returned `CString` it
|
||||
* will cause a segfault.
|
||||
*/
|
||||
void free_cstring(char *block);
|
||||
|
||||
bool is_ok(const enum OperationStatus *self);
|
||||
|
||||
bool is_error(const enum OperationStatus *self);
|
||||
100
indexer_ffi/src/api/lifecycle.rs
Normal file
100
indexer_ffi/src/api/lifecycle.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use std::{ffi::c_char, path::PathBuf};
|
||||
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::{IndexerServiceFFI, api::PointerResult, errors::OperationStatus};
|
||||
|
||||
pub type InitializedIndexerServiceFFIResult = PointerResult<IndexerServiceFFI, OperationStatus>;
|
||||
|
||||
/// Creates and starts an indexer based on the provided
|
||||
/// configuration file path.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `config_path`: A pointer to a string representing the path to the configuration file.
|
||||
/// - `port`: Number representing a port, on which indexers RPC will start.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An `InitializedIndexerServiceFFIResult` containing either a pointer to the
|
||||
/// initialized `IndexerServiceFFI` or an error code.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn start_indexer(
|
||||
config_path: *const c_char,
|
||||
port: u16,
|
||||
) -> InitializedIndexerServiceFFIResult {
|
||||
setup_indexer(config_path, port).map_or_else(
|
||||
InitializedIndexerServiceFFIResult::from_error,
|
||||
InitializedIndexerServiceFFIResult::from_value,
|
||||
)
|
||||
}
|
||||
|
||||
/// Initializes and starts an indexer based on the provided
|
||||
/// configuration file path.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `config_path`: A pointer to a string representing the path to the configuration file.
|
||||
/// - `port`: Number representing a port, on which indexers RPC will start.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Result` containing either the initialized `IndexerServiceFFI` or an
|
||||
/// error code.
|
||||
fn setup_indexer(
|
||||
config_path: *const c_char,
|
||||
port: u16,
|
||||
) -> Result<IndexerServiceFFI, OperationStatus> {
|
||||
let user_config_path = PathBuf::from(
|
||||
unsafe { std::ffi::CStr::from_ptr(config_path) }
|
||||
.to_str()
|
||||
.map_err(|e| {
|
||||
log::error!("Could not convert the config path to string: {e}");
|
||||
OperationStatus::InitializationError
|
||||
})?,
|
||||
);
|
||||
let config = indexer_service::IndexerConfig::from_path(&user_config_path).map_err(|e| {
|
||||
log::error!("Failed to read config: {e}");
|
||||
OperationStatus::InitializationError
|
||||
})?;
|
||||
|
||||
let rt = Runtime::new().unwrap();
|
||||
|
||||
let indexer_handle = rt
|
||||
.block_on(indexer_service::run_server(config, port))
|
||||
.map_err(|e| {
|
||||
log::error!("Could not start indexer service: {e}");
|
||||
OperationStatus::InitializationError
|
||||
})?;
|
||||
|
||||
Ok(IndexerServiceFFI::new(indexer_handle, rt))
|
||||
}
|
||||
|
||||
/// 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 stop_indexer(indexer: *mut IndexerServiceFFI) -> OperationStatus {
|
||||
if indexer.is_null() {
|
||||
log::error!("Attempted to stop a null indexer pointer. This is a bug. Aborting.");
|
||||
return OperationStatus::NullPointer;
|
||||
}
|
||||
|
||||
let indexer = unsafe { Box::from_raw(indexer) };
|
||||
drop(indexer);
|
||||
|
||||
OperationStatus::Ok
|
||||
}
|
||||
14
indexer_ffi/src/api/memory.rs
Normal file
14
indexer_ffi/src/api/memory.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use std::ffi::{CString, c_char};
|
||||
|
||||
/// # Safety
|
||||
/// It's up to the caller to pass a proper pointer, if somehow from c/c++ side
|
||||
/// this is called with a type which doesn't come from a returned `CString` it
|
||||
/// will cause a segfault.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn free_cstring(block: *mut c_char) {
|
||||
if block.is_null() {
|
||||
log::error!("Trying to free a null pointer. Exiting");
|
||||
return;
|
||||
}
|
||||
drop(unsafe { CString::from_raw(block) });
|
||||
}
|
||||
5
indexer_ffi/src/api/mod.rs
Normal file
5
indexer_ffi/src/api/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub use result::PointerResult;
|
||||
|
||||
pub mod lifecycle;
|
||||
pub mod memory;
|
||||
pub mod result;
|
||||
29
indexer_ffi/src/api/result.rs
Normal file
29
indexer_ffi/src/api/result.rs
Normal file
@ -0,0 +1,29 @@
|
||||
/// 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.
|
||||
#[repr(C)]
|
||||
pub struct PointerResult<Type, Error> {
|
||||
pub value: *mut Type,
|
||||
pub error: Error,
|
||||
}
|
||||
|
||||
impl<Type, Error: Default> PointerResult<Type, Error> {
|
||||
pub fn from_pointer(pointer: *mut Type) -> Self {
|
||||
Self {
|
||||
value: pointer,
|
||||
error: Error::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_value(value: Type) -> Self {
|
||||
Self::from_pointer(Box::into_raw(Box::new(value)))
|
||||
}
|
||||
|
||||
pub const fn from_error(error: Error) -> Self {
|
||||
Self {
|
||||
value: std::ptr::null_mut(),
|
||||
error,
|
||||
}
|
||||
}
|
||||
}
|
||||
22
indexer_ffi/src/errors.rs
Normal file
22
indexer_ffi/src/errors.rs
Normal file
@ -0,0 +1,22 @@
|
||||
#[derive(Default, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
pub enum OperationStatus {
|
||||
#[default]
|
||||
Ok = 0x0,
|
||||
NullPointer = 0x1,
|
||||
InitializationError = 0x2,
|
||||
}
|
||||
|
||||
impl OperationStatus {
|
||||
#[must_use]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn is_ok(&self) -> bool {
|
||||
*self == Self::Ok
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn is_error(&self) -> bool {
|
||||
!self.is_ok()
|
||||
}
|
||||
}
|
||||
42
indexer_ffi/src/indexer.rs
Normal file
42
indexer_ffi/src/indexer.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use std::ffi::c_void;
|
||||
|
||||
use indexer_service::IndexerHandle;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IndexerServiceFFI {
|
||||
indexer_handle: *mut c_void,
|
||||
runtime: *mut c_void,
|
||||
}
|
||||
|
||||
impl IndexerServiceFFI {
|
||||
pub fn new(indexer_handle: indexer_service::IndexerHandle, runtime: Runtime) -> Self {
|
||||
Self {
|
||||
// Box the complex types and convert to opaque pointers
|
||||
indexer_handle: Box::into_raw(Box::new(indexer_handle)).cast::<c_void>(),
|
||||
runtime: Box::into_raw(Box::new(runtime)).cast::<c_void>(),
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to safely take ownership back
|
||||
#[must_use]
|
||||
pub fn into_parts(self) -> (Box<IndexerHandle>, Box<Runtime>) {
|
||||
let overwatch = unsafe { Box::from_raw(self.indexer_handle.cast::<IndexerHandle>()) };
|
||||
let runtime = unsafe { Box::from_raw(self.runtime.cast::<Runtime>()) };
|
||||
(overwatch, runtime)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Drop to prevent memory leaks
|
||||
impl Drop for IndexerServiceFFI {
|
||||
fn drop(&mut self) {
|
||||
if self.indexer_handle.is_null() {
|
||||
log::error!("Attempted to drop a null indexer pointer. This is a bug");
|
||||
}
|
||||
if self.runtime.is_null() {
|
||||
log::error!("Attempted to drop a null tokio runtime pointer. This is a bug");
|
||||
}
|
||||
drop(unsafe { Box::from_raw(self.indexer_handle.cast::<IndexerHandle>()) });
|
||||
drop(unsafe { Box::from_raw(self.runtime.cast::<Runtime>()) });
|
||||
}
|
||||
}
|
||||
8
indexer_ffi/src/lib.rs
Normal file
8
indexer_ffi/src/lib.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![allow(clippy::undocumented_unsafe_blocks, reason = "It is an FFI")]
|
||||
|
||||
pub use errors::OperationStatus;
|
||||
pub use indexer::IndexerServiceFFI;
|
||||
|
||||
mod api;
|
||||
mod errors;
|
||||
mod indexer;
|
||||
Loading…
x
Reference in New Issue
Block a user