Skip to content

Commit

Permalink
refactor library loading
Browse files Browse the repository at this point in the history
and update docs but remove inline docs comments until lib is more mature
  • Loading branch information
sneurlax committed Oct 12, 2024
1 parent 44c1676 commit cceaf7a
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 157 deletions.
14 changes: 10 additions & 4 deletions impls/monero.rs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,22 @@ 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`:
```
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.
30 changes: 30 additions & 0 deletions impls/monero.rs/example/README.md
Original file line number Diff line number Diff line change
@@ -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`
19 changes: 10 additions & 9 deletions impls/monero.rs/example/src/main.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
213 changes: 78 additions & 135 deletions impls/monero.rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -20,124 +23,38 @@ pub enum WalletError {

pub type WalletResult<T> = Result<T, WalletError>;

/// 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<c_void>,
library: Arc<Library>,
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<Self> {
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<PathBuf> = 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<Arc<Self>> {
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<unsafe extern "C" fn() -> *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<unsafe extern "C" fn() -> *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<Self>,
path: &str,
password: &str,
language: &str,
Expand All @@ -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(),
Expand All @@ -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<Library> {
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<PathBuf> {
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<c_void>,
manager: WalletManager,
manager: Arc<WalletManager>,
}

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<String> {
let c_seed_offset = CString::new(seed_offset)
.map_err(|_| WalletError::FfiError("Invalid seed_offset".to_string()))?;
Expand All @@ -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() {
Expand All @@ -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<String> {
unsafe {
let func: Symbol<unsafe extern "C" fn(*mut c_void, u64, u64) -> *const c_char> =
Expand All @@ -257,19 +202,17 @@ impl Wallet {
}
}

/// Checks if the wallet is deterministic.
pub fn is_deterministic(&self) -> bool {
pub fn is_deterministic(&self) -> WalletResult<bool> {
unsafe {
let func: Symbol<unsafe extern "C" fn(*mut c_void) -> 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<unsafe extern "C" fn(*mut c_void) -> *const c_char> = self
Expand Down
Loading

0 comments on commit cceaf7a

Please sign in to comment.