mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-07-17 13:20:17 +00:00
feat: add bulk public account reads
Add ordered batched account lookup across sequencer RPC, wallet, and C FFI. Bound batch size to keep worst-case responses within transport limits and expose matching FFI ownership and error contracts. Closes #617
This commit is contained in:
parent
ea41d5a738
commit
5362a12cd1
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -9097,6 +9097,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"jsonrpsee",
|
||||
"sequencer_service_protocol",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -9,6 +9,7 @@ use key_protocol::key_management::KeyChain;
|
||||
use lee::Data;
|
||||
use lee_core::account::Nonce;
|
||||
use log::info;
|
||||
use sequencer_service_rpc::{ClientError, MAX_ACCOUNTS_PER_REQUEST, RpcClient as _};
|
||||
use tokio::test;
|
||||
use wallet::{
|
||||
account::{AccountIdWithPrivacy, HumanReadableAccount, Label},
|
||||
@ -38,6 +39,51 @@ async fn get_existing_account() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn get_public_accounts_returns_states_in_input_order() -> Result<()> {
|
||||
let ctx = TestContext::new().await?;
|
||||
let account_ids = ctx.existing_public_accounts();
|
||||
let ordered_account_ids = vec![account_ids[1], account_ids[0], account_ids[1]];
|
||||
|
||||
let empty_accounts = ctx.sequencer_client().get_accounts(Vec::new()).await?;
|
||||
let single_account = ctx
|
||||
.sequencer_client()
|
||||
.get_accounts(vec![account_ids[0]])
|
||||
.await?;
|
||||
let accounts = ctx
|
||||
.sequencer_client()
|
||||
.get_accounts(ordered_account_ids)
|
||||
.await?;
|
||||
let unknown_account = ctx
|
||||
.sequencer_client()
|
||||
.get_accounts(vec![lee::AccountId::new([0; 32])])
|
||||
.await?;
|
||||
let oversized_error = ctx
|
||||
.sequencer_client()
|
||||
.get_accounts(vec![account_ids[0]; MAX_ACCOUNTS_PER_REQUEST + 1])
|
||||
.await
|
||||
.expect_err("oversized account batches must be rejected");
|
||||
|
||||
assert!(empty_accounts.is_empty());
|
||||
assert_eq!(single_account, vec![accounts[1].clone()]);
|
||||
assert_eq!(accounts.len(), 3);
|
||||
assert_eq!(accounts[0].balance, 20_000);
|
||||
assert_eq!(accounts[1].balance, 10_000);
|
||||
assert_eq!(accounts[2], accounts[0]);
|
||||
assert_eq!(unknown_account, vec![lee::Account::default()]);
|
||||
let ClientError::Call(oversized_error) = oversized_error else {
|
||||
panic!("expected an RPC call error for oversized batch")
|
||||
};
|
||||
assert_eq!(oversized_error.code(), -32602);
|
||||
assert!(
|
||||
oversized_error
|
||||
.message()
|
||||
.contains("Too many accounts requested")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn new_public_account_with_label() -> Result<()> {
|
||||
let mut ctx = TestContext::new().await?;
|
||||
|
||||
@ -16,6 +16,7 @@ use std::{
|
||||
ffi::{CStr, CString, c_char},
|
||||
io::Write as _,
|
||||
path::Path,
|
||||
ptr, slice,
|
||||
str::FromStr as _,
|
||||
time::Duration,
|
||||
};
|
||||
@ -31,9 +32,9 @@ use log::info;
|
||||
use tempfile::tempdir;
|
||||
use wallet::{account::HumanReadableAccount, program_facades::vault::Vault};
|
||||
use wallet_ffi::{
|
||||
FfiAccount, FfiAccountIdWithPrivacy, FfiAccountIdentity, FfiAccountList, FfiBytes32,
|
||||
FfiPrivateAccountKeys, FfiProgramId, FfiPublicAccountKey, FfiTransferResult, FfiU128,
|
||||
WalletHandle, error,
|
||||
FfiAccount, FfiAccountDataList, FfiAccountIdWithPrivacy, FfiAccountIdentity, FfiAccountList,
|
||||
FfiBytes32, FfiPrivateAccountKeys, FfiProgramId, FfiPublicAccountKey, FfiTransferResult,
|
||||
FfiU128, WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST, WalletHandle, error,
|
||||
generic_transaction::{FfiProgramWithDependencies, FfiTransactionResult},
|
||||
label::{AccountIdResolvedFromLabel, LabelAvailability, LabelList},
|
||||
wallet::FfiCreateWalletOutput,
|
||||
@ -101,6 +102,13 @@ unsafe extern "C" {
|
||||
out_account: *mut FfiAccount,
|
||||
) -> error::WalletFfiError;
|
||||
|
||||
fn wallet_ffi_get_accounts_public(
|
||||
handle: *mut WalletHandle,
|
||||
account_ids: *const FfiBytes32,
|
||||
account_ids_len: usize,
|
||||
out_accounts: *mut FfiAccountDataList,
|
||||
) -> error::WalletFfiError;
|
||||
|
||||
fn wallet_ffi_get_account_private(
|
||||
handle: *mut WalletHandle,
|
||||
account_id: *const FfiBytes32,
|
||||
@ -109,6 +117,8 @@ unsafe extern "C" {
|
||||
|
||||
fn wallet_ffi_free_account_data(account: *mut FfiAccount);
|
||||
|
||||
fn wallet_ffi_free_accounts_public(accounts: *mut FfiAccountDataList);
|
||||
|
||||
fn wallet_ffi_get_public_account_key(
|
||||
handle: *mut WalletHandle,
|
||||
account_id: *const FfiBytes32,
|
||||
@ -650,6 +660,69 @@ fn test_wallet_ffi_get_account_public() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_get_accounts_public() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let existing_account_ids = ctx.ctx().existing_public_accounts();
|
||||
let account_ids = [existing_account_ids[1], existing_account_ids[0]];
|
||||
let home = tempfile::tempdir()?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let ffi_account_ids = account_ids.map(FfiBytes32::from);
|
||||
let mut ffi_accounts = FfiAccountDataList::default();
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_get_accounts_public(
|
||||
wallet_ffi_handle,
|
||||
ffi_account_ids.as_ptr(),
|
||||
ffi_account_ids.len(),
|
||||
&raw mut ffi_accounts,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let accounts = unsafe { slice::from_raw_parts(ffi_accounts.accounts, ffi_accounts.count) };
|
||||
let [first, second] = accounts else {
|
||||
panic!("expected two accounts, got {}", accounts.len());
|
||||
};
|
||||
assert_eq!(first.balance.data, 20_000_u128.to_le_bytes());
|
||||
assert_eq!(second.balance.data, 10_000_u128.to_le_bytes());
|
||||
|
||||
let mut empty_accounts = FfiAccountDataList::default();
|
||||
unsafe {
|
||||
wallet_ffi_get_accounts_public(wallet_ffi_handle, ptr::null(), 0, &raw mut empty_accounts)
|
||||
.unwrap();
|
||||
}
|
||||
assert!(empty_accounts.accounts.is_null());
|
||||
assert_eq!(empty_accounts.count, 0);
|
||||
|
||||
let error = unsafe {
|
||||
wallet_ffi_get_accounts_public(wallet_ffi_handle, ptr::null(), 1, &raw mut empty_accounts)
|
||||
};
|
||||
assert_eq!(error, error::WalletFfiError::NullPointer);
|
||||
|
||||
let oversized_ids = vec![FfiBytes32::default(); WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST + 1];
|
||||
let error = unsafe {
|
||||
wallet_ffi_get_accounts_public(
|
||||
wallet_ffi_handle,
|
||||
oversized_ids.as_ptr(),
|
||||
oversized_ids.len(),
|
||||
&raw mut empty_accounts,
|
||||
)
|
||||
};
|
||||
assert_eq!(error, error::WalletFfiError::InvalidArgument);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_accounts_public(&raw mut ffi_accounts);
|
||||
wallet_ffi_free_accounts_public(&raw mut empty_accounts);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_get_account_private() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
|
||||
@ -12,6 +12,9 @@ sequencer_service_protocol.workspace = true
|
||||
|
||||
jsonrpsee = { workspace = true, features = ["macros"] }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json.workspace = true
|
||||
|
||||
[features]
|
||||
client = ["jsonrpsee/client"]
|
||||
server = ["jsonrpsee/server"]
|
||||
|
||||
@ -10,6 +10,13 @@ use sequencer_service_protocol::{
|
||||
LeeTransaction, MembershipProof, Nonce, ProgramId,
|
||||
};
|
||||
|
||||
/// Maximum number of full accounts returned by one `getAccounts` request.
|
||||
///
|
||||
/// A full account may contain 100 KiB of data. JSON encodes each byte as a decimal number, so this
|
||||
/// limit keeps a worst-case batch below jsonrpsee's default 10 MiB response-body limit, with at
|
||||
/// least 512 KiB of headroom for the JSON-RPC envelope.
|
||||
pub const MAX_ACCOUNTS_PER_REQUEST: usize = 24;
|
||||
|
||||
#[cfg(all(not(feature = "server"), not(feature = "client")))]
|
||||
compile_error!("At least one of `server` or `client` features must be enabled.");
|
||||
|
||||
@ -76,6 +83,16 @@ pub trait Rpc {
|
||||
account_ids: Vec<AccountId>,
|
||||
) -> Result<Vec<Nonce>, ErrorObjectOwned>;
|
||||
|
||||
/// Get full account states in input order.
|
||||
///
|
||||
/// Returns an invalid-params error when more than [`MAX_ACCOUNTS_PER_REQUEST`] IDs are
|
||||
/// supplied.
|
||||
#[method(name = "getAccounts")]
|
||||
async fn get_accounts(
|
||||
&self,
|
||||
account_ids: Vec<AccountId>,
|
||||
) -> Result<Vec<Account>, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getProofsAndRoot")]
|
||||
async fn get_proofs_and_root(
|
||||
&self,
|
||||
@ -93,3 +110,36 @@ pub trait Rpc {
|
||||
|
||||
// =============================================================================================
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const MAX_ACCOUNT_DATA_SIZE: usize = 100 * 1024;
|
||||
const DEFAULT_MAX_RESPONSE_BODY_SIZE: usize = 10 * 1024 * 1024;
|
||||
const RESPONSE_BODY_HEADROOM: usize = 512 * 1024;
|
||||
|
||||
#[test]
|
||||
fn maximum_accounts_response_fits_default_response_body_limit() {
|
||||
let mut account = Account {
|
||||
program_owner: [u32::MAX; 8],
|
||||
balance: u128::MAX,
|
||||
nonce: u128::MAX.into(),
|
||||
..Account::default()
|
||||
};
|
||||
account.data = vec![u8::MAX; MAX_ACCOUNT_DATA_SIZE]
|
||||
.try_into()
|
||||
.expect("maximum-size account data should be valid");
|
||||
|
||||
let mut encoded = br#"{"jsonrpc":"2.0","result":"#.to_vec();
|
||||
serde_json::to_writer(&mut encoded, &vec![account; MAX_ACCOUNTS_PER_REQUEST])
|
||||
.expect("response result should serialize");
|
||||
encoded.extend_from_slice(br#", "id":18446744073709551615}"#);
|
||||
|
||||
assert!(
|
||||
encoded.len() <= DEFAULT_MAX_RESPONSE_BODY_SIZE - RESPONSE_BODY_HEADROOM,
|
||||
"maximum batch response is {} bytes",
|
||||
encoded.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ use sequencer_service_protocol::{
|
||||
Account, AccountId, Block, BlockId, ChannelId, Commitment, CommitmentSetDigest, HashType,
|
||||
MembershipProof, Nonce, ProgramId,
|
||||
};
|
||||
use sequencer_service_rpc::MAX_ACCOUNTS_PER_REQUEST;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
const NOT_FOUND_ERROR_CODE: i32 = -31999;
|
||||
@ -162,6 +163,28 @@ impl<BC: BlockPublisherTrait + Send + 'static> sequencer_service_rpc::RpcServer
|
||||
Ok(nonces)
|
||||
}
|
||||
|
||||
async fn get_accounts(
|
||||
&self,
|
||||
account_ids: Vec<AccountId>,
|
||||
) -> Result<Vec<Account>, ErrorObjectOwned> {
|
||||
if account_ids.len() > MAX_ACCOUNTS_PER_REQUEST {
|
||||
return Err(ErrorObjectOwned::owned(
|
||||
ErrorCode::InvalidParams.code(),
|
||||
format!(
|
||||
"Too many accounts requested: got {}, maximum is {MAX_ACCOUNTS_PER_REQUEST}",
|
||||
account_ids.len()
|
||||
),
|
||||
None::<()>,
|
||||
));
|
||||
}
|
||||
|
||||
let sequencer = self.sequencer.lock().await;
|
||||
Ok(account_ids
|
||||
.into_iter()
|
||||
.map(|account_id| sequencer.state().get_account_by_id(account_id))
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn get_proofs_and_root(
|
||||
&self,
|
||||
commitments: Vec<Commitment>,
|
||||
|
||||
@ -28,7 +28,7 @@ include_version = true
|
||||
no_includes = false
|
||||
|
||||
[export]
|
||||
include = ["Ffi.*", "WalletFfiError", "WalletHandle"]
|
||||
include = ["Ffi.*", "WalletFfiError", "WalletHandle", "WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST"]
|
||||
|
||||
[enum]
|
||||
rename_variants = "ScreamingSnakeCase"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
//! Account management functions.
|
||||
|
||||
use std::{ffi::c_char, ptr, str::FromStr as _};
|
||||
use std::{ffi::c_char, ptr, slice, str::FromStr as _};
|
||||
|
||||
use key_protocol::key_management::{key_tree::chain_index::ChainIndex, KeyChain};
|
||||
use lee::AccountId;
|
||||
@ -10,11 +10,11 @@ use crate::{
|
||||
block_on, c_str_to_string,
|
||||
error::{print_error, WalletFfiError},
|
||||
types::{
|
||||
FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, FfiPrivateAccountKeys,
|
||||
WalletHandle,
|
||||
FfiAccount, FfiAccountDataList, FfiAccountList, FfiAccountListEntry, FfiBytes32,
|
||||
FfiPrivateAccountKeys, WalletHandle,
|
||||
},
|
||||
wallet::get_wallet,
|
||||
FfiU128,
|
||||
FfiU128, WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST,
|
||||
};
|
||||
|
||||
/// Create a new public account.
|
||||
@ -414,6 +414,99 @@ pub unsafe extern "C" fn wallet_ffi_get_account_public(
|
||||
WalletFfiError::Success
|
||||
}
|
||||
|
||||
/// Get full public account data from the network in input order.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `handle`: Valid wallet handle
|
||||
/// - `account_ids`: Array of account IDs (32 bytes each); may be null when `account_ids_len` is 0
|
||||
/// - `account_ids_len`: Number of account IDs; must not exceed
|
||||
/// `WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST`
|
||||
/// - `out_accounts`: Output list for account data
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Success` on successful query
|
||||
/// - `InvalidArgument` when `account_ids_len` exceeds `WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST`
|
||||
/// - Error code on failure
|
||||
///
|
||||
/// # Memory
|
||||
/// The returned list must be freed with `wallet_ffi_free_accounts_public()`.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
||||
/// - `account_ids` must point to an array of `account_ids_len` `FfiBytes32` values when the length
|
||||
/// is non-zero
|
||||
/// - `out_accounts` must be a valid pointer to an `FfiAccountDataList` struct
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_get_accounts_public(
|
||||
handle: *mut WalletHandle,
|
||||
account_ids: *const FfiBytes32,
|
||||
account_ids_len: usize,
|
||||
out_accounts: *mut FfiAccountDataList,
|
||||
) -> WalletFfiError {
|
||||
let wrapper = match get_wallet(handle) {
|
||||
Ok(w) => w,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
if out_accounts.is_null() || (account_ids_len > 0 && account_ids.is_null()) {
|
||||
print_error("Null pointer argument");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
if account_ids_len > WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST {
|
||||
print_error(format!(
|
||||
"Too many accounts requested: got {account_ids_len}, maximum is \
|
||||
{WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST}"
|
||||
));
|
||||
return WalletFfiError::InvalidArgument;
|
||||
}
|
||||
|
||||
let account_ids = if account_ids_len == 0 {
|
||||
Vec::new()
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts(account_ids, account_ids_len) }
|
||||
.iter()
|
||||
.map(|account_id| AccountId::new(account_id.data))
|
||||
.collect()
|
||||
};
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to lock wallet: {e}"));
|
||||
return WalletFfiError::InternalError;
|
||||
}
|
||||
};
|
||||
|
||||
let accounts = match block_on(wallet.get_accounts_public(account_ids)) {
|
||||
Ok(accounts) => accounts,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to get accounts: {e}"));
|
||||
return WalletFfiError::NetworkError;
|
||||
}
|
||||
};
|
||||
|
||||
let count = accounts.len();
|
||||
if count == 0 {
|
||||
unsafe {
|
||||
*out_accounts = FfiAccountDataList::default();
|
||||
}
|
||||
} else {
|
||||
let accounts = accounts
|
||||
.into_iter()
|
||||
.map(FfiAccount::from)
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice();
|
||||
let accounts = Box::into_raw(accounts).cast::<FfiAccount>();
|
||||
|
||||
unsafe {
|
||||
*out_accounts = FfiAccountDataList { accounts, count };
|
||||
}
|
||||
}
|
||||
|
||||
WalletFfiError::Success
|
||||
}
|
||||
|
||||
/// Get full private account data from the local storage.
|
||||
///
|
||||
/// # Parameters
|
||||
@ -481,12 +574,42 @@ pub unsafe extern "C" fn wallet_ffi_free_account_data(account: *mut FfiAccount)
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let account = &*account;
|
||||
if !account.data.is_null() && account.data_len > 0 {
|
||||
let slice = std::slice::from_raw_parts_mut(account.data.cast_mut(), account.data_len);
|
||||
drop(Box::from_raw(std::ptr::from_mut::<[u8]>(slice)));
|
||||
free_account_data(&mut *account);
|
||||
}
|
||||
}
|
||||
|
||||
/// Free public accounts returned by `wallet_ffi_get_accounts_public`.
|
||||
///
|
||||
/// # Safety
|
||||
/// The list must be either null or a valid list returned by
|
||||
/// `wallet_ffi_get_accounts_public`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_free_accounts_public(accounts: *mut FfiAccountDataList) {
|
||||
if accounts.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let accounts = &mut *accounts;
|
||||
if !accounts.accounts.is_null() && accounts.count > 0 {
|
||||
let list = slice::from_raw_parts_mut(accounts.accounts, accounts.count);
|
||||
for account in list.iter_mut() {
|
||||
free_account_data(account);
|
||||
}
|
||||
drop(Box::from_raw(std::ptr::from_mut::<[FfiAccount]>(list)));
|
||||
}
|
||||
*accounts = FfiAccountDataList::default();
|
||||
}
|
||||
}
|
||||
|
||||
fn free_account_data(account: &mut FfiAccount) {
|
||||
if !account.data.is_null() && account.data_len > 0 {
|
||||
unsafe {
|
||||
let data = slice::from_raw_parts_mut(account.data.cast_mut(), account.data_len);
|
||||
drop(Box::from_raw(std::ptr::from_mut::<[u8]>(data)));
|
||||
}
|
||||
}
|
||||
*account = FfiAccount::default();
|
||||
}
|
||||
|
||||
/// Import a public account private key into wallet storage.
|
||||
@ -653,3 +776,32 @@ pub unsafe extern "C" fn wallet_ffi_import_private_account(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use lee::{Account, Data};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn free_accounts_public_releases_nested_account_data() {
|
||||
let expected_data = vec![1, 2, 3, 4];
|
||||
let account = Account {
|
||||
data: Data::try_from(expected_data.clone()).expect("account data should be valid"),
|
||||
..Account::default()
|
||||
};
|
||||
let accounts = vec![FfiAccount::from(account)].into_boxed_slice();
|
||||
let count = accounts.len();
|
||||
let accounts = Box::into_raw(accounts).cast::<FfiAccount>();
|
||||
let mut list = FfiAccountDataList { accounts, count };
|
||||
|
||||
let data = unsafe { slice::from_raw_parts((*list.accounts).data, expected_data.len()) };
|
||||
assert_eq!(data, expected_data);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_accounts_public(&raw mut list);
|
||||
}
|
||||
assert!(list.accounts.is_null());
|
||||
assert_eq!(list.count, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +45,8 @@ pub enum WalletFfiError {
|
||||
InvalidKeyValue = 16,
|
||||
/// Invalid program bytecode.
|
||||
InvalidBytecode = 17,
|
||||
/// An argument is outside the supported range.
|
||||
InvalidArgument = 18,
|
||||
/// Internal error (catch-all).
|
||||
InternalError = 99,
|
||||
}
|
||||
|
||||
@ -56,6 +56,14 @@ pub mod types;
|
||||
pub mod vault;
|
||||
pub mod wallet;
|
||||
|
||||
/// Maximum number of account IDs accepted by `wallet_ffi_get_accounts_public`.
|
||||
pub const WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST: usize = 24;
|
||||
|
||||
const _: () = assert!(
|
||||
WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST == sequencer_service_rpc::MAX_ACCOUNTS_PER_REQUEST,
|
||||
"wallet FFI and sequencer RPC account batch limits must match"
|
||||
);
|
||||
|
||||
static TOKIO_RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
|
||||
|
||||
/// Get a reference to the global runtime.
|
||||
|
||||
@ -102,6 +102,22 @@ impl Default for FfiAccount {
|
||||
}
|
||||
}
|
||||
|
||||
/// List of full account data returned by `wallet_ffi_get_accounts_public`.
|
||||
#[repr(C)]
|
||||
pub struct FfiAccountDataList {
|
||||
pub accounts: *mut FfiAccount,
|
||||
pub count: usize,
|
||||
}
|
||||
|
||||
impl Default for FfiAccountDataList {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
accounts: std::ptr::null_mut(),
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Public keys for a private account (safe to expose).
|
||||
#[repr(C)]
|
||||
pub struct FfiPrivateAccountKeys {
|
||||
|
||||
@ -31,6 +31,11 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* Maximum number of account IDs accepted by `wallet_ffi_get_accounts_public`.
|
||||
*/
|
||||
#define WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST 24
|
||||
|
||||
/**
|
||||
* Error codes returned by FFI functions.
|
||||
*/
|
||||
@ -107,6 +112,10 @@ typedef enum WalletFfiError {
|
||||
* Invalid program bytecode.
|
||||
*/
|
||||
INVALID_BYTECODE = 17,
|
||||
/**
|
||||
* An argument is outside the supported range.
|
||||
*/
|
||||
INVALID_ARGUMENT = 18,
|
||||
/**
|
||||
* Internal error (catch-all).
|
||||
*/
|
||||
@ -219,6 +228,14 @@ typedef struct FfiAccount {
|
||||
struct FfiU128 nonce;
|
||||
} FfiAccount;
|
||||
|
||||
/**
|
||||
* List of full account data returned by `wallet_ffi_get_accounts_public`.
|
||||
*/
|
||||
typedef struct FfiAccountDataList {
|
||||
struct FfiAccount *accounts;
|
||||
uintptr_t count;
|
||||
} FfiAccountDataList;
|
||||
|
||||
/**
|
||||
* Result of a transfer operation.
|
||||
*/
|
||||
@ -488,6 +505,35 @@ enum WalletFfiError wallet_ffi_get_account_public(struct WalletHandle *handle,
|
||||
const struct FfiBytes32 *account_id,
|
||||
struct FfiAccount *out_account);
|
||||
|
||||
/**
|
||||
* Get full public account data from the network in input order.
|
||||
*
|
||||
* # Parameters
|
||||
* - `handle`: Valid wallet handle
|
||||
* - `account_ids`: Array of account IDs (32 bytes each); may be null when `account_ids_len` is 0
|
||||
* - `account_ids_len`: Number of account IDs; must not exceed
|
||||
* `WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST`
|
||||
* - `out_accounts`: Output list for account data
|
||||
*
|
||||
* # Returns
|
||||
* - `Success` on successful query
|
||||
* - `InvalidArgument` when `account_ids_len` exceeds `WALLET_FFI_MAX_ACCOUNTS_PER_REQUEST`
|
||||
* - Error code on failure
|
||||
*
|
||||
* # Memory
|
||||
* The returned list must be freed with `wallet_ffi_free_accounts_public()`.
|
||||
*
|
||||
* # Safety
|
||||
* - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
||||
* - `account_ids` must point to an array of `account_ids_len` `FfiBytes32` values when the length
|
||||
* is non-zero
|
||||
* - `out_accounts` must be a valid pointer to an `FfiAccountDataList` struct
|
||||
*/
|
||||
enum WalletFfiError wallet_ffi_get_accounts_public(struct WalletHandle *handle,
|
||||
const struct FfiBytes32 *account_ids,
|
||||
uintptr_t account_ids_len,
|
||||
struct FfiAccountDataList *out_accounts);
|
||||
|
||||
/**
|
||||
* Get full private account data from the local storage.
|
||||
*
|
||||
@ -521,6 +567,15 @@ enum WalletFfiError wallet_ffi_get_account_private(struct WalletHandle *handle,
|
||||
*/
|
||||
void wallet_ffi_free_account_data(struct FfiAccount *account);
|
||||
|
||||
/**
|
||||
* Free public accounts returned by `wallet_ffi_get_accounts_public`.
|
||||
*
|
||||
* # Safety
|
||||
* The list must be either null or a valid list returned by
|
||||
* `wallet_ffi_get_accounts_public`.
|
||||
*/
|
||||
void wallet_ffi_free_accounts_public(struct FfiAccountDataList *accounts);
|
||||
|
||||
/**
|
||||
* Import a public account private key into wallet storage.
|
||||
*
|
||||
|
||||
@ -424,6 +424,11 @@ impl WalletCore {
|
||||
Ok(self.sequencer_client.get_accounts_nonces(accs).await?)
|
||||
}
|
||||
|
||||
/// Get public accounts from the sequencer in input order.
|
||||
pub async fn get_accounts_public(&self, account_ids: Vec<AccountId>) -> Result<Vec<Account>> {
|
||||
Ok(self.sequencer_client.get_accounts(account_ids).await?)
|
||||
}
|
||||
|
||||
pub async fn get_account(&self, account_id: AccountIdWithPrivacy) -> Result<Account> {
|
||||
match account_id {
|
||||
AccountIdWithPrivacy::Public(acc_id) => self.get_account_public(acc_id).await,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user