Skip to content

Commit

Permalink
feat(ffi): added 3 functions (#4266)
Browse files Browse the repository at this point in the history
Description
---
Added 2 preview functions for coin split and join, and wallet_get_all_utxos

Motivation and Context
---
To let mobile users appraise the fee and expected outputs without performing the actual split/join operation.

How Has This Been Tested?
---
unit tests
  • Loading branch information
agubarev authored Jul 6, 2022
1 parent 5f580e6 commit 2be17df
Show file tree
Hide file tree
Showing 2 changed files with 245 additions and 0 deletions.
210 changes: 210 additions & 0 deletions base_layer/wallet_ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ pub struct TariUtxo {
pub commitment: *mut c_char,
pub value: u64,
pub mined_height: u64,
pub status: u8,
}

impl TryFrom<DbUnblindedOutput> for TariUtxo {
Expand All @@ -266,6 +267,20 @@ impl TryFrom<DbUnblindedOutput> for TariUtxo {
.into_raw(),
value: x.unblinded_output.value.as_u64(),
mined_height: x.mined_height.unwrap_or(0),
status: match x.status {
OutputStatus::Unspent => 0,
OutputStatus::Spent => 1,
OutputStatus::EncumberedToBeReceived => 2,
OutputStatus::EncumberedToBeSpent => 3,
OutputStatus::Invalid => 4,
OutputStatus::CancelledInbound => 5,
OutputStatus::UnspentMinedUnconfirmed => 6,
OutputStatus::ShortTermEncumberedToBeReceived => 7,
OutputStatus::ShortTermEncumberedToBeSpent => 8,
OutputStatus::SpentMinedUnconfirmed => 9,
OutputStatus::AbandonedCoinbase => 10,
OutputStatus::NotStored => 11,
},
})
}
}
Expand Down Expand Up @@ -299,6 +314,7 @@ pub struct TariOutputs {
}

// WARNING: must be destroyed properly after use
// TODO: dedup
impl TryFrom<Vec<DbUnblindedOutput>> for TariOutputs {
type Error = InterfaceError;

Expand All @@ -318,6 +334,20 @@ impl TryFrom<Vec<DbUnblindedOutput>> for TariOutputs {
.into_raw(),
value: x.unblinded_output.value.as_u64(),
mined_height: x.mined_height.unwrap_or(0),
status: match x.status {
OutputStatus::Unspent => 0,
OutputStatus::Spent => 1,
OutputStatus::EncumberedToBeReceived => 2,
OutputStatus::EncumberedToBeSpent => 3,
OutputStatus::Invalid => 4,
OutputStatus::CancelledInbound => 5,
OutputStatus::UnspentMinedUnconfirmed => 6,
OutputStatus::ShortTermEncumberedToBeReceived => 7,
OutputStatus::ShortTermEncumberedToBeSpent => 8,
OutputStatus::SpentMinedUnconfirmed => 9,
OutputStatus::AbandonedCoinbase => 10,
OutputStatus::NotStored => 11,
},
})
})
.try_collect::<TariUtxo, Vec<TariUtxo>, InterfaceError>()?,
Expand Down Expand Up @@ -4478,6 +4508,86 @@ pub unsafe extern "C" fn wallet_get_utxos(
}
}

/// This function returns a list of all UTXO values, commitment's hex values and states.
///
/// ## Arguments
/// * `wallet` - The TariWallet pointer,
/// * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null.
/// Functions as an out parameter.
///
/// ## Returns
/// `*mut TariOutputs` - Returns a struct with an array pointer, length and capacity (needed for proper destruction
/// after use).
///
/// ## States
/// 0 - Unspent
/// 1 - Spent
/// 2 - EncumberedToBeReceived
/// 3 - EncumberedToBeSpent
/// 4 - Invalid
/// 5 - CancelledInbound
/// 6 - UnspentMinedUnconfirmed
/// 7 - ShortTermEncumberedToBeReceived
/// 8 - ShortTermEncumberedToBeSpent
/// 9 - SpentMinedUnconfirmed
/// 10 - AbandonedCoinbase
/// 11 - NotStored
///
/// # Safety
/// `destroy_tari_outputs()` must be called after use.
/// Items that fail to produce `.as_transaction_output()` are omitted from the list and a `warn!()` message is logged to
/// LOG_TARGET.
#[no_mangle]
pub unsafe extern "C" fn wallet_get_all_utxos(wallet: *mut TariWallet, error_ptr: *mut i32) -> *mut TariOutputs {
if wallet.is_null() {
error!(target: LOG_TARGET, "wallet pointer is null");
ptr::replace(
error_ptr,
LibWalletError::from(InterfaceError::NullError("wallet".to_string())).code,
);
return ptr::null_mut();
}

let q = OutputBackendQuery {
tip_height: i64::MAX,
status: vec![],
commitments: vec![],
pagination: None,
value_min: None,
value_max: None,
sorting: vec![],
};

match (*wallet).wallet.output_db.fetch_outputs_by(q) {
Ok(outputs) => match TariOutputs::try_from(outputs) {
Ok(tari_outputs) => {
ptr::replace(error_ptr, 0);
Box::into_raw(Box::new(tari_outputs))
},
Err(e) => {
error!(
target: LOG_TARGET,
"failed to convert outputs to `TariOutputs`: {:#?}", e
);
ptr::replace(error_ptr, LibWalletError::from(e).code);
ptr::null_mut()
},
},

Err(e) => {
error!(target: LOG_TARGET, "failed to obtain outputs: {:#?}", e);
ptr::replace(
error_ptr,
LibWalletError::from(WalletError::OutputManagerError(
OutputManagerError::OutputManagerStorageError(e),
))
.code,
);
ptr::null_mut()
},
}
}

