From 707ea7d3790ffff351833b160db87e866e14c461 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 11 Feb 2026 16:28:24 -0300 Subject: [PATCH] add get private account and init private account ffi methods --- integration_tests/tests/wallet_ffi.rs | 219 ++++++++++++++++++++++++-- wallet-ffi/src/account.rs | 55 +++++++ wallet-ffi/src/transfer.rs | 79 ++++++++++ 3 files changed, 343 insertions(+), 10 deletions(-) diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index 2afd162c..e934d1ea 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -2,6 +2,7 @@ use std::{ collections::HashSet, ffi::{CStr, CString, c_char}, io::Write, + path::{Path, PathBuf}, time::Duration, }; @@ -24,6 +25,11 @@ unsafe extern "C" { password: *const c_char, ) -> *mut WalletHandle; + fn wallet_ffi_open( + config_path: *const c_char, + storage_path: *const c_char, + ) -> *mut WalletHandle; + fn wallet_ffi_destroy(handle: *mut WalletHandle); fn wallet_ffi_create_account_public( @@ -56,6 +62,12 @@ unsafe extern "C" { out_account: *mut FfiAccount, ) -> error::WalletFfiError; + fn wallet_ffi_get_account_private( + handle: *mut WalletHandle, + account_id: *const FfiBytes32, + out_account: *mut FfiAccount, + ) -> error::WalletFfiError; + fn wallet_ffi_free_account_data(account: *mut FfiAccount); fn wallet_ffi_get_public_account_key( @@ -96,12 +108,29 @@ unsafe extern "C" { account_id: *const FfiBytes32, out_result: *mut FfiTransferResult, ) -> error::WalletFfiError; + + fn wallet_ffi_register_private_account( + handle: *mut WalletHandle, + account_id: *const FfiBytes32, + out_result: *mut FfiTransferResult, + ) -> error::WalletFfiError; + + fn wallet_ffi_save(handle: *mut WalletHandle) -> error::WalletFfiError; + + fn wallet_ffi_sync_to_block(handle: *mut WalletHandle, block_id: u64) -> error::WalletFfiError; + + fn wallet_ffi_get_current_block_height( + handle: *mut WalletHandle, + out_block_height: *mut u64, + ) -> error::WalletFfiError; } -fn new_wallet_ffi_with_test_context_config(ctx: &BlockingTestContext) -> *mut WalletHandle { - let tempdir = tempfile::tempdir().unwrap(); - let config_path = tempdir.path().join("wallet_config.json"); - let storage_path = tempdir.path().join("storage.json"); +fn new_wallet_ffi_with_test_context_config( + ctx: &BlockingTestContext, + home: &Path, +) -> *mut WalletHandle { + let config_path = home.join("wallet_config.json"); + let storage_path = home.join("storage.json"); let mut config = ctx.ctx().wallet().config().to_owned(); if let Some(config_overrides) = ctx.ctx().wallet().config_overrides().clone() { config.apply_overrides(config_overrides); @@ -161,6 +190,15 @@ fn new_wallet_rust_with_default_config(password: &str) -> WalletCore { .unwrap() } +fn load_existing_ffi_wallet(home: &Path) -> *mut WalletHandle { + let config_path = home.join("wallet_config.json"); + let storage_path = home.join("storage.json"); + let config_path = CString::new(config_path.to_str().unwrap()).unwrap(); + let storage_path = CString::new(storage_path.to_str().unwrap()).unwrap(); + + unsafe { wallet_ffi_open(config_path.as_ptr(), storage_path.as_ptr()) } +} + #[test] fn test_wallet_ffi_create_public_accounts() { let password = "password_for_tests"; @@ -232,6 +270,56 @@ fn test_wallet_ffi_create_private_accounts() { assert_eq!(new_private_account_ids_ffi, new_private_account_ids_rust) } +#[test] +fn test_wallet_ffi_save_and_load_persistent_storage() -> Result<()> { + let ctx = BlockingTestContext::new()?; + let mut out_private_account_id = FfiBytes32::from_bytes([0; 32]); + let home = tempfile::tempdir().unwrap(); + + // Create a private account with the wallet FFI and save it + unsafe { + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path()); + wallet_ffi_create_account_private( + wallet_ffi_handle, + (&mut out_private_account_id) as *mut FfiBytes32, + ); + + wallet_ffi_save(wallet_ffi_handle); + wallet_ffi_destroy(wallet_ffi_handle); + } + + let private_account_keys = unsafe { + let wallet_ffi_handle = load_existing_ffi_wallet(home.path()); + + let mut private_account = FfiAccount::default(); + + let result = wallet_ffi_get_account_private( + wallet_ffi_handle, + (&out_private_account_id) as *const FfiBytes32, + (&mut private_account) as *mut FfiAccount, + ); + assert_eq!(result, error::WalletFfiError::Success); + + let mut out_keys = FfiPrivateAccountKeys::default(); + let result = wallet_ffi_get_private_account_keys( + wallet_ffi_handle, + (&out_private_account_id) as *const FfiBytes32, + (&mut out_keys) as *mut FfiPrivateAccountKeys, + ); + assert_eq!(result, error::WalletFfiError::Success); + + wallet_ffi_destroy(wallet_ffi_handle); + + out_keys + }; + + assert_eq!( + nssa::AccountId::from(&private_account_keys.npk()), + out_private_account_id.into() + ); + + Ok(()) +} #[test] fn test_wallet_ffi_list_accounts() { @@ -326,7 +414,8 @@ fn test_wallet_ffi_list_accounts() { fn test_wallet_ffi_get_balance_public() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_public_accounts()[0]; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx); + let home = tempfile::tempdir().unwrap(); + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path()); let balance = unsafe { let mut out_balance: [u8; 16] = [0; 16]; @@ -354,7 +443,8 @@ fn test_wallet_ffi_get_balance_public() -> Result<()> { fn test_wallet_ffi_get_account_public() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_public_accounts()[0]; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx); + let home = tempfile::tempdir().unwrap(); + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path()); let mut out_account = FfiAccount::default(); let account: Account = unsafe { @@ -385,11 +475,48 @@ fn test_wallet_ffi_get_account_public() -> Result<()> { Ok(()) } +#[test] +fn test_wallet_ffi_get_account_private() -> Result<()> { + let ctx = BlockingTestContext::new()?; + let account_id: AccountId = ctx.ctx().existing_private_accounts()[0]; + let home = tempfile::tempdir().unwrap(); + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path()); + let mut out_account = FfiAccount::default(); + + let account: Account = unsafe { + let ffi_account_id = FfiBytes32::from(&account_id); + let _result = wallet_ffi_get_account_private( + wallet_ffi_handle, + (&ffi_account_id) as *const FfiBytes32, + (&mut out_account) as *mut FfiAccount, + ); + (&out_account).try_into().unwrap() + }; + + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!(account.balance, 10000); + assert!(account.data.is_empty()); + assert_eq!(account.nonce, 0); + + unsafe { + wallet_ffi_free_account_data((&mut out_account) as *mut FfiAccount); + wallet_ffi_destroy(wallet_ffi_handle); + } + + info!("Successfully retrieved account with correct details"); + + Ok(()) +} + #[test] fn test_wallet_ffi_get_public_account_keys() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_public_accounts()[0]; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx); + let home = tempfile::tempdir().unwrap(); + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path()); let mut out_key = FfiPublicAccountKey::default(); let key: PublicKey = unsafe { @@ -426,7 +553,8 @@ fn test_wallet_ffi_get_public_account_keys() -> Result<()> { fn test_wallet_ffi_get_private_account_keys() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_private_accounts()[0]; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx); + let home = tempfile::tempdir().unwrap(); + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path()); let mut keys = FfiPrivateAccountKeys::default(); unsafe { @@ -504,7 +632,8 @@ fn test_wallet_ffi_base58_to_account_id() { #[test] fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> { let ctx = BlockingTestContext::new().unwrap(); - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx); + let home = tempfile::tempdir().unwrap(); + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path()); // Create a new uninitialized public account let mut out_account_id = FfiBytes32::from_bytes([0; 32]); @@ -563,10 +692,80 @@ fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> { Ok(()) } +#[test] +fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> { + let ctx = BlockingTestContext::new().unwrap(); + let home = tempfile::tempdir().unwrap(); + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path()); + + // Create a new uninitialized public account + let mut out_account_id = FfiBytes32::from_bytes([0; 32]); + unsafe { + wallet_ffi_create_account_private( + wallet_ffi_handle, + (&mut out_account_id) as *mut FfiBytes32, + ); + } + + // Check its program owner is the default program id + let account: Account = unsafe { + let mut out_account = FfiAccount::default(); + wallet_ffi_get_account_private( + wallet_ffi_handle, + (&out_account_id) as *const FfiBytes32, + (&mut out_account) as *mut FfiAccount, + ); + (&out_account).try_into().unwrap() + }; + assert_eq!(account.program_owner, DEFAULT_PROGRAM_ID); + + // Call the init funciton + let mut transfer_result = FfiTransferResult::default(); + unsafe { + wallet_ffi_register_private_account( + wallet_ffi_handle, + (&out_account_id) as *const FfiBytes32, + (&mut transfer_result) as *mut FfiTransferResult, + ); + } + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + unsafe { + let mut current_height = 0; + wallet_ffi_get_current_block_height(wallet_ffi_handle, (&mut current_height) as *mut u64); + wallet_ffi_sync_to_block(wallet_ffi_handle, current_height); + }; + + // Check that the program owner is now the authenticated transfer program + let account: Account = unsafe { + let mut out_account = FfiAccount::default(); + let _result = wallet_ffi_get_account_private( + wallet_ffi_handle, + (&out_account_id) as *const FfiBytes32, + (&mut out_account) as *mut FfiAccount, + ); + (&out_account).try_into().unwrap() + }; + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + + unsafe { + wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult); + wallet_ffi_destroy(wallet_ffi_handle); + } + + Ok(()) +} + #[test] fn test_wallet_ffi_transfer_public() -> Result<()> { let ctx = BlockingTestContext::new().unwrap(); - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx); + let home = tempfile::tempdir().unwrap(); + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path()); let from: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[0]).into(); let to: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[1]).into(); let amount: [u8; 16] = 100u128.to_le_bytes(); diff --git a/wallet-ffi/src/account.rs b/wallet-ffi/src/account.rs index 1a90683a..08e0138a 100644 --- a/wallet-ffi/src/account.rs +++ b/wallet-ffi/src/account.rs @@ -354,6 +354,61 @@ pub unsafe extern "C" fn wallet_ffi_get_account_public( WalletFfiError::Success } +/// Get full private account data from the local storage. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `account_id`: The account ID (32 bytes) +/// - `out_account`: Output pointer for account data +/// +/// # Returns +/// - `Success` on successful query +/// - Error code on failure +/// +/// # Memory +/// The account data must be freed with `wallet_ffi_free_account_data()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct +/// - `out_account` must be a valid pointer to a `FfiAccount` struct +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_get_account_private( + handle: *mut WalletHandle, + account_id: *const FfiBytes32, + out_account: *mut FfiAccount, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if account_id.is_null() || out_account.is_null() { + print_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + let account_id = AccountId::new(unsafe { (*account_id).data }); + + let Some(account) = wallet.get_account_private(account_id) else { + return WalletFfiError::AccountNotFound; + }; + + unsafe { + *out_account = account.into(); + } + + WalletFfiError::Success +} + /// Free account data returned by `wallet_ffi_get_account_public`. /// /// # Safety diff --git a/wallet-ffi/src/transfer.rs b/wallet-ffi/src/transfer.rs index a7f970b9..030841c2 100644 --- a/wallet-ffi/src/transfer.rs +++ b/wallet-ffi/src/transfer.rs @@ -179,6 +179,85 @@ pub unsafe extern "C" fn wallet_ffi_register_public_account( } } + +/// Register a private account on the network. +/// +/// This initializes a private account. The account must be +/// owned by this wallet. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `account_id`: Account ID to register +/// - `out_result`: Output pointer for registration result +/// +/// # Returns +/// - `Success` if the registration was submitted successfully +/// - Error code on failure +/// +/// # Memory +/// The result must be freed with `wallet_ffi_free_transfer_result()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct +/// - `out_result` must be a valid pointer to a `FfiTransferResult` struct +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_register_private_account( + handle: *mut WalletHandle, + account_id: *const FfiBytes32, + out_result: *mut FfiTransferResult, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if account_id.is_null() || out_result.is_null() { + print_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + let account_id = AccountId::new(unsafe { (*account_id).data }); + + let transfer = NativeTokenTransfer(&wallet); + + match block_on(transfer.register_account_private(account_id)) { + Ok(Ok((res, _secret))) => { + let tx_hash = CString::new(res.tx_hash) + .map(|s| s.into_raw()) + .unwrap_or(ptr::null_mut()); + + unsafe { + (*out_result).tx_hash = tx_hash; + (*out_result).success = true; + } + WalletFfiError::Success + } + Ok(Err(e)) => { + print_error(format!("Registration failed: {:?}", e)); + unsafe { + (*out_result).tx_hash = ptr::null_mut(); + (*out_result).success = false; + } + match e { + ExecutionFailureKind::KeyNotFoundError => WalletFfiError::KeyNotFound, + ExecutionFailureKind::SequencerError => WalletFfiError::NetworkError, + ExecutionFailureKind::SequencerClientError(_) => WalletFfiError::NetworkError, + _ => WalletFfiError::InternalError, + } + } + Err(e) => e, + } +} + /// Free a transfer result returned by `wallet_ffi_transfer_public` or /// `wallet_ffi_register_public_account`. ///