From cceaf7acc873b461664b0d07093aed8e5fb4488b Mon Sep 17 00:00:00 2001 From: sneurlax Date: Sat, 12 Oct 2024 00:16:25 -0500 Subject: [PATCH] refactor library loading and update docs but remove inline docs comments until lib is more mature --- impls/monero.rs/README.md | 14 +- impls/monero.rs/example/README.md | 30 ++++ impls/monero.rs/example/src/main.rs | 19 +-- impls/monero.rs/src/lib.rs | 213 ++++++++++------------------ impls/monero.rs/src/main.rs | 19 +-- 5 files changed, 138 insertions(+), 157 deletions(-) create mode 100644 impls/monero.rs/example/README.md diff --git a/impls/monero.rs/README.md b/impls/monero.rs/README.md index 22a991a..e8dc15c 100644 --- a/impls/monero.rs/README.md +++ b/impls/monero.rs/README.md @@ -14,10 +14,17 @@ Build or download the `monero_c` library for your architecture. Follow the upstream docs at https://github.com/MrCyjaneK/monero_c or download the latest release from https://github.com/MrCyjaneK/monero_c/releases. The library can be placed in one of several supported locations relative to the binary in use: -- `../../release` (as in `monero_c/release`) -- `./lib` (as in `monero_c/impls/monero_rust/lib`) +- `../../../../release` (as in `monero_c/release`) +- `../../lib` (as in `monero_c/impls/monero_rust/lib`) - `.` (as in `monero_c/impls/monero_rust/target/debug` or `release`) +and should match your platform as in: +- Android: `libmonero_libwallet2_api_c.so` +- iOS: `MoneroWallet.framework/MoneroWallet` +- Linux: `monero_libwallet2_api_c.so` +- macOS: `monero_libwallet2_api_c.dylib` +- Windows: `monero_libwallet2_api_c.dll` + ### Run demo With the library in a supported location, from `monero_c/impls/monero_rust`: ``` @@ -25,5 +32,4 @@ cargo run ``` ## Using `monero_rust` in your own crate -Refer to the `example` folder. The `monero_c` library must be in `lib/` or in -the same directory as the binary (*eg.* in `example/target/debug/`). +Refer to the `example` folder. Library placement is the same as for the demo. diff --git a/impls/monero.rs/example/README.md b/impls/monero.rs/example/README.md new file mode 100644 index 0000000..3b6ba4c --- /dev/null +++ b/impls/monero.rs/example/README.md @@ -0,0 +1,30 @@ +# `monero_c/impls/monero.rs/example` +Refer to the latest documentation at +https://github.com/MrCyjaneK/monero_c/blob/master/README.md and +https://github.com/MrCyjaneK/monero_c/blob/master/impls/monero.rs/README.md for +the latest documentation. + +## `monero_c` library +A `monero_c` library is required to use these bindings. Build or download the +`monero_c` library for your architecture. Follow the upstream docs at +https://github.com/MrCyjaneK/monero_c or download the latest release from +https://github.com/MrCyjaneK/monero_c/releases. The library can be placed in +one of several supported locations relative to the binary in use: +- `.` + that is, if your binary is in `target/debug` or `target/release`, the library + should also be adjacent to the binary in `release` or `debug`, respectively. + If you're distributing your binary, place the library in the same directory. +- `../../lib` + so if your binary gets built to `target/profile`, then your library can be + in `lib`. +- `../../../../release` + which is a holdover from the original `monero_c` bindings and may not be + practical for your project unless it's also structured as a child of the + `monero_c` repository. + +and should match your platform as in: +- Android: `libmonero_libwallet2_api_c.so` +- iOS: `MoneroWallet.framework/MoneroWallet` +- Linux: `monero_libwallet2_api_c.so` +- macOS: `monero_libwallet2_api_c.dylib` +- Windows: `monero_libwallet2_api_c.dll` diff --git a/impls/monero.rs/example/src/main.rs b/impls/monero.rs/example/src/main.rs index aae688b..099c6d4 100644 --- a/impls/monero.rs/example/src/main.rs +++ b/impls/monero.rs/example/src/main.rs @@ -1,25 +1,26 @@ -use monero_rust::{WalletError, WalletManager, NETWORK_TYPE_MAINNET}; +use monero_rust::{network, WalletError, WalletManager}; fn main() -> Result<(), WalletError> { let wallet_manager = WalletManager::new(None)?; let wallet = wallet_manager.create_wallet( - "wallet", + "wallet_name", "password", "English", - NETWORK_TYPE_MAINNET, + network::MAINNET, )?; + println!("Wallet created successfully."); + match wallet.get_seed("") { Ok(seed) => println!("Seed: {}", seed), - Err(e) => { - eprintln!("Failed to get seed: {:?}", e); - return Err(e); - } + Err(e) => eprintln!("Failed to get seed: {:?}", e), } - let address = wallet.get_address(0, 0)?; - println!("Wallet Address: {}", address); + match wallet.get_address(0, 0) { + Ok(address) => println!("Primary address: {}", address), + Err(e) => eprintln!("Failed to get address: {:?}", e), + } Ok(()) } diff --git a/impls/monero.rs/src/lib.rs b/impls/monero.rs/src/lib.rs index 0891699..12abcb9 100644 --- a/impls/monero.rs/src/lib.rs +++ b/impls/monero.rs/src/lib.rs @@ -6,9 +6,12 @@ use std::sync::Arc; use libloading::{Library, Symbol}; -pub const NETWORK_TYPE_MAINNET: c_int = 0; -pub const NETWORK_TYPE_TESTNET: c_int = 1; -pub const NETWORK_TYPE_STAGENET: c_int = 2; +pub mod network { + use std::os::raw::c_int; + pub const MAINNET: c_int = 0; + pub const TESTNET: c_int = 1; + pub const STAGENET: c_int = 2; +} #[derive(Debug)] pub enum WalletError { @@ -20,124 +23,38 @@ pub enum WalletError { pub type WalletResult = Result; -/// Manages the loading of the Monero wallet library and provides factory methods to create wallets. -/// -/// The `WalletManager` is responsible for loading the Monero C library (`wallet2_api_c`) and -/// provides methods to create and manage `Wallet` instances. -#[derive(Clone)] pub struct WalletManager { ptr: NonNull, - library: Arc, + library: Library, } -#[cfg(target_os = "android")] -const LIB_NAME: &str = "libmonero_libwallet2_api_c.so"; -#[cfg(target_os = "ios")] +#[cfg(target_os = "windows")] +const LIB_NAME: &str = "monero_libwallet2_api_c.dll"; +#[cfg(target_os = "macos")] +const LIB_NAME: &str = "monero_libwallet2_api_c.dylib"; +#[cfg(any(target_os = "ios", target_os = "android"))] const LIB_NAME: &str = "MoneroWallet.framework/MoneroWallet"; #[cfg(target_os = "linux")] const LIB_NAME: &str = "monero_libwallet2_api_c.so"; -#[cfg(target_os = "macos")] -const LIB_NAME: &str = "monero_libwallet2_api_c.dylib"; -#[cfg(target_os = "windows")] -const LIB_NAME: &str = "monero_libwallet2_api_c.dll"; impl WalletManager { - /// Creates a new `WalletManager`, loading the Monero wallet library. - /// - /// If `lib_path` is provided, the library will be loaded from that path. - /// Otherwise, it attempts to load the library from several default locations. - /// - /// # Errors - /// - /// Returns `WalletError::LibraryLoadError` if the library cannot be loaded. - pub fn new(lib_path: Option<&str>) -> WalletResult { - let library = if let Some(path) = lib_path { - // Load the library from the specified path. - unsafe { Library::new(path).map_err(|e| WalletError::LibraryLoadError(e.to_string()))? } - } else { - // Attempt to load the library from multiple candidate paths. - let exe_path = std::env::current_exe() - .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; - let exe_dir = exe_path.parent().ok_or_else(|| { - WalletError::LibraryLoadError("Failed to get executable directory".to_string()) - })?; - - // Prepare the list of candidate paths. - let mut candidates: Vec = Vec::new(); - - // Candidate 1: ../../../../release/ relative to the executable. - if let Some(lib_dir) = exe_dir - .parent() - .and_then(|p| p.parent()) - .and_then(|p| p.parent()) - .and_then(|p| p.parent()) - { - let lib_path = lib_dir.join("release").join(LIB_NAME); - candidates.push(lib_path); - } + /// Creates a new `WalletManager`, loading the Monero wallet library (`wallet2_api_c`). + pub fn new(lib_path: Option<&str>) -> WalletResult> { + let library = Self::load_library(lib_path)?; - // Candidate 2: ../../lib/ relative to the executable. - if let Some(lib_dir) = exe_dir.parent().and_then(|p| p.parent()) { - let lib_path = lib_dir.join("lib").join(LIB_NAME); - candidates.push(lib_path); - } - - // Candidate 3: library in the same directory as the executable. - candidates.push(exe_dir.join(LIB_NAME)); - - // Candidate 4: Let the library loader search standard library paths. - candidates.push(PathBuf::from(LIB_NAME)); - - // Try to load the library from the candidate paths. - let mut library = None; - for candidate in &candidates { - match unsafe { Library::new(candidate) } { - Ok(lib) => { - library = Some(lib); - break; - } - Err(err) => { - eprintln!( - "Failed to load library from {}: {}", - candidate.display(), - err - ); - continue; // Try next candidate. - } - } - } - - // If none of the candidates worked, return an error. - library.ok_or_else(|| { - WalletError::LibraryLoadError(format!( - "Failed to load {} from paths: {:?}", - LIB_NAME, candidates - )) - })? - }; + unsafe { + let func: Symbol *mut c_void> = library + .get(b"MONERO_WalletManagerFactory_getWalletManager\0") + .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; - let library = Arc::new(library); + let ptr = NonNull::new(func()).ok_or(WalletError::NullPointer)?; - // Get the wallet manager pointer from the library. - unsafe { - let func: Symbol *mut c_void> = - library - .get(b"MONERO_WalletManagerFactory_getWalletManager\0") - .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; - let ptr = func(); - NonNull::new(ptr) - .map(|nn_ptr| WalletManager { ptr: nn_ptr, library }) - .ok_or(WalletError::NullPointer) + Ok(Arc::new(WalletManager { ptr, library })) } } - /// Creates a new `Wallet` using the specified parameters. - /// - /// # Errors - /// - /// Returns `WalletError::NullPointer` if the wallet creation fails. pub fn create_wallet( - &self, + self: &Arc, path: &str, password: &str, language: &str, @@ -163,6 +80,7 @@ impl WalletManager { .library .get(b"MONERO_WalletManager_createWallet\0") .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; + let wallet_ptr = func( self.ptr.as_ptr(), c_path.as_ptr(), @@ -174,32 +92,68 @@ impl WalletManager { NonNull::new(wallet_ptr) .map(|ptr| Wallet { ptr, - manager: self.clone(), + manager: Arc::clone(self), }) .ok_or(WalletError::NullPointer) } } + + fn load_library(lib_path: Option<&str>) -> WalletResult { + if let Some(path) = lib_path { + unsafe { Library::new(path).map_err(|e| WalletError::LibraryLoadError(e.to_string())) } + } else { + let exe_path = std::env::current_exe() + .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; + let exe_dir = exe_path.parent().ok_or_else(|| { + WalletError::LibraryLoadError("Failed to get executable directory".to_string()) + })?; + + let candidates = Self::get_library_candidates(exe_dir); + + candidates + .into_iter() + .find_map(|path| unsafe { Library::new(&path).ok() }) + .ok_or_else(|| { + WalletError::LibraryLoadError(format!( + "Failed to load {} from standard paths", + LIB_NAME + )) + }) + } + } + + fn get_library_candidates(exe_dir: &std::path::Path) -> Vec { + let mut candidates = Vec::new(); + + // Candidate 1: ../../../../release/ relative to the executable. + if let Some(lib_dir) = exe_dir.ancestors().nth(4) { + candidates.push(lib_dir.join("release").join(LIB_NAME)); + } + + // Candidate 2: ../../lib/ relative to the executable. + if let Some(lib_dir) = exe_dir.ancestors().nth(2) { + candidates.push(lib_dir.join("lib").join(LIB_NAME)); + } + + // Candidate 3: Same directory as the executable. + candidates.push(exe_dir.join(LIB_NAME)); + // TODO: This should probably be the first candidate for binary + // distribution purposes; it will likely be the first place the library + // will be found in a binary distribution. + + // Candidate 4: Standard library paths. + candidates.push(PathBuf::from(LIB_NAME)); + + candidates + } } -/// Represents a Monero wallet instance. -/// -/// The `Wallet` struct provides methods to interact with a Monero wallet, such as retrieving -/// the seed, getting addresses, and checking if the wallet is deterministic. pub struct Wallet { ptr: NonNull, - manager: WalletManager, + manager: Arc, } impl Wallet { - /// Retrieves the seed of the wallet. - /// - /// # Arguments - /// - /// * `seed_offset` - An optional seed offset. - /// - /// # Errors - /// - /// Returns `WalletError::FfiError` if the seed cannot be retrieved. pub fn get_seed(&self, seed_offset: &str) -> WalletResult { let c_seed_offset = CString::new(seed_offset) .map_err(|_| WalletError::FfiError("Invalid seed_offset".to_string()))?; @@ -210,6 +164,7 @@ impl Wallet { .library .get(b"MONERO_Wallet_seed\0") .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; + let seed_ptr = func(self.ptr.as_ptr(), c_seed_offset.as_ptr()); if seed_ptr.is_null() { @@ -227,16 +182,6 @@ impl Wallet { } } - /// Retrieves the address of the wallet for the given account and address indices. - /// - /// # Arguments - /// - /// * `account_index` - The account index. - /// * `address_index` - The address index. - /// - /// # Errors - /// - /// Returns `WalletError::FfiError` if the address cannot be retrieved. pub fn get_address(&self, account_index: u64, address_index: u64) -> WalletResult { unsafe { let func: Symbol *const c_char> = @@ -257,19 +202,17 @@ impl Wallet { } } - /// Checks if the wallet is deterministic. - pub fn is_deterministic(&self) -> bool { + pub fn is_deterministic(&self) -> WalletResult { unsafe { let func: Symbol bool> = self .manager .library .get(b"MONERO_Wallet_isDeterministic\0") - .expect("Failed to load MONERO_Wallet_isDeterministic"); - func(self.ptr.as_ptr()) + .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; + Ok(func(self.ptr.as_ptr())) } } - /// Retrieves the last error from the wallet. fn get_last_error(&self) -> WalletError { unsafe { let error_func: Symbol *const c_char> = self diff --git a/impls/monero.rs/src/main.rs b/impls/monero.rs/src/main.rs index aae688b..099c6d4 100644 --- a/impls/monero.rs/src/main.rs +++ b/impls/monero.rs/src/main.rs @@ -1,25 +1,26 @@ -use monero_rust::{WalletError, WalletManager, NETWORK_TYPE_MAINNET}; +use monero_rust::{network, WalletError, WalletManager}; fn main() -> Result<(), WalletError> { let wallet_manager = WalletManager::new(None)?; let wallet = wallet_manager.create_wallet( - "wallet", + "wallet_name", "password", "English", - NETWORK_TYPE_MAINNET, + network::MAINNET, )?; + println!("Wallet created successfully."); + match wallet.get_seed("") { Ok(seed) => println!("Seed: {}", seed), - Err(e) => { - eprintln!("Failed to get seed: {:?}", e); - return Err(e); - } + Err(e) => eprintln!("Failed to get seed: {:?}", e), } - let address = wallet.get_address(0, 0)?; - println!("Wallet Address: {}", address); + match wallet.get_address(0, 0) { + Ok(address) => println!("Primary address: {}", address), + Err(e) => eprintln!("Failed to get address: {:?}", e), + } Ok(()) }