Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 45 additions & 39 deletions key-wallet-ffi/src/managed_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use key_wallet::managed_account::address_pool::AddressPool;
use key_wallet::managed_account::managed_account_trait::ManagedAccountTrait;
use key_wallet::managed_account::managed_platform_account::ManagedPlatformAccount;
use key_wallet::managed_account::{ManagedCoreFundsAccount, ManagedCoreKeysAccount};
use key_wallet::AccountType;
use key_wallet::{AccountType, ManagedAccountType};

/// Internal handle variant: a funds-bearing account or a keys-only account.
pub(crate) enum FFIManagedCoreAccountInner {
Expand Down Expand Up @@ -1285,9 +1285,13 @@ pub unsafe extern "C" fn managed_core_account_get_external_address_pool(
let account = &*account;
let managed_account = account.keys_account();

// Get external address pool if this is a standard account
// External pool exists for the dual-pool account types (Standard and CoinJoin).
match managed_account.managed_account_type() {
key_wallet::managed_account::managed_account_type::ManagedAccountType::Standard {
ManagedAccountType::Standard {
external_addresses,
..
}
| ManagedAccountType::CoinJoin {
external_addresses,
..
} => {
Expand Down Expand Up @@ -1321,9 +1325,14 @@ pub unsafe extern "C" fn managed_core_account_get_internal_address_pool(
let account = &*account;
let managed_account = account.keys_account();

// Get internal address pool if this is a standard account
// Internal pool exists for the dual-pool account types (Standard and CoinJoin).
use key_wallet::managed_account::managed_account_type::ManagedAccountType;
match managed_account.managed_account_type() {
key_wallet::managed_account::managed_account_type::ManagedAccountType::Standard {
ManagedAccountType::Standard {
internal_addresses,
..
}
| ManagedAccountType::CoinJoin {
internal_addresses,
..
} => {
Expand Down Expand Up @@ -1363,21 +1372,19 @@ pub unsafe extern "C" fn managed_core_account_get_address_pool(

use key_wallet::managed_account::managed_account_type::ManagedAccountType;

match pool_type {
let pool = match pool_type {
FFIAddressPoolType::External => {
// Only standard accounts have external pools
match managed_account.managed_account_type() {
ManagedAccountType::Standard {
external_addresses,
..
} => {
let ffi_pool = FFIAddressPool {
pool: external_addresses as *const AddressPool as *mut AddressPool,
pool_type: FFIAddressPoolType::External,
};
Box::into_raw(Box::new(ffi_pool))
}
_ => std::ptr::null_mut(),
} => external_addresses,
ManagedAccountType::CoinJoin {
external_addresses,
..
} => external_addresses,
_ => return std::ptr::null_mut(),
}
}
FFIAddressPoolType::Internal => {
Expand All @@ -1386,29 +1393,29 @@ pub unsafe extern "C" fn managed_core_account_get_address_pool(
ManagedAccountType::Standard {
internal_addresses,
..
} => {
let ffi_pool = FFIAddressPool {
pool: internal_addresses as *const AddressPool as *mut AddressPool,
pool_type: FFIAddressPoolType::Internal,
};
Box::into_raw(Box::new(ffi_pool))
}
_ => std::ptr::null_mut(),
} => internal_addresses,
ManagedAccountType::CoinJoin {
internal_addresses,
..
} => internal_addresses,
_ => return std::ptr::null_mut(),
}
}
FFIAddressPoolType::Single => {
// Get the single address pool for non-standard accounts
let pool_ref = match managed_account.managed_account_type() {
match managed_account.managed_account_type() {
ManagedAccountType::Standard {
..
} => {
// Standard accounts don't have a "single" pool
return std::ptr::null_mut();
}
ManagedAccountType::CoinJoin {
addresses,
..
} => addresses,
} => {
// Conjoin accounts don't have a "single" pool
return std::ptr::null_mut();
}
ManagedAccountType::IdentityRegistration {
addresses,
} => addresses,
Expand Down Expand Up @@ -1452,15 +1459,15 @@ pub unsafe extern "C" fn managed_core_account_get_address_pool(
addresses,
..
} => addresses,
};

let ffi_pool = FFIAddressPool {
pool: pool_ref as *const AddressPool as *mut AddressPool,
pool_type: FFIAddressPoolType::Single,
};
Box::into_raw(Box::new(ffi_pool))
}
}
}
};

let ffi_pool = FFIAddressPool {
pool: pool as *const AddressPool as *mut AddressPool,
pool_type,
};
Box::into_raw(Box::new(ffi_pool))
}

// ==================== Platform Payment Account Functions ====================
Expand Down Expand Up @@ -2256,20 +2263,19 @@ mod tests {

let cj_account = cj_result.account;

// Test that external/internal return null for CoinJoin account
let cj_external = managed_core_account_get_external_address_pool(cj_account);
assert!(cj_external.is_null());
assert!(!cj_external.is_null());

let cj_internal = managed_core_account_get_internal_address_pool(cj_account);
assert!(cj_internal.is_null());
assert!(!cj_internal.is_null());

// Test that Single pool works for CoinJoin account
let cj_single =
managed_core_account_get_address_pool(cj_account, FFIAddressPoolType::Single);
assert!(!cj_single.is_null());
assert!(cj_single.is_null());

// Clean up
address_pool_free(cj_single);
address_pool_free(cj_external);
address_pool_free(cj_internal);
managed_core_account_free(cj_account);
wallet_manager_free_wallet_ids(wallet_ids_out, count_out);
wallet_manager_free(manager);
Expand Down
14 changes: 10 additions & 4 deletions key-wallet-ffi/src/utxo_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,10 +344,16 @@ mod utxo_tests {
let mut coinjoin_account = ManagedCoreFundsAccount::new(
ManagedAccountType::CoinJoin {
index: 0,
addresses: key_wallet::managed_account::address_pool::AddressPoolBuilder::default()
.base_path(key_wallet::DerivationPath::from(vec![]))
.build()
.unwrap(),
external_addresses:
key_wallet::managed_account::address_pool::AddressPoolBuilder::default()
.base_path(key_wallet::DerivationPath::from(vec![]))
.build()
.unwrap(),
internal_addresses:
key_wallet::managed_account::address_pool::AddressPoolBuilder::default()
.base_path(key_wallet::DerivationPath::from(vec![]))
.build()
.unwrap(),
},
Network::Testnet,
);
Expand Down
26 changes: 19 additions & 7 deletions key-wallet/src/managed_account/managed_account_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,20 +585,32 @@ impl ManagedAccountCollection {
AccountType::CoinJoin {
index,
} => {
// CoinJoin addresses live on the external branch (m/9'/coin'/4'/account'/0/index)
// to match Dash Core, so derive through the `External` pool which uses [0, index].
let mut coinjoin_path = base_path;
coinjoin_path.push(crate::bip32::ChildNumber::from_normal_idx(0)?);
let addresses = AddressPool::new(
coinjoin_path,
// Dual-pool: external (.../0/index) for Dash Core mixed coins, internal
// (.../1/index) for DashSync mixing-change. Watch both so no funds are missed.
let mut external_path = base_path.clone();
external_path.push(crate::bip32::ChildNumber::from_normal_idx(0)?);
let external_addresses = AddressPool::new(
external_path,
AddressPoolType::External,
DEFAULT_COINJOIN_GAP_LIMIT,
network,
key_source,
)?;

let mut internal_path = base_path;
internal_path.push(crate::bip32::ChildNumber::from_normal_idx(1)?);
let internal_addresses = AddressPool::new(
internal_path,
AddressPoolType::Internal,
DEFAULT_COINJOIN_GAP_LIMIT,
network,
key_source,
)?;

ManagedAccountType::CoinJoin {
index,
addresses,
external_addresses,
internal_addresses,
}
}
AccountType::IdentityRegistration => {
Expand Down
8 changes: 4 additions & 4 deletions key-wallet/src/managed_account/managed_account_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ pub trait ManagedAccountTrait {
..
} => self.get_next_receive_address_index(),
ManagedAccountType::CoinJoin {
addresses,
external_addresses: addresses,
..
}
| ManagedAccountType::IdentityRegistration {
Expand Down Expand Up @@ -237,7 +237,7 @@ pub trait ManagedAccountTrait {
..
} => Err("Standard accounts must use next_receive_address or next_change_address"),
ManagedAccountType::CoinJoin {
addresses,
external_addresses: addresses,
..
}
| ManagedAccountType::IdentityRegistration {
Expand Down Expand Up @@ -320,7 +320,7 @@ pub trait ManagedAccountTrait {
"Standard accounts must use next_receive_address_with_info or next_change_address_with_info",
),
ManagedAccountType::CoinJoin {
addresses,
external_addresses: addresses,
..
}
| ManagedAccountType::IdentityRegistration {
Expand Down Expand Up @@ -559,7 +559,7 @@ pub trait ManagedAccountTrait {
..
} => None,
ManagedAccountType::CoinJoin {
addresses,
external_addresses: addresses,
..
}
| ManagedAccountType::IdentityRegistration {
Expand Down
62 changes: 42 additions & 20 deletions key-wallet/src/managed_account/managed_account_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,18 @@ pub enum ManagedAccountType {
/// Internal (change) address pool
internal_addresses: AddressPool,
},
/// CoinJoin account for private transactions
/// CoinJoin account for private transactions.
///
/// Dual-pool like `Standard`: Dash Core receives mixed coins on the external branch
/// (m/9'/coin'/4'/account'/0/index), while DashSync also uses the internal branch
/// (.../1/index) for mixing-change, so both chains must be watched to see all funds.
CoinJoin {
/// Account index
index: u32,
/// CoinJoin address pool
addresses: AddressPool,
/// External (mixed-coin receive) address pool — .../0/index
external_addresses: AddressPool,
/// Internal (mixing-change) address pool — .../1/index
internal_addresses: AddressPool,
},
/// Identity registration funding
IdentityRegistration {
Expand Down Expand Up @@ -211,14 +217,15 @@ impl ManagedAccountType {
external_addresses,
internal_addresses,
..
} => {
vec![external_addresses, internal_addresses]
}
Self::CoinJoin {
addresses,
| Self::CoinJoin {
external_addresses,
internal_addresses,
..
} => {
vec![external_addresses, internal_addresses]
}
| Self::IdentityRegistration {
Self::IdentityRegistration {
addresses,
..
}
Expand Down Expand Up @@ -282,14 +289,15 @@ impl ManagedAccountType {
external_addresses,
internal_addresses,
..
} => {
vec![external_addresses, internal_addresses]
}
Self::CoinJoin {
addresses,
| Self::CoinJoin {
external_addresses,
internal_addresses,
..
} => {
vec![external_addresses, internal_addresses]
}
| Self::IdentityRegistration {
Self::IdentityRegistration {
addresses,
..
}
Expand Down Expand Up @@ -529,23 +537,37 @@ impl ManagedAccountType {
AccountType::CoinJoin {
index,
} => {
// CoinJoin addresses live on the external branch (m/9'/coin'/4'/account'/0/index)
// to match Dash Core, so derive through the `External` pool which uses [0, index].
let mut path = account_type
// Dual-pool: Dash Core receives mixed coins on the external branch
// (m/9'/coin'/4'/account'/0/index); DashSync also uses the internal branch
// (.../1/index) for mixing-change. Watch both so no funds are missed.
let base_path = account_type
.derivation_path(network)
.unwrap_or_else(|_| DerivationPath::master());
path.push(crate::bip32::ChildNumber::from_normal_idx(0)?);
let pool = AddressPool::new(
path,

let mut external_path = base_path.clone();
external_path.push(crate::bip32::ChildNumber::from_normal_idx(0)?);
let external_pool = AddressPool::new(
external_path,
AddressPoolType::External,
DEFAULT_COINJOIN_GAP_LIMIT,
network,
key_source,
)?;

let mut internal_path = base_path;
internal_path.push(crate::bip32::ChildNumber::from_normal_idx(1)?);
let internal_pool = AddressPool::new(
internal_path,
AddressPoolType::Internal,
DEFAULT_COINJOIN_GAP_LIMIT,
network,
key_source,
)?;

Ok(Self::CoinJoin {
index,
addresses: pool,
external_addresses: external_pool,
internal_addresses: internal_pool,
})
}
AccountType::IdentityRegistration => {
Expand Down
4 changes: 2 additions & 2 deletions key-wallet/src/tests/account_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ fn test_coinjoin_account_creation() {
.unwrap();
let pool = match &managed_type {
ManagedAccountType::CoinJoin {
addresses,
external_addresses,
..
} => addresses,
} => external_addresses,
_ => panic!("Expected CoinJoin managed account type"),
};
let first_address =
Expand Down
Loading
Loading