/// Frees memory for a `TariOutputs`
///
/// ## Arguments
Expand Down Expand Up @@ -9369,6 +9479,106 @@ mod test {
}
}

#[test]
#[allow(clippy::too_many_lines)]
fn test_wallet_get_all_utxos() {
unsafe {
let mut error = 0;
let error_ptr = &mut error as *mut c_int;
let mut recovery_in_progress = true;
let recovery_in_progress_ptr = &mut recovery_in_progress as *mut bool;

let secret_key_alice = private_key_generate();
let db_name_alice = CString::new(random::string(8).as_str()).unwrap();
let db_name_alice_str: *const c_char = CString::into_raw(db_name_alice) as *const c_char;
let alice_temp_dir = tempdir().unwrap();
let db_path_alice = CString::new(alice_temp_dir.path().to_str().unwrap()).unwrap();
let db_path_alice_str: *const c_char = CString::into_raw(db_path_alice) as *const c_char;
let transport_config_alice = transport_memory_create();
let address_alice = transport_memory_get_address(transport_config_alice, error_ptr);
let address_alice_str = CStr::from_ptr(address_alice).to_str().unwrap().to_owned();
let address_alice_str: *const c_char = CString::new(address_alice_str).unwrap().into_raw() as *const c_char;
let network = CString::new(NETWORK_STRING).unwrap();
let network_str: *const c_char = CString::into_raw(network) as *const c_char;

let alice_config = comms_config_create(
address_alice_str,
transport_config_alice,
db_name_alice_str,
db_path_alice_str,
20,
10800,
error_ptr,
);

let alice_wallet = wallet_create(
alice_config,
ptr::null(),
0,
0,
ptr::null(),
ptr::null(),
network_str,
received_tx_callback,
received_tx_reply_callback,
received_tx_finalized_callback,
broadcast_callback,
mined_callback,
mined_unconfirmed_callback,
scanned_callback,
scanned_unconfirmed_callback,
transaction_send_result_callback,
tx_cancellation_callback,
txo_validation_complete_callback,
contacts_liveness_data_updated_callback,
balance_updated_callback,
transaction_validation_complete_callback,
saf_messages_received_callback,
connectivity_status_callback,
recovery_in_progress_ptr,
error_ptr,
);

(0..10).for_each(|i| {
let (_, uout) = create_test_input((1000 * i).into(), 0, &PedersenCommitmentFactory::default());
(*alice_wallet)
.runtime
.block_on((*alice_wallet).wallet.output_manager_service.add_output(uout, None))
.unwrap();
});

let outputs = wallet_get_utxos(alice_wallet, 0, 100, TariUtxoSort::ValueAsc, 0, error_ptr);
let utxos: &[TariUtxo] = slice::from_raw_parts_mut((*outputs).ptr, (*outputs).len);
assert_eq!(error, 0);

let payload = utxos[0..3]
.iter()
.map(|x| CString::from_raw(x.commitment).into_string().unwrap())
.collect::<Vec<String>>();

let commitments = Box::into_raw(Box::new(TariVector::from_string_vec(payload).unwrap())) as *mut TariVector;
let result = wallet_coin_join(alice_wallet, commitments, 5, error_ptr);
assert_eq!(error, 0);
assert!(result > 0);

let outputs = wallet_get_all_utxos(alice_wallet, error_ptr);
let utxos: &[TariUtxo] = slice::from_raw_parts_mut((*outputs).ptr, (*outputs).len);
assert_eq!(error, 0);
assert_eq!((*outputs).len, 11);
assert_eq!(utxos.len(), 11);
destroy_tari_outputs(outputs);

string_destroy(network_str as *mut c_char);
string_destroy(db_name_alice_str as *mut c_char);
string_destroy(db_path_alice_str as *mut c_char);
string_destroy(address_alice_str as *mut c_char);
private_key_destroy(secret_key_alice);
transport_config_destroy(transport_config_alice);
comms_config_destroy(alice_config);
wallet_destroy(alice_wallet);
}
}

#[test]
#[allow(clippy::too_many_lines, clippy::needless_collect)]
fn test_wallet_coin_join() {
Expand Down
35 changes: 35 additions & 0 deletions base_layer/wallet_ffi/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ struct TariUtxo {
char *commitment;
uint64_t value;
uint64_t mined_height;
uint8_t status;
};

struct TariOutputs {
Expand Down Expand Up @@ -2277,6 +2278,40 @@ struct TariOutputs *wallet_get_utxos(struct TariWallet *wallet,
uint64_t dust_threshold,
int32_t *error_ptr);

/**
* This function returns a list of all UTXO values, commitment's hex values and states.
*
* ## Arguments
* * `wallet` - The TariWallet pointer,
* * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null.
* Functions as an out parameter.
*
* ## Returns
* `*mut TariOutputs` - Returns a struct with an array pointer, length and capacity (needed for proper destruction
* after use).
*
* ## States
* 0 - Unspent
* 1 - Spent
* 2 - EncumberedToBeReceived
* 3 - EncumberedToBeSpent
* 4 - Invalid
* 5 - CancelledInbound
* 6 - UnspentMinedUnconfirmed
* 7 - ShortTermEncumberedToBeReceived
* 8 - ShortTermEncumberedToBeSpent
* 9 - SpentMinedUnconfirmed
* 10 - AbandonedCoinbase
* 11 - NotStored
*
* # Safety
* `destroy_tari_outputs()` must be called after use.
* Items that fail to produce `.as_transaction_output()` are omitted from the list and a `warn!()` message is logged to
* LOG_TARGET.
*/
struct TariOutputs *wallet_get_all_utxos(struct TariWallet *wallet,
int32_t *error_ptr);

/**
* Frees memory for a `TariOutputs`
*
Expand Down

0 comments on commit 2be17df

Please sign in to comment.