From 9880a46bdccca6c984ba06df4bf3df5cc1534947 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 22 Apr 2026 17:39:27 +0300 Subject: [PATCH 01/11] feat: indexer client added to ffi --- Cargo.lock | 2 ++ indexer_ffi/Cargo.toml | 3 +++ indexer_ffi/indexer_ffi.h | 1 + indexer_ffi/src/api/client.rs | 36 ++++++++++++++++++++++++++++++++ indexer_ffi/src/api/lifecycle.rs | 18 ++++++++++++++-- indexer_ffi/src/api/mod.rs | 1 + indexer_ffi/src/indexer.rs | 32 ++++++++++++++++++++++++---- 7 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 indexer_ffi/src/api/client.rs diff --git a/Cargo.lock b/Cargo.lock index 3d46ad65..0d997442 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3485,7 +3485,9 @@ dependencies = [ "cbindgen", "indexer_service", "log", + "sequencer_core", "tokio", + "url", ] [[package]] diff --git a/indexer_ffi/Cargo.toml b/indexer_ffi/Cargo.toml index b55230c6..ed3de7ae 100644 --- a/indexer_ffi/Cargo.toml +++ b/indexer_ffi/Cargo.toml @@ -6,6 +6,9 @@ version = "0.1.0" [dependencies] indexer_service.workspace = true +sequencer_core.workspace = true + +url.workspace = true log = { workspace = true } tokio = { features = ["rt-multi-thread"], workspace = true } diff --git a/indexer_ffi/indexer_ffi.h b/indexer_ffi/indexer_ffi.h index 7c7d9a4d..d764d116 100644 --- a/indexer_ffi/indexer_ffi.h +++ b/indexer_ffi/indexer_ffi.h @@ -12,6 +12,7 @@ typedef enum OperationStatus { typedef struct IndexerServiceFFI { void *indexer_handle; void *runtime; + void *indexer_client; } IndexerServiceFFI; /** diff --git a/indexer_ffi/src/api/client.rs b/indexer_ffi/src/api/client.rs new file mode 100644 index 00000000..825a57de --- /dev/null +++ b/indexer_ffi/src/api/client.rs @@ -0,0 +1,36 @@ +use std::net::SocketAddr; + +use url::Url; + +use crate::OperationStatus; + +#[derive(Debug, Clone, Copy)] +pub enum UrlProtocol { + Http, + Ws, +} + +impl std::fmt::Display for UrlProtocol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Http => write!(f, "http"), + Self::Ws => write!(f, "ws"), + } + } +} + +pub(crate) fn addr_to_url(protocol: UrlProtocol, addr: SocketAddr) -> Result { + // Convert 0.0.0.0 to 127.0.0.1 for client connections + // When binding to port 0, the server binds to 0.0.0.0: + // but clients need to connect to 127.0.0.1: to work reliably + let url_string = if addr.ip().is_unspecified() { + format!("{protocol}://127.0.0.1:{}", addr.port()) + } else { + format!("{protocol}://{addr}") + }; + + url_string.parse().map_err(|e| { + log::error!("Could not parse indexer url: {e}"); + OperationStatus::InitializationError + }) +} diff --git a/indexer_ffi/src/api/lifecycle.rs b/indexer_ffi/src/api/lifecycle.rs index 735efd4d..087f7803 100644 --- a/indexer_ffi/src/api/lifecycle.rs +++ b/indexer_ffi/src/api/lifecycle.rs @@ -1,8 +1,16 @@ use std::{ffi::c_char, path::PathBuf}; +use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait}; use tokio::runtime::Runtime; -use crate::{IndexerServiceFFI, api::PointerResult, errors::OperationStatus}; +use crate::{ + IndexerServiceFFI, + api::{ + PointerResult, + client::{UrlProtocol, addr_to_url}, + }, + errors::OperationStatus, +}; pub type InitializedIndexerServiceFFIResult = PointerResult; @@ -67,7 +75,13 @@ fn setup_indexer( OperationStatus::InitializationError })?; - Ok(IndexerServiceFFI::new(indexer_handle, rt)) + let indexer_url = addr_to_url(UrlProtocol::Ws, indexer_handle.addr())?; + let indexer_client = rt.block_on(IndexerClient::new(&indexer_url)).map_err(|e| { + log::error!("Could not start indexer client: {e}"); + OperationStatus::InitializationError + })?; + + Ok(IndexerServiceFFI::new(indexer_handle, rt, indexer_client)) } /// Stops and frees the resources associated with the given indexer service. diff --git a/indexer_ffi/src/api/mod.rs b/indexer_ffi/src/api/mod.rs index e84a3913..a20cb6a5 100644 --- a/indexer_ffi/src/api/mod.rs +++ b/indexer_ffi/src/api/mod.rs @@ -1,5 +1,6 @@ pub use result::PointerResult; +pub mod client; pub mod lifecycle; pub mod memory; pub mod result; diff --git a/indexer_ffi/src/indexer.rs b/indexer_ffi/src/indexer.rs index a3991388..be01f7f9 100644 --- a/indexer_ffi/src/indexer.rs +++ b/indexer_ffi/src/indexer.rs @@ -1,20 +1,27 @@ use std::{ffi::c_void, net::SocketAddr}; use indexer_service::IndexerHandle; +use sequencer_core::indexer_client::IndexerClient; use tokio::runtime::Runtime; #[repr(C)] pub struct IndexerServiceFFI { indexer_handle: *mut c_void, runtime: *mut c_void, + indexer_client: *mut c_void, } impl IndexerServiceFFI { - pub fn new(indexer_handle: indexer_service::IndexerHandle, runtime: Runtime) -> Self { + pub fn new( + indexer_handle: indexer_service::IndexerHandle, + runtime: Runtime, + indexer_client: IndexerClient, + ) -> Self { Self { // Box the complex types and convert to opaque pointers indexer_handle: Box::into_raw(Box::new(indexer_handle)).cast::(), runtime: Box::into_raw(Box::new(runtime)).cast::(), + indexer_client: Box::into_raw(Box::new(indexer_client)).cast::(), } } @@ -25,10 +32,11 @@ impl IndexerServiceFFI { /// The caller must ensure that: /// - `self` is a valid object(contains valid pointers in all fields) #[must_use] - pub unsafe fn into_parts(self) -> (Box, Box) { + pub unsafe fn into_parts(self) -> (Box, Box, Box) { let indexer_handle = unsafe { Box::from_raw(self.indexer_handle.cast::()) }; let runtime = unsafe { Box::from_raw(self.runtime.cast::()) }; - (indexer_handle, runtime) + let indexer_client = unsafe { Box::from_raw(self.indexer_client.cast::()) }; + (indexer_handle, runtime, indexer_client) } /// Helper to get indexer handle addr. @@ -49,7 +57,7 @@ impl IndexerServiceFFI { indexer_handle.addr() } - /// Helper to get indexer handle addr. + /// Helper to get indexer handle ref. /// /// # Safety /// @@ -64,6 +72,22 @@ impl IndexerServiceFFI { .expect("Indexer Handle must be non-null pointer") } } + + /// Helper to get indexer client ref. + /// + /// # Safety + /// + /// The caller must ensure that: + /// - `self` is a valid object(contains valid pointers in all fields) + #[must_use] + pub const unsafe fn client(&self) -> &IndexerClient { + unsafe { + self.indexer_client + .cast::() + .as_ref() + .expect("Indexer Client must be non-null pointer") + } + } } // Implement Drop to prevent memory leaks From 9fc2e39c1092eb3f3f88a97f406a0e2ce1b2ad6d Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Thu, 23 Apr 2026 17:07:19 +0300 Subject: [PATCH 02/11] 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(()) } From b736b229ef69d39adbf210f08384a54489c442ba Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Fri, 24 Apr 2026 17:37:09 +0300 Subject: [PATCH 03/11] fix: types halfway done --- indexer_ffi/indexer_ffi.h | 113 +++++++++++++++++++++++++++++++++ indexer_ffi/src/api/convert.rs | 12 ++++ indexer_ffi/src/api/mod.rs | 2 + indexer_ffi/src/api/query.rs | 41 +++++++++++- indexer_ffi/src/api/types.rs | 99 +++++++++++++++++++++++++++++ 5 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 indexer_ffi/src/api/convert.rs create mode 100644 indexer_ffi/src/api/types.rs diff --git a/indexer_ffi/indexer_ffi.h b/indexer_ffi/indexer_ffi.h index 1db1784a..b734b59f 100644 --- a/indexer_ffi/indexer_ffi.h +++ b/indexer_ffi/indexer_ffi.h @@ -10,6 +10,24 @@ typedef enum OperationStatus { ClientError = 3, } OperationStatus; +typedef enum TransactionKind { + Public = 0, + Private, + ProgramDeploy, +} TransactionKind; + +typedef enum BedrockStatus { + Pending = 0, + Safe, + Finalized, +} BedrockStatus; + +typedef struct Vec_AccountId Vec_AccountId; + +typedef struct Vec_Nonce Vec_Nonce; + +typedef struct Vec_u32 Vec_u32; + typedef struct IndexerServiceFFI { void *indexer_handle; void *runtime; @@ -40,6 +58,80 @@ typedef struct PointerResult_u64__OperationStatus { enum OperationStatus error; } PointerResult_u64__OperationStatus; +typedef uint64_t BlockId; + +typedef uint8_t HashType[32]; + +typedef uint64_t Timestamp; + +typedef uint8_t Signature[64]; + +typedef struct BlockHeader { + BlockId block_id; + HashType prev_block_hash; + HashType hash; + Timestamp timestamp; + Signature signature; +} BlockHeader; + +typedef uint32_t ProgramId[8]; + +typedef struct PublicMessage { + ProgramId program_id; + struct Vec_AccountId account_ids; + struct Vec_Nonce nonces; + struct Vec_u32 instruction_data; +} PublicMessage; + +typedef struct WitnessSet { + +} WitnessSet; + +typedef struct PublicTransactionBody { + HashType hash; + struct PublicMessage message; + struct WitnessSet witness_set; +} PublicTransactionBody; + +typedef struct TransactionBody { + struct PublicTransactionBody *public_body; +} TransactionBody; + +typedef struct Transaction { + struct TransactionBody body; + enum TransactionKind kind; +} Transaction; + +typedef struct BlockBody { + struct Transaction *txs; + uintptr_t len; +} BlockBody; + +typedef uint8_t MsgId[32]; + +typedef struct Block { + struct BlockHeader header; + struct BlockBody body; + enum BedrockStatus bedrock_status; + MsgId bedrock_parent_id; +} Block; + +typedef struct BlockOpt { + struct Block *block; + bool is_ok; +} BlockOpt; + +/** + * 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_BlockOpt__OperationStatus { + struct BlockOpt *value; + enum OperationStatus error; +} PointerResult_BlockOpt__OperationStatus; + /** * Creates and starts an indexer based on the provided * configuration file path. @@ -104,6 +196,27 @@ void free_cstring(char *block); */ struct PointerResult_u64__OperationStatus query_last_block(const struct IndexerServiceFFI *indexer); +/** + * 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_BlockOpt__OperationStatus query_block(const struct IndexerServiceFFI *indexer, + BlockId block_id); + bool is_ok(const enum OperationStatus *self); bool is_error(const enum OperationStatus *self); diff --git a/indexer_ffi/src/api/convert.rs b/indexer_ffi/src/api/convert.rs new file mode 100644 index 00000000..d970de38 --- /dev/null +++ b/indexer_ffi/src/api/convert.rs @@ -0,0 +1,12 @@ +use crate::api::types::BlockOpt; + +impl From> for BlockOpt { + fn from(value: Option) -> Self { + match value { + None => BlockOpt { block: std::ptr::null_mut(), is_ok: false }, + Some(block_orig) => BlockOpt { block: block_orig.into(), is_ok: true } + } + } +} + +impl From<> \ No newline at end of file diff --git a/indexer_ffi/src/api/mod.rs b/indexer_ffi/src/api/mod.rs index 43284dc8..9221a65c 100644 --- a/indexer_ffi/src/api/mod.rs +++ b/indexer_ffi/src/api/mod.rs @@ -5,3 +5,5 @@ pub mod lifecycle; pub mod memory; pub mod query; pub mod result; +pub mod types; +pub mod convert; diff --git a/indexer_ffi/src/api/query.rs b/indexer_ffi/src/api/query.rs index 9f9523cf..c4dd26b1 100644 --- a/indexer_ffi/src/api/query.rs +++ b/indexer_ffi/src/api/query.rs @@ -1,6 +1,6 @@ use indexer_service_rpc::RpcClient; -use crate::{IndexerServiceFFI, api::PointerResult, errors::OperationStatus}; +use crate::{IndexerServiceFFI, api::{PointerResult, types::{Block, BlockId, BlockOpt}}, errors::OperationStatus}; /// Stops and frees the resources associated with the given indexer service. /// @@ -39,3 +39,42 @@ pub unsafe extern "C" fn query_last_block( PointerResult::from_value, ) } + +/// 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_block( + indexer: *const IndexerServiceFFI, + block_id: BlockId, +) -> 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_block_by_id(block_id)) + .map_or_else( + |_| PointerResult::from_error(OperationStatus::ClientError), + |block| PointerResult::from_value(block.into()), + ) +} diff --git a/indexer_ffi/src/api/types.rs b/indexer_ffi/src/api/types.rs new file mode 100644 index 00000000..920b4764 --- /dev/null +++ b/indexer_ffi/src/api/types.rs @@ -0,0 +1,99 @@ +pub type HashType = [u8; 32]; +pub type MsgId = [u8; 32]; +pub type BlockId = u64; +pub type Timestamp = u64; +pub type Signature = [u8; 64]; +pub type ProgramId = [u32; 8]; +pub type AccountId = [u8; 32]; +pub type Nonce = u128; +pub type PublicKey = [u8; 32]; + +#[repr(C)] +pub struct Block { + pub header: BlockHeader, + pub body: BlockBody, + pub bedrock_status: BedrockStatus, + pub bedrock_parent_id: MsgId, +} + +#[repr(C)] +pub struct BlockOpt { + pub block: *const Block, + pub is_ok: bool, +} + +#[repr(C)] +pub struct PublicMessage { + pub program_id: ProgramId, + pub account_ids: Vec, + pub nonces: Vec, + pub instruction_data: Vec, +} + +#[repr(C)] +pub struct PublicTransactionBody { + pub hash: HashType, + pub message: PublicMessage, + pub witness_set: Vec<(Signature, PublicKey)>, +} + +#[repr(C)] +pub struct PrivateTransactionBody { + +} + +#[repr(C)] +pub struct ProgramDeploymentTransactionBody { + +} + +#[repr(C)] +pub struct TransactionBody { + pub public_body: *const PublicTransactionBody, + pub private_body: *const PrivateTransactionBody, + pub program_deployment_body: *const ProgramDeploymentTransactionBody, +} + +#[repr(C)] +pub struct Transaction { + pub body: TransactionBody, + pub kind: TransactionKind, +} + +#[repr(C)] +pub struct BlockBody { + pub txs: *const Transaction, + pub len: usize, +} + +impl Default for BlockBody { + fn default() -> Self { + Self { + txs: std::ptr::null_mut(), + len: 0, + } + } +} + +#[repr(C)] +pub struct BlockHeader { + pub block_id: BlockId, + pub prev_block_hash: HashType, + pub hash: HashType, + pub timestamp: Timestamp, + pub signature: Signature, +} + +#[repr(C)] +pub enum BedrockStatus { + Pending = 0x0, + Safe, + Finalized, +} + +#[repr(C)] +pub enum TransactionKind { + Public = 0x0, + Private, + ProgramDeploy, +} \ No newline at end of file From 37f59281c07ab11747011dbbfef8d7fd4ca3718a Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Mon, 27 Apr 2026 15:38:06 +0300 Subject: [PATCH 04/11] fix: all types added --- Cargo.lock | 2 + indexer_ffi/Cargo.toml | 2 + indexer_ffi/indexer_ffi.h | 83 +--------- indexer_ffi/src/api/convert.rs | 12 +- indexer_ffi/src/api/mod.rs | 2 +- indexer_ffi/src/api/query.rs | 9 +- indexer_ffi/src/api/types.rs | 99 ------------ indexer_ffi/src/api/types/account.rs | 195 +++++++++++++++++++++++ indexer_ffi/src/api/types/block.rs | 36 +++++ indexer_ffi/src/api/types/mod.rs | 29 ++++ indexer_ffi/src/api/types/transaction.rs | 101 ++++++++++++ wallet-ffi/Cargo.toml | 1 + 12 files changed, 384 insertions(+), 187 deletions(-) delete mode 100644 indexer_ffi/src/api/types.rs create mode 100644 indexer_ffi/src/api/types/account.rs create mode 100644 indexer_ffi/src/api/types/block.rs create mode 100644 indexer_ffi/src/api/types/mod.rs create mode 100644 indexer_ffi/src/api/types/transaction.rs diff --git a/Cargo.lock b/Cargo.lock index 769b5fb3..6517f789 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3486,6 +3486,8 @@ dependencies = [ "indexer_service", "indexer_service_rpc", "log", + "nssa", + "nssa_core", "sequencer_core", "tokio", "url", diff --git a/indexer_ffi/Cargo.toml b/indexer_ffi/Cargo.toml index 8099ad80..425e322a 100644 --- a/indexer_ffi/Cargo.toml +++ b/indexer_ffi/Cargo.toml @@ -5,6 +5,8 @@ name = "indexer_ffi" version = "0.1.0" [dependencies] +nssa_core.workspace = true +nssa.workspace = true indexer_service.workspace = true sequencer_core.workspace = true indexer_service_rpc.workspace = true diff --git a/indexer_ffi/indexer_ffi.h b/indexer_ffi/indexer_ffi.h index b734b59f..cce80837 100644 --- a/indexer_ffi/indexer_ffi.h +++ b/indexer_ffi/indexer_ffi.h @@ -10,24 +10,6 @@ typedef enum OperationStatus { ClientError = 3, } OperationStatus; -typedef enum TransactionKind { - Public = 0, - Private, - ProgramDeploy, -} TransactionKind; - -typedef enum BedrockStatus { - Pending = 0, - Safe, - Finalized, -} BedrockStatus; - -typedef struct Vec_AccountId Vec_AccountId; - -typedef struct Vec_Nonce Vec_Nonce; - -typedef struct Vec_u32 Vec_u32; - typedef struct IndexerServiceFFI { void *indexer_handle; void *runtime; @@ -58,69 +40,6 @@ typedef struct PointerResult_u64__OperationStatus { enum OperationStatus error; } PointerResult_u64__OperationStatus; -typedef uint64_t BlockId; - -typedef uint8_t HashType[32]; - -typedef uint64_t Timestamp; - -typedef uint8_t Signature[64]; - -typedef struct BlockHeader { - BlockId block_id; - HashType prev_block_hash; - HashType hash; - Timestamp timestamp; - Signature signature; -} BlockHeader; - -typedef uint32_t ProgramId[8]; - -typedef struct PublicMessage { - ProgramId program_id; - struct Vec_AccountId account_ids; - struct Vec_Nonce nonces; - struct Vec_u32 instruction_data; -} PublicMessage; - -typedef struct WitnessSet { - -} WitnessSet; - -typedef struct PublicTransactionBody { - HashType hash; - struct PublicMessage message; - struct WitnessSet witness_set; -} PublicTransactionBody; - -typedef struct TransactionBody { - struct PublicTransactionBody *public_body; -} TransactionBody; - -typedef struct Transaction { - struct TransactionBody body; - enum TransactionKind kind; -} Transaction; - -typedef struct BlockBody { - struct Transaction *txs; - uintptr_t len; -} BlockBody; - -typedef uint8_t MsgId[32]; - -typedef struct Block { - struct BlockHeader header; - struct BlockBody body; - enum BedrockStatus bedrock_status; - MsgId bedrock_parent_id; -} Block; - -typedef struct BlockOpt { - struct Block *block; - bool is_ok; -} BlockOpt; - /** * Simple wrapper around a pointer to a value or an error. * @@ -128,7 +47,7 @@ typedef struct BlockOpt { * dereferencing the pointer. */ typedef struct PointerResult_BlockOpt__OperationStatus { - struct BlockOpt *value; + BlockOpt *value; enum OperationStatus error; } PointerResult_BlockOpt__OperationStatus; diff --git a/indexer_ffi/src/api/convert.rs b/indexer_ffi/src/api/convert.rs index d970de38..f5636b89 100644 --- a/indexer_ffi/src/api/convert.rs +++ b/indexer_ffi/src/api/convert.rs @@ -3,10 +3,14 @@ use crate::api::types::BlockOpt; impl From> for BlockOpt { fn from(value: Option) -> Self { match value { - None => BlockOpt { block: std::ptr::null_mut(), is_ok: false }, - Some(block_orig) => BlockOpt { block: block_orig.into(), is_ok: true } + None => BlockOpt { + block: std::ptr::null_mut(), + is_ok: false, + }, + Some(block_orig) => BlockOpt { + block: block_orig.into(), + is_ok: true, + }, } } } - -impl From<> \ No newline at end of file diff --git a/indexer_ffi/src/api/mod.rs b/indexer_ffi/src/api/mod.rs index 9221a65c..4c80629b 100644 --- a/indexer_ffi/src/api/mod.rs +++ b/indexer_ffi/src/api/mod.rs @@ -1,9 +1,9 @@ pub use result::PointerResult; pub mod client; +pub mod convert; pub mod lifecycle; pub mod memory; pub mod query; pub mod result; pub mod types; -pub mod convert; diff --git a/indexer_ffi/src/api/query.rs b/indexer_ffi/src/api/query.rs index c4dd26b1..510f1383 100644 --- a/indexer_ffi/src/api/query.rs +++ b/indexer_ffi/src/api/query.rs @@ -1,6 +1,13 @@ use indexer_service_rpc::RpcClient; -use crate::{IndexerServiceFFI, api::{PointerResult, types::{Block, BlockId, BlockOpt}}, errors::OperationStatus}; +use crate::{ + IndexerServiceFFI, + api::{ + PointerResult, + types::{Block, BlockId, BlockOpt}, + }, + errors::OperationStatus, +}; /// Stops and frees the resources associated with the given indexer service. /// diff --git a/indexer_ffi/src/api/types.rs b/indexer_ffi/src/api/types.rs deleted file mode 100644 index 920b4764..00000000 --- a/indexer_ffi/src/api/types.rs +++ /dev/null @@ -1,99 +0,0 @@ -pub type HashType = [u8; 32]; -pub type MsgId = [u8; 32]; -pub type BlockId = u64; -pub type Timestamp = u64; -pub type Signature = [u8; 64]; -pub type ProgramId = [u32; 8]; -pub type AccountId = [u8; 32]; -pub type Nonce = u128; -pub type PublicKey = [u8; 32]; - -#[repr(C)] -pub struct Block { - pub header: BlockHeader, - pub body: BlockBody, - pub bedrock_status: BedrockStatus, - pub bedrock_parent_id: MsgId, -} - -#[repr(C)] -pub struct BlockOpt { - pub block: *const Block, - pub is_ok: bool, -} - -#[repr(C)] -pub struct PublicMessage { - pub program_id: ProgramId, - pub account_ids: Vec, - pub nonces: Vec, - pub instruction_data: Vec, -} - -#[repr(C)] -pub struct PublicTransactionBody { - pub hash: HashType, - pub message: PublicMessage, - pub witness_set: Vec<(Signature, PublicKey)>, -} - -#[repr(C)] -pub struct PrivateTransactionBody { - -} - -#[repr(C)] -pub struct ProgramDeploymentTransactionBody { - -} - -#[repr(C)] -pub struct TransactionBody { - pub public_body: *const PublicTransactionBody, - pub private_body: *const PrivateTransactionBody, - pub program_deployment_body: *const ProgramDeploymentTransactionBody, -} - -#[repr(C)] -pub struct Transaction { - pub body: TransactionBody, - pub kind: TransactionKind, -} - -#[repr(C)] -pub struct BlockBody { - pub txs: *const Transaction, - pub len: usize, -} - -impl Default for BlockBody { - fn default() -> Self { - Self { - txs: std::ptr::null_mut(), - len: 0, - } - } -} - -#[repr(C)] -pub struct BlockHeader { - pub block_id: BlockId, - pub prev_block_hash: HashType, - pub hash: HashType, - pub timestamp: Timestamp, - pub signature: Signature, -} - -#[repr(C)] -pub enum BedrockStatus { - Pending = 0x0, - Safe, - Finalized, -} - -#[repr(C)] -pub enum TransactionKind { - Public = 0x0, - Private, - ProgramDeploy, -} \ No newline at end of file diff --git a/indexer_ffi/src/api/types/account.rs b/indexer_ffi/src/api/types/account.rs new file mode 100644 index 00000000..e36230ae --- /dev/null +++ b/indexer_ffi/src/api/types/account.rs @@ -0,0 +1,195 @@ +//! C-compatible type definitions for the FFI layer. + +use std::ptr; + +use crate::api::types::FfiVec; + +/// 32-byte array type for `AccountId`, keys, hashes, etc. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FfiBytes32 { + pub data: [u8; 32], +} + +/// 64-byte array type for signatures, etc. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct FfiBytes64 { + pub data: [u8; 64], +} + +impl Default for FfiBytes64 { + fn default() -> Self { + Self { data: [0; 64] } + } +} + +/// Program ID - 8 u32 values (32 bytes total). +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FfiProgramId { + pub data: [u32; 8], +} + +/// U128 - 16 bytes little endian. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FfiU128 { + pub data: [u8; 16], +} + +/// Account data structure - C-compatible version of nssa Account. +/// +/// Note: `balance` and `nonce` are u128 values represented as little-endian +/// byte arrays since C doesn't have native u128 support. +#[repr(C)] +pub struct FfiAccount { + pub program_owner: FfiProgramId, + /// Balance as little-endian [u8; 16]. + pub balance: FfiU128, + /// Pointer to account data bytes. + pub data: *const u8, + /// Length of account data. + pub data_len: usize, + /// Nonce as little-endian [u8; 16]. + pub nonce: FfiU128, +} + +#[repr(C)] +pub struct FfiAccountList { + pub entries: *const FfiAccount, + pub len: usize, +} + +impl Default for FfiAccount { + fn default() -> Self { + Self { + program_owner: FfiProgramId::default(), + balance: FfiU128::default(), + data: std::ptr::null(), + data_len: 0, + nonce: FfiU128::default(), + } + } +} + +/// Public keys for a private account (safe to expose). +#[repr(C)] +pub struct FfiPrivateAccountKeys { + /// Nullifier public key (32 bytes). + pub nullifier_public_key: FfiBytes32, + /// viewing public key (compressed secp256k1 point). + pub viewing_public_key: *const u8, + /// Length of viewing public key (typically 33 bytes). + pub viewing_public_key_len: usize, +} + +impl Default for FfiPrivateAccountKeys { + fn default() -> Self { + Self { + nullifier_public_key: FfiBytes32::default(), + viewing_public_key: std::ptr::null(), + viewing_public_key_len: 0, + } + } +} + +/// Public key info for a public account. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FfiPublicAccountKey { + pub public_key: FfiBytes32, +} + +/// Single entry in the account list. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct FfiAccountIdListEntry { + pub account_id: FfiBytes32, + pub is_public: bool, +} + +/// List of accounts returned by `wallet_ffi_list_accounts`. +#[repr(C)] +pub struct FfiAccountIdList { + pub entries: *mut FfiAccountIdListEntry, + pub count: usize, +} + +impl Default for FfiAccountIdList { + fn default() -> Self { + Self { + entries: std::ptr::null_mut(), + count: 0, + } + } +} + +pub type FfiVecBytes32 = FfiVec; + +// Helper functions to convert between Rust and FFI types + +impl FfiBytes32 { + /// Create from a 32-byte array. + #[must_use] + pub const fn from_bytes(bytes: [u8; 32]) -> Self { + Self { data: bytes } + } + + /// Create from an `AccountId`. + #[must_use] + pub const fn from_account_id(id: &nssa::AccountId) -> Self { + Self { data: *id.value() } + } +} + +impl From for FfiU128 { + fn from(value: u128) -> Self { + Self { + data: value.to_le_bytes(), + } + } +} + +impl From<&nssa::AccountId> for FfiBytes32 { + fn from(id: &nssa::AccountId) -> Self { + Self::from_account_id(id) + } +} + +impl From for FfiAccount { + #[expect( + clippy::as_conversions, + reason = "We need to convert to byte arrays for FFI" + )] + fn from(value: nssa::Account) -> Self { + // Convert account data to FFI type + let data_vec: Vec = value.data.into(); + let data_len = data_vec.len(); + let data = if data_len > 0 { + let data_boxed = data_vec.into_boxed_slice(); + Box::into_raw(data_boxed) as *const u8 + } else { + ptr::null() + }; + + let program_owner = FfiProgramId { + data: value.program_owner, + }; + Self { + program_owner, + balance: value.balance.into(), + data, + data_len, + nonce: value.nonce.0.into(), + } + } +} + +impl From for FfiPublicAccountKey { + fn from(value: nssa::PublicKey) -> Self { + Self { + public_key: FfiBytes32::from_bytes(*value.value()), + } + } +} diff --git a/indexer_ffi/src/api/types/block.rs b/indexer_ffi/src/api/types/block.rs new file mode 100644 index 00000000..a31bd495 --- /dev/null +++ b/indexer_ffi/src/api/types/block.rs @@ -0,0 +1,36 @@ +use crate::api::types::{ + FfiBlockId, FfiHashType, FfiMsgId, FfiSignature, FfiTimestamp, FfiVec, + transaction::FfiTransaction, +}; + +#[repr(C)] +pub struct FfiBlock { + pub header: FfiBlockHeader, + pub body: FfiBlockBody, + pub bedrock_status: FfiBedrockStatus, + pub bedrock_parent_id: FfiMsgId, +} + +#[repr(C)] +pub struct FfiBlockOpt { + pub block: *const FfiBlock, + pub is_some: bool, +} + +pub type FfiBlockBody = FfiVec; + +#[repr(C)] +pub struct FfiBlockHeader { + pub block_id: FfiBlockId, + pub prev_block_hash: FfiHashType, + pub hash: FfiHashType, + pub timestamp: FfiTimestamp, + pub signature: FfiSignature, +} + +#[repr(C)] +pub enum FfiBedrockStatus { + Pending = 0x0, + Safe, + Finalized, +} diff --git a/indexer_ffi/src/api/types/mod.rs b/indexer_ffi/src/api/types/mod.rs new file mode 100644 index 00000000..73336c05 --- /dev/null +++ b/indexer_ffi/src/api/types/mod.rs @@ -0,0 +1,29 @@ +use crate::api::types::account::{FfiBytes32, FfiBytes64, FfiU128}; + +pub mod account; +pub mod block; +pub mod transaction; + +pub type FfiHashType = FfiBytes32; +pub type FfiMsgId = FfiBytes32; +pub type FfiBlockId = u64; +pub type FfiTimestamp = u64; +pub type FfiSignature = FfiBytes64; +pub type FfiAccountId = FfiBytes32; +pub type FfiNonce = FfiU128; +pub type FfiPublicKey = FfiBytes32; + +#[repr(C)] +pub struct FfiVec { + pub entries: *const T, + pub len: usize, +} + +impl Default for FfiVec { + fn default() -> Self { + Self { + entries: std::ptr::null(), + len: 0, + } + } +} diff --git a/indexer_ffi/src/api/types/transaction.rs b/indexer_ffi/src/api/types/transaction.rs new file mode 100644 index 00000000..4f92d492 --- /dev/null +++ b/indexer_ffi/src/api/types/transaction.rs @@ -0,0 +1,101 @@ +use crate::api::types::{ + FfiHashType, FfiNonce, FfiPublicKey, FfiSignature, FfiVec, + account::{FfiAccountIdList, FfiAccountList, FfiBytes32, FfiProgramId, FfiVecBytes32}, +}; + +#[repr(C)] +pub struct FfiPublicTransactionBody { + pub hash: FfiHashType, + pub message: FfiPublicMessage, + pub witness_set: FfiSignaturePubKeyList, +} + +pub type FfiNonceList = FfiVec; + +pub type FfiInstructionDataList = FfiVec; + +#[repr(C)] +pub struct FfiPublicMessage { + pub program_id: FfiProgramId, + pub account_ids: FfiAccountIdList, + pub nonces: FfiNonceList, + pub instruction_data: FfiInstructionDataList, +} + +#[repr(C)] +pub struct FfiPrivateTransactionBody { + pub hash: FfiHashType, + pub message: FfiPrivacyPreservingMessage, + pub witness_set: FfiSignaturePubKeyList, + pub proof: FfiProofOpt, +} + +#[repr(C)] +pub struct FfiPrivacyPreservingMessage { + pub public_account_ids: FfiAccountIdList, + pub nonces: FfiNonceList, + pub public_post_states: FfiAccountList, + pub encrypted_private_post_states: FfiVec, + pub new_commitments: FfiVecBytes32, + pub new_nullifiers: FfiVec, + pub block_validity_window: [u64; 2], + pub timestamp_validity_window: [u64; 2], +} + +#[repr(C)] +pub struct NullifierCommitmentSet { + pub nullifier: FfiBytes32, + pub commitment_set_digest: FfiBytes32, +} + +#[repr(C)] +pub struct FfiEncryptedAccountData { + pub ciphertext: FfiVec, + pub epk: FfiVec, + pub view_tag: u8, +} + +#[repr(C)] +pub struct FfiSignaturePubKeyEntry { + pub signature: FfiSignature, + pub public_key: FfiPublicKey, +} + +pub struct FfiSignaturePubKeyList { + pub entries: *const FfiSignaturePubKeyEntry, + pub len: usize, +} + +#[repr(C)] +pub struct FfiProofOpt { + pub proof: FfiVec, + pub is_some: bool, +} + +#[repr(C)] +pub struct FfiProgramDeploymentTransactionBody { + pub hash: FfiHashType, + pub message: FfiProgramDeploymentMessage, +} + +pub type FfiProgramDeploymentMessage = FfiVec; + +#[repr(C)] +pub struct FfiTransactionBody { + pub public_body: *const FfiPublicTransactionBody, + pub private_body: *const FfiPrivateTransactionBody, + pub program_deployment_body: *const FfiProgramDeploymentTransactionBody, +} + +#[repr(C)] +pub struct FfiTransaction { + pub body: FfiTransactionBody, + pub kind: FfiTransactionKind, +} + +#[repr(C)] +pub enum FfiTransactionKind { + Public = 0x0, + Private, + ProgramDeploy, +} diff --git a/wallet-ffi/Cargo.toml b/wallet-ffi/Cargo.toml index 0af20a54..37d552ec 100644 --- a/wallet-ffi/Cargo.toml +++ b/wallet-ffi/Cargo.toml @@ -15,6 +15,7 @@ wallet.workspace = true nssa.workspace = true nssa_core.workspace = true sequencer_service_rpc = { workspace = true, features = ["client"] } + tokio.workspace = true [build-dependencies] From 89ea8842071b8e2450c97c8f2230ae378f94223c Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Tue, 28 Apr 2026 19:36:23 +0300 Subject: [PATCH 05/11] feat: working queries --- Cargo.lock | 2 +- indexer_ffi/Cargo.toml | 2 +- indexer_ffi/indexer_ffi.h | 261 +++++++++++++++++++++- indexer_ffi/src/api/convert.rs | 16 -- indexer_ffi/src/api/lifecycle.rs | 2 +- indexer_ffi/src/api/mod.rs | 1 - indexer_ffi/src/api/query.rs | 16 +- indexer_ffi/src/api/types/account.rs | 40 +--- indexer_ffi/src/api/types/block.rs | 48 +++- indexer_ffi/src/api/types/mod.rs | 75 ++++++- indexer_ffi/src/api/types/transaction.rs | 224 +++++++++++++++++-- indexer_ffi/src/api/types/vectors.rs | 31 +++ integration_tests/src/test_context_ffi.rs | 2 +- integration_tests/tests/indexer_ffi.rs | 1 + 14 files changed, 618 insertions(+), 103 deletions(-) delete mode 100644 indexer_ffi/src/api/convert.rs create mode 100644 indexer_ffi/src/api/types/vectors.rs diff --git a/Cargo.lock b/Cargo.lock index 6517f789..37ab61e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3484,10 +3484,10 @@ version = "0.1.0" dependencies = [ "cbindgen", "indexer_service", + "indexer_service_protocol", "indexer_service_rpc", "log", "nssa", - "nssa_core", "sequencer_core", "tokio", "url", diff --git a/indexer_ffi/Cargo.toml b/indexer_ffi/Cargo.toml index 425e322a..0a140d19 100644 --- a/indexer_ffi/Cargo.toml +++ b/indexer_ffi/Cargo.toml @@ -5,11 +5,11 @@ name = "indexer_ffi" version = "0.1.0" [dependencies] -nssa_core.workspace = true nssa.workspace = true indexer_service.workspace = true sequencer_core.workspace = true indexer_service_rpc.workspace = true +indexer_service_protocol.workspace = true url.workspace = true log = { workspace = true } diff --git a/indexer_ffi/indexer_ffi.h b/indexer_ffi/indexer_ffi.h index cce80837..1094e1de 100644 --- a/indexer_ffi/indexer_ffi.h +++ b/indexer_ffi/indexer_ffi.h @@ -10,6 +10,18 @@ typedef enum OperationStatus { ClientError = 3, } OperationStatus; +typedef enum FfiTransactionKind { + Public = 0, + Private, + ProgramDeploy, +} FfiTransactionKind; + +typedef enum FfiBedrockStatus { + Pending = 0, + Safe, + Finalized, +} FfiBedrockStatus; + typedef struct IndexerServiceFFI { void *indexer_handle; void *runtime; @@ -40,16 +52,255 @@ typedef struct PointerResult_u64__OperationStatus { enum OperationStatus error; } PointerResult_u64__OperationStatus; +typedef uint64_t FfiBlockId; + +/** + * 32-byte array type for `AccountId`, keys, hashes, etc. + */ +typedef struct FfiBytes32 { + uint8_t data[32]; +} FfiBytes32; + +typedef struct FfiBytes32 FfiHashType; + +typedef uint64_t FfiTimestamp; + +/** + * 64-byte array type for signatures, etc. + */ +typedef struct FfiBytes64 { + uint8_t data[64]; +} FfiBytes64; + +typedef struct FfiBytes64 FfiSignature; + +typedef struct FfiBlockHeader { + FfiBlockId block_id; + FfiHashType prev_block_hash; + FfiHashType hash; + FfiTimestamp timestamp; + FfiSignature signature; +} FfiBlockHeader; + +/** + * Program ID - 8 u32 values (32 bytes total). + */ +typedef struct FfiProgramId { + uint32_t data[8]; +} FfiProgramId; + +typedef struct FfiBytes32 FfiAccountId; + +typedef struct FfiVec_FfiAccountId { + FfiAccountId *entries; + uintptr_t len; + uintptr_t capacity; +} FfiVec_FfiAccountId; + +typedef struct FfiVec_FfiAccountId FfiAccountIdList; + +/** + * U128 - 16 bytes little endian. + */ +typedef struct FfiU128 { + uint8_t data[16]; +} FfiU128; + +typedef struct FfiU128 FfiNonce; + +typedef struct FfiVec_FfiNonce { + FfiNonce *entries; + uintptr_t len; + uintptr_t capacity; +} FfiVec_FfiNonce; + +typedef struct FfiVec_FfiNonce FfiNonceList; + +typedef struct FfiVec_u32 { + uint32_t *entries; + uintptr_t len; + uintptr_t capacity; +} FfiVec_u32; + +typedef struct FfiVec_u32 FfiInstructionDataList; + +typedef struct FfiPublicMessage { + struct FfiProgramId program_id; + FfiAccountIdList account_ids; + FfiNonceList nonces; + FfiInstructionDataList instruction_data; +} FfiPublicMessage; + +typedef struct FfiBytes32 FfiPublicKey; + +typedef struct FfiSignaturePubKeyEntry { + FfiSignature signature; + FfiPublicKey public_key; +} FfiSignaturePubKeyEntry; + +typedef struct FfiVec_FfiSignaturePubKeyEntry { + struct FfiSignaturePubKeyEntry *entries; + uintptr_t len; + uintptr_t capacity; +} FfiVec_FfiSignaturePubKeyEntry; + +typedef struct FfiVec_FfiSignaturePubKeyEntry FfiSignaturePubKeyList; + +typedef struct FfiPublicTransactionBody { + FfiHashType hash; + struct FfiPublicMessage message; + FfiSignaturePubKeyList witness_set; +} FfiPublicTransactionBody; + +/** + * Account data structure - C-compatible version of nssa Account. + * + * Note: `balance` and `nonce` are u128 values represented as little-endian + * byte arrays since C doesn't have native u128 support. + */ +typedef struct FfiAccount { + struct FfiProgramId program_owner; + /** + * Balance as little-endian [u8; 16]. + */ + struct FfiU128 balance; + /** + * Pointer to account data bytes. + */ + const uint8_t *data; + /** + * Length of account data. + */ + uintptr_t data_len; + /** + * Nonce as little-endian [u8; 16]. + */ + struct FfiU128 nonce; +} FfiAccount; + +typedef struct FfiVec_FfiAccount { + struct FfiAccount *entries; + uintptr_t len; + uintptr_t capacity; +} FfiVec_FfiAccount; + +typedef struct FfiVec_FfiAccount FfiAccountList; + +typedef struct FfiVec_u8 { + uint8_t *entries; + uintptr_t len; + uintptr_t capacity; +} FfiVec_u8; + +typedef struct FfiVec_u8 FfiVecU8; + +typedef struct FfiEncryptedAccountData { + FfiVecU8 ciphertext; + FfiVecU8 epk; + uint8_t view_tag; +} FfiEncryptedAccountData; + +typedef struct FfiVec_FfiEncryptedAccountData { + struct FfiEncryptedAccountData *entries; + uintptr_t len; + uintptr_t capacity; +} FfiVec_FfiEncryptedAccountData; + +typedef struct FfiVec_FfiEncryptedAccountData FfiEncryptedAccountDataList; + +typedef struct FfiVec_FfiBytes32 { + struct FfiBytes32 *entries; + uintptr_t len; + uintptr_t capacity; +} FfiVec_FfiBytes32; + +typedef struct FfiVec_FfiBytes32 FfiVecBytes32; + +typedef struct FfiNullifierCommitmentSet { + struct FfiBytes32 nullifier; + struct FfiBytes32 commitment_set_digest; +} FfiNullifierCommitmentSet; + +typedef struct FfiVec_FfiNullifierCommitmentSet { + struct FfiNullifierCommitmentSet *entries; + uintptr_t len; + uintptr_t capacity; +} FfiVec_FfiNullifierCommitmentSet; + +typedef struct FfiVec_FfiNullifierCommitmentSet FfiNullifierCommitmentSetList; + +typedef struct FfiPrivacyPreservingMessage { + FfiAccountIdList public_account_ids; + FfiNonceList nonces; + FfiAccountList public_post_states; + FfiEncryptedAccountDataList encrypted_private_post_states; + FfiVecBytes32 new_commitments; + FfiNullifierCommitmentSetList new_nullifiers; + uint64_t block_validity_window[2]; + uint64_t timestamp_validity_window[2]; +} FfiPrivacyPreservingMessage; + +typedef FfiVecU8 FfiProof; + +typedef struct FfiPrivateTransactionBody { + FfiHashType hash; + struct FfiPrivacyPreservingMessage message; + FfiSignaturePubKeyList witness_set; + FfiProof proof; +} FfiPrivateTransactionBody; + +typedef FfiVecU8 FfiProgramDeploymentMessage; + +typedef struct FfiProgramDeploymentTransactionBody { + FfiHashType hash; + FfiProgramDeploymentMessage message; +} FfiProgramDeploymentTransactionBody; + +typedef struct FfiTransactionBody { + struct FfiPublicTransactionBody *public_body; + struct FfiPrivateTransactionBody *private_body; + struct FfiProgramDeploymentTransactionBody *program_deployment_body; +} FfiTransactionBody; + +typedef struct FfiTransaction { + struct FfiTransactionBody body; + enum FfiTransactionKind kind; +} FfiTransaction; + +typedef struct FfiVec_FfiTransaction { + struct FfiTransaction *entries; + uintptr_t len; + uintptr_t capacity; +} FfiVec_FfiTransaction; + +typedef struct FfiVec_FfiTransaction FfiBlockBody; + +typedef struct FfiBytes32 FfiMsgId; + +typedef struct FfiBlock { + struct FfiBlockHeader header; + FfiBlockBody body; + enum FfiBedrockStatus bedrock_status; + FfiMsgId bedrock_parent_id; +} FfiBlock; + +typedef struct FfiOption_FfiBlock { + struct FfiBlock *value; + bool is_some; +} FfiOption_FfiBlock; + +typedef struct FfiOption_FfiBlock FfiBlockOpt; + /** * 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_BlockOpt__OperationStatus { - BlockOpt *value; +typedef struct PointerResult_FfiBlockOpt__OperationStatus { + FfiBlockOpt *value; enum OperationStatus error; -} PointerResult_BlockOpt__OperationStatus; +} PointerResult_FfiBlockOpt__OperationStatus; /** * Creates and starts an indexer based on the provided @@ -133,8 +384,8 @@ struct PointerResult_u64__OperationStatus query_last_block(const struct IndexerS * - The `IndexerServiceFFI` instance was created by this library * - The pointer will not be used after this function returns */ -struct PointerResult_BlockOpt__OperationStatus query_block(const struct IndexerServiceFFI *indexer, - BlockId block_id); +struct PointerResult_FfiBlockOpt__OperationStatus query_block(const struct IndexerServiceFFI *indexer, + FfiBlockId block_id); bool is_ok(const enum OperationStatus *self); diff --git a/indexer_ffi/src/api/convert.rs b/indexer_ffi/src/api/convert.rs deleted file mode 100644 index f5636b89..00000000 --- a/indexer_ffi/src/api/convert.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::api::types::BlockOpt; - -impl From> for BlockOpt { - fn from(value: Option) -> Self { - match value { - None => BlockOpt { - block: std::ptr::null_mut(), - is_ok: false, - }, - Some(block_orig) => BlockOpt { - block: block_orig.into(), - is_ok: true, - }, - } - } -} diff --git a/indexer_ffi/src/api/lifecycle.rs b/indexer_ffi/src/api/lifecycle.rs index 087f7803..15c6e619 100644 --- a/indexer_ffi/src/api/lifecycle.rs +++ b/indexer_ffi/src/api/lifecycle.rs @@ -1,6 +1,6 @@ use std::{ffi::c_char, path::PathBuf}; -use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait}; +use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait as _}; use tokio::runtime::Runtime; use crate::{ diff --git a/indexer_ffi/src/api/mod.rs b/indexer_ffi/src/api/mod.rs index 4c80629b..ea2b91d7 100644 --- a/indexer_ffi/src/api/mod.rs +++ b/indexer_ffi/src/api/mod.rs @@ -1,7 +1,6 @@ pub use result::PointerResult; pub mod client; -pub mod convert; pub mod lifecycle; pub mod memory; pub mod query; diff --git a/indexer_ffi/src/api/query.rs b/indexer_ffi/src/api/query.rs index 510f1383..e840fed7 100644 --- a/indexer_ffi/src/api/query.rs +++ b/indexer_ffi/src/api/query.rs @@ -1,10 +1,10 @@ -use indexer_service_rpc::RpcClient; +use indexer_service_rpc::RpcClient as _; use crate::{ IndexerServiceFFI, api::{ PointerResult, - types::{Block, BlockId, BlockOpt}, + types::{FfiBlockId, block::FfiBlockOpt}, }, errors::OperationStatus, }; @@ -66,8 +66,8 @@ pub unsafe extern "C" fn query_last_block( #[unsafe(no_mangle)] pub unsafe extern "C" fn query_block( indexer: *const IndexerServiceFFI, - block_id: BlockId, -) -> PointerResult { + block_id: FfiBlockId, +) -> 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); @@ -82,6 +82,12 @@ pub unsafe extern "C" fn query_block( .block_on(client.get_block_by_id(block_id)) .map_or_else( |_| PointerResult::from_error(OperationStatus::ClientError), - |block| PointerResult::from_value(block.into()), + |block_opt| { + let block_ffi = block_opt.map_or_else(FfiBlockOpt::from_none, |block| { + FfiBlockOpt::from_value(block.into()) + }); + + PointerResult::from_value(block_ffi) + }, ) } diff --git a/indexer_ffi/src/api/types/account.rs b/indexer_ffi/src/api/types/account.rs index e36230ae..568bfa1c 100644 --- a/indexer_ffi/src/api/types/account.rs +++ b/indexer_ffi/src/api/types/account.rs @@ -2,7 +2,7 @@ use std::ptr; -use crate::api::types::FfiVec; +use indexer_service_protocol::ProgramId; /// 32-byte array type for `AccountId`, keys, hashes, etc. #[repr(C)] @@ -31,6 +31,12 @@ pub struct FfiProgramId { pub data: [u32; 8], } +impl From for FfiProgramId { + fn from(value: ProgramId) -> Self { + Self { data: value.0 } + } +} + /// U128 - 16 bytes little endian. #[repr(C)] #[derive(Clone, Copy, Default)] @@ -55,12 +61,6 @@ pub struct FfiAccount { pub nonce: FfiU128, } -#[repr(C)] -pub struct FfiAccountList { - pub entries: *const FfiAccount, - pub len: usize, -} - impl Default for FfiAccount { fn default() -> Self { Self { @@ -101,32 +101,6 @@ pub struct FfiPublicAccountKey { pub public_key: FfiBytes32, } -/// Single entry in the account list. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct FfiAccountIdListEntry { - pub account_id: FfiBytes32, - pub is_public: bool, -} - -/// List of accounts returned by `wallet_ffi_list_accounts`. -#[repr(C)] -pub struct FfiAccountIdList { - pub entries: *mut FfiAccountIdListEntry, - pub count: usize, -} - -impl Default for FfiAccountIdList { - fn default() -> Self { - Self { - entries: std::ptr::null_mut(), - count: 0, - } - } -} - -pub type FfiVecBytes32 = FfiVec; - // Helper functions to convert between Rust and FFI types impl FfiBytes32 { diff --git a/indexer_ffi/src/api/types/block.rs b/indexer_ffi/src/api/types/block.rs index a31bd495..1ee8215f 100644 --- a/indexer_ffi/src/api/types/block.rs +++ b/indexer_ffi/src/api/types/block.rs @@ -1,6 +1,7 @@ +use indexer_service_protocol::{BedrockStatus, Block, BlockHeader}; + use crate::api::types::{ - FfiBlockId, FfiHashType, FfiMsgId, FfiSignature, FfiTimestamp, FfiVec, - transaction::FfiTransaction, + FfiBlockId, FfiHashType, FfiMsgId, FfiOption, FfiSignature, FfiTimestamp, vectors::FfiBlockBody, }; #[repr(C)] @@ -11,13 +12,24 @@ pub struct FfiBlock { pub bedrock_parent_id: FfiMsgId, } -#[repr(C)] -pub struct FfiBlockOpt { - pub block: *const FfiBlock, - pub is_some: bool, +impl From for FfiBlock { + fn from(value: Block) -> Self { + Self { + header: value.header.into(), + body: value + .body + .transactions + .into_iter() + .map(Into::into) + .collect::>() + .into(), + bedrock_status: value.bedrock_status.into(), + bedrock_parent_id: value.bedrock_parent_id.into(), + } + } } -pub type FfiBlockBody = FfiVec; +pub type FfiBlockOpt = FfiOption; #[repr(C)] pub struct FfiBlockHeader { @@ -28,9 +40,31 @@ pub struct FfiBlockHeader { pub signature: FfiSignature, } +impl From for FfiBlockHeader { + fn from(value: BlockHeader) -> Self { + Self { + block_id: value.block_id, + prev_block_hash: value.prev_block_hash.into(), + hash: value.hash.into(), + timestamp: value.timestamp, + signature: value.signature.into(), + } + } +} + #[repr(C)] pub enum FfiBedrockStatus { Pending = 0x0, Safe, Finalized, } + +impl From for FfiBedrockStatus { + fn from(value: BedrockStatus) -> Self { + match value { + BedrockStatus::Finalized => Self::Finalized, + BedrockStatus::Pending => Self::Pending, + BedrockStatus::Safe => Self::Safe, + } + } +} diff --git a/indexer_ffi/src/api/types/mod.rs b/indexer_ffi/src/api/types/mod.rs index 73336c05..d1b3e572 100644 --- a/indexer_ffi/src/api/types/mod.rs +++ b/indexer_ffi/src/api/types/mod.rs @@ -1,8 +1,11 @@ +use indexer_service_protocol::{AccountId, HashType, MantleMsgId, PublicKey, Signature}; + use crate::api::types::account::{FfiBytes32, FfiBytes64, FfiU128}; pub mod account; pub mod block; pub mod transaction; +pub mod vectors; pub type FfiHashType = FfiBytes32; pub type FfiMsgId = FfiBytes32; @@ -13,17 +16,73 @@ pub type FfiAccountId = FfiBytes32; pub type FfiNonce = FfiU128; pub type FfiPublicKey = FfiBytes32; -#[repr(C)] -pub struct FfiVec { - pub entries: *const T, - pub len: usize, +impl From for FfiHashType { + fn from(value: HashType) -> Self { + Self { data: value.0 } + } } -impl Default for FfiVec { - fn default() -> Self { +impl From for FfiMsgId { + fn from(value: MantleMsgId) -> Self { + Self { data: value.0 } + } +} + +impl From for FfiSignature { + fn from(value: Signature) -> Self { + Self { data: value.0 } + } +} + +impl From for FfiAccountId { + fn from(value: AccountId) -> Self { + Self { data: value.value } + } +} + +impl From for FfiPublicKey { + fn from(value: PublicKey) -> Self { + Self { data: value.0 } + } +} + +#[repr(C)] +pub struct FfiVec { + pub entries: *mut T, + pub len: usize, + pub capacity: usize, +} + +impl From> for FfiVec { + fn from(value: Vec) -> Self { + let (entries, len, capacity) = value.into_raw_parts(); Self { - entries: std::ptr::null(), - len: 0, + entries, + len, + capacity, + } + } +} + +#[repr(C)] +pub struct FfiOption { + pub value: *mut T, + pub is_some: bool, +} + +impl FfiOption { + pub fn from_value(val: T) -> Self { + Self { + value: Box::into_raw(Box::new(val)), + is_some: true, + } + } + + #[must_use] + pub const fn from_none() -> Self { + Self { + value: std::ptr::null_mut(), + is_some: false, } } } diff --git a/indexer_ffi/src/api/types/transaction.rs b/indexer_ffi/src/api/types/transaction.rs index 4f92d492..86e28133 100644 --- a/indexer_ffi/src/api/types/transaction.rs +++ b/indexer_ffi/src/api/types/transaction.rs @@ -1,6 +1,17 @@ +use indexer_service_protocol::{ + CommitmentSetDigest, EncryptedAccountData, Nullifier, PrivacyPreservingMessage, + PrivacyPreservingTransaction, ProgramDeploymentTransaction, PublicKey, PublicMessage, + PublicTransaction, Signature, Transaction, ValidityWindow, +}; + use crate::api::types::{ - FfiHashType, FfiNonce, FfiPublicKey, FfiSignature, FfiVec, - account::{FfiAccountIdList, FfiAccountList, FfiBytes32, FfiProgramId, FfiVecBytes32}, + FfiHashType, FfiPublicKey, FfiSignature, + account::{FfiBytes32, FfiProgramId}, + vectors::{ + FfiAccountIdList, FfiAccountList, FfiEncryptedAccountDataList, FfiInstructionDataList, + FfiNonceList, FfiNullifierCommitmentSetList, FfiProgramDeploymentMessage, FfiProof, + FfiSignaturePubKeyList, FfiVecBytes32, FfiVecU8, + }, }; #[repr(C)] @@ -10,9 +21,21 @@ pub struct FfiPublicTransactionBody { pub witness_set: FfiSignaturePubKeyList, } -pub type FfiNonceList = FfiVec; - -pub type FfiInstructionDataList = FfiVec; +impl From for FfiPublicTransactionBody { + fn from(value: PublicTransaction) -> Self { + Self { + hash: value.hash.into(), + message: value.message.into(), + witness_set: value + .witness_set + .signatures_and_public_keys + .into_iter() + .map(Into::into) + .collect::>() + .into(), + } + } +} #[repr(C)] pub struct FfiPublicMessage { @@ -22,12 +45,55 @@ pub struct FfiPublicMessage { pub instruction_data: FfiInstructionDataList, } +impl From for FfiPublicMessage { + fn from(value: PublicMessage) -> Self { + Self { + program_id: value.program_id.into(), + account_ids: value + .account_ids + .into_iter() + .map(Into::into) + .collect::>() + .into(), + nonces: value + .nonces + .into_iter() + .map(Into::into) + .collect::>() + .into(), + instruction_data: value.instruction_data.into(), + } + } +} + #[repr(C)] pub struct FfiPrivateTransactionBody { pub hash: FfiHashType, pub message: FfiPrivacyPreservingMessage, pub witness_set: FfiSignaturePubKeyList, - pub proof: FfiProofOpt, + pub proof: FfiProof, +} + +impl From for FfiPrivateTransactionBody { + fn from(value: PrivacyPreservingTransaction) -> Self { + Self { + hash: value.hash.into(), + message: value.message.into(), + witness_set: value + .witness_set + .signatures_and_public_keys + .into_iter() + .map(Into::into) + .collect::>() + .into(), + proof: value + .witness_set + .proof + .expect("Private execution: proof must be present") + .0 + .into(), + } + } } #[repr(C)] @@ -35,41 +101,106 @@ pub struct FfiPrivacyPreservingMessage { pub public_account_ids: FfiAccountIdList, pub nonces: FfiNonceList, pub public_post_states: FfiAccountList, - pub encrypted_private_post_states: FfiVec, + pub encrypted_private_post_states: FfiEncryptedAccountDataList, pub new_commitments: FfiVecBytes32, - pub new_nullifiers: FfiVec, + pub new_nullifiers: FfiNullifierCommitmentSetList, pub block_validity_window: [u64; 2], pub timestamp_validity_window: [u64; 2], } +impl From for FfiPrivacyPreservingMessage { + fn from(value: PrivacyPreservingMessage) -> Self { + Self { + public_account_ids: value + .public_account_ids + .into_iter() + .map(Into::into) + .collect::>() + .into(), + nonces: value + .nonces + .into_iter() + .map(Into::into) + .collect::>() + .into(), + public_post_states: value + .public_post_states + .into_iter() + .map(|acc_ind| -> nssa::Account { + acc_ind.try_into().expect("Source is in blocks, must fit") + }) + .map(Into::into) + .collect::>() + .into(), + encrypted_private_post_states: value + .encrypted_private_post_states + .into_iter() + .map(Into::into) + .collect::>() + .into(), + new_commitments: value + .new_commitments + .into_iter() + .map(|comm| FfiBytes32 { data: comm.0 }) + .collect::>() + .into(), + new_nullifiers: value + .new_nullifiers + .into_iter() + .map(Into::into) + .collect::>() + .into(), + block_validity_window: cast_validity_window(value.block_validity_window), + timestamp_validity_window: cast_validity_window(value.timestamp_validity_window), + } + } +} + #[repr(C)] -pub struct NullifierCommitmentSet { +pub struct FfiNullifierCommitmentSet { pub nullifier: FfiBytes32, pub commitment_set_digest: FfiBytes32, } +impl From<(Nullifier, CommitmentSetDigest)> for FfiNullifierCommitmentSet { + fn from(value: (Nullifier, CommitmentSetDigest)) -> Self { + Self { + nullifier: FfiBytes32 { data: value.0.0 }, + commitment_set_digest: FfiBytes32 { data: value.1.0 }, + } + } +} + #[repr(C)] pub struct FfiEncryptedAccountData { - pub ciphertext: FfiVec, - pub epk: FfiVec, + pub ciphertext: FfiVecU8, + pub epk: FfiVecU8, pub view_tag: u8, } +impl From for FfiEncryptedAccountData { + fn from(value: EncryptedAccountData) -> Self { + Self { + ciphertext: value.ciphertext.0.into(), + epk: value.epk.0.into(), + view_tag: value.view_tag, + } + } +} + #[repr(C)] pub struct FfiSignaturePubKeyEntry { pub signature: FfiSignature, pub public_key: FfiPublicKey, } -pub struct FfiSignaturePubKeyList { - pub entries: *const FfiSignaturePubKeyEntry, - pub len: usize, -} - -#[repr(C)] -pub struct FfiProofOpt { - pub proof: FfiVec, - pub is_some: bool, +impl From<(Signature, PublicKey)> for FfiSignaturePubKeyEntry { + fn from(value: (Signature, PublicKey)) -> Self { + Self { + signature: value.0.into(), + public_key: value.1.into(), + } + } } #[repr(C)] @@ -78,13 +209,20 @@ pub struct FfiProgramDeploymentTransactionBody { pub message: FfiProgramDeploymentMessage, } -pub type FfiProgramDeploymentMessage = FfiVec; +impl From for FfiProgramDeploymentTransactionBody { + fn from(value: ProgramDeploymentTransaction) -> Self { + Self { + hash: value.hash.into(), + message: value.message.bytecode.into(), + } + } +} #[repr(C)] pub struct FfiTransactionBody { - pub public_body: *const FfiPublicTransactionBody, - pub private_body: *const FfiPrivateTransactionBody, - pub program_deployment_body: *const FfiProgramDeploymentTransactionBody, + pub public_body: *mut FfiPublicTransactionBody, + pub private_body: *mut FfiPrivateTransactionBody, + pub program_deployment_body: *mut FfiProgramDeploymentTransactionBody, } #[repr(C)] @@ -93,9 +231,47 @@ pub struct FfiTransaction { pub kind: FfiTransactionKind, } +impl From for FfiTransaction { + fn from(value: Transaction) -> Self { + match value { + Transaction::Public(pub_tx) => Self { + body: FfiTransactionBody { + public_body: Box::into_raw(Box::new(pub_tx.into())), + private_body: std::ptr::null_mut(), + program_deployment_body: std::ptr::null_mut(), + }, + kind: FfiTransactionKind::Public, + }, + Transaction::PrivacyPreserving(priv_tx) => Self { + body: FfiTransactionBody { + public_body: std::ptr::null_mut(), + private_body: Box::into_raw(Box::new(priv_tx.into())), + program_deployment_body: std::ptr::null_mut(), + }, + kind: FfiTransactionKind::Public, + }, + Transaction::ProgramDeployment(pr_dep_tx) => Self { + body: FfiTransactionBody { + public_body: std::ptr::null_mut(), + private_body: std::ptr::null_mut(), + program_deployment_body: Box::into_raw(Box::new(pr_dep_tx.into())), + }, + kind: FfiTransactionKind::Public, + }, + } + } +} + #[repr(C)] pub enum FfiTransactionKind { Public = 0x0, Private, ProgramDeploy, } + +fn cast_validity_window(window: ValidityWindow) -> [u64; 2] { + [ + window.0.0.unwrap_or_default(), + window.0.1.unwrap_or(u64::MAX), + ] +} diff --git a/indexer_ffi/src/api/types/vectors.rs b/indexer_ffi/src/api/types/vectors.rs new file mode 100644 index 00000000..7c8c2073 --- /dev/null +++ b/indexer_ffi/src/api/types/vectors.rs @@ -0,0 +1,31 @@ +use crate::api::types::{ + FfiAccountId, FfiNonce, FfiVec, + account::{FfiAccount, FfiBytes32}, + transaction::{ + FfiEncryptedAccountData, FfiNullifierCommitmentSet, FfiSignaturePubKeyEntry, FfiTransaction, + }, +}; + +pub type FfiVecU8 = FfiVec; + +pub type FfiAccountList = FfiVec; + +pub type FfiAccountIdList = FfiVec; + +pub type FfiVecBytes32 = FfiVec; + +pub type FfiBlockBody = FfiVec; + +pub type FfiNonceList = FfiVec; + +pub type FfiInstructionDataList = FfiVec; + +pub type FfiSignaturePubKeyList = FfiVec; + +pub type FfiProof = FfiVecU8; + +pub type FfiProgramDeploymentMessage = FfiVecU8; + +pub type FfiEncryptedAccountDataList = FfiVec; + +pub type FfiNullifierCommitmentSetList = FfiVec; diff --git a/integration_tests/src/test_context_ffi.rs b/integration_tests/src/test_context_ffi.rs index 1bda9e2c..cdf7db9a 100644 --- a/integration_tests/src/test_context_ffi.rs +++ b/integration_tests/src/test_context_ffi.rs @@ -271,7 +271,7 @@ impl BlockingTestContextFFI { #[must_use] pub const fn indexer_ffi(&self) -> *const IndexerServiceFFI { - &(self.indexer_ffi) + &raw const (self.indexer_ffi) } } diff --git a/integration_tests/tests/indexer_ffi.rs b/integration_tests/tests/indexer_ffi.rs index b75f1b2b..d0c643f7 100644 --- a/integration_tests/tests/indexer_ffi.rs +++ b/integration_tests/tests/indexer_ffi.rs @@ -1,6 +1,7 @@ #![expect( clippy::shadow_unrelated, clippy::tests_outside_test_module, + clippy::undocumented_unsafe_blocks, reason = "We don't care about these in tests" )] From a201fc646c85493e34a62ff11c86b52aa3598b75 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Tue, 28 Apr 2026 19:42:58 +0300 Subject: [PATCH 06/11] fix: main merge --- common/src/test_utils.rs | 10 +++- indexer/core/src/block_store.rs | 9 +-- indexer_ffi/src/indexer.rs | 18 ++++-- storage/src/indexer/mod.rs | 102 +++++++++++++++++++------------- 4 files changed, 83 insertions(+), 56 deletions(-) diff --git a/common/src/test_utils.rs b/common/src/test_utils.rs index 720bd2f9..267d10ce 100644 --- a/common/src/test_utils.rs +++ b/common/src/test_utils.rs @@ -3,7 +3,7 @@ use nssa::AccountId; use crate::{ HashType, block::{Block, HashableBlockData}, - transaction::NSSATransaction, + transaction::{NSSATransaction, clock_invocation}, }; // Helpers @@ -15,7 +15,7 @@ pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey { // Dummy producers -/// Produce dummy block with. +/// Produce dummy block with provided transactions + clock transaction an the end. /// /// `id` - block id, provide zero for genesis. /// @@ -26,8 +26,12 @@ pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey { pub fn produce_dummy_block( id: u64, prev_hash: Option, - transactions: Vec, + mut transactions: Vec, ) -> Block { + transactions.push(NSSATransaction::Public(clock_invocation( + id.saturating_mul(100), + ))); + let block_data = HashableBlockData { block_id: id, prev_block_hash: prev_hash.unwrap_or_default(), diff --git a/indexer/core/src/block_store.rs b/indexer/core/src/block_store.rs index 611dec8d..cff07b0f 100644 --- a/indexer/core/src/block_store.rs +++ b/indexer/core/src/block_store.rs @@ -243,14 +243,9 @@ mod tests { &sign_key, ); let block_id = u64::try_from(i).unwrap(); - let block_timestamp = block_id.saturating_mul(100); - let clock_tx = NSSATransaction::Public(clock_invocation(block_timestamp)); - let next_block = common::test_utils::produce_dummy_block( - block_id, - Some(prev_hash), - vec![tx, clock_tx], - ); + let next_block = + common::test_utils::produce_dummy_block(block_id, Some(prev_hash), vec![tx]); prev_hash = next_block.header.hash; storage diff --git a/indexer_ffi/src/indexer.rs b/indexer_ffi/src/indexer.rs index 1bc6fb3b..102900f6 100644 --- a/indexer_ffi/src/indexer.rs +++ b/indexer_ffi/src/indexer.rs @@ -109,13 +109,23 @@ impl IndexerServiceFFI { // Implement Drop to prevent memory leaks impl Drop for IndexerServiceFFI { fn drop(&mut self) { - if self.indexer_handle.is_null() { + let Self { + indexer_handle, + runtime, + indexer_client, + } = self; + + if indexer_handle.is_null() { log::error!("Attempted to drop a null indexer pointer. This is a bug"); } - if self.runtime.is_null() { + if 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::()) }); - drop(unsafe { Box::from_raw(self.runtime.cast::()) }); + if indexer_client.is_null() { + log::error!("Attempted to drop a null client pointer. This is a bug"); + } + drop(unsafe { Box::from_raw(indexer_handle.cast::()) }); + drop(unsafe { Box::from_raw(runtime.cast::()) }); + drop(unsafe { Box::from_raw(indexer_client.cast::()) }); } } diff --git a/storage/src/indexer/mod.rs b/storage/src/indexer/mod.rs index 85f2a278..7ef21258 100644 --- a/storage/src/indexer/mod.rs +++ b/storage/src/indexer/mod.rs @@ -1,6 +1,9 @@ use std::{path::Path, sync::Arc}; -use common::block::Block; +use common::{ + block::Block, + transaction::{NSSATransaction, clock_invocation}, +}; use nssa::V03State; use rocksdb::{ BoundColumnFamily, ColumnFamilyDescriptor, DBWithThreadMode, MultiThreaded, Options, @@ -169,22 +172,52 @@ impl RocksDBIO { for block in self.get_block_batch_seq( start.checked_add(1).expect("Will be lesser that u64::MAX")..=block_id, )? { - for transaction in block.body.transactions { - transaction - .transaction_stateless_check() - .map_err(|err| { - DbError::db_interaction_error(format!( - "transaction pre check failed with err {err:?}" - )) - })? - .execute_check_on_state( - &mut breakpoint, + let expected_clock = + NSSATransaction::Public(clock_invocation(block.header.timestamp)); + + if let Some((clock_tx, user_txs)) = block.body.transactions.split_last() { + if *clock_tx != expected_clock { + return Err(DbError::db_interaction_error( + "Last transaction in block must be the clock invocation for the block timestamp" + .to_owned(), + )); + } + for transaction in user_txs { + transaction + .clone() + .transaction_stateless_check() + .map_err(|err| { + DbError::db_interaction_error(format!( + "transaction pre check failed with err {err:?}" + )) + })? + .execute_check_on_state( + &mut breakpoint, + block.header.block_id, + block.header.timestamp, + ) + .map_err(|err| { + DbError::db_interaction_error(format!( + "transaction execution failed with err {err:?}" + )) + })?; + } + + let NSSATransaction::Public(clock_public_tx) = clock_tx else { + return Err(DbError::db_interaction_error( + "Clock invocation must be a public transaction".to_owned(), + )); + }; + + breakpoint + .transition_from_public_transaction( + clock_public_tx, block.header.block_id, block.header.timestamp, ) .map_err(|err| { DbError::db_interaction_error(format!( - "transaction execution failed with err {err:?}" + "clock transaction execution failed with err {err:?}" )) })?; } @@ -213,6 +246,7 @@ fn closest_breakpoint_id(block_id: u64) -> u64 { #[expect(clippy::shadow_unrelated, reason = "Fine for tests")] #[cfg(test)] mod tests { + use common::test_utils::produce_dummy_block; use nssa::{AccountId, PublicKey}; use tempfile::tempdir; @@ -302,7 +336,7 @@ mod tests { let transfer_tx = common::test_utils::create_transaction_native_token_transfer(from, 0, to, 1, &sign_key); - let block = common::test_utils::produce_dummy_block(2, Some(prev_hash), vec![transfer_tx]); + let block = produce_dummy_block(2, Some(prev_hash), vec![transfer_tx]); dbio.put_block(&block, [1; 32]).unwrap(); @@ -369,11 +403,7 @@ mod tests { 1, &sign_key, ); - let block = common::test_utils::produce_dummy_block( - (i + 1).into(), - Some(prev_hash), - vec![transfer_tx], - ); + let block = produce_dummy_block((i + 1).into(), Some(prev_hash), vec![transfer_tx]); dbio.put_block(&block, [i; 32]).unwrap(); } @@ -439,7 +469,7 @@ mod tests { let prev_hash = last_block.header.hash; let transfer_tx = common::test_utils::create_transaction_native_token_transfer(from, 0, to, 1, &sign_key); - let block = common::test_utils::produce_dummy_block(2, Some(prev_hash), vec![transfer_tx]); + let block = produce_dummy_block(2, Some(prev_hash), vec![transfer_tx]); let control_hash1 = block.header.hash; @@ -451,7 +481,7 @@ mod tests { let prev_hash = last_block.header.hash; let transfer_tx = common::test_utils::create_transaction_native_token_transfer(from, 1, to, 1, &sign_key); - let block = common::test_utils::produce_dummy_block(3, Some(prev_hash), vec![transfer_tx]); + let block = produce_dummy_block(3, Some(prev_hash), vec![transfer_tx]); let control_hash2 = block.header.hash; @@ -466,7 +496,7 @@ mod tests { let control_tx_hash1 = transfer_tx.hash(); - let block = common::test_utils::produce_dummy_block(4, Some(prev_hash), vec![transfer_tx]); + let block = produce_dummy_block(4, Some(prev_hash), vec![transfer_tx]); dbio.put_block(&block, [3; 32]).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); @@ -478,7 +508,7 @@ mod tests { let control_tx_hash2 = transfer_tx.hash(); - let block = common::test_utils::produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]); + let block = produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]); dbio.put_block(&block, [4; 32]).unwrap(); let control_block_id1 = dbio.get_block_id_by_hash(control_hash1.0).unwrap().unwrap(); @@ -526,7 +556,7 @@ mod tests { let prev_hash = last_block.header.hash; let transfer_tx = common::test_utils::create_transaction_native_token_transfer(from, 0, to, 1, &sign_key); - let block = common::test_utils::produce_dummy_block(2, Some(prev_hash), vec![transfer_tx]); + let block = produce_dummy_block(2, Some(prev_hash), vec![transfer_tx]); block_res.push(block.clone()); dbio.put_block(&block, [1; 32]).unwrap(); @@ -537,7 +567,7 @@ mod tests { let prev_hash = last_block.header.hash; let transfer_tx = common::test_utils::create_transaction_native_token_transfer(from, 1, to, 1, &sign_key); - let block = common::test_utils::produce_dummy_block(3, Some(prev_hash), vec![transfer_tx]); + let block = produce_dummy_block(3, Some(prev_hash), vec![transfer_tx]); block_res.push(block.clone()); dbio.put_block(&block, [2; 32]).unwrap(); @@ -549,7 +579,7 @@ mod tests { let transfer_tx = common::test_utils::create_transaction_native_token_transfer(from, 2, to, 1, &sign_key); - let block = common::test_utils::produce_dummy_block(4, Some(prev_hash), vec![transfer_tx]); + let block = produce_dummy_block(4, Some(prev_hash), vec![transfer_tx]); block_res.push(block.clone()); dbio.put_block(&block, [3; 32]).unwrap(); @@ -560,7 +590,7 @@ mod tests { let transfer_tx = common::test_utils::create_transaction_native_token_transfer(from, 3, to, 1, &sign_key); - let block = common::test_utils::produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]); + let block = produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]); block_res.push(block.clone()); dbio.put_block(&block, [4; 32]).unwrap(); @@ -633,11 +663,7 @@ mod tests { tx_hash_res.push(transfer_tx1.hash().0); tx_hash_res.push(transfer_tx2.hash().0); - let block = common::test_utils::produce_dummy_block( - 2, - Some(prev_hash), - vec![transfer_tx1, transfer_tx2], - ); + let block = produce_dummy_block(2, Some(prev_hash), vec![transfer_tx1, transfer_tx2]); dbio.put_block(&block, [1; 32]).unwrap(); @@ -652,11 +678,7 @@ mod tests { tx_hash_res.push(transfer_tx1.hash().0); tx_hash_res.push(transfer_tx2.hash().0); - let block = common::test_utils::produce_dummy_block( - 3, - Some(prev_hash), - vec![transfer_tx1, transfer_tx2], - ); + let block = produce_dummy_block(3, Some(prev_hash), vec![transfer_tx1, transfer_tx2]); dbio.put_block(&block, [2; 32]).unwrap(); @@ -671,11 +693,7 @@ mod tests { tx_hash_res.push(transfer_tx1.hash().0); tx_hash_res.push(transfer_tx2.hash().0); - let block = common::test_utils::produce_dummy_block( - 4, - Some(prev_hash), - vec![transfer_tx1, transfer_tx2], - ); + let block = produce_dummy_block(4, Some(prev_hash), vec![transfer_tx1, transfer_tx2]); dbio.put_block(&block, [3; 32]).unwrap(); @@ -687,7 +705,7 @@ mod tests { common::test_utils::create_transaction_native_token_transfer(from, 6, to, 1, &sign_key); tx_hash_res.push(transfer_tx.hash().0); - let block = common::test_utils::produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]); + let block = produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]); dbio.put_block(&block, [4; 32]).unwrap(); From 113a68c22c3c4cf3ed3026dba64060de277ae286 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 29 Apr 2026 13:55:44 +0300 Subject: [PATCH 07/11] fix: correct free --- Cargo.lock | 14 +- indexer_ffi/indexer_ffi.h | 315 ++++++++++++++++++++++- indexer_ffi/src/api/query.rs | 263 ++++++++++++++++++- indexer_ffi/src/api/types/account.rs | 156 +++-------- indexer_ffi/src/api/types/block.rs | 136 +++++++++- indexer_ffi/src/api/types/mod.rs | 72 +++++- indexer_ffi/src/api/types/transaction.rs | 254 +++++++++++++++++- indexer_ffi/src/api/types/vectors.rs | 4 +- 8 files changed, 1051 insertions(+), 163 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 37ab61e0..27831459 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -629,9 +629,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "astral-tokio-tar" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c23f3af104b40a3430ccb90ed5f7bd877a8dc5c26fc92fde51a22b40890dcf9" +checksum = "4ce73b17c62717c4b6a9af10b43e87c578b0cac27e00666d48304d3b7d2c0693" dependencies = [ "filetime", "futures-core", @@ -2108,7 +2108,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2409,7 +2409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7090,7 +7090,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -8023,7 +8023,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -9306,7 +9306,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/indexer_ffi/indexer_ffi.h b/indexer_ffi/indexer_ffi.h index 1094e1de..7626b3b3 100644 --- a/indexer_ffi/indexer_ffi.h +++ b/indexer_ffi/indexer_ffi.h @@ -167,11 +167,15 @@ typedef struct FfiAccount { /** * Pointer to account data bytes. */ - const uint8_t *data; + uint8_t *data; /** * Length of account data. */ uintptr_t data_len; + /** + * Capacity of account data. + */ + uintptr_t data_cap; /** * Nonce as little-endian [u8; 16]. */ @@ -302,6 +306,66 @@ typedef struct PointerResult_FfiBlockOpt__OperationStatus { enum OperationStatus error; } PointerResult_FfiBlockOpt__OperationStatus; +/** + * 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_FfiAccount__OperationStatus { + struct FfiAccount *value; + enum OperationStatus error; +} PointerResult_FfiAccount__OperationStatus; + +typedef struct FfiOption_FfiTransaction { + struct FfiTransaction *value; + bool is_some; +} FfiOption_FfiTransaction; + +/** + * 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_FfiOption_FfiTransaction_____OperationStatus { + struct FfiOption_FfiTransaction *value; + enum OperationStatus error; +} PointerResult_FfiOption_FfiTransaction_____OperationStatus; + +typedef struct FfiVec_FfiBlock { + struct FfiBlock *entries; + uintptr_t len; + uintptr_t capacity; +} FfiVec_FfiBlock; + +/** + * 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_FfiBlock_____OperationStatus { + struct FfiVec_FfiBlock *value; + enum OperationStatus error; +} PointerResult_FfiVec_FfiBlock_____OperationStatus; + +typedef struct FfiOption_u64 { + uint64_t *value; + bool is_some; +} FfiOption_u64; + +/** + * 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_FfiTransaction_____OperationStatus { + struct FfiVec_FfiTransaction *value; + enum OperationStatus error; +} PointerResult_FfiVec_FfiTransaction_____OperationStatus; + /** * Creates and starts an indexer based on the provided * configuration file path. @@ -347,46 +411,275 @@ enum OperationStatus stop_indexer(struct IndexerServiceFFI *indexer); void free_cstring(char *block); /** - * Stops and frees the resources associated with the given indexer service. + * Query the last block id from indexer. * * # Arguments * - * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be stopped. + * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. * * # Returns * - * An `OperationStatus` indicating success or failure. + * A `PointerResult` 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); /** - * Stops and frees the resources associated with the given indexer service. + * Query the block by id from indexer. * * # Arguments * - * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be stopped. + * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. + * - `block_id`: `u64` number of block id * * # Returns * - * An `OperationStatus` indicating success or failure. + * A `PointerResult` 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_FfiBlockOpt__OperationStatus query_block(const struct IndexerServiceFFI *indexer, FfiBlockId block_id); +/** + * Query the block by id from indexer. + * + * # Arguments + * + * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. + * - `hash`: `FfiHashType` - hash of block + * + * # Returns + * + * A `PointerResult` indicating success or failure. + * + * # Safety + * + * The caller must ensure that: + * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance + */ +struct PointerResult_FfiBlockOpt__OperationStatus query_block_by_hash(const struct IndexerServiceFFI *indexer, + FfiHashType hash); + +/** + * Query the account by id from indexer. + * + * # Arguments + * + * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. + * - `account_id`: `FfiAccountId` - id of queried account + * + * # Returns + * + * A `PointerResult` indicating success or failure. + * + * # Safety + * + * The caller must ensure that: + * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance + */ +struct PointerResult_FfiAccount__OperationStatus query_account(const struct IndexerServiceFFI *indexer, + FfiAccountId account_id); + +/** + * Query the trasnaction by hash from indexer. + * + * # Arguments + * + * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. + * - `hash`: `FfiHashType` - hash of transaction + * + * # Returns + * + * A `PointerResult, OperationStatus>` indicating success or failure. + * + * # Safety + * + * The caller must ensure that: + * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance + */ +struct PointerResult_FfiOption_FfiTransaction_____OperationStatus query_transaction(const struct IndexerServiceFFI *indexer, + FfiHashType hash); + +/** + * Query the blocks by block range from indexer. + * + * # Arguments + * + * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. + * - `before`: `FfiOption` - end block of query + * - `limit`: `u64` - number of blocks to query before `before` + * + * # Returns + * + * A `PointerResult, OperationStatus>` indicating success or failure. + * + * # Safety + * + * The caller must ensure that: + * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance + */ +struct PointerResult_FfiVec_FfiBlock_____OperationStatus query_block_vec(const struct IndexerServiceFFI *indexer, + struct FfiOption_u64 before, + uint64_t limit); + +/** + * Query the transactions range by account id from indexer. + * + * # Arguments + * + * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. + * - `account_id`: `FfiAccountId` - id of queried account + * - `offset`: `u64` - first tx id of query + * - `limit`: `u64` - number of tx ids to query after `offset` + * + * # Returns + * + * A `PointerResult, OperationStatus>` indicating success or failure. + * + * # Safety + * + * The caller must ensure that: + * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance + */ +struct PointerResult_FfiVec_FfiTransaction_____OperationStatus query_transactions_by_account(const struct IndexerServiceFFI *indexer, + FfiAccountId account_id, + uint64_t offset, + uint64_t limit); + +/** + * Frees the resources associated with the given ffi account. + * + * # Arguments + * + * - `val`: An instance of `FfiAccount`. + * + * # Returns + * + * void. + * + * # Safety + * + * The caller must ensure that: + * - `val` is a valid instance of `FfiAccount`. + */ +void free_ffi_account(struct FfiAccount val); + +/** + * Frees the resources associated with the given ffi block. + * + * # Arguments + * + * - `val`: An instance of `FfiBlock`. + * + * # Returns + * + * void. + * + * # Safety + * + * The caller must ensure that: + * - `val` is a valid instance of `FfiBlock`. + */ +void free_ffi_block(struct FfiBlock val); + +/** + * Frees the resources associated with the given ffi block option. + * + * # Arguments + * + * - `val`: An instance of `FfiBlockOpt`. + * + * # Returns + * + * void. + * + * # Safety + * + * The caller must ensure that: + * - `val` is a valid instance of `FfiBlockOpt`. + */ +void free_ffi_block_opt(FfiBlockOpt val); + +/** + * Frees the resources associated with the given ffi block vector. + * + * # Arguments + * + * - `val`: An instance of `FfiVec`. + * + * # Returns + * + * void. + * + * # Safety + * + * The caller must ensure that: + * - `val` is a valid instance of `FfiVec`. + */ +void free_ffi_block_vec(struct FfiVec_FfiBlock val); + +/** + * Frees the resources associated with the given ffi transaction. + * + * # Arguments + * + * - `val`: An instance of `FfiTransaction`. + * + * # Returns + * + * void. + * + * # Safety + * + * The caller must ensure that: + * - `val` is a valid instance of `FfiTransaction`. + */ +void free_ffi_transaction(struct FfiTransaction val); + +/** + * Frees the resources associated with the given ffi transaction option. + * + * # Arguments + * + * - `val`: An instance of `FfiOption`. + * + * # Returns + * + * void. + * + * # Safety + * + * The caller must ensure that: + * - `val` is a valid instance of `FfiOption`. + */ +void free_ffi_transaction_opt(struct FfiOption_FfiTransaction val); + +/** + * Frees the resources associated with the given vector of ffi transactions. + * + * # Arguments + * + * - `val`: An instance of `FfiVec`. + * + * # Returns + * + * void. + * + * # Safety + * + * The caller must ensure that: + * - `val` is a valid instance of `FfiVec`. + */ +void free_ffi_transaction_vec(struct FfiVec_FfiTransaction val); + bool is_ok(const enum OperationStatus *self); bool is_error(const enum OperationStatus *self); diff --git a/indexer_ffi/src/api/query.rs b/indexer_ffi/src/api/query.rs index e840fed7..1e39d961 100644 --- a/indexer_ffi/src/api/query.rs +++ b/indexer_ffi/src/api/query.rs @@ -1,30 +1,34 @@ +use indexer_service_protocol::{AccountId, HashType}; use indexer_service_rpc::RpcClient as _; use crate::{ IndexerServiceFFI, api::{ PointerResult, - types::{FfiBlockId, block::FfiBlockOpt}, + types::{ + FfiAccountId, FfiBlockId, FfiHashType, FfiOption, FfiVec, + account::FfiAccount, + block::{FfiBlock, FfiBlockOpt}, + transaction::FfiTransaction, + }, }, errors::OperationStatus, }; -/// Stops and frees the resources associated with the given indexer service. +/// Query the last block id from indexer. /// /// # Arguments /// -/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be stopped. +/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. /// /// # Returns /// -/// An `OperationStatus` indicating success or failure. +/// A `PointerResult` 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, @@ -47,22 +51,21 @@ pub unsafe extern "C" fn query_last_block( ) } -/// Stops and frees the resources associated with the given indexer service. +/// Query the block by id from indexer. /// /// # Arguments /// -/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be stopped. +/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. +/// - `block_id`: `u64` number of block id /// /// # Returns /// -/// An `OperationStatus` indicating success or failure. +/// A `PointerResult` 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_block( indexer: *const IndexerServiceFFI, @@ -91,3 +94,241 @@ pub unsafe extern "C" fn query_block( }, ) } + +/// Query the block by id from indexer. +/// +/// # Arguments +/// +/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. +/// - `hash`: `FfiHashType` - hash of block +/// +/// # Returns +/// +/// A `PointerResult` indicating success or failure. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance +#[unsafe(no_mangle)] +pub unsafe extern "C" fn query_block_by_hash( + indexer: *const IndexerServiceFFI, + hash: FfiHashType, +) -> 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_block_by_hash(HashType(hash.data))) + .map_or_else( + |_| PointerResult::from_error(OperationStatus::ClientError), + |block_opt| { + let block_ffi = block_opt.map_or_else(FfiBlockOpt::from_none, |block| { + FfiBlockOpt::from_value(block.into()) + }); + + PointerResult::from_value(block_ffi) + }, + ) +} + +/// Query the account by id from indexer. +/// +/// # Arguments +/// +/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. +/// - `account_id`: `FfiAccountId` - id of queried account +/// +/// # Returns +/// +/// A `PointerResult` indicating success or failure. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance +#[unsafe(no_mangle)] +pub unsafe extern "C" fn query_account( + indexer: *const IndexerServiceFFI, + account_id: FfiAccountId, +) -> 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_account(AccountId { + value: account_id.data, + })) + .map_or_else( + |_| PointerResult::from_error(OperationStatus::ClientError), + |acc| { + let acc_nssa: nssa::Account = + acc.try_into().expect("Source is in blocks, must fit"); + PointerResult::from_value(acc_nssa.into()) + }, + ) +} + +/// Query the trasnaction by hash from indexer. +/// +/// # Arguments +/// +/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. +/// - `hash`: `FfiHashType` - hash of transaction +/// +/// # Returns +/// +/// A `PointerResult, OperationStatus>` indicating success or failure. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance +#[unsafe(no_mangle)] +pub unsafe extern "C" fn query_transaction( + indexer: *const IndexerServiceFFI, + hash: FfiHashType, +) -> PointerResult, 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 client = unsafe { indexer.client() }; + let runtime = unsafe { indexer.runtime() }; + + runtime + .block_on(client.get_transaction(HashType(hash.data))) + .map_or_else( + |_| PointerResult::from_error(OperationStatus::ClientError), + |tx_opt| { + let tx_ffi = tx_opt.map_or_else(FfiOption::::from_none, |tx| { + FfiOption::::from_value(tx.into()) + }); + + PointerResult::from_value(tx_ffi) + }, + ) +} + +/// Query the blocks by block range from indexer. +/// +/// # Arguments +/// +/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. +/// - `before`: `FfiOption` - end block of query +/// - `limit`: `u64` - number of blocks to query before `before` +/// +/// # Returns +/// +/// A `PointerResult, OperationStatus>` indicating success or failure. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance +#[unsafe(no_mangle)] +pub unsafe extern "C" fn query_block_vec( + indexer: *const IndexerServiceFFI, + before: FfiOption, + limit: u64, +) -> PointerResult, 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 client = unsafe { indexer.client() }; + let runtime = unsafe { indexer.runtime() }; + + let before_std = before.is_some.then(|| unsafe { *before.value }); + + runtime + .block_on(client.get_blocks(before_std, limit)) + .map_or_else( + |_| PointerResult::from_error(OperationStatus::ClientError), + |block_vec| { + PointerResult::from_value( + block_vec + .into_iter() + .map(Into::into) + .collect::>() + .into(), + ) + }, + ) +} + +/// Query the transactions range by account id from indexer. +/// +/// # Arguments +/// +/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. +/// - `account_id`: `FfiAccountId` - id of queried account +/// - `offset`: `u64` - first tx id of query +/// - `limit`: `u64` - number of tx ids to query after `offset` +/// +/// # Returns +/// +/// A `PointerResult, OperationStatus>` indicating success or failure. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance +#[unsafe(no_mangle)] +pub unsafe extern "C" fn query_transactions_by_account( + indexer: *const IndexerServiceFFI, + account_id: FfiAccountId, + offset: u64, + limit: u64, +) -> PointerResult, 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 client = unsafe { indexer.client() }; + let runtime = unsafe { indexer.runtime() }; + + runtime + .block_on(client.get_transactions_by_account( + AccountId { + value: account_id.data, + }, + offset, + limit, + )) + .map_or_else( + |_| PointerResult::from_error(OperationStatus::ClientError), + |tx_vec| { + PointerResult::from_value( + tx_vec + .into_iter() + .map(Into::into) + .collect::>() + .into(), + ) + }, + ) +} diff --git a/indexer_ffi/src/api/types/account.rs b/indexer_ffi/src/api/types/account.rs index 568bfa1c..853e44d4 100644 --- a/indexer_ffi/src/api/types/account.rs +++ b/indexer_ffi/src/api/types/account.rs @@ -1,48 +1,6 @@ -//! C-compatible type definitions for the FFI layer. - -use std::ptr; - use indexer_service_protocol::ProgramId; -/// 32-byte array type for `AccountId`, keys, hashes, etc. -#[repr(C)] -#[derive(Clone, Copy, Default)] -pub struct FfiBytes32 { - pub data: [u8; 32], -} - -/// 64-byte array type for signatures, etc. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct FfiBytes64 { - pub data: [u8; 64], -} - -impl Default for FfiBytes64 { - fn default() -> Self { - Self { data: [0; 64] } - } -} - -/// Program ID - 8 u32 values (32 bytes total). -#[repr(C)] -#[derive(Clone, Copy, Default)] -pub struct FfiProgramId { - pub data: [u32; 8], -} - -impl From for FfiProgramId { - fn from(value: ProgramId) -> Self { - Self { data: value.0 } - } -} - -/// U128 - 16 bytes little endian. -#[repr(C)] -#[derive(Clone, Copy, Default)] -pub struct FfiU128 { - pub data: [u8; 16], -} +use crate::api::types::{FfiBytes32, FfiProgramId, FfiU128}; /// Account data structure - C-compatible version of nssa Account. /// @@ -54,77 +12,17 @@ pub struct FfiAccount { /// Balance as little-endian [u8; 16]. pub balance: FfiU128, /// Pointer to account data bytes. - pub data: *const u8, + pub data: *mut u8, /// Length of account data. pub data_len: usize, + /// Capacity of account data. + pub data_cap: usize, /// Nonce as little-endian [u8; 16]. pub nonce: FfiU128, } -impl Default for FfiAccount { - fn default() -> Self { - Self { - program_owner: FfiProgramId::default(), - balance: FfiU128::default(), - data: std::ptr::null(), - data_len: 0, - nonce: FfiU128::default(), - } - } -} - -/// Public keys for a private account (safe to expose). -#[repr(C)] -pub struct FfiPrivateAccountKeys { - /// Nullifier public key (32 bytes). - pub nullifier_public_key: FfiBytes32, - /// viewing public key (compressed secp256k1 point). - pub viewing_public_key: *const u8, - /// Length of viewing public key (typically 33 bytes). - pub viewing_public_key_len: usize, -} - -impl Default for FfiPrivateAccountKeys { - fn default() -> Self { - Self { - nullifier_public_key: FfiBytes32::default(), - viewing_public_key: std::ptr::null(), - viewing_public_key_len: 0, - } - } -} - -/// Public key info for a public account. -#[repr(C)] -#[derive(Clone, Copy, Default)] -pub struct FfiPublicAccountKey { - pub public_key: FfiBytes32, -} - // Helper functions to convert between Rust and FFI types -impl FfiBytes32 { - /// Create from a 32-byte array. - #[must_use] - pub const fn from_bytes(bytes: [u8; 32]) -> Self { - Self { data: bytes } - } - - /// Create from an `AccountId`. - #[must_use] - pub const fn from_account_id(id: &nssa::AccountId) -> Self { - Self { data: *id.value() } - } -} - -impl From for FfiU128 { - fn from(value: u128) -> Self { - Self { - data: value.to_le_bytes(), - } - } -} - impl From<&nssa::AccountId> for FfiBytes32 { fn from(id: &nssa::AccountId) -> Self { Self::from_account_id(id) @@ -132,20 +30,8 @@ impl From<&nssa::AccountId> for FfiBytes32 { } impl From for FfiAccount { - #[expect( - clippy::as_conversions, - reason = "We need to convert to byte arrays for FFI" - )] fn from(value: nssa::Account) -> Self { - // Convert account data to FFI type - let data_vec: Vec = value.data.into(); - let data_len = data_vec.len(); - let data = if data_len > 0 { - let data_boxed = data_vec.into_boxed_slice(); - Box::into_raw(data_boxed) as *const u8 - } else { - ptr::null() - }; + let (data, data_len, data_cap) = value.data.into_inner().into_raw_parts(); let program_owner = FfiProgramId { data: value.program_owner, @@ -155,15 +41,41 @@ impl From for FfiAccount { balance: value.balance.into(), data, data_len, + data_cap, nonce: value.nonce.0.into(), } } } -impl From for FfiPublicAccountKey { - fn from(value: nssa::PublicKey) -> Self { +impl From for indexer_service_protocol::Account { + fn from(value: FfiAccount) -> Self { Self { - public_key: FfiBytes32::from_bytes(*value.value()), + program_owner: ProgramId(value.program_owner.data), + balance: value.balance.into(), + data: indexer_service_protocol::Data(unsafe { + Vec::from_raw_parts(value.data, value.data_len, value.data_cap) + }), + nonce: value.nonce.into(), } } } + +/// Frees the resources associated with the given ffi account. +/// +/// # Arguments +/// +/// - `val`: An instance of `FfiAccount`. +/// +/// # Returns +/// +/// void. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `val` is a valid instance of `FfiAccount`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn free_ffi_account(val: FfiAccount) { + let orig_val: indexer_service_protocol::Account = val.into(); + drop(orig_val); +} diff --git a/indexer_ffi/src/api/types/block.rs b/indexer_ffi/src/api/types/block.rs index 1ee8215f..f7e0c778 100644 --- a/indexer_ffi/src/api/types/block.rs +++ b/indexer_ffi/src/api/types/block.rs @@ -1,7 +1,10 @@ -use indexer_service_protocol::{BedrockStatus, Block, BlockHeader}; +use indexer_service_protocol::{ + BedrockStatus, Block, BlockHeader, HashType, MantleMsgId, Signature, +}; use crate::api::types::{ - FfiBlockId, FfiHashType, FfiMsgId, FfiOption, FfiSignature, FfiTimestamp, vectors::FfiBlockBody, + FfiBlockId, FfiHashType, FfiMsgId, FfiOption, FfiSignature, FfiTimestamp, FfiVec, + transaction::free_ffi_transaction_vec, vectors::FfiBlockBody, }; #[repr(C)] @@ -29,6 +32,23 @@ impl From for FfiBlock { } } +// impl From> for Block { +// fn from(value: Box) -> Self { +// Self { +// header: BlockHeader { +// block_id: value.header.block_id, +// prev_block_hash: HashType(value.header.prev_block_hash.data), +// hash: HashType(value.header.hash.data), +// timestamp: value.header.timestamp, +// signature: Signature(value.header.signature.data), +// }, +// body: (), +// bedrock_status: value.bedrock_status.into(), +// bedrock_parent_id: MantleMsgId(value.bedrock_parent_id.data), +// } +// } +// } + pub type FfiBlockOpt = FfiOption; #[repr(C)] @@ -68,3 +88,115 @@ impl From for FfiBedrockStatus { } } } + +impl From for BedrockStatus { + fn from(value: FfiBedrockStatus) -> Self { + match value { + FfiBedrockStatus::Finalized => Self::Finalized, + FfiBedrockStatus::Pending => Self::Pending, + FfiBedrockStatus::Safe => Self::Safe, + } + } +} + +/// Frees the resources associated with the given ffi block. +/// +/// # Arguments +/// +/// - `val`: An instance of `FfiBlock`. +/// +/// # Returns +/// +/// void. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `val` is a valid instance of `FfiBlock`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn free_ffi_block(val: FfiBlock) { + // We don't really need all the casts, but just in case + // All except `ffi_tx_ffi_vec` is Copy types, so no need for Drop + let _ = BlockHeader { + block_id: val.header.block_id, + prev_block_hash: HashType(val.header.prev_block_hash.data), + hash: HashType(val.header.hash.data), + timestamp: val.header.timestamp, + signature: Signature(val.header.signature.data), + }; + let ffi_tx_ffi_vec = val.body; + + #[expect(clippy::let_underscore_must_use, reason = "No use for this Copy type")] + let _: BedrockStatus = val.bedrock_status.into(); + + let _ = MantleMsgId(val.bedrock_parent_id.data); + + unsafe { + free_ffi_transaction_vec(ffi_tx_ffi_vec); + }; +} + +/// Frees the resources associated with the given ffi block option. +/// +/// # Arguments +/// +/// - `val`: An instance of `FfiBlockOpt`. +/// +/// # Returns +/// +/// void. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `val` is a valid instance of `FfiBlockOpt`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn free_ffi_block_opt(val: FfiBlockOpt) { + if val.is_some { + let value = unsafe { Box::from_raw(val.value) }; + + // We don't really need all the casts, but just in case + // All except `ffi_tx_ffi_vec` is Copy types, so no need for Drop + let _ = BlockHeader { + block_id: value.header.block_id, + prev_block_hash: HashType(value.header.prev_block_hash.data), + hash: HashType(value.header.hash.data), + timestamp: value.header.timestamp, + signature: Signature(value.header.signature.data), + }; + let ffi_tx_ffi_vec = value.body; + + #[expect(clippy::let_underscore_must_use, reason = "No use for this Copy type")] + let _: BedrockStatus = value.bedrock_status.into(); + + let _ = MantleMsgId(value.bedrock_parent_id.data); + + unsafe { + free_ffi_transaction_vec(ffi_tx_ffi_vec); + }; + } +} + +/// Frees the resources associated with the given ffi block vector. +/// +/// # Arguments +/// +/// - `val`: An instance of `FfiVec`. +/// +/// # Returns +/// +/// void. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `val` is a valid instance of `FfiVec`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn free_ffi_block_vec(val: FfiVec) { + let ffi_block_std_vec: Vec<_> = val.into(); + for block in ffi_block_std_vec { + unsafe { + free_ffi_block(block); + } + } +} diff --git a/indexer_ffi/src/api/types/mod.rs b/indexer_ffi/src/api/types/mod.rs index d1b3e572..22b44c88 100644 --- a/indexer_ffi/src/api/types/mod.rs +++ b/indexer_ffi/src/api/types/mod.rs @@ -1,12 +1,72 @@ -use indexer_service_protocol::{AccountId, HashType, MantleMsgId, PublicKey, Signature}; - -use crate::api::types::account::{FfiBytes32, FfiBytes64, FfiU128}; +use indexer_service_protocol::{AccountId, HashType, MantleMsgId, ProgramId, PublicKey, Signature}; pub mod account; pub mod block; pub mod transaction; pub mod vectors; +/// 32-byte array type for `AccountId`, keys, hashes, etc. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FfiBytes32 { + pub data: [u8; 32], +} + +/// 64-byte array type for signatures, etc. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct FfiBytes64 { + pub data: [u8; 64], +} + +/// Program ID - 8 u32 values (32 bytes total). +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FfiProgramId { + pub data: [u32; 8], +} + +impl From for FfiProgramId { + fn from(value: ProgramId) -> Self { + Self { data: value.0 } + } +} + +/// U128 - 16 bytes little endian. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FfiU128 { + pub data: [u8; 16], +} + +impl FfiBytes32 { + /// Create from a 32-byte array. + #[must_use] + pub const fn from_bytes(bytes: [u8; 32]) -> Self { + Self { data: bytes } + } + + /// Create from an `AccountId`. + #[must_use] + pub const fn from_account_id(id: &nssa::AccountId) -> Self { + Self { data: *id.value() } + } +} + +impl From for FfiU128 { + fn from(value: u128) -> Self { + Self { + data: value.to_le_bytes(), + } + } +} + +impl From for u128 { + fn from(value: FfiU128) -> Self { + Self::from_le_bytes(value.data) + } +} + pub type FfiHashType = FfiBytes32; pub type FfiMsgId = FfiBytes32; pub type FfiBlockId = u64; @@ -64,6 +124,12 @@ impl From> for FfiVec { } } +impl From> for Vec { + fn from(value: FfiVec) -> Self { + unsafe { Self::from_raw_parts(value.entries, value.len, value.capacity) } + } +} + #[repr(C)] pub struct FfiOption { pub value: *mut T, diff --git a/indexer_ffi/src/api/types/transaction.rs b/indexer_ffi/src/api/types/transaction.rs index 86e28133..ff14276f 100644 --- a/indexer_ffi/src/api/types/transaction.rs +++ b/indexer_ffi/src/api/types/transaction.rs @@ -1,12 +1,13 @@ use indexer_service_protocol::{ - CommitmentSetDigest, EncryptedAccountData, Nullifier, PrivacyPreservingMessage, - PrivacyPreservingTransaction, ProgramDeploymentTransaction, PublicKey, PublicMessage, - PublicTransaction, Signature, Transaction, ValidityWindow, + AccountId, Ciphertext, Commitment, CommitmentSetDigest, EncryptedAccountData, + EphemeralPublicKey, HashType, Nullifier, PrivacyPreservingMessage, + PrivacyPreservingTransaction, ProgramDeploymentMessage, ProgramDeploymentTransaction, + ProgramId, Proof, PublicKey, PublicMessage, PublicTransaction, Signature, Transaction, + ValidityWindow, WitnessSet, }; use crate::api::types::{ - FfiHashType, FfiPublicKey, FfiSignature, - account::{FfiBytes32, FfiProgramId}, + FfiBytes32, FfiHashType, FfiOption, FfiProgramId, FfiPublicKey, FfiSignature, FfiVec, vectors::{ FfiAccountIdList, FfiAccountList, FfiEncryptedAccountDataList, FfiInstructionDataList, FfiNonceList, FfiNullifierCommitmentSetList, FfiProgramDeploymentMessage, FfiProof, @@ -37,6 +38,46 @@ impl From for FfiPublicTransactionBody { } } +impl From> for PublicTransaction { + fn from(value: Box) -> Self { + Self { + hash: HashType(value.hash.data), + message: PublicMessage { + program_id: ProgramId(value.message.program_id.data), + account_ids: { + let std_vec: Vec<_> = value.message.account_ids.into(); + std_vec + .into_iter() + .map(|ffi_val| AccountId { + value: ffi_val.data, + }) + .collect() + }, + nonces: { + let std_vec: Vec<_> = value.message.nonces.into(); + std_vec.into_iter().map(Into::into).collect() + }, + instruction_data: value.message.instruction_data.into(), + }, + witness_set: WitnessSet { + signatures_and_public_keys: { + let std_vec: Vec<_> = value.witness_set.into(); + std_vec + .into_iter() + .map(|ffi_val| { + ( + Signature(ffi_val.signature.data), + PublicKey(ffi_val.public_key.data), + ) + }) + .collect() + }, + proof: None, + }, + } + } +} + #[repr(C)] pub struct FfiPublicMessage { pub program_id: FfiProgramId, @@ -96,6 +137,84 @@ impl From for FfiPrivateTransactionBody { } } +impl From> for PrivacyPreservingTransaction { + fn from(value: Box) -> Self { + Self { + hash: HashType(value.hash.data), + message: PrivacyPreservingMessage { + public_account_ids: { + let std_vec: Vec<_> = value.message.public_account_ids.into(); + std_vec + .into_iter() + .map(|ffi_val| AccountId { + value: ffi_val.data, + }) + .collect() + }, + nonces: { + let std_vec: Vec<_> = value.message.nonces.into(); + std_vec.into_iter().map(Into::into).collect() + }, + public_post_states: { + let std_vec: Vec<_> = value.message.public_post_states.into(); + std_vec.into_iter().map(Into::into).collect() + }, + encrypted_private_post_states: { + let std_vec: Vec<_> = value.message.encrypted_private_post_states.into(); + std_vec + .into_iter() + .map(|ffi_val| EncryptedAccountData { + ciphertext: Ciphertext(ffi_val.ciphertext.into()), + epk: EphemeralPublicKey(ffi_val.epk.into()), + view_tag: ffi_val.view_tag, + }) + .collect() + }, + new_commitments: { + let std_vec: Vec<_> = value.message.new_commitments.into(); + std_vec + .into_iter() + .map(|ffi_val| Commitment(ffi_val.data)) + .collect() + }, + new_nullifiers: { + let std_vec: Vec<_> = value.message.new_nullifiers.into(); + std_vec + .into_iter() + .map(|ffi_val| { + ( + Nullifier(ffi_val.nullifier.data), + CommitmentSetDigest(ffi_val.commitment_set_digest.data), + ) + }) + .collect() + }, + block_validity_window: cast_ffi_validity_window( + value.message.block_validity_window, + ), + timestamp_validity_window: cast_ffi_validity_window( + value.message.timestamp_validity_window, + ), + }, + witness_set: WitnessSet { + signatures_and_public_keys: { + let std_vec: Vec<_> = value.witness_set.into(); + std_vec + .into_iter() + .map(|ffi_val| { + ( + Signature(ffi_val.signature.data), + PublicKey(ffi_val.public_key.data), + ) + }) + .collect() + }, + proof: Some(Proof(value.proof.into())), + }, + } + } +} + #[repr(C)] pub struct FfiPrivacyPreservingMessage { pub public_account_ids: FfiAccountIdList, @@ -209,6 +328,17 @@ pub struct FfiProgramDeploymentTransactionBody { pub message: FfiProgramDeploymentMessage, } +impl From> for ProgramDeploymentTransaction { + fn from(value: Box) -> Self { + Self { + hash: HashType(value.hash.data), + message: ProgramDeploymentMessage { + bytecode: value.message.into(), + }, + } + } +} + impl From for FfiProgramDeploymentTransactionBody { fn from(value: ProgramDeploymentTransaction) -> Self { Self { @@ -269,9 +399,123 @@ pub enum FfiTransactionKind { ProgramDeploy, } +/// Frees the resources associated with the given ffi transaction. +/// +/// # Arguments +/// +/// - `val`: An instance of `FfiTransaction`. +/// +/// # Returns +/// +/// void. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `val` is a valid instance of `FfiTransaction`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn free_ffi_transaction(val: FfiTransaction) { + match val.kind { + FfiTransactionKind::Public => { + let body = unsafe { Box::from_raw(val.body.public_body) }; + let std_body: PublicTransaction = body.into(); + drop(std_body); + } + FfiTransactionKind::Private => { + let body = unsafe { Box::from_raw(val.body.private_body) }; + let std_body: PrivacyPreservingTransaction = body.into(); + drop(std_body); + } + FfiTransactionKind::ProgramDeploy => { + let body = unsafe { Box::from_raw(val.body.program_deployment_body) }; + let std_body: ProgramDeploymentTransaction = body.into(); + drop(std_body); + } + } +} + +/// Frees the resources associated with the given ffi transaction option. +/// +/// # Arguments +/// +/// - `val`: An instance of `FfiOption`. +/// +/// # Returns +/// +/// void. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `val` is a valid instance of `FfiOption`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn free_ffi_transaction_opt(val: FfiOption) { + if val.is_some { + let value = unsafe { Box::from_raw(val.value) }; + + match value.kind { + FfiTransactionKind::Public => { + let body = unsafe { Box::from_raw(value.body.public_body) }; + let std_body: PublicTransaction = body.into(); + drop(std_body); + } + FfiTransactionKind::Private => { + let body = unsafe { Box::from_raw(value.body.private_body) }; + let std_body: PrivacyPreservingTransaction = body.into(); + drop(std_body); + } + FfiTransactionKind::ProgramDeploy => { + let body = unsafe { Box::from_raw(value.body.program_deployment_body) }; + let std_body: ProgramDeploymentTransaction = body.into(); + drop(std_body); + } + } + } +} + +/// Frees the resources associated with the given vector of ffi transactions. +/// +/// # Arguments +/// +/// - `val`: An instance of `FfiVec`. +/// +/// # Returns +/// +/// void. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `val` is a valid instance of `FfiVec`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn free_ffi_transaction_vec(val: FfiVec) { + let ffi_tx_std_vec: Vec<_> = val.into(); + for tx in ffi_tx_std_vec { + unsafe { + free_ffi_transaction(tx); + } + } +} + fn cast_validity_window(window: ValidityWindow) -> [u64; 2] { [ window.0.0.unwrap_or_default(), window.0.1.unwrap_or(u64::MAX), ] } + +const fn cast_ffi_validity_window(ffi_window: [u64; 2]) -> ValidityWindow { + let left = if ffi_window[0] == 0 { + None + } else { + Some(ffi_window[0]) + }; + + let right = if ffi_window[1] == u64::MAX { + None + } else { + Some(ffi_window[1]) + }; + + ValidityWindow((left, right)) +} diff --git a/indexer_ffi/src/api/types/vectors.rs b/indexer_ffi/src/api/types/vectors.rs index 7c8c2073..46f08737 100644 --- a/indexer_ffi/src/api/types/vectors.rs +++ b/indexer_ffi/src/api/types/vectors.rs @@ -1,6 +1,6 @@ use crate::api::types::{ - FfiAccountId, FfiNonce, FfiVec, - account::{FfiAccount, FfiBytes32}, + FfiAccountId, FfiBytes32, FfiNonce, FfiVec, + account::FfiAccount, transaction::{ FfiEncryptedAccountData, FfiNullifierCommitmentSet, FfiSignaturePubKeyEntry, FfiTransaction, }, From 06a6983ef355d611686a01aa0f36bf8fc2a06385 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 29 Apr 2026 17:24:00 +0300 Subject: [PATCH 08/11] fix: tests updated --- Cargo.lock | 1 + indexer_ffi/src/api/types/account.rs | 1 + indexer_ffi/src/api/types/mod.rs | 11 ++++ indexer_ffi/src/indexer.rs | 2 +- integration_tests/Cargo.toml | 1 + integration_tests/tests/indexer_ffi.rs | 91 ++++++++++++++++++-------- 6 files changed, 77 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27831459..8defea3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3606,6 +3606,7 @@ dependencies = [ "hex", "indexer_ffi", "indexer_service", + "indexer_service_protocol", "indexer_service_rpc", "key_protocol", "log", diff --git a/indexer_ffi/src/api/types/account.rs b/indexer_ffi/src/api/types/account.rs index 853e44d4..7893657b 100644 --- a/indexer_ffi/src/api/types/account.rs +++ b/indexer_ffi/src/api/types/account.rs @@ -7,6 +7,7 @@ use crate::api::types::{FfiBytes32, FfiProgramId, FfiU128}; /// Note: `balance` and `nonce` are u128 values represented as little-endian /// byte arrays since C doesn't have native u128 support. #[repr(C)] +#[derive(Clone)] pub struct FfiAccount { pub program_owner: FfiProgramId, /// Balance as little-endian [u8; 16]. diff --git a/indexer_ffi/src/api/types/mod.rs b/indexer_ffi/src/api/types/mod.rs index 22b44c88..2e7a77ad 100644 --- a/indexer_ffi/src/api/types/mod.rs +++ b/indexer_ffi/src/api/types/mod.rs @@ -130,6 +130,17 @@ impl From> for Vec { } } +impl FfiVec { + /// # Safety + /// + /// `index` must be lesser than `self.len`. + #[must_use] + pub unsafe fn get(&self, index: usize) -> &T { + let ptr = unsafe { self.entries.add(index) }; + unsafe { &*ptr } + } +} + #[repr(C)] pub struct FfiOption { pub value: *mut T, diff --git a/indexer_ffi/src/indexer.rs b/indexer_ffi/src/indexer.rs index 102900f6..c64708db 100644 --- a/indexer_ffi/src/indexer.rs +++ b/indexer_ffi/src/indexer.rs @@ -126,6 +126,6 @@ impl Drop for IndexerServiceFFI { } drop(unsafe { Box::from_raw(indexer_handle.cast::()) }); drop(unsafe { Box::from_raw(runtime.cast::()) }); - drop(unsafe { Box::from_raw(indexer_client.cast::()) }); + drop(unsafe { Box::from_raw(indexer_client.cast::()) }); } } diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 53f0ee98..465ff301 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -24,6 +24,7 @@ sequencer_service_rpc = { workspace = true, features = ["client"] } wallet-ffi.workspace = true indexer_ffi.workspace = true testnet_initial_state.workspace = true +indexer_service_protocol.workspace = true url.workspace = true diff --git a/integration_tests/tests/indexer_ffi.rs b/integration_tests/tests/indexer_ffi.rs index d0c643f7..8ac74ee6 100644 --- a/integration_tests/tests/indexer_ffi.rs +++ b/integration_tests/tests/indexer_ffi.rs @@ -6,8 +6,13 @@ )] use anyhow::{Context as _, Result}; -use indexer_ffi::{IndexerServiceFFI, OperationStatus, api::PointerResult}; -use indexer_service_rpc::RpcClient as _; +use indexer_ffi::{ + IndexerServiceFFI, OperationStatus, + api::{ + PointerResult, + types::{FfiAccountId, FfiOption, FfiVec, account::FfiAccount, block::FfiBlock}, + }, +}; use integration_tests::{ TIME_TO_WAIT_FOR_BLOCK_SECONDS, format_private_account_id, format_public_account_id, test_context_ffi::BlockingTestContextFFI, verify_commitment_is_in_state, @@ -23,6 +28,17 @@ unsafe extern "C" { unsafe fn query_last_block( indexer: *const IndexerServiceFFI, ) -> PointerResult; + + unsafe fn query_block_vec( + indexer: *const IndexerServiceFFI, + before: FfiOption, + limit: u64, + ) -> PointerResult, OperationStatus>; + + unsafe fn query_account( + indexer: *const IndexerServiceFFI, + account_id: FfiAccountId, + ) -> PointerResult; } #[test] @@ -57,7 +73,6 @@ fn indexer_test_run_ffi() -> Result<()> { fn indexer_ffi_block_batching() -> Result<()> { let blocking_ctx = BlockingTestContextFFI::new()?; let runtime_wrapped = blocking_ctx.runtime(); - let ctx = blocking_ctx.ctx(); // WAIT info!("Waiting for indexer to parse blocks"); @@ -65,31 +80,36 @@ fn indexer_ffi_block_batching() -> Result<()> { tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await; }); - let last_block_indexer = runtime_wrapped - .block_on(ctx.indexer_client().get_last_finalized_block_id()) - .unwrap(); + 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 = unsafe { *last_block_indexer_ffi_res.value }; info!("Last block on ind now is {last_block_indexer}"); assert!(last_block_indexer > 1); - // Getting wide batch to fit all blocks (from latest backwards) - let mut block_batch = runtime_wrapped - .block_on(ctx.indexer_client().get_blocks(None, 100)) - .unwrap(); + let before_ffi = FfiOption::::from_none(); + let limit = 100; - // Reverse to check chain consistency from oldest to newest - block_batch.reverse(); + let block_batch_ffi_res = + unsafe { query_block_vec(blocking_ctx.indexer_ffi(), before_ffi, limit) }; - // Checking chain consistency - let mut prev_block_hash = block_batch.first().unwrap().header.hash; + assert!(block_batch_ffi_res.error.is_ok()); - for block in &block_batch[1..] { - assert_eq!(block.header.prev_block_hash, prev_block_hash); + let block_batch = unsafe { &*block_batch_ffi_res.value }; + + let mut last_block_prev_hash = unsafe { block_batch.get(0) }.header.prev_block_hash.data; + + for i in 1..block_batch.len { + let block = unsafe { block_batch.get(i) }; + + assert_eq!(last_block_prev_hash, block.header.hash.data); info!("Block {} chain-consistent", block.header.block_id); - prev_block_hash = block.header.hash; + last_block_prev_hash = block.header.prev_block_hash.data; } Ok(()) @@ -99,6 +119,7 @@ fn indexer_ffi_block_batching() -> Result<()> { fn indexer_ffi_state_consistency() -> Result<()> { let mut blocking_ctx = BlockingTestContextFFI::new()?; let runtime_wrapped = blocking_ctx.runtime_clone(); + let indexer_ffi = blocking_ctx.indexer_ffi(); let ctx = blocking_ctx.ctx_mut(); let command = Command::AuthTransfer(AuthTransferSubcommand::Send { @@ -190,14 +211,21 @@ fn indexer_ffi_state_consistency() -> Result<()> { tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await; }); - let acc1_ind_state = runtime_wrapped.block_on( - ctx.indexer_client() - .get_account(ctx.existing_public_accounts()[0].into()), - )?; - let acc2_ind_state = runtime_wrapped.block_on( - ctx.indexer_client() - .get_account(ctx.existing_public_accounts()[1].into()), - )?; + let acc1_ind_state_ffi = + unsafe { query_account(indexer_ffi, (&ctx.existing_public_accounts()[0]).into()) }; + + assert!(acc1_ind_state_ffi.error.is_ok()); + + let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value }.clone(); + let acc1_ind_state: indexer_service_protocol::Account = acc1_ind_state_pre.into(); + + let acc2_ind_state_ffi = + unsafe { query_account(indexer_ffi, (&ctx.existing_public_accounts()[1]).into()) }; + + assert!(acc2_ind_state_ffi.error.is_ok()); + + let acc2_ind_state_pre = unsafe { &*acc2_ind_state_ffi.value }.clone(); + let acc2_ind_state: indexer_service_protocol::Account = acc2_ind_state_pre.into(); info!("Checking correct state transition"); let acc1_seq_state = @@ -223,6 +251,7 @@ fn indexer_ffi_state_consistency() -> Result<()> { fn indexer_ffi_state_consistency_with_labels() -> Result<()> { let mut blocking_ctx = BlockingTestContextFFI::new()?; let runtime_wrapped = blocking_ctx.runtime_clone(); + let indexer_ffi = blocking_ctx.indexer_ffi(); let ctx = blocking_ctx.ctx_mut(); // Assign labels to both accounts @@ -283,10 +312,14 @@ fn indexer_ffi_state_consistency_with_labels() -> Result<()> { tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await; }); - let acc1_ind_state = runtime_wrapped.block_on( - ctx.indexer_client() - .get_account(ctx.existing_public_accounts()[0].into()), - )?; + let acc1_ind_state_ffi = + unsafe { query_account(indexer_ffi, (&ctx.existing_public_accounts()[0]).into()) }; + + assert!(acc1_ind_state_ffi.error.is_ok()); + + let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value }.clone(); + let acc1_ind_state: indexer_service_protocol::Account = acc1_ind_state_pre.into(); + let acc1_seq_state = runtime_wrapped.block_on(sequencer_service_rpc::RpcClient::get_account( ctx.sequencer_client(), From 0e914adf0ab79e53f5e9291945d8b2dae4234cfe Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Tue, 5 May 2026 15:45:24 +0300 Subject: [PATCH 09/11] fix: merge fix --- .deny.toml | 2 ++ Cargo.lock | 15 ++++++++------- indexer_ffi/Cargo.toml | 3 ++- indexer_ffi/src/api/lifecycle.rs | 2 +- indexer_ffi/src/client.rs | 33 ++++++++++++++++++++++++++++++++ indexer_ffi/src/indexer.rs | 3 ++- indexer_ffi/src/lib.rs | 1 + 7 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 indexer_ffi/src/client.rs diff --git a/.deny.toml b/.deny.toml index e65cdd34..57b5f759 100644 --- a/.deny.toml +++ b/.deny.toml @@ -14,6 +14,8 @@ ignore = [ { id = "RUSTSEC-2025-0141", reason = "`bincode` is unmaintained but continuing to use it." }, { id = "RUSTSEC-2023-0089", reason = "atomic-polyfill is pulled transitively via risc0-zkvm; waiting on upstream fix (see https://github.com/risc0/risc0/issues/3453)" }, { id = "RUSTSEC-2026-0097", reason = "`rand` v0.8.5 is present transitively from logos crates, modification may break integration" }, + { id = "RUSTSEC-2026-0118", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration"}, + { id = "RUSTSEC-2026-0119", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration"}, ] yanked = "deny" unused-ignored-advisory = "deny" diff --git a/Cargo.lock b/Cargo.lock index e7a116e4..1c49d7e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2064,7 +2064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 2.0.117", + "syn 1.0.109", ] [[package]] @@ -2558,7 +2558,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3797,13 +3797,14 @@ dependencies = [ name = "indexer_ffi" version = "0.1.0" dependencies = [ + "anyhow", "cbindgen", "indexer_service", "indexer_service_protocol", "indexer_service_rpc", + "jsonrpsee", "log", "nssa", - "sequencer_core", "tokio", "url", ] @@ -6341,7 +6342,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -8126,7 +8127,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -9095,7 +9096,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -10381,7 +10382,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/indexer_ffi/Cargo.toml b/indexer_ffi/Cargo.toml index 0a140d19..1deb8fad 100644 --- a/indexer_ffi/Cargo.toml +++ b/indexer_ffi/Cargo.toml @@ -7,13 +7,14 @@ version = "0.1.0" [dependencies] nssa.workspace = true indexer_service.workspace = true -sequencer_core.workspace = true indexer_service_rpc.workspace = true indexer_service_protocol.workspace = true url.workspace = true log = { workspace = true } tokio = { features = ["rt-multi-thread"], workspace = true } +jsonrpsee.workspace = true +anyhow.workspace = true [build-dependencies] cbindgen = "0.29" diff --git a/indexer_ffi/src/api/lifecycle.rs b/indexer_ffi/src/api/lifecycle.rs index 15c6e619..c9cd859d 100644 --- a/indexer_ffi/src/api/lifecycle.rs +++ b/indexer_ffi/src/api/lifecycle.rs @@ -1,6 +1,5 @@ use std::{ffi::c_char, path::PathBuf}; -use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait as _}; use tokio::runtime::Runtime; use crate::{ @@ -9,6 +8,7 @@ use crate::{ PointerResult, client::{UrlProtocol, addr_to_url}, }, + client::{IndexerClient, IndexerClientTrait as _}, errors::OperationStatus, }; diff --git a/indexer_ffi/src/client.rs b/indexer_ffi/src/client.rs new file mode 100644 index 00000000..f05b350e --- /dev/null +++ b/indexer_ffi/src/client.rs @@ -0,0 +1,33 @@ +use std::{ops::Deref, sync::Arc}; + +use anyhow::{Context as _, Result}; +use log::info; +pub use url::Url; + +pub trait IndexerClientTrait: Clone { + async fn new(indexer_url: &Url) -> Result; +} + +#[derive(Clone)] +pub struct IndexerClient(Arc); + +impl IndexerClientTrait for IndexerClient { + async fn new(indexer_url: &Url) -> Result { + info!("Connecting to Indexer at {indexer_url}"); + + let client = jsonrpsee::ws_client::WsClientBuilder::default() + .build(indexer_url) + .await + .context("Failed to create websocket client")?; + + Ok(Self(Arc::new(client))) + } +} + +impl Deref for IndexerClient { + type Target = jsonrpsee::ws_client::WsClient; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/indexer_ffi/src/indexer.rs b/indexer_ffi/src/indexer.rs index c64708db..33800356 100644 --- a/indexer_ffi/src/indexer.rs +++ b/indexer_ffi/src/indexer.rs @@ -1,9 +1,10 @@ use std::{ffi::c_void, net::SocketAddr}; use indexer_service::IndexerHandle; -use sequencer_core::indexer_client::IndexerClient; use tokio::runtime::Runtime; +use crate::client::IndexerClient; + #[repr(C)] pub struct IndexerServiceFFI { indexer_handle: *mut c_void, diff --git a/indexer_ffi/src/lib.rs b/indexer_ffi/src/lib.rs index fe594ec0..5806a074 100644 --- a/indexer_ffi/src/lib.rs +++ b/indexer_ffi/src/lib.rs @@ -4,5 +4,6 @@ pub use errors::OperationStatus; pub use indexer::IndexerServiceFFI; pub mod api; +mod client; mod errors; mod indexer; From 6054ae113a0d2c4cbced51c1b7e9209a6e5a8851 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Thu, 7 May 2026 14:29:06 +0300 Subject: [PATCH 10/11] fix(indexer_ffi): suggestion fix --- indexer_ffi/Cargo.toml | 2 +- indexer_ffi/src/api/types/account.rs | 55 ++++++++++-- indexer_ffi/src/api/types/block.rs | 51 ++++++------ indexer_ffi/src/api/types/transaction.rs | 101 ++++++++++++++--------- integration_tests/tests/indexer_ffi.rs | 6 +- 5 files changed, 138 insertions(+), 77 deletions(-) diff --git a/indexer_ffi/Cargo.toml b/indexer_ffi/Cargo.toml index 1deb8fad..1e6b1468 100644 --- a/indexer_ffi/Cargo.toml +++ b/indexer_ffi/Cargo.toml @@ -7,7 +7,7 @@ version = "0.1.0" [dependencies] nssa.workspace = true indexer_service.workspace = true -indexer_service_rpc.workspace = true +indexer_service_rpc = { workspace = true, features = ["client"] } indexer_service_protocol.workspace = true url.workspace = true diff --git a/indexer_ffi/src/api/types/account.rs b/indexer_ffi/src/api/types/account.rs index 7893657b..6c35347f 100644 --- a/indexer_ffi/src/api/types/account.rs +++ b/indexer_ffi/src/api/types/account.rs @@ -7,7 +7,6 @@ use crate::api::types::{FfiBytes32, FfiProgramId, FfiU128}; /// Note: `balance` and `nonce` are u128 values represented as little-endian /// byte arrays since C doesn't have native u128 support. #[repr(C)] -#[derive(Clone)] pub struct FfiAccount { pub program_owner: FfiProgramId, /// Balance as little-endian [u8; 16]. @@ -32,31 +31,69 @@ impl From<&nssa::AccountId> for FfiBytes32 { impl From for FfiAccount { fn from(value: nssa::Account) -> Self { - let (data, data_len, data_cap) = value.data.into_inner().into_raw_parts(); + let nssa::Account { + program_owner, + balance, + data, + nonce, + } = value; + + let (data, data_len, data_cap) = data.into_inner().into_raw_parts(); let program_owner = FfiProgramId { - data: value.program_owner, + data: program_owner, }; Self { program_owner, - balance: value.balance.into(), + balance: balance.into(), data, data_len, data_cap, - nonce: value.nonce.0.into(), + nonce: nonce.0.into(), } } } impl From for indexer_service_protocol::Account { fn from(value: FfiAccount) -> Self { + let FfiAccount { + program_owner, + balance, + data, + data_cap, + data_len, + nonce, + } = value; + Self { - program_owner: ProgramId(value.program_owner.data), - balance: value.balance.into(), + program_owner: ProgramId(program_owner.data), + balance: balance.into(), data: indexer_service_protocol::Data(unsafe { - Vec::from_raw_parts(value.data, value.data_len, value.data_cap) + Vec::from_raw_parts(data, data_len, data_cap) }), - nonce: value.nonce.into(), + nonce: nonce.into(), + } + } +} + +impl From<&FfiAccount> for indexer_service_protocol::Account { + fn from(value: &FfiAccount) -> Self { + let &FfiAccount { + program_owner, + balance, + data, + data_cap, + data_len, + nonce, + } = value; + + Self { + program_owner: ProgramId(program_owner.data), + balance: balance.into(), + data: indexer_service_protocol::Data(unsafe { + Vec::from_raw_parts(data, data_len, data_cap) + }), + nonce: nonce.into(), } } } diff --git a/indexer_ffi/src/api/types/block.rs b/indexer_ffi/src/api/types/block.rs index f7e0c778..bca2fdb5 100644 --- a/indexer_ffi/src/api/types/block.rs +++ b/indexer_ffi/src/api/types/block.rs @@ -17,38 +17,27 @@ pub struct FfiBlock { impl From for FfiBlock { fn from(value: Block) -> Self { + let Block { + header, + body, + bedrock_status, + bedrock_parent_id, + } = value; + Self { - header: value.header.into(), - body: value - .body + header: header.into(), + body: body .transactions .into_iter() .map(Into::into) .collect::>() .into(), - bedrock_status: value.bedrock_status.into(), - bedrock_parent_id: value.bedrock_parent_id.into(), + bedrock_status: bedrock_status.into(), + bedrock_parent_id: bedrock_parent_id.into(), } } } -// impl From> for Block { -// fn from(value: Box) -> Self { -// Self { -// header: BlockHeader { -// block_id: value.header.block_id, -// prev_block_hash: HashType(value.header.prev_block_hash.data), -// hash: HashType(value.header.hash.data), -// timestamp: value.header.timestamp, -// signature: Signature(value.header.signature.data), -// }, -// body: (), -// bedrock_status: value.bedrock_status.into(), -// bedrock_parent_id: MantleMsgId(value.bedrock_parent_id.data), -// } -// } -// } - pub type FfiBlockOpt = FfiOption; #[repr(C)] @@ -62,12 +51,20 @@ pub struct FfiBlockHeader { impl From for FfiBlockHeader { fn from(value: BlockHeader) -> Self { + let BlockHeader { + block_id, + prev_block_hash, + hash, + timestamp, + signature, + } = value; + Self { - block_id: value.block_id, - prev_block_hash: value.prev_block_hash.into(), - hash: value.hash.into(), - timestamp: value.timestamp, - signature: value.signature.into(), + block_id, + prev_block_hash: prev_block_hash.into(), + hash: hash.into(), + timestamp, + signature: signature.into(), } } } diff --git a/indexer_ffi/src/api/types/transaction.rs b/indexer_ffi/src/api/types/transaction.rs index ff14276f..ee3bd01b 100644 --- a/indexer_ffi/src/api/types/transaction.rs +++ b/indexer_ffi/src/api/types/transaction.rs @@ -24,11 +24,16 @@ pub struct FfiPublicTransactionBody { impl From for FfiPublicTransactionBody { fn from(value: PublicTransaction) -> Self { + let PublicTransaction { + hash, + message, + witness_set, + } = value; + Self { - hash: value.hash.into(), - message: value.message.into(), - witness_set: value - .witness_set + hash: hash.into(), + message: message.into(), + witness_set: witness_set .signatures_and_public_keys .into_iter() .map(Into::into) @@ -88,21 +93,26 @@ pub struct FfiPublicMessage { impl From for FfiPublicMessage { fn from(value: PublicMessage) -> Self { + let PublicMessage { + program_id, + account_ids, + nonces, + instruction_data, + } = value; + Self { - program_id: value.program_id.into(), - account_ids: value - .account_ids + program_id: program_id.into(), + account_ids: account_ids .into_iter() .map(Into::into) .collect::>() .into(), - nonces: value - .nonces + nonces: nonces .into_iter() .map(Into::into) .collect::>() .into(), - instruction_data: value.instruction_data.into(), + instruction_data: instruction_data.into(), } } } @@ -117,18 +127,22 @@ pub struct FfiPrivateTransactionBody { impl From for FfiPrivateTransactionBody { fn from(value: PrivacyPreservingTransaction) -> Self { + let PrivacyPreservingTransaction { + hash, + message, + witness_set, + } = value; + Self { - hash: value.hash.into(), - message: value.message.into(), - witness_set: value - .witness_set + hash: hash.into(), + message: message.into(), + witness_set: witness_set .signatures_and_public_keys .into_iter() .map(Into::into) .collect::>() .into(), - proof: value - .witness_set + proof: witness_set .proof .expect("Private execution: proof must be present") .0 @@ -229,21 +243,29 @@ pub struct FfiPrivacyPreservingMessage { impl From for FfiPrivacyPreservingMessage { fn from(value: PrivacyPreservingMessage) -> Self { + let PrivacyPreservingMessage { + public_account_ids, + nonces, + public_post_states, + encrypted_private_post_states, + new_commitments, + new_nullifiers, + block_validity_window, + timestamp_validity_window, + } = value; + Self { - public_account_ids: value - .public_account_ids + public_account_ids: public_account_ids .into_iter() .map(Into::into) .collect::>() .into(), - nonces: value - .nonces + nonces: nonces .into_iter() .map(Into::into) .collect::>() .into(), - public_post_states: value - .public_post_states + public_post_states: public_post_states .into_iter() .map(|acc_ind| -> nssa::Account { acc_ind.try_into().expect("Source is in blocks, must fit") @@ -251,26 +273,23 @@ impl From for FfiPrivacyPreservingMessage { .map(Into::into) .collect::>() .into(), - encrypted_private_post_states: value - .encrypted_private_post_states + encrypted_private_post_states: encrypted_private_post_states .into_iter() .map(Into::into) .collect::>() .into(), - new_commitments: value - .new_commitments + new_commitments: new_commitments .into_iter() .map(|comm| FfiBytes32 { data: comm.0 }) .collect::>() .into(), - new_nullifiers: value - .new_nullifiers + new_nullifiers: new_nullifiers .into_iter() .map(Into::into) .collect::>() .into(), - block_validity_window: cast_validity_window(value.block_validity_window), - timestamp_validity_window: cast_validity_window(value.timestamp_validity_window), + block_validity_window: cast_validity_window(block_validity_window), + timestamp_validity_window: cast_validity_window(timestamp_validity_window), } } } @@ -299,10 +318,16 @@ pub struct FfiEncryptedAccountData { impl From for FfiEncryptedAccountData { fn from(value: EncryptedAccountData) -> Self { + let EncryptedAccountData { + ciphertext, + epk, + view_tag, + } = value; + Self { - ciphertext: value.ciphertext.0.into(), - epk: value.epk.0.into(), - view_tag: value.view_tag, + ciphertext: ciphertext.0.into(), + epk: epk.0.into(), + view_tag, } } } @@ -341,9 +366,11 @@ impl From> for ProgramDeploymentTransac impl From for FfiProgramDeploymentTransactionBody { fn from(value: ProgramDeploymentTransaction) -> Self { + let ProgramDeploymentTransaction { hash, message } = value; + Self { - hash: value.hash.into(), - message: value.message.bytecode.into(), + hash: hash.into(), + message: message.bytecode.into(), } } } @@ -378,7 +405,7 @@ impl From for FfiTransaction { private_body: Box::into_raw(Box::new(priv_tx.into())), program_deployment_body: std::ptr::null_mut(), }, - kind: FfiTransactionKind::Public, + kind: FfiTransactionKind::Private, }, Transaction::ProgramDeployment(pr_dep_tx) => Self { body: FfiTransactionBody { @@ -386,7 +413,7 @@ impl From for FfiTransaction { private_body: std::ptr::null_mut(), program_deployment_body: Box::into_raw(Box::new(pr_dep_tx.into())), }, - kind: FfiTransactionKind::Public, + kind: FfiTransactionKind::ProgramDeploy, }, } } diff --git a/integration_tests/tests/indexer_ffi.rs b/integration_tests/tests/indexer_ffi.rs index 96196fbd..bbc329e3 100644 --- a/integration_tests/tests/indexer_ffi.rs +++ b/integration_tests/tests/indexer_ffi.rs @@ -218,7 +218,7 @@ fn indexer_ffi_state_consistency() -> Result<()> { assert!(acc1_ind_state_ffi.error.is_ok()); - let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value }.clone(); + let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value }; let acc1_ind_state: indexer_service_protocol::Account = acc1_ind_state_pre.into(); let acc2_ind_state_ffi = @@ -226,7 +226,7 @@ fn indexer_ffi_state_consistency() -> Result<()> { assert!(acc2_ind_state_ffi.error.is_ok()); - let acc2_ind_state_pre = unsafe { &*acc2_ind_state_ffi.value }.clone(); + let acc2_ind_state_pre = unsafe { &*acc2_ind_state_ffi.value }; let acc2_ind_state: indexer_service_protocol::Account = acc2_ind_state_pre.into(); info!("Checking correct state transition"); @@ -320,7 +320,7 @@ fn indexer_ffi_state_consistency_with_labels() -> Result<()> { assert!(acc1_ind_state_ffi.error.is_ok()); - let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value }.clone(); + let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value }; let acc1_ind_state: indexer_service_protocol::Account = acc1_ind_state_pre.into(); let acc1_seq_state = From 45ccf14e7720216368d84199006243e3768bcb02 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Thu, 7 May 2026 14:32:03 +0300 Subject: [PATCH 11/11] fix(indexer_ffi): move into indexer --- Cargo.toml | 4 ++-- {indexer_ffi => indexer/ffi}/Cargo.toml | 0 {indexer_ffi => indexer/ffi}/build.rs | 0 {indexer_ffi => indexer/ffi}/cbindgen.toml | 0 {indexer_ffi => indexer/ffi}/indexer_ffi.h | 0 {indexer_ffi => indexer/ffi}/src/api/client.rs | 0 {indexer_ffi => indexer/ffi}/src/api/lifecycle.rs | 0 {indexer_ffi => indexer/ffi}/src/api/memory.rs | 0 {indexer_ffi => indexer/ffi}/src/api/mod.rs | 0 {indexer_ffi => indexer/ffi}/src/api/query.rs | 0 {indexer_ffi => indexer/ffi}/src/api/result.rs | 0 {indexer_ffi => indexer/ffi}/src/api/types/account.rs | 0 {indexer_ffi => indexer/ffi}/src/api/types/block.rs | 0 {indexer_ffi => indexer/ffi}/src/api/types/mod.rs | 0 {indexer_ffi => indexer/ffi}/src/api/types/transaction.rs | 0 {indexer_ffi => indexer/ffi}/src/api/types/vectors.rs | 0 {indexer_ffi => indexer/ffi}/src/client.rs | 0 {indexer_ffi => indexer/ffi}/src/errors.rs | 0 {indexer_ffi => indexer/ffi}/src/indexer.rs | 0 {indexer_ffi => indexer/ffi}/src/lib.rs | 0 20 files changed, 2 insertions(+), 2 deletions(-) rename {indexer_ffi => indexer/ffi}/Cargo.toml (100%) rename {indexer_ffi => indexer/ffi}/build.rs (100%) rename {indexer_ffi => indexer/ffi}/cbindgen.toml (100%) rename {indexer_ffi => indexer/ffi}/indexer_ffi.h (100%) rename {indexer_ffi => indexer/ffi}/src/api/client.rs (100%) rename {indexer_ffi => indexer/ffi}/src/api/lifecycle.rs (100%) rename {indexer_ffi => indexer/ffi}/src/api/memory.rs (100%) rename {indexer_ffi => indexer/ffi}/src/api/mod.rs (100%) rename {indexer_ffi => indexer/ffi}/src/api/query.rs (100%) rename {indexer_ffi => indexer/ffi}/src/api/result.rs (100%) rename {indexer_ffi => indexer/ffi}/src/api/types/account.rs (100%) rename {indexer_ffi => indexer/ffi}/src/api/types/block.rs (100%) rename {indexer_ffi => indexer/ffi}/src/api/types/mod.rs (100%) rename {indexer_ffi => indexer/ffi}/src/api/types/transaction.rs (100%) rename {indexer_ffi => indexer/ffi}/src/api/types/vectors.rs (100%) rename {indexer_ffi => indexer/ffi}/src/client.rs (100%) rename {indexer_ffi => indexer/ffi}/src/errors.rs (100%) rename {indexer_ffi => indexer/ffi}/src/indexer.rs (100%) rename {indexer_ffi => indexer/ffi}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 4fcaf9f6..1bce967f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ members = [ "examples/program_deployment/methods", "examples/program_deployment/methods/guest", "testnet_initial_state", - "indexer_ffi", + "indexer/ffi", ] [workspace.dependencies] @@ -57,7 +57,7 @@ indexer_service_protocol = { path = "indexer/service/protocol" } indexer_service_rpc = { path = "indexer/service/rpc" } wallet = { path = "wallet" } wallet-ffi = { path = "wallet-ffi", default-features = false } -indexer_ffi = { path = "indexer_ffi" } +indexer_ffi = { path = "indexer/ffi" } clock_core = { path = "programs/clock/core" } token_core = { path = "programs/token/core" } token_program = { path = "programs/token" } diff --git a/indexer_ffi/Cargo.toml b/indexer/ffi/Cargo.toml similarity index 100% rename from indexer_ffi/Cargo.toml rename to indexer/ffi/Cargo.toml diff --git a/indexer_ffi/build.rs b/indexer/ffi/build.rs similarity index 100% rename from indexer_ffi/build.rs rename to indexer/ffi/build.rs diff --git a/indexer_ffi/cbindgen.toml b/indexer/ffi/cbindgen.toml similarity index 100% rename from indexer_ffi/cbindgen.toml rename to indexer/ffi/cbindgen.toml diff --git a/indexer_ffi/indexer_ffi.h b/indexer/ffi/indexer_ffi.h similarity index 100% rename from indexer_ffi/indexer_ffi.h rename to indexer/ffi/indexer_ffi.h diff --git a/indexer_ffi/src/api/client.rs b/indexer/ffi/src/api/client.rs similarity index 100% rename from indexer_ffi/src/api/client.rs rename to indexer/ffi/src/api/client.rs diff --git a/indexer_ffi/src/api/lifecycle.rs b/indexer/ffi/src/api/lifecycle.rs similarity index 100% rename from indexer_ffi/src/api/lifecycle.rs rename to indexer/ffi/src/api/lifecycle.rs diff --git a/indexer_ffi/src/api/memory.rs b/indexer/ffi/src/api/memory.rs similarity index 100% rename from indexer_ffi/src/api/memory.rs rename to indexer/ffi/src/api/memory.rs diff --git a/indexer_ffi/src/api/mod.rs b/indexer/ffi/src/api/mod.rs similarity index 100% rename from indexer_ffi/src/api/mod.rs rename to indexer/ffi/src/api/mod.rs diff --git a/indexer_ffi/src/api/query.rs b/indexer/ffi/src/api/query.rs similarity index 100% rename from indexer_ffi/src/api/query.rs rename to indexer/ffi/src/api/query.rs diff --git a/indexer_ffi/src/api/result.rs b/indexer/ffi/src/api/result.rs similarity index 100% rename from indexer_ffi/src/api/result.rs rename to indexer/ffi/src/api/result.rs diff --git a/indexer_ffi/src/api/types/account.rs b/indexer/ffi/src/api/types/account.rs similarity index 100% rename from indexer_ffi/src/api/types/account.rs rename to indexer/ffi/src/api/types/account.rs diff --git a/indexer_ffi/src/api/types/block.rs b/indexer/ffi/src/api/types/block.rs similarity index 100% rename from indexer_ffi/src/api/types/block.rs rename to indexer/ffi/src/api/types/block.rs diff --git a/indexer_ffi/src/api/types/mod.rs b/indexer/ffi/src/api/types/mod.rs similarity index 100% rename from indexer_ffi/src/api/types/mod.rs rename to indexer/ffi/src/api/types/mod.rs diff --git a/indexer_ffi/src/api/types/transaction.rs b/indexer/ffi/src/api/types/transaction.rs similarity index 100% rename from indexer_ffi/src/api/types/transaction.rs rename to indexer/ffi/src/api/types/transaction.rs diff --git a/indexer_ffi/src/api/types/vectors.rs b/indexer/ffi/src/api/types/vectors.rs similarity index 100% rename from indexer_ffi/src/api/types/vectors.rs rename to indexer/ffi/src/api/types/vectors.rs diff --git a/indexer_ffi/src/client.rs b/indexer/ffi/src/client.rs similarity index 100% rename from indexer_ffi/src/client.rs rename to indexer/ffi/src/client.rs diff --git a/indexer_ffi/src/errors.rs b/indexer/ffi/src/errors.rs similarity index 100% rename from indexer_ffi/src/errors.rs rename to indexer/ffi/src/errors.rs diff --git a/indexer_ffi/src/indexer.rs b/indexer/ffi/src/indexer.rs similarity index 100% rename from indexer_ffi/src/indexer.rs rename to indexer/ffi/src/indexer.rs diff --git a/indexer_ffi/src/lib.rs b/indexer/ffi/src/lib.rs similarity index 100% rename from indexer_ffi/src/lib.rs rename to indexer/ffi/src/lib.rs