feat(c-bindings): Wallet API (#1960)

This commit is contained in:
Álex 2025-12-17 11:53:17 +01:00 committed by GitHub
parent 3f623e0c9d
commit 22ee405d18
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 650 additions and 53 deletions

62
Cargo.lock generated
View File

@ -85,9 +85,9 @@ dependencies = [
[[package]] [[package]]
name = "aligned" name = "aligned"
version = "0.4.2" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923" checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
dependencies = [ dependencies = [
"as-slice", "as-slice",
] ]
@ -1709,7 +1709,7 @@ dependencies = [
"nomos-tracing-service", "nomos-tracing-service",
"nomos-utils", "nomos-utils",
"rand 0.8.5", "rand 0.8.5",
"reqwest 0.12.25", "reqwest 0.12.26",
"serde", "serde",
"serde_json", "serde_json",
"serde_path_to_error", "serde_path_to_error",
@ -1994,7 +1994,7 @@ dependencies = [
"nomos-core", "nomos-core",
"nomos-da-messages", "nomos-da-messages",
"nomos-http-api-common", "nomos-http-api-common",
"reqwest 0.12.25", "reqwest 0.12.26",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 1.0.69", "thiserror 1.0.69",
@ -3182,7 +3182,7 @@ dependencies = [
"futures", "futures",
"nomos-core", "nomos-core",
"nomos-http-api-common", "nomos-http-api-common",
"reqwest 0.12.25", "reqwest 0.12.26",
"serde", "serde",
] ]
@ -4346,7 +4346,7 @@ dependencies = [
"rgb", "rgb",
"tiff", "tiff",
"zune-core 0.5.0", "zune-core 0.5.0",
"zune-jpeg 0.5.6", "zune-jpeg 0.5.7",
] ]
[[package]] [[package]]
@ -5283,13 +5283,13 @@ dependencies = [
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.10" version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"libc", "libc",
"redox_syscall", "redox_syscall 0.6.0",
] ]
[[package]] [[package]]
@ -5603,9 +5603,9 @@ dependencies = [
[[package]] [[package]]
name = "moxcms" name = "moxcms"
version = "0.7.10" version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608" checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
dependencies = [ dependencies = [
"num-traits", "num-traits",
"pxfm", "pxfm",
@ -5929,7 +5929,7 @@ dependencies = [
"nomos-sdp", "nomos-sdp",
"nomos-storage", "nomos-storage",
"overwatch", "overwatch",
"reqwest 0.12.25", "reqwest 0.12.26",
"serde", "serde",
"serde_json", "serde_json",
"subnetworks-assignations", "subnetworks-assignations",
@ -6101,7 +6101,14 @@ name = "nomos-c"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cbindgen", "cbindgen",
"chain-service",
"cryptarchia-engine",
"key-management-system-keys",
"nomos-api",
"nomos-core",
"nomos-node", "nomos-node",
"nomos-wallet",
"num-bigint",
"overwatch", "overwatch",
"serde_yaml", "serde_yaml",
"tokio", "tokio",
@ -6117,7 +6124,7 @@ dependencies = [
"kzgrs-backend", "kzgrs-backend",
"nomos-core", "nomos-core",
"nomos-tracing", "nomos-tracing",
"reqwest 0.12.25", "reqwest 0.12.26",
"serde_json", "serde_json",
"tokio", "tokio",
"tracing", "tracing",
@ -6587,7 +6594,7 @@ dependencies = [
"opentelemetry-semantic-conventions", "opentelemetry-semantic-conventions",
"opentelemetry_sdk", "opentelemetry_sdk",
"rand 0.8.5", "rand 0.8.5",
"reqwest 0.12.25", "reqwest 0.12.26",
"serde", "serde",
"tokio", "tokio",
"tracing", "tracing",
@ -6979,7 +6986,7 @@ dependencies = [
"bytes", "bytes",
"http 1.4.0", "http 1.4.0",
"opentelemetry", "opentelemetry",
"reqwest 0.12.25", "reqwest 0.12.26",
] ]
[[package]] [[package]]
@ -7167,7 +7174,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall", "redox_syscall 0.5.18",
"smallvec", "smallvec",
"windows-link 0.2.1", "windows-link 0.2.1",
] ]
@ -8334,6 +8341,15 @@ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
] ]
[[package]]
name = "redox_syscall"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5"
dependencies = [
"bitflags 2.10.0",
]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.6" version = "0.4.6"
@ -8461,9 +8477,9 @@ dependencies = [
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.25" version = "0.12.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@ -10057,7 +10073,7 @@ dependencies = [
"nomos-wallet", "nomos-wallet",
"num-bigint", "num-bigint",
"rand 0.8.5", "rand 0.8.5",
"reqwest 0.12.25", "reqwest 0.12.26",
"serde_json", "serde_json",
"serde_yaml", "serde_yaml",
"serial_test", "serial_test",
@ -10604,7 +10620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3beec919fbdf99d719de8eda6adae3281f8a5b71ae40431f44dc7423053d34" checksum = "ba3beec919fbdf99d719de8eda6adae3281f8a5b71ae40431f44dc7423053d34"
dependencies = [ dependencies = [
"loki-api", "loki-api",
"reqwest 0.12.25", "reqwest 0.12.26",
"serde", "serde",
"serde_json", "serde_json",
"snap", "snap",
@ -11040,7 +11056,7 @@ dependencies = [
"axum", "axum",
"mime_guess", "mime_guess",
"regex", "regex",
"reqwest 0.12.25", "reqwest 0.12.26",
"rust-embed", "rust-embed",
"serde", "serde",
"serde_json", "serde_json",
@ -12297,9 +12313,9 @@ dependencies = [
[[package]] [[package]]
name = "zune-jpeg" name = "zune-jpeg"
version = "0.5.6" version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f520eebad972262a1dde0ec455bce4f8b298b1e5154513de58c114c4c54303e8" checksum = "51d915729b0e7d5fe35c2f294c5dc10b30207cc637920e5b59077bfa3da63f28"
dependencies = [ dependencies = [
"zune-core 0.5.0", "zune-core 0.5.0",
] ]

View File

@ -10,10 +10,17 @@ repository = { workspace = true }
version = { workspace = true } version = { workspace = true }
[dependencies] [dependencies]
nomos-node = { default-features = true, workspace = true } chain-service = { workspace = true }
overwatch = { workspace = true } cryptarchia-engine = { workspace = true }
serde_yaml = { default-features = false, workspace = true } key-management-system-keys = { workspace = true }
tokio = { default-features = false, features = ["rt-multi-thread"], workspace = true } nomos-api = { workspace = true }
nomos-core = { workspace = true }
nomos-node = { default-features = true, workspace = true }
nomos-wallet = { workspace = true }
num-bigint = { version = "0.4", default-features = false }
overwatch = { workspace = true }
serde_yaml = { default-features = false, workspace = true }
tokio = { default-features = false, features = ["rt-multi-thread"], workspace = true }
[build-dependencies] [build-dependencies]
cbindgen = "0.29" cbindgen = "0.29"

View File

@ -0,0 +1,125 @@
use crate::{
NomosNode,
api::{PointerResult, free},
errors::OperationStatus,
};
#[repr(C)]
pub enum State {
Bootstrapping = 0x0,
Online = 0x1,
}
impl From<cryptarchia_engine::State> for State {
fn from(value: cryptarchia_engine::State) -> Self {
match value {
cryptarchia_engine::State::Bootstrapping => Self::Bootstrapping,
cryptarchia_engine::State::Online => Self::Online,
}
}
}
pub type Hash = [u8; 32];
pub type HeaderId = Hash;
#[repr(C)]
pub struct CryptarchiaInfo {
pub lib: HeaderId,
pub tip: HeaderId,
pub slot: u64,
pub height: u64,
pub mode: State,
}
impl From<chain_service::CryptarchiaInfo> for CryptarchiaInfo {
fn from(value: chain_service::CryptarchiaInfo) -> Self {
Self {
lib: value.lib.into(),
tip: value.tip.into(),
slot: u64::from(value.slot),
height: value.height,
mode: State::from(value.mode),
}
}
}
/// Gets the current Cryptarchia info.
///
/// This is a synchronous wrapper around the asynchronous
/// [`cryptarchia_info`](nomos_api::http::consensus::cryptarchia_info) function.
///
/// # Arguments
///
/// - `node`: A [`NomosNode`] instance.
///
/// # Returns
///
/// A `Result` containing the [`CryptarchiaInfo`] on success, or an
/// [`OperationStatus`] error on failure.
pub(crate) fn get_cryptarchia_info_sync(
node: &NomosNode,
) -> Result<chain_service::CryptarchiaInfo, OperationStatus> {
let Ok(runtime) = tokio::runtime::Runtime::new() else {
eprintln!("[get_cryptarchia_info_sync] Failed to create tokio runtime. Aborting.");
return Err(OperationStatus::RuntimeError);
};
let Ok(cryptarchia_info) = runtime.block_on(nomos_api::http::consensus::cryptarchia_info(
node.get_overwatch_handle(),
)) else {
eprintln!("[get_cryptarchia_info_sync] Failed to get cryptarchia info. Aborting.");
return Err(OperationStatus::RelayError);
};
Ok(cryptarchia_info)
}
pub type CryptarchiaInfoResult = PointerResult<CryptarchiaInfo, OperationStatus>;
/// Get the current Cryptarchia info.
///
/// # Arguments
///
/// - `node`: A non-null pointer to a [`NomosNode`].
///
/// # Returns
///
/// A [`CryptarchiaInfoResult`] containing a pointer to the allocated
/// [`CryptarchiaInfo`] struct on success, or an [`OperationStatus`] error on
/// failure.
///
/// # Safety
///
/// This function is unsafe because it dereferences raw pointers.
/// The caller must ensure that all pointers are non-null and point to valid
/// memory.
///
/// # Memory Management
///
/// This function allocates memory for the output [`CryptarchiaInfo`] struct.
/// The caller must free this memory using the [`free_cryptarchia_info`]
/// function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn get_cryptarchia_info(node: *const NomosNode) -> CryptarchiaInfoResult {
if node.is_null() {
eprintln!("[get_cryptarchia_info] Received a null `node` pointer. Exiting.");
return CryptarchiaInfoResult::from_error(OperationStatus::NullPtr);
}
let node = unsafe { &*node };
match get_cryptarchia_info_sync(node) {
Ok(cryptarchia_info) => {
let cryptarchia_info = CryptarchiaInfo::from(cryptarchia_info);
CryptarchiaInfoResult::from_value(cryptarchia_info)
}
Err(error) => CryptarchiaInfoResult::from_error(error),
}
}
/// Frees the memory allocated for a [`CryptarchiaInfo`] struct.
///
/// # Arguments
///
/// - `pointer`: A pointer to the [`CryptarchiaInfo`] struct to be freed.
#[unsafe(no_mangle)]
pub extern "C" fn free_cryptarchia_info(pointer: *mut CryptarchiaInfo) {
free::<CryptarchiaInfo>(pointer);
}

View File

@ -3,31 +3,40 @@ use std::ffi::c_char;
use nomos_node::{Config, get_services_to_start, run_node_from_config}; use nomos_node::{Config, get_services_to_start, run_node_from_config};
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use crate::{NomosNode, errors::NomosNodeErrorCode}; use crate::{NomosNode, api::PointerResult, errors::NomosNodeErrorCode};
#[repr(C)] pub type InitializedNomosNodeResult = PointerResult<NomosNode, NomosNodeErrorCode>;
pub struct InitializedNomosNodeResult {
nomos_node: *mut NomosNode,
error_code: NomosNodeErrorCode,
}
/// Creates and starts a Nomos node based on the provided configuration file
/// path.
///
/// # Arguments
///
/// - `config_path`: A pointer to a string representing the path to the
/// configuration file.
///
/// # Returns
///
/// An `InitializedNomosNodeResult` containing either a pointer to the
/// initialized `NomosNode` or an error code.
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn start_nomos_node(config_path: *const c_char) -> InitializedNomosNodeResult { pub extern "C" fn start_nomos_node(config_path: *const c_char) -> InitializedNomosNodeResult {
match initialize_nomos_node(config_path) { initialize_nomos_node(config_path).map_or_else(
Ok(nomos_node) => { InitializedNomosNodeResult::from_error,
let node_ptr = Box::into_raw(Box::new(nomos_node)); InitializedNomosNodeResult::from_value,
InitializedNomosNodeResult { )
nomos_node: node_ptr,
error_code: NomosNodeErrorCode::None,
}
}
Err(error_code) => InitializedNomosNodeResult {
nomos_node: core::ptr::null_mut(),
error_code,
},
}
} }
/// Initializes and starts a Nomos node based on the provided configuration file
/// path.
///
/// # Arguments
///
/// - `config_path`: A pointer to a string representing the path to the
/// configuration file.
///
/// # Returns
///
/// A `Result` containing either the initialized `NomosNode` or an error code.
fn initialize_nomos_node(config_path: *const c_char) -> Result<NomosNode, NomosNodeErrorCode> { fn initialize_nomos_node(config_path: *const c_char) -> Result<NomosNode, NomosNodeErrorCode> {
// TODO: Remove flags when dynamic run of services is implemented. // TODO: Remove flags when dynamic run of services is implemented.
let must_blend_service_group_start = true; let must_blend_service_group_start = true;
@ -79,13 +88,23 @@ fn initialize_nomos_node(config_path: *const c_char) -> Result<NomosNode, NomosN
Ok(NomosNode::new(app, rt)) Ok(NomosNode::new(app, rt))
} }
#[unsafe(no_mangle)] /// Stops and frees the resources associated with the given Nomos node.
///
/// # Arguments
///
/// - `node`: A pointer to the `NomosNode` instance to be stopped.
///
/// # Returns
///
/// An `NomosNodeErrorCode` indicating success or failure.
///
/// # Safety /// # Safety
/// ///
/// The caller must ensure that: /// The caller must ensure that:
/// - `node` is a valid pointer to a `NomosNode` instance /// - `node` is a valid pointer to a `NomosNode` instance
/// - The `NomosNode` instance was created by this library /// - The `NomosNode` instance was created by this library
/// - The pointer will not be used after this function returns /// - The pointer will not be used after this function returns
#[unsafe(no_mangle)]
pub unsafe extern "C" fn stop_node(node: *mut NomosNode) -> NomosNodeErrorCode { pub unsafe extern "C" fn stop_node(node: *mut NomosNode) -> NomosNodeErrorCode {
if node.is_null() { if node.is_null() {
eprintln!("Attempted to stop a null node pointer. This is a bug. Aborting."); eprintln!("Attempted to stop a null node pointer. This is a bug. Aborting.");

12
nomos-c/src/api/memory.rs Normal file
View File

@ -0,0 +1,12 @@
/// Frees memory allocated for a given pointer.
///
/// # Arguments
///
/// * `pointer` - A pointer to the memory to be freed.
pub fn free<Type>(pointer: *mut Type) {
if !pointer.is_null() {
unsafe {
drop(Box::from_raw(pointer));
}
}
}

View File

@ -1 +1,8 @@
pub mod cryptarchia;
pub mod lifecycle; pub mod lifecycle;
pub(crate) mod memory;
pub(crate) mod result;
pub mod wallet;
pub(crate) use memory::free;
pub(crate) use result::{PointerResult, ValueResult};

55
nomos-c/src/api/result.rs Normal file
View File

@ -0,0 +1,55 @@
/// Simple wrapper around a value or an error.
///
/// Value is not guaranteed. You should check the error field before accessing
/// the value.
#[repr(C)]
pub struct ValueResult<Type, Error> {
pub value: Type,
pub error: Error,
}
impl<Type: Default, Error: Default> ValueResult<Type, Error> {
pub fn from_value(value: Type) -> Self {
Self {
value,
error: Error::default(),
}
}
pub fn from_error(error: Error) -> Self {
Self {
value: Type::default(),
error,
}
}
}
/// Simple wrapper around a pointer to a value or an error.
///
/// Pointer is not guaranteed. You should check the error field before
/// dereferencing the pointer.
#[repr(C)]
pub struct PointerResult<Type, Error> {
pub value: *mut Type,
pub error: Error,
}
impl<Type, Error: Default> PointerResult<Type, Error> {
pub fn from_pointer(pointer: *mut Type) -> Self {
Self {
value: pointer,
error: Error::default(),
}
}
pub fn from_value(value: Type) -> Self {
Self::from_pointer(Box::into_raw(Box::new(value)))
}
pub const fn from_error(error: Error) -> Self {
Self {
value: std::ptr::null_mut(),
error,
}
}
}

326
nomos-c/src/api/wallet.rs Normal file
View File

@ -0,0 +1,326 @@
use key_management_system_keys::keys::ZkPublicKey;
use nomos_core::mantle::{SignedMantleTx, Transaction as _, Value};
use nomos_wallet::{WalletService, api::WalletApi};
use num_bigint::BigUint;
use crate::{
NomosNode,
api::{
ValueResult,
cryptarchia::{Hash, HeaderId, get_cryptarchia_info_sync},
free,
},
errors::OperationStatus,
};
/// Get the balance of a wallet address
///
/// This is a synchronous wrapper around [`WalletApi::get_balance`].
///
/// # Arguments
///
/// - `node`: A [`NomosNode`] instance.
/// - `tip`: The header ID to query the balance at.
/// - `wallet_address`: The public key of the wallet address to query.
///
/// # Returns
///
/// A `Result` containing an [`Option<Value>`] on success, or an
/// [`OperationStatus`] error on failure.
pub(crate) fn get_balance_sync(
node: &NomosNode,
tip: nomos_core::header::HeaderId,
wallet_address: ZkPublicKey,
) -> Result<Option<Value>, OperationStatus> {
let Ok(runtime) = tokio::runtime::Runtime::new() else {
eprintln!("[Failed]to create tokio runtime. Aborting.");
return Err(OperationStatus::RuntimeError);
};
runtime
.block_on(async {
let api = WalletApi::<WalletService<_, _, _, _, _>, _>::from_overwatch_handle(
node.get_overwatch_handle(),
)
.await;
api.get_balance(tip, wallet_address).await
})
.map_err(|_| OperationStatus::DynError)
}
pub type BalanceResult = ValueResult<Value, OperationStatus>;
/// Get the balance of a wallet address
///
/// # Arguments
///
/// - `node`: A non-null pointer to a [`NomosNode`] instance.
/// - `wallet_address`: A non-null pointer to the public key bytes of the wallet
/// address to query.
/// - `optional_tip`: An optional pointer to the header ID to query the balance
/// at. If null, the current tip will be used.
///
/// # Returns
///
/// A [`ValueResult`] containing the balance on success, or an
/// [`OperationStatus`] error on failure.
///
/// # Safety
///
/// This function is unsafe because it dereferences raw pointers. The caller
/// must ensure that all pointers are valid.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn get_balance(
node: *const NomosNode,
wallet_address: *const u8,
optional_tip: *const HeaderId,
) -> BalanceResult {
if node.is_null() {
eprintln!("[get_balance] Received a null `node` pointer. Exiting.");
return BalanceResult::from_error(OperationStatus::NullPtr);
}
if wallet_address.is_null() {
eprintln!("[get_balance] Received a null `wallet_address` pointer. Exiting.");
return BalanceResult::from_error(OperationStatus::NullPtr);
}
let node = unsafe { &*node };
let tip = if optional_tip.is_null() {
match get_cryptarchia_info_sync(node) {
Ok(cryptarchia_info) => cryptarchia_info.tip,
Err(error) => return BalanceResult::from_error(error),
}
} else {
nomos_core::header::HeaderId::from(unsafe { *optional_tip })
};
let wallet_address_bytes = unsafe { std::slice::from_raw_parts(wallet_address, 32) };
let wallet_address = ZkPublicKey::from(BigUint::from_bytes_le(wallet_address_bytes));
match get_balance_sync(node, tip, wallet_address) {
Ok(Some(balance)) => BalanceResult::from_value(balance),
Ok(None) => BalanceResult::from_error(OperationStatus::NotFound),
Err(status) => BalanceResult::from_error(status),
}
}
#[repr(C)]
pub struct TransferFundsArguments {
pub optional_tip: *const HeaderId,
pub change_public_key: *const u8,
pub funding_public_keys: *const *const u8,
pub funding_public_keys_len: usize,
pub recipient_public_key: *const u8,
pub amount: u64,
}
impl TransferFundsArguments {
/// Validates the arguments of the [`TransferFundsArguments`] struct.
///
/// # Returns
///
/// A `Result` indicating success or containing an error message and status.
///
/// # Safety
///
/// This function is unsafe because it dereferences raw pointers. The caller
/// must ensure that all pointers are valid.
pub unsafe fn validate(&self) -> Result<(), (String, OperationStatus)> {
if self.change_public_key.is_null() {
return Err((
"TransferFunds contains a null `change_public_key` pointer.".to_owned(),
OperationStatus::NullPtr,
));
}
if self.funding_public_keys.is_null() {
return Err((
"TransferFunds contains a null `funding_public_keys` pointer.".to_owned(),
OperationStatus::NullPtr,
));
}
for i in 0..self.funding_public_keys_len {
let funding_public_key_pointer = unsafe { self.funding_public_keys.add(i) };
let funding_public_key = unsafe { *funding_public_key_pointer };
if funding_public_key.is_null() {
let error_message =
format!("TransferFunds contains a null pointer at `funding_public_keys[{i}]`.");
return Err((error_message, OperationStatus::NullPtr));
}
}
if self.recipient_public_key.is_null() {
return Err((
"TransferFunds contains a null `recipient_public_key` pointer.".to_owned(),
OperationStatus::NullPtr,
));
}
Ok(())
}
}
/// Transfer funds from some addresses to another.
///
/// This is a synchronous wrapper around [`WalletApi::transfer_funds`].
///
/// This function does not validate the arguments. It assumes they have already
/// been validated.
///
/// # Arguments
///
/// - `node`: A [`NomosNode`] instance.
/// - `tip`: The header ID at which to perform the transfer.
/// - `change_public_key`: The public key to receive any change from the
/// transaction.
/// - `funding_public_keys`: A vector of public keys to fund the transaction.
/// - `recipient_public_key`: The public key of the recipient.
/// - `amount`: The amount to transfer.
///
/// # Returns
///
/// A `Result` containing a [`SignedMantleTx`] on success, or an
/// [`OperationStatus`] error on failure.
pub(crate) fn transfer_funds_sync(
node: &NomosNode,
tip: nomos_core::header::HeaderId,
change_public_key: ZkPublicKey,
funding_public_keys: Vec<ZkPublicKey>,
recipient_public_key: ZkPublicKey,
amount: u64,
) -> Result<SignedMantleTx, OperationStatus> {
let Ok(runtime) = tokio::runtime::Runtime::new() else {
eprintln!("[transfer_funds_sync] Failed to create tokio runtime. Aborting.");
return Err(OperationStatus::RuntimeError);
};
runtime
.block_on(async {
let api = WalletApi::<WalletService<_, _, _, _, _>, _>::from_overwatch_handle(
node.get_overwatch_handle(),
)
.await;
api.transfer_funds(
tip,
change_public_key,
funding_public_keys,
recipient_public_key,
amount,
)
.await
})
.map_err(|_| OperationStatus::DynError)
}
pub type TransferFundsResult = ValueResult<Hash, OperationStatus>;
/// Transfer funds from some addresses to another.
///
/// # Arguments
///
/// - `node`: A non-null pointer to a [`NomosNode`] instance.
/// - `arguments`: A non-null pointer to a [`TransferFundsArguments`] struct
/// containing the transaction arguments.
///
/// # Returns
///
/// A [`TransferFundsResult`] containing a pointer to a [`Hash`] where the
/// transaction hash will be written on success, or an [`OperationStatus`] error
/// on failure. The hash will be written in little-endian format.
///
/// # Safety
///
/// This function is unsafe because it dereferences raw pointers. The caller
/// must ensure that all pointers are valid.
///
/// # Memory Management
///
/// This function allocates memory for the output [`CryptarchiaInfo`] struct.
/// The caller must free this memory using the [`free_cryptarchia_info`]
/// function.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn transfer_funds(
node: *const NomosNode,
arguments: *const TransferFundsArguments,
) -> TransferFundsResult {
if node.is_null() {
eprintln!("[transfer_funds] Received a null `node` pointer. Exiting.");
return TransferFundsResult::from_error(OperationStatus::NullPtr);
}
if arguments.is_null() {
eprintln!("[transfer_funds] Received a null `arguments` pointer. Exiting.");
return TransferFundsResult::from_error(OperationStatus::NullPtr);
}
let arguments = unsafe { &*arguments };
if let Err((error_message, status)) = unsafe { arguments.validate() } {
eprintln!("[transfer_funds] {error_message} Exiting.");
return TransferFundsResult::from_error(status);
}
let node = unsafe { &*node };
let tip = if arguments.optional_tip.is_null() {
match get_cryptarchia_info_sync(node) {
Ok(cryptarchia_info) => cryptarchia_info.tip,
Err(status) => {
eprintln!("[transfer_funds] Failed to get cryptarchia info. Aborting.");
return TransferFundsResult::from_error(status);
}
}
} else {
nomos_core::header::HeaderId::from(unsafe { *arguments.optional_tip })
};
let change_public_key = {
let change_public_key_bytes =
unsafe { std::slice::from_raw_parts(arguments.change_public_key, 32) };
ZkPublicKey::from(BigUint::from_bytes_le(change_public_key_bytes))
};
let funding_public_keys = {
let funding_public_keys_pointers = unsafe {
std::slice::from_raw_parts(
arguments.funding_public_keys,
arguments.funding_public_keys_len,
)
};
funding_public_keys_pointers
.iter()
.map(|funding_public_key_pointer| {
let funding_public_key_bytes =
unsafe { std::slice::from_raw_parts(*funding_public_key_pointer, 32) };
ZkPublicKey::from(BigUint::from_bytes_le(funding_public_key_bytes))
})
.collect::<Vec<_>>()
};
let recipient_public_key = {
let recipient_public_key_bytes =
unsafe { std::slice::from_raw_parts(arguments.recipient_public_key, 32) };
ZkPublicKey::from(BigUint::from_bytes_le(recipient_public_key_bytes))
};
let amount = Value::from(arguments.amount);
match transfer_funds_sync(
node,
tip,
change_public_key,
funding_public_keys,
recipient_public_key,
amount,
) {
Ok(transaction) => {
let transaction_hash = transaction.hash().as_signing_bytes();
let Ok(transaction_hash_array) = transaction_hash.iter().as_slice().try_into() else {
eprintln!("[transfer_funds] Failed to convert transaction hash to array. Exiting.");
return TransferFundsResult::from_error(OperationStatus::RuntimeError);
};
TransferFundsResult::from_value(transaction_hash_array)
}
Err(status) => TransferFundsResult::from_error(status),
}
}
/// Frees the memory allocated for a [`Hash`] value.
///
/// # Arguments
///
/// - `pointer`: A pointer to the [`Hash`] to be freed.
#[unsafe(no_mangle)]
pub extern "C" fn free_transfer_funds(pointer: *mut Hash) {
free::<Hash>(pointer);
}

View File

@ -1,7 +1,30 @@
#[repr(u8)] #[derive(Default)]
#[repr(C)]
pub enum NomosNodeErrorCode { pub enum NomosNodeErrorCode {
#[default]
None = 0x0, None = 0x0,
CouldNotInitialize = 0x1, CouldNotInitialize = 0x1,
StopError = 0x2, StopError = 0x2,
NullPtr = 0x3, NullPtr = 0x3,
} }
#[derive(Default, PartialEq, Eq)]
#[repr(C)]
pub enum OperationStatus {
#[default]
Ok = 0x0,
NotFound = 0x1,
NullPtr = 0x2,
RelayError = 0x3,
ChannelSendError = 0x4,
ChannelReceiveError = 0x5,
ServiceError = 0x6,
RuntimeError = 0x7,
DynError = 0x8,
}
impl OperationStatus {
pub fn is_ok(&self) -> bool {
*self == Self::Ok
}
}

View File

@ -28,7 +28,7 @@ impl NomosNode {
// Helper methods to safely access the inner types // Helper methods to safely access the inner types
#[must_use] #[must_use]
const fn get_overwatch_handle(&self) -> &OverwatchHandle<RuntimeServiceId> { pub(crate) const fn get_overwatch_handle(&self) -> &OverwatchHandle<RuntimeServiceId> {
unsafe { unsafe {
self.overwatch self.overwatch
.cast::<NomosOverwatch>() .cast::<NomosOverwatch>()
@ -39,7 +39,7 @@ impl NomosNode {
} }
#[must_use] #[must_use]
fn get_runtime_handle(&self) -> &Handle { pub(crate) fn get_runtime_handle(&self) -> &Handle {
unsafe { unsafe {
self.runtime self.runtime
.cast::<Runtime>() .cast::<Runtime>()

View File

@ -5,11 +5,12 @@ use nomos_core::{
}; };
use overwatch::{ use overwatch::{
DynError, DynError,
overwatch::OverwatchHandle,
services::{AsServiceId, ServiceData, relay::OutboundRelay}, services::{AsServiceId, ServiceData, relay::OutboundRelay},
}; };
use tokio::sync::oneshot; use tokio::sync::oneshot;
use crate::{WalletMsg, WalletServiceSettings}; use crate::{WalletMsg, WalletService, WalletServiceSettings};
pub trait WalletServiceData: pub trait WalletServiceData:
ServiceData<Settings = WalletServiceSettings, Message = WalletMsg> ServiceData<Settings = WalletServiceSettings, Message = WalletMsg>
@ -21,7 +22,7 @@ pub trait WalletServiceData:
} }
impl<Kms, Cryptarchia, Tx, Storage, RuntimeServiceId> WalletServiceData impl<Kms, Cryptarchia, Tx, Storage, RuntimeServiceId> WalletServiceData
for crate::WalletService<Kms, Cryptarchia, Tx, Storage, RuntimeServiceId> for WalletService<Kms, Cryptarchia, Tx, Storage, RuntimeServiceId>
{ {
type Kms = Kms; type Kms = Kms;
type Cryptarchia = Cryptarchia; type Cryptarchia = Cryptarchia;
@ -50,6 +51,12 @@ where
} }
} }
#[must_use]
pub async fn from_overwatch_handle(handle: &OverwatchHandle<RuntimeServiceId>) -> Self {
let relay = handle.relay::<Wallet>().await.unwrap();
Self::new(relay)
}
pub async fn get_balance( pub async fn get_balance(
&self, &self,
tip: HeaderId, tip: HeaderId,