diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs index 67d2c5ce..62cf99fc 100644 --- a/integration_tests/tests/auth_transfer/private.rs +++ b/integration_tests/tests/auth_transfer/private.rs @@ -533,3 +533,109 @@ async fn initialize_private_account_using_label() -> Result<()> { Ok(()) } + +#[test] +async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Both transfers below will target this same node with distinct identifiers. + let chain_index = ctx.wallet_mut().create_private_accounts_key(None); + let (npk, vpk) = { + let node = ctx + .wallet() + .storage() + .user_data + .private_key_tree + .key_map + .get(&chain_index) + .expect("node was just inserted"); + let key_chain = &node.value.0; + (key_chain.nullifier_public_key, key_chain.viewing_public_key.clone()) + }; + + let npk_hex = hex::encode(npk.0); + let vpk_hex = hex::encode(vpk.0); + + let identifier_1 = 1_u128; + let identifier_2 = 2_u128; + + let sender_0: AccountId = ctx.existing_public_accounts()[0]; + let sender_1: AccountId = ctx.existing_public_accounts()[1]; + + wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::AuthTransfer(AuthTransferSubcommand::Send { + from: Some(format_public_account_id(sender_0)), + from_label: None, + to: None, + to_label: None, + to_npk: Some(npk_hex.clone()), + to_vpk: Some(vpk_hex.clone()), + to_identifier: Some(identifier_1), + amount: 100, + }), + ) + .await?; + + wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::AuthTransfer(AuthTransferSubcommand::Send { + from: Some(format_public_account_id(sender_1)), + from_label: None, + to: None, + to_label: None, + to_npk: Some(npk_hex), + to_vpk: Some(vpk_hex), + to_identifier: Some(identifier_2), + amount: 200, + }), + ) + .await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::SyncPrivate {}), + ) + .await?; + + // Both accounts must be discovered with the correct balances. + let account_id_1 = AccountId::from((&npk, identifier_1)); + let acc_1 = ctx + .wallet() + .get_account_private(account_id_1) + .context("account for identifier 1 not found after sync")?; + assert_eq!(acc_1.balance, 100); + + let account_id_2 = AccountId::from((&npk, identifier_2)); + let acc_2 = ctx + .wallet() + .get_account_private(account_id_2) + .context("account for identifier 2 not found after sync")?; + assert_eq!(acc_2.balance, 200); + + // Both account ids must resolve to the same key node. + let tree = &ctx.wallet().storage().user_data.private_key_tree; + let ci_1 = tree + .account_id_map + .get(&account_id_1) + .context("account_id_1 missing from private_key_tree.account_id_map")?; + let ci_2 = tree + .account_id_map + .get(&account_id_2) + .context("account_id_2 missing from private_key_tree.account_id_map")?; + assert_eq!( + ci_1, ci_2, + "identifiers 1 and 2 under the same NPK must share a single chain_index" + ); + assert_eq!( + ci_1, &chain_index, + "both accounts must resolve to the key node created at the start of the test" + ); + + info!("Successfully transferred to two distinct identifiers under the same NPK"); + + Ok(()) +} diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index b2621c82..d49724d4 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -159,4 +159,21 @@ mod tests { assert_eq!(account_id, expected_account_id); } + + #[test] + fn account_id_from_nullifier_public_key_identifier_1() { + let nsk = [ + 57, 5, 64, 115, 153, 56, 184, 51, 207, 238, 99, 165, 147, 214, 213, 151, 30, 251, 30, + 196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28, + ]; + let npk = NullifierPublicKey::from(&nsk); + let expected_account_id = AccountId::new([ + 203, 201, 109, 245, 40, 54, 195, 12, 55, 33, 0, 86, 245, 65, 70, 156, 24, 249, 26, 95, + 56, 247, 99, 121, 165, 182, 234, 255, 19, 127, 191, 72, + ]); + + let account_id = AccountId::from((&npk, 1)); + + assert_eq!(account_id, expected_account_id); + } } diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 2739be75..a09ae957 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -304,21 +304,7 @@ impl WalletSubcommand for AccountSubcommand { Self::New(new_subcommand) => new_subcommand.handle_subcommand(wallet_core).await, Self::SyncPrivate => { let curr_last_block = wallet_core.sequencer_client.get_last_block_id().await?; - - if wallet_core - .storage - .user_data - .private_key_tree - .account_id_map - .is_empty() - { - wallet_core.last_synced_block = curr_last_block; - - wallet_core.store_persistent_data().await?; - } else { - wallet_core.sync_to_block(curr_last_block).await?; - } - + wallet_core.sync_to_block(curr_last_block).await?; Ok(SubcommandReturnValue::SyncedToBlock(curr_last_block)) } Self::List { long } => {