mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-06-27 11:29:28 +00:00
Merge pull request #518 from logos-blockchain/Pravdyvy/ffi-mnemonic
feat(wallet-ffi)!: mnemonic-based updates
This commit is contained in:
commit
fb8cbac40e
@ -18,7 +18,6 @@ ignore = [
|
||||
|
||||
{ id = "RUSTSEC-2024-0370", reason = "transitive dependency of `logos-blockchain-http-api-common`, can't do anything than wait for upstream fix" },
|
||||
{ id = "RUSTSEC-2026-0173", reason = "`proc-macro-error2` is unmaintained; pulled in transitively via `leptos_macro` and `overwatch-derive`, waiting on upstream fix" },
|
||||
{ id = "RUSTSEC-2026-0185", reason = "`quinn-proto` remote memory exhaustion; pulled in transitively via `logos-blockchain-libp2p`, waiting on upstream fix" },
|
||||
]
|
||||
yanked = "deny"
|
||||
unused-ignored-advisory = "deny"
|
||||
|
||||
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -2038,7 +2038,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"syn 2.0.117",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -7610,9 +7610,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.14"
|
||||
version = "0.11.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
|
||||
checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.3.4",
|
||||
@ -10765,6 +10765,7 @@ dependencies = [
|
||||
name = "wallet-ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bip39",
|
||||
"cbindgen",
|
||||
"common",
|
||||
"key_protocol",
|
||||
|
||||
2222
flake.lock
generated
2222
flake.lock
generated
File diff suppressed because it is too large
Load Diff
@ -33,6 +33,7 @@ use wallet_ffi::{
|
||||
FfiAccount, FfiAccountIdentity, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys,
|
||||
FfiPublicAccountKey, FfiTransferResult, FfiU128, WalletHandle, error,
|
||||
generic_transaction::{FfiProgramWithDependencies, FfiTransactionResult},
|
||||
wallet::FfiCreateWalletOutput,
|
||||
};
|
||||
|
||||
unsafe extern "C" {
|
||||
@ -40,7 +41,7 @@ unsafe extern "C" {
|
||||
config_path: *const c_char,
|
||||
storage_path: *const c_char,
|
||||
password: *const c_char,
|
||||
) -> *mut WalletHandle;
|
||||
) -> FfiCreateWalletOutput;
|
||||
|
||||
fn wallet_ffi_open(
|
||||
config_path: *const c_char,
|
||||
@ -214,6 +215,13 @@ unsafe extern "C" {
|
||||
out_block_height: *mut u64,
|
||||
) -> error::WalletFfiError;
|
||||
|
||||
fn wallet_ffi_restore_data(
|
||||
handle: *mut WalletHandle,
|
||||
mnemonic: *const c_char,
|
||||
password: *const c_char,
|
||||
depth: u32,
|
||||
) -> error::WalletFfiError;
|
||||
|
||||
fn wallet_ffi_resolve_public_account(
|
||||
account_id: FfiBytes32,
|
||||
needs_sign: bool,
|
||||
@ -254,7 +262,7 @@ unsafe extern "C" {
|
||||
fn new_wallet_ffi_with_test_context_config(
|
||||
ctx: &BlockingTestContext,
|
||||
home: &Path,
|
||||
) -> Result<*mut WalletHandle> {
|
||||
) -> Result<FfiCreateWalletOutput> {
|
||||
let config_path = home.join("wallet_config.json");
|
||||
let storage_path = home.join("storage.json");
|
||||
let mut config = ctx.ctx().wallet().config().to_owned();
|
||||
@ -275,7 +283,7 @@ fn new_wallet_ffi_with_test_context_config(
|
||||
let storage_path = CString::new(storage_path.to_str().unwrap())?;
|
||||
let password = CString::new(ctx.ctx().wallet_password())?;
|
||||
|
||||
let wallet_ffi_handle = unsafe {
|
||||
let create_wallet_result = unsafe {
|
||||
wallet_ffi_create_new(
|
||||
config_path.as_ptr(),
|
||||
storage_path.as_ptr(),
|
||||
@ -293,8 +301,10 @@ fn new_wallet_ffi_with_test_context_config(
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let private_key_hex = CString::new(private_key_hex)?;
|
||||
unsafe { wallet_ffi_import_public_account(wallet_ffi_handle, private_key_hex.as_ptr()) }
|
||||
.unwrap();
|
||||
unsafe {
|
||||
wallet_ffi_import_public_account(create_wallet_result.wallet, private_key_hex.as_ptr())
|
||||
}
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for (account_id, _chain_index) in source_key_chain.private_account_ids() {
|
||||
@ -317,7 +327,7 @@ fn new_wallet_ffi_with_test_context_config(
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_import_private_account(
|
||||
wallet_ffi_handle,
|
||||
create_wallet_result.wallet,
|
||||
key_chain_json.as_ptr(),
|
||||
chain_index_ptr,
|
||||
&raw const identifier,
|
||||
@ -327,10 +337,10 @@ fn new_wallet_ffi_with_test_context_config(
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(wallet_ffi_handle)
|
||||
Ok(create_wallet_result)
|
||||
}
|
||||
|
||||
fn new_wallet_ffi_with_default_config(password: &str) -> Result<*mut WalletHandle> {
|
||||
fn new_wallet_ffi_with_default_config(password: &str) -> Result<FfiCreateWalletOutput> {
|
||||
let tempdir = tempdir()?;
|
||||
let config_path = tempdir.path().join("wallet_config.json");
|
||||
let storage_path = tempdir.path().join("storage.json");
|
||||
@ -338,13 +348,15 @@ fn new_wallet_ffi_with_default_config(password: &str) -> Result<*mut WalletHandl
|
||||
let storage_path_c = CString::new(storage_path.to_str().unwrap())?;
|
||||
let password = CString::new(password)?;
|
||||
|
||||
Ok(unsafe {
|
||||
let create_wallet_result = unsafe {
|
||||
wallet_ffi_create_new(
|
||||
config_path_c.as_ptr(),
|
||||
storage_path_c.as_ptr(),
|
||||
password.as_ptr(),
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
Ok(create_wallet_result)
|
||||
}
|
||||
|
||||
fn load_existing_ffi_wallet(home: &Path) -> Result<*mut WalletHandle> {
|
||||
@ -365,7 +377,10 @@ fn wallet_ffi_create_public_accounts() -> Result<()> {
|
||||
let new_public_account_ids_ffi = unsafe {
|
||||
let mut account_ids = Vec::new();
|
||||
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password)?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_default_config(password)?;
|
||||
for _ in 0..n_accounts {
|
||||
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
||||
wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut out_account_id).unwrap();
|
||||
@ -401,7 +416,10 @@ fn wallet_ffi_create_private_accounts() -> Result<()> {
|
||||
let new_npks_ffi = unsafe {
|
||||
let mut npks = Vec::new();
|
||||
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password)?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_default_config(password)?;
|
||||
for _ in 0..n_accounts {
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap();
|
||||
@ -430,7 +448,10 @@ fn wallet_ffi_save_and_load_persistent_storage() -> Result<()> {
|
||||
let home = tempfile::tempdir()?;
|
||||
// Create a receiving key and save
|
||||
let first_npk = unsafe {
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap();
|
||||
let npk = out_keys.nullifier_public_key.data;
|
||||
@ -467,7 +488,10 @@ fn test_wallet_ffi_list_accounts() -> Result<()> {
|
||||
|
||||
// Create the wallet FFI and track which account IDs were created as public/private
|
||||
let (wallet_ffi_handle, created_public_ids) = unsafe {
|
||||
let handle = new_wallet_ffi_with_default_config(password)?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_default_config(password)?;
|
||||
let mut public_ids: Vec<[u8; 32]> = Vec::new();
|
||||
|
||||
// Create 5 public accounts and 5 receiving keys
|
||||
@ -532,7 +556,10 @@ fn test_wallet_ffi_get_balance_public() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
|
||||
let balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
@ -562,7 +589,10 @@ fn test_wallet_ffi_get_account_public() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let mut out_account = FfiAccount::default();
|
||||
|
||||
let account: Account = unsafe {
|
||||
@ -599,7 +629,10 @@ 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()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let mut out_account = FfiAccount::default();
|
||||
|
||||
let account: Account = unsafe {
|
||||
@ -635,7 +668,10 @@ fn test_wallet_ffi_get_public_account_keys() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let mut out_key = FfiPublicAccountKey::default();
|
||||
|
||||
let key: PublicKey = unsafe {
|
||||
@ -674,7 +710,10 @@ fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let account_id: AccountId = ctx.ctx().existing_private_accounts()[0];
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let mut keys = FfiPrivateAccountKeys::default();
|
||||
|
||||
unsafe {
|
||||
@ -756,7 +795,10 @@ fn wallet_ffi_base58_to_account_id() -> Result<()> {
|
||||
fn wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = 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]);
|
||||
@ -819,7 +861,10 @@ fn wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
|
||||
fn wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
|
||||
// Create a new private account
|
||||
let mut out_account_id = FfiBytes32::default();
|
||||
@ -876,7 +921,10 @@ fn wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
|
||||
fn test_wallet_ffi_transfer_public() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = 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] = 100_u128.to_le_bytes();
|
||||
@ -930,7 +978,10 @@ fn test_wallet_ffi_transfer_public() -> Result<()> {
|
||||
fn test_wallet_ffi_transfer_shielded() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let from: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into();
|
||||
let (to, to_keys) = unsafe {
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
@ -1006,7 +1057,10 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
|
||||
fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let from: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into();
|
||||
let to: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into();
|
||||
let amount: [u8; 16] = 100_u128.to_le_bytes();
|
||||
@ -1066,7 +1120,10 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
|
||||
fn test_wallet_ffi_transfer_private() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
|
||||
let from: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into();
|
||||
let (to, to_keys) = unsafe {
|
||||
@ -1138,11 +1195,303 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restore_keys_from_seed_ffi() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
|
||||
let mnemonic = unsafe { CString::from_raw(mnemonic) };
|
||||
|
||||
// Create 2 new private accounts
|
||||
let (private_account_id_1, private_account_1_keys) = unsafe {
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap();
|
||||
let account_id = lee::AccountId::for_regular_private_account(&out_keys.npk(), 0_u128);
|
||||
let to: FfiBytes32 = account_id.into();
|
||||
(to, out_keys)
|
||||
};
|
||||
|
||||
let (private_account_id_2, private_account_2_keys) = unsafe {
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap();
|
||||
let account_id = lee::AccountId::for_regular_private_account(&out_keys.npk(), 0_u128);
|
||||
let to: FfiBytes32 = account_id.into();
|
||||
(to, out_keys)
|
||||
};
|
||||
|
||||
// Create 2 new public accounts
|
||||
let mut public_account_id_1 = FfiBytes32::default();
|
||||
unsafe {
|
||||
wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut public_account_id_1).unwrap();
|
||||
}
|
||||
|
||||
let mut public_account_id_2 = FfiBytes32::default();
|
||||
unsafe {
|
||||
wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut public_account_id_2).unwrap();
|
||||
}
|
||||
|
||||
info!("Accounts created");
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
|
||||
|
||||
// Sync private account local storage with onchain encrypted state
|
||||
unsafe {
|
||||
let mut current_height = 0;
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap();
|
||||
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap();
|
||||
};
|
||||
|
||||
// Send funds to accounts
|
||||
let from_private: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into();
|
||||
let from_public: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into();
|
||||
|
||||
let amount_1: [u8; 16] = 100_u128.to_le_bytes();
|
||||
|
||||
let mut transfer_result_1 = FfiTransferResult::default();
|
||||
unsafe {
|
||||
let to_identifier = FfiU128 {
|
||||
data: 0_u128.to_le_bytes(),
|
||||
};
|
||||
wallet_ffi_transfer_private(
|
||||
wallet_ffi_handle,
|
||||
&raw const from_private,
|
||||
&raw const private_account_1_keys,
|
||||
&raw const to_identifier,
|
||||
&raw const amount_1,
|
||||
&raw mut transfer_result_1,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
|
||||
|
||||
// Sync private account local storage with onchain encrypted state
|
||||
unsafe {
|
||||
let mut current_height = 0;
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap();
|
||||
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap();
|
||||
};
|
||||
|
||||
let amount_2: [u8; 16] = 101_u128.to_le_bytes();
|
||||
|
||||
let mut transfer_result_2 = FfiTransferResult::default();
|
||||
unsafe {
|
||||
let to_identifier = FfiU128 {
|
||||
data: 0_u128.to_le_bytes(),
|
||||
};
|
||||
wallet_ffi_transfer_private(
|
||||
wallet_ffi_handle,
|
||||
&raw const from_private,
|
||||
&raw const private_account_2_keys,
|
||||
&raw const to_identifier,
|
||||
&raw const amount_2,
|
||||
&raw mut transfer_result_2,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
|
||||
|
||||
// Sync private account local storage with onchain encrypted state
|
||||
unsafe {
|
||||
let mut current_height = 0;
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap();
|
||||
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap();
|
||||
};
|
||||
|
||||
let amount_3: [u8; 16] = 102_u128.to_le_bytes();
|
||||
|
||||
let mut transfer_result_3 = FfiTransferResult::default();
|
||||
unsafe {
|
||||
wallet_ffi_transfer_public(
|
||||
wallet_ffi_handle,
|
||||
&raw const from_public,
|
||||
&raw const public_account_id_1,
|
||||
&raw const amount_3,
|
||||
&raw mut transfer_result_3,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
|
||||
|
||||
// Sync private account local storage with onchain encrypted state
|
||||
unsafe {
|
||||
let mut current_height = 0;
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap();
|
||||
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap();
|
||||
};
|
||||
|
||||
let amount_4: [u8; 16] = 103_u128.to_le_bytes();
|
||||
|
||||
let mut transfer_result_4 = FfiTransferResult::default();
|
||||
unsafe {
|
||||
wallet_ffi_transfer_public(
|
||||
wallet_ffi_handle,
|
||||
&raw const from_public,
|
||||
&raw const public_account_id_2,
|
||||
&raw const amount_4,
|
||||
&raw mut transfer_result_4,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
|
||||
|
||||
// Sync private account local storage with onchain encrypted state
|
||||
unsafe {
|
||||
let mut current_height = 0;
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap();
|
||||
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap();
|
||||
};
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_transfer_result(&raw mut transfer_result_1);
|
||||
wallet_ffi_free_transfer_result(&raw mut transfer_result_2);
|
||||
wallet_ffi_free_transfer_result(&raw mut transfer_result_3);
|
||||
wallet_ffi_free_transfer_result(&raw mut transfer_result_4);
|
||||
}
|
||||
|
||||
info!("Preparation complete, performing keys restoration");
|
||||
|
||||
let password = CString::new(ctx.ctx().wallet_password())?;
|
||||
|
||||
info!("Checking balance correctness before restoration");
|
||||
|
||||
let private_account_id_1_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
&raw const private_account_id_1,
|
||||
false,
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
let private_account_id_2_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
&raw const private_account_id_2,
|
||||
false,
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
let public_account_id_1_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
&raw const public_account_id_1,
|
||||
true,
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
let public_account_id_2_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
&raw const public_account_id_2,
|
||||
true,
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
assert_eq!(private_account_id_1_balance, 100);
|
||||
assert_eq!(private_account_id_2_balance, 101);
|
||||
assert_eq!(public_account_id_1_balance, 102);
|
||||
assert_eq!(public_account_id_2_balance, 103);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_restore_data(wallet_ffi_handle, mnemonic.as_ptr(), password.as_ptr(), 5)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Sync private account local storage with onchain encrypted state
|
||||
unsafe {
|
||||
let mut current_height = 0;
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap();
|
||||
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap();
|
||||
};
|
||||
|
||||
info!("Checking balance correctness after restoration");
|
||||
|
||||
let private_account_id_1_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
&raw const private_account_id_1,
|
||||
false,
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
let private_account_id_2_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
&raw const private_account_id_2,
|
||||
false,
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
let public_account_id_1_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
&raw const public_account_id_1,
|
||||
true,
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
let public_account_id_2_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
&raw const public_account_id_2,
|
||||
true,
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
assert_eq!(private_account_id_1_balance, 100);
|
||||
assert_eq!(private_account_id_2_balance, 101);
|
||||
assert_eq!(public_account_id_1_balance, 102);
|
||||
assert_eq!(public_account_id_2_balance, 103);
|
||||
|
||||
info!("Accounts restored");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_bridge_withdraw() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let from: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into();
|
||||
let bridge_account: FfiBytes32 = lee::system_bridge_account_id().into();
|
||||
let bedrock_account_pk = FfiBytes32::from_bytes([0x42; 32]);
|
||||
@ -1202,7 +1551,10 @@ fn test_wallet_ffi_bridge_withdraw() -> Result<()> {
|
||||
fn test_wallet_ffi_transfer_generic_public() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = 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 = 100_u128;
|
||||
@ -1294,7 +1646,10 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> {
|
||||
fn test_wallet_ffi_transfer_generic_private() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let from: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into();
|
||||
let to: FfiBytes32 = ctx.ctx().existing_private_accounts()[1].into();
|
||||
let amount = 100_u128;
|
||||
@ -1400,7 +1755,10 @@ fn test_wallet_ffi_transfer_generic_private() -> Result<()> {
|
||||
fn test_wallet_ffi_vault_balance_and_claim_public() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
|
||||
let sender = ctx.ctx().existing_public_accounts()[0];
|
||||
let owner = ctx.ctx().existing_public_accounts()[1];
|
||||
@ -1482,7 +1840,10 @@ fn test_wallet_ffi_vault_balance_and_claim_public() -> Result<()> {
|
||||
fn test_wallet_ffi_vault_balance_and_claim_private() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let FfiCreateWalletOutput {
|
||||
wallet: wallet_ffi_handle,
|
||||
mnemonic: _,
|
||||
} = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
|
||||
let sender = ctx.ctx().existing_public_accounts()[0];
|
||||
let owner = ctx.ctx().existing_private_accounts()[0];
|
||||
|
||||
@ -21,6 +21,7 @@ tokio.workspace = true
|
||||
key_protocol.workspace = true
|
||||
serde_json.workspace = true
|
||||
risc0-zkvm.workspace = true
|
||||
bip39.workspace = true
|
||||
vault_core.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
//! Wallet lifecycle management functions.
|
||||
|
||||
use std::{
|
||||
ffi::{c_char, CStr},
|
||||
ffi::{c_char, CStr, CString},
|
||||
path::PathBuf,
|
||||
ptr,
|
||||
str::FromStr as _,
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use wallet::WalletCore;
|
||||
use bip39::Mnemonic;
|
||||
use wallet::{cli::execute_keys_restoration, WalletCore};
|
||||
|
||||
use crate::{
|
||||
c_str_to_string,
|
||||
block_on, c_str_to_string,
|
||||
error::{print_error, WalletFfiError},
|
||||
types::WalletHandle,
|
||||
};
|
||||
@ -20,6 +22,22 @@ pub(crate) struct WalletWrapper {
|
||||
pub core: Mutex<WalletCore>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FfiCreateWalletOutput {
|
||||
pub wallet: *mut WalletHandle,
|
||||
/// C compatible(null terminated) string.
|
||||
pub mnemonic: *mut c_char,
|
||||
}
|
||||
|
||||
impl Default for FfiCreateWalletOutput {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
wallet: std::ptr::null_mut(),
|
||||
mnemonic: std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to get the wallet wrapper from an opaque handle.
|
||||
pub(crate) fn get_wallet(
|
||||
handle: *mut WalletHandle,
|
||||
@ -71,8 +89,8 @@ fn c_str_to_path(ptr: *const c_char, name: &str) -> Result<PathBuf, WalletFfiErr
|
||||
/// - `password`: Password for encrypting the wallet seed
|
||||
///
|
||||
/// # Returns
|
||||
/// - Opaque wallet handle on success
|
||||
/// - Null pointer on error (call `wallet_ffi_get_last_error()` for details)
|
||||
/// - Result, which contains opaque wallet handle and mnemonic words on success
|
||||
/// - Result with null pointers on error (call `wallet_ffi_get_last_error()` for details)
|
||||
///
|
||||
/// # Safety
|
||||
/// All string parameters must be valid null-terminated UTF-8 strings.
|
||||
@ -81,29 +99,40 @@ pub unsafe extern "C" fn wallet_ffi_create_new(
|
||||
config_path: *const c_char,
|
||||
storage_path: *const c_char,
|
||||
password: *const c_char,
|
||||
) -> *mut WalletHandle {
|
||||
) -> FfiCreateWalletOutput {
|
||||
let Ok(config_path) = c_str_to_path(config_path, "config_path") else {
|
||||
return ptr::null_mut();
|
||||
return FfiCreateWalletOutput::default();
|
||||
};
|
||||
|
||||
let Ok(storage_path) = c_str_to_path(storage_path, "storage_path") else {
|
||||
return ptr::null_mut();
|
||||
return FfiCreateWalletOutput::default();
|
||||
};
|
||||
|
||||
let Ok(password) = c_str_to_string(password, "password") else {
|
||||
return ptr::null_mut();
|
||||
return FfiCreateWalletOutput::default();
|
||||
};
|
||||
|
||||
match WalletCore::new_init_storage(config_path, storage_path, None, &password) {
|
||||
Ok((core, _mnemonic)) => {
|
||||
Ok((core, mnemonic)) => {
|
||||
let wrapper = Box::new(WalletWrapper {
|
||||
core: Mutex::new(core),
|
||||
});
|
||||
Box::into_raw(wrapper).cast::<WalletHandle>()
|
||||
let handle = Box::into_raw(wrapper).cast::<WalletHandle>();
|
||||
|
||||
let Ok(c_mnemonic_string) = CString::new(mnemonic.to_string()) else {
|
||||
return FfiCreateWalletOutput::default();
|
||||
};
|
||||
|
||||
let raw_pointer = CString::into_raw(c_mnemonic_string);
|
||||
|
||||
FfiCreateWalletOutput {
|
||||
wallet: handle,
|
||||
mnemonic: raw_pointer,
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to create wallet: {e}"));
|
||||
ptr::null_mut()
|
||||
FfiCreateWalletOutput::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -204,6 +233,81 @@ pub unsafe extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfi
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore wallet data from mnemonic and password.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `handle`: Valid wallet handle
|
||||
/// - `mnemonic`: Valid pointer to instance of `* char`, provided by `wallet_ffi_create_new`
|
||||
/// - `password`: Valid pointer to C string.
|
||||
/// - `depth`: Depth of a reconstructed tree
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Success` on successful restoration
|
||||
/// - Error code on failure
|
||||
///
|
||||
/// # Safety
|
||||
/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
||||
/// - `mnemonic` must be a valid pointer to instance of `* char`, provided by
|
||||
/// `wallet_ffi_create_new`
|
||||
/// - `password` must be a valid pointer to C string.
|
||||
/// - `depth` parameter induces exponential growth in execution time, be aware of it.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_restore_data(
|
||||
handle: *mut WalletHandle,
|
||||
mnemonic: *const c_char,
|
||||
password: *const c_char,
|
||||
depth: u32,
|
||||
) -> WalletFfiError {
|
||||
let wrapper = match get_wallet(handle) {
|
||||
Ok(w) => w,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
let mut wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to lock wallet: {e}"));
|
||||
return WalletFfiError::InternalError;
|
||||
}
|
||||
};
|
||||
|
||||
let Ok(password) = c_str_to_string(password, "password") else {
|
||||
return WalletFfiError::NullPointer;
|
||||
};
|
||||
|
||||
let Ok(mnemonic) = c_str_to_string(mnemonic, "mnemonic") else {
|
||||
return WalletFfiError::NullPointer;
|
||||
};
|
||||
|
||||
let mnemonic = match Mnemonic::from_str(&mnemonic) {
|
||||
Ok(mn) => mn,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to parse mnemonic: {e}"));
|
||||
return WalletFfiError::SerializationError;
|
||||
}
|
||||
};
|
||||
|
||||
let res = match wallet.restore_storage(&mnemonic, &password) {
|
||||
Ok(()) => WalletFfiError::Success,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to restore wallet data: {e}"));
|
||||
WalletFfiError::StorageError
|
||||
}
|
||||
};
|
||||
|
||||
if res == WalletFfiError::Success {
|
||||
match block_on(execute_keys_restoration(&mut wallet, depth)) {
|
||||
Ok(()) => WalletFfiError::Success,
|
||||
Err(err) => {
|
||||
print_error(format!("Failed to restore wallet data: {err}"));
|
||||
WalletFfiError::StorageError
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the sequencer address from the wallet configuration.
|
||||
///
|
||||
/// # Parameters
|
||||
|
||||
@ -299,6 +299,14 @@ typedef struct FfiPublicAccountKey {
|
||||
struct FfiBytes32 public_key;
|
||||
} FfiPublicAccountKey;
|
||||
|
||||
typedef struct FfiCreateWalletOutput {
|
||||
struct WalletHandle *wallet;
|
||||
/**
|
||||
* C compatible(null terminated) string.
|
||||
*/
|
||||
char *mnemonic;
|
||||
} FfiCreateWalletOutput;
|
||||
|
||||
/**
|
||||
* Create a new public account.
|
||||
*
|
||||
@ -1452,15 +1460,15 @@ enum WalletFfiError wallet_ffi_vault_claim_private(struct WalletHandle *handle,
|
||||
* - `password`: Password for encrypting the wallet seed
|
||||
*
|
||||
* # Returns
|
||||
* - Opaque wallet handle on success
|
||||
* - Null pointer on error (call `wallet_ffi_get_last_error()` for details)
|
||||
* - Result, which contains opaque wallet handle and mnemonic words on success
|
||||
* - Result with null pointers on error (call `wallet_ffi_get_last_error()` for details)
|
||||
*
|
||||
* # Safety
|
||||
* All string parameters must be valid null-terminated UTF-8 strings.
|
||||
*/
|
||||
struct WalletHandle *wallet_ffi_create_new(const char *config_path,
|
||||
const char *storage_path,
|
||||
const char *password);
|
||||
struct FfiCreateWalletOutput wallet_ffi_create_new(const char *config_path,
|
||||
const char *storage_path,
|
||||
const char *password);
|
||||
|
||||
/**
|
||||
* Open an existing wallet from storage.
|
||||
@ -1510,6 +1518,31 @@ void wallet_ffi_destroy(struct WalletHandle *handle);
|
||||
*/
|
||||
enum WalletFfiError wallet_ffi_save(struct WalletHandle *handle);
|
||||
|
||||
/**
|
||||
* Restore wallet data from mnemonic and password.
|
||||
*
|
||||
* # Parameters
|
||||
* - `handle`: Valid wallet handle
|
||||
* - `mnemonic`: Valid pointer to instance of `* char`, provided by `wallet_ffi_create_new`
|
||||
* - `password`: Valid pointer to C string.
|
||||
* - `depth`: Depth of a reconstructed tree
|
||||
*
|
||||
* # Returns
|
||||
* - `Success` on successful restoration
|
||||
* - Error code on failure
|
||||
*
|
||||
* # Safety
|
||||
* - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
||||
* - `mnemonic` must be a valid pointer to instance of `* char`, provided by
|
||||
* `wallet_ffi_create_new`
|
||||
* - `password` must be a valid pointer to C string.
|
||||
* - `depth` parameter induces exponential growth in execution time, be aware of it.
|
||||
*/
|
||||
enum WalletFfiError wallet_ffi_restore_data(struct WalletHandle *handle,
|
||||
const char *mnemonic,
|
||||
const char *password,
|
||||
uint32_t depth);
|
||||
|
||||
/**
|
||||
* Get the sequencer address from the wallet configuration.
|
||||
*
|
||||
|
||||
@ -872,3 +872,26 @@ impl WalletCore {
|
||||
&self.config_overrides
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{ffi::CString, str::FromStr as _};
|
||||
|
||||
use bip39::Mnemonic;
|
||||
|
||||
#[test]
|
||||
fn mnemonic_roundtrip() {
|
||||
let mnemonic =
|
||||
Mnemonic::from_entropy(&[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).unwrap();
|
||||
|
||||
let c_mnemonic_string = CString::new(mnemonic.to_string()).unwrap();
|
||||
let c_mnemonic_string_raw = c_mnemonic_string.into_raw();
|
||||
// Safety: Will be safe, pointer is created from CString
|
||||
let c_str = unsafe { CString::from_raw(c_mnemonic_string_raw) };
|
||||
let mn_string = c_str.to_str().unwrap();
|
||||
|
||||
let mn_ret = Mnemonic::from_str(mn_string).unwrap();
|
||||
|
||||
assert_eq!(mnemonic, mn_ret);
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,6 +94,7 @@ impl Storage {
|
||||
|
||||
self.key_chain = UserKeyChain::new_with_accounts(public_tree, private_tree);
|
||||
self.labels = BTreeMap::new();
|
||||
self.last_synced_block = 0;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user