From 113a68c22c3c4cf3ed3026dba64060de277ae286 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 29 Apr 2026 13:55:44 +0300 Subject: [PATCH] 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, },