Skip to content

Commit

Permalink
refactor library loading
Browse files Browse the repository at this point in the history
and update docs
  • Loading branch information
sneurlax committed Oct 12, 2024
1 parent 44c1676 commit dc103c8
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 150 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(())
}
201 changes: 73 additions & 128 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,14 +23,9 @@ 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")]
Expand All @@ -42,102 +40,23 @@ const LIB_NAME: &str = "monero_libwallet2_api_c.dylib";
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);
}

// 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);
}
/// 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 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 +82,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 +94,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 +166,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 +184,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 +204,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 dc103c8

Please sign in to comment.