From d367f0b56fc86df3662f1d01b5037f63163892a3 Mon Sep 17 00:00:00 2001 From: Andrei Gubarev <1062334+agubarev@users.noreply.github.com> Date: Mon, 4 Jul 2022 14:30:48 +0300 Subject: [PATCH] feat(wallet_ffi): wallet_coin_split changes (#4254) Description --- Removed `amount_per_split` from the FFI function. Motivation and Context --- Per request from the Aurora team. How Has This Been Tested? --- unit tests --- .../src/output_manager_service/handle.rs | 22 ++ .../src/output_manager_service/service.rs | 234 +++++++++++++-- base_layer/wallet/src/wallet.rs | 28 ++ base_layer/wallet_ffi/src/lib.rs | 269 +++++++++++++----- base_layer/wallet_ffi/wallet.h | 76 +++-- 5 files changed, 511 insertions(+), 118 deletions(-) diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index 9b3893dd3f..808ae89f2d 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -109,6 +109,7 @@ pub enum OutputManagerRequest { ValidateUtxos, RevalidateTxos, CreateCoinSplit((Vec, MicroTari, usize, MicroTari)), + CreateCoinSplitEven((Vec, usize, MicroTari)), CreateCoinJoin { commitments: Vec, fee_per_gram: MicroTari, @@ -177,6 +178,7 @@ impl fmt::Display for OutputManagerRequest { ValidateUtxos => write!(f, "ValidateUtxos"), RevalidateTxos => write!(f, "RevalidateTxos"), CreateCoinSplit(v) => write!(f, "CreateCoinSplit ({:?})", v.0), + CreateCoinSplitEven(v) => write!(f, "CreateCoinSplitEven ({:?})", v.0), CreateCoinJoin { commitments, fee_per_gram, @@ -695,6 +697,26 @@ impl OutputManagerHandle { } } + pub async fn create_coin_split_even( + &mut self, + commitments: Vec, + split_count: usize, + fee_per_gram: MicroTari, + ) -> Result<(TxId, Transaction, MicroTari), OutputManagerError> { + match self + .handle + .call(OutputManagerRequest::CreateCoinSplitEven(( + commitments, + split_count, + fee_per_gram, + ))) + .await?? + { + OutputManagerResponse::Transaction(ct) => Ok(ct), + _ => Err(OutputManagerError::UnexpectedApiResponse), + } + } + pub async fn create_coin_join( &mut self, commitments: Vec, diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 1389d4d9b9..91b8946fb0 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -384,11 +384,27 @@ where }, OutputManagerRequest::CreateCoinSplit((commitments, amount_per_split, split_count, fee_per_gram)) => { if commitments.is_empty() { - self.create_coin_split_auto(amount_per_split, split_count, fee_per_gram) + self.create_coin_split_auto(Some(amount_per_split), split_count, fee_per_gram) .await .map(OutputManagerResponse::Transaction) } else { - self.create_coin_split_with_commitments(commitments, amount_per_split, split_count, fee_per_gram) + self.create_coin_split_with_commitments( + commitments, + Some(amount_per_split), + split_count, + fee_per_gram, + ) + .await + .map(OutputManagerResponse::Transaction) + } + }, + OutputManagerRequest::CreateCoinSplitEven((commitments, split_count, fee_per_gram)) => { + if commitments.is_empty() { + self.create_coin_split_auto(None, split_count, fee_per_gram) + .await + .map(OutputManagerResponse::Transaction) + } else { + self.create_coin_split_with_commitments(commitments, None, split_count, fee_per_gram) .await .map(OutputManagerResponse::Transaction) } @@ -1703,7 +1719,7 @@ where async fn create_coin_split_with_commitments( &mut self, commitments: Vec, - amount_per_split: MicroTari, + amount_per_split: Option, number_of_splits: usize, fee_per_gram: MicroTari, ) -> Result<(TxId, Transaction, MicroTari), OutputManagerError> { @@ -1717,28 +1733,208 @@ where None, )?; - self.create_coin_split(src_outputs, amount_per_split, number_of_splits, fee_per_gram) - .await + match amount_per_split { + None => { + self.create_coin_split_even(src_outputs, number_of_splits, fee_per_gram) + .await + }, + Some(amount_per_split) => { + self.create_coin_split(src_outputs, amount_per_split, number_of_splits, fee_per_gram) + .await + }, + } } async fn create_coin_split_auto( &mut self, - amount_per_split: MicroTari, + amount_per_split: Option, number_of_splits: usize, fee_per_gram: MicroTari, ) -> Result<(TxId, Transaction, MicroTari), OutputManagerError> { - let src_outputs = self - .select_utxos( - MicroTari::from(amount_per_split.as_u64() * number_of_splits as u64), - fee_per_gram, - number_of_splits, - self.default_metadata_size() * number_of_splits, - UtxoSelectionCriteria::largest_first(), + match amount_per_split { + None => Err(OutputManagerError::InvalidArgument( + "coin split without `amount_per_split` is not supported yet".to_string(), + )), + Some(amount_per_split) => { + let selection = self + .select_utxos( + amount_per_split * MicroTari(number_of_splits as u64), + fee_per_gram, + number_of_splits, + self.default_metadata_size() * number_of_splits, + UtxoSelectionCriteria::largest_first(), + ) + .await?; + + self.create_coin_split(selection.utxos, amount_per_split, number_of_splits, fee_per_gram) + .await + }, + } + } + + #[allow(clippy::too_many_lines)] + async fn create_coin_split_even( + &mut self, + src_outputs: Vec, + number_of_splits: usize, + fee_per_gram: MicroTari, + ) -> Result<(TxId, Transaction, MicroTari), OutputManagerError> { + if number_of_splits == 0 { + return Err(OutputManagerError::InvalidArgument( + "number_of_splits must be greater than 0".to_string(), + )); + } + + let covenant = Covenant::default(); + let default_metadata_size = self.default_metadata_size(); + let mut dest_outputs = Vec::with_capacity(number_of_splits + 1); + + // accumulated value amount from given source outputs + let accumulated_amount = src_outputs + .iter() + .fold(MicroTari::zero(), |acc, x| acc + x.unblinded_output.value); + + let fee = self.get_fee_calc().calculate( + fee_per_gram, + 1, + src_outputs.len(), + number_of_splits, + default_metadata_size * number_of_splits, + ); + + let aftertax_amount = accumulated_amount.saturating_sub(fee); + let amount_per_split = MicroTari(aftertax_amount.as_u64() / number_of_splits as u64); + let unspent_remainder = MicroTari(aftertax_amount.as_u64() % amount_per_split.as_u64()); + + // preliminary balance check + if self.get_balance(None)?.available_balance < (aftertax_amount + fee) { + return Err(OutputManagerError::NotEnoughFunds); + } + + trace!(target: LOG_TARGET, "initializing new split (even) transaction"); + + let mut tx_builder = SenderTransactionProtocol::builder(0, self.resources.consensus_constants.clone()); + tx_builder + .with_lock_height(0) + .with_fee_per_gram(fee_per_gram) + .with_offset(PrivateKey::random(&mut OsRng)) + .with_private_nonce(PrivateKey::random(&mut OsRng)) + .with_rewindable_outputs(self.resources.rewind_data.clone()); + + // collecting inputs from source outputs + let inputs: Vec = src_outputs + .iter() + .map(|src_out| { + src_out + .unblinded_output + .as_transaction_input(&self.resources.factories.commitment) + }) + .try_collect()?; + + // adding inputs to the transaction + src_outputs.iter().zip(inputs).for_each(|(src_output, input)| { + trace!( + target: LOG_TARGET, + "adding transaction input: output_hash=: {:?}", + src_output.hash + ); + tx_builder.with_input(input, src_output.unblinded_output.clone()); + }); + + for i in 1..=number_of_splits { + // NOTE: adding the unspent `change` to the last output + let amount_per_split = if i == number_of_splits { + amount_per_split + unspent_remainder + } else { + amount_per_split + }; + + let noop_script = script!(Nop); + let (spending_key, script_private_key) = self.get_spend_and_script_keys().await?; + let output_features = OutputFeatures { + recovery_byte: self.calculate_recovery_byte(spending_key.clone(), accumulated_amount.as_u64(), true)?, + ..Default::default() + }; + + // generating sender's keypair + let sender_offset_private_key = PrivateKey::random(&mut OsRng); + let sender_offset_public_key = PublicKey::from_secret_key(&sender_offset_private_key); + + let commitment_signature = TransactionOutput::create_final_metadata_signature( + TransactionOutputVersion::get_current_version(), + amount_per_split, + &spending_key, + &noop_script, + &output_features, + &sender_offset_private_key, + &covenant, + )?; + + let output = DbUnblindedOutput::rewindable_from_unblinded_output( + UnblindedOutput::new_current_version( + amount_per_split, + spending_key, + output_features, + noop_script, + inputs!(PublicKey::from_secret_key(&script_private_key)), + script_private_key, + sender_offset_public_key, + commitment_signature, + 0, + covenant.clone(), + ), + &self.resources.factories, + &self.resources.rewind_data.clone(), + None, + None, + )?; + + tx_builder + .with_output(output.unblinded_output.clone(), sender_offset_private_key) + .map_err(|e| OutputManagerError::BuildError(e.message))?; + + dest_outputs.push(output); + } + + let mut stp = tx_builder + .build::( + &self.resources.factories, + None, + self.last_seen_tip_height.unwrap_or(u64::MAX), ) - .await?; + .map_err(|e| OutputManagerError::BuildError(e.message))?; - self.create_coin_split(src_outputs.utxos, amount_per_split, number_of_splits, fee_per_gram) - .await + // The Transaction Protocol built successfully so we will pull the unspent outputs out of the unspent list and + // store them until the transaction times out OR is confirmed + let tx_id = stp.get_tx_id()?; + + trace!( + target: LOG_TARGET, + "Encumber coin split (even) transaction (tx_id={}) outputs", + tx_id + ); + + // encumbering transaction + self.resources + .db + .encumber_outputs(tx_id, src_outputs.clone(), dest_outputs)?; + self.confirm_encumberance(tx_id)?; + + trace!( + target: LOG_TARGET, + "finalizing coin split transaction (tx_id={}).", + tx_id + ); + + // finalizing transaction + stp.finalize( + KernelFeatures::empty(), + &self.resources.factories, + None, + self.last_seen_tip_height.unwrap_or(u64::MAX), + )?; + + Ok((tx_id, stp.take_transaction()?, aftertax_amount + fee)) } #[allow(clippy::too_many_lines)] @@ -1755,6 +1951,12 @@ where )); } + if amount_per_split == MicroTari::zero() { + return Err(OutputManagerError::InvalidArgument( + "amount_per_split must be greater than 0".to_string(), + )); + } + let covenant = Covenant::default(); let default_metadata_size = self.default_metadata_size(); let mut dest_outputs = Vec::with_capacity(number_of_splits + 1); diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 59e2e3234e..3a3a0490f0 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -565,6 +565,34 @@ where } } + /// Do a coin split + pub async fn coin_split_even( + &mut self, + commitments: Vec, + split_count: usize, + fee_per_gram: MicroTari, + message: String, + ) -> Result { + let coin_split_tx = self + .output_manager_service + .create_coin_split_even(commitments, split_count, fee_per_gram) + .await; + + match coin_split_tx { + Ok((tx_id, split_tx, amount)) => { + let coin_tx = self + .transaction_service + .submit_transaction(tx_id, split_tx, amount, message) + .await; + match coin_tx { + Ok(_) => Ok(tx_id), + Err(e) => Err(WalletError::TransactionServiceError(e)), + } + }, + Err(e) => Err(WalletError::OutputManagerError(e)), + } + } + pub async fn coin_join( &mut self, commitments: Vec, diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index ae29ff8be7..77b9aea440 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -263,10 +263,10 @@ impl TryFrom for TariUtxo { } } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[repr(C)] pub enum TariTypeTag { - String = 0, + Text = 0, Utxo = 1, Commitment = 2, } @@ -274,13 +274,56 @@ pub enum TariTypeTag { impl Display for TariTypeTag { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - TariTypeTag::String => write!(f, "String"), + TariTypeTag::Text => write!(f, "Text"), TariTypeTag::Utxo => write!(f, "Utxo"), TariTypeTag::Commitment => write!(f, "Commitment"), } } } +#[derive(Debug, Clone)] +#[repr(C)] +pub struct TariOutputs { + pub len: usize, + pub cap: usize, + pub ptr: *mut TariUtxo, +} + +// WARNING: must be destroyed properly after use +impl TryFrom> for TariOutputs { + type Error = InterfaceError; + + fn try_from(outputs: Vec) -> Result { + let mut outputs = ManuallyDrop::new( + outputs + .into_iter() + .map(|x| { + Ok(TariUtxo { + commitment: CString::new(x.commitment.to_hex()) + .map_err(|e| { + InterfaceError::InvalidArgument(format!( + "failed to obtain hex from a commitment: {:?}", + e + )) + })? + .into_raw(), + value: x.unblinded_output.value.as_u64(), + mined_height: x.mined_height.unwrap_or(0), + }) + }) + .try_collect::, InterfaceError>()?, + ); + + Ok(Self { + len: outputs.len(), + cap: outputs.capacity(), + ptr: outputs.as_mut_ptr(), + }) + } +} + +/// -------------------------------- Vector ------------------------------------------------ /// + #[derive(Debug, Clone)] #[repr(C)] pub struct TariVector { @@ -300,7 +343,7 @@ impl TariVector { ); Ok(Self { - tag: TariTypeTag::String, + tag: TariTypeTag::Text, len: strings.len(), cap: strings.capacity(), ptr: strings.as_mut_ptr() as *mut c_void, @@ -317,7 +360,7 @@ impl TariVector { } fn to_string_vec(&self) -> Result, InterfaceError> { - if self.tag != TariTypeTag::String { + if self.tag != TariTypeTag::Text { return Err(InterfaceError::InvalidArgument(format!( "expecting String, got {}", self.tag @@ -333,7 +376,12 @@ impl TariVector { Ok(unsafe { Vec::from_raw_parts(self.ptr as *mut *mut c_char, self.len, self.cap) .into_iter() - .map(|x| CString::from_raw(x).into_string().unwrap()) + .map(|x| { + CStr::from_ptr(x) + .to_str() + .expect("failed to convert from a vector of strings") + .to_string() + }) .collect() }) } @@ -367,44 +415,101 @@ impl TariVector { } } -#[derive(Debug, Clone)] -#[repr(C)] -pub struct TariOutputs { - pub len: usize, - pub cap: usize, - pub ptr: *mut TariUtxo, +/// Initialize a new `TariVector` +/// +/// ## Arguments +/// `tag` - A predefined type-tag of the vector's payload. +/// +/// ## Returns +/// `*mut TariVector` - Returns a pointer to a `TariVector`. +/// +/// # Safety +/// `destroy_tari_vector()` must be called to free the allocated memory. +#[no_mangle] +pub unsafe extern "C" fn create_tari_vector(tag: TariTypeTag) -> *mut TariVector { + let mut v = ManuallyDrop::new(Vec::with_capacity(2)); + Box::into_raw(Box::new(TariVector { + tag, + len: v.len(), + cap: v.capacity(), + ptr: v.as_mut_ptr() as *mut c_void, + })) } -// WARNING: must be destroyed properly after use -impl TryFrom> for TariOutputs { - type Error = InterfaceError; - - fn try_from(outputs: Vec) -> Result { - let mut outputs = ManuallyDrop::new( - outputs - .into_iter() - .map(|x| { - Ok(TariUtxo { - commitment: CString::new(x.commitment.to_hex()) - .map_err(|e| { - InterfaceError::InvalidArgument(format!( - "failed to obtain hex from a commitment: {:?}", - e - )) - })? - .into_raw(), - value: x.unblinded_output.value.as_u64(), - mined_height: x.mined_height.unwrap_or(0), - }) - }) - .try_collect::, InterfaceError>()?, +/// Appending a given value to the back of the vector. +/// +/// ## Arguments +/// `s` - An item to push. +/// +/// ## Returns +/// +/// +/// # Safety +/// `destroy_tari_vector()` must be called to free the allocated memory. +#[no_mangle] +pub unsafe extern "C" fn tari_vector_push_string(tv: *mut TariVector, s: *const c_char, error_ptr: *mut i32) { + if tv.is_null() { + error!(target: LOG_TARGET, "tari vector pointer is null"); + ptr::replace( + error_ptr, + LibWalletError::from(InterfaceError::NullError("vector".to_string())).code, ); + return; + } - Ok(Self { - len: outputs.len(), - cap: outputs.capacity(), - ptr: outputs.as_mut_ptr(), - }) + // unpacking into native vector + let mut v = match (*tv).to_string_vec() { + Ok(v) => v, + Err(e) => { + error!(target: LOG_TARGET, "{:#?}", e); + ptr::replace(error_ptr, LibWalletError::from(e).code); + return; + }, + }; + + let s = match CStr::from_ptr(s).to_str() { + Ok(cs) => cs.to_string(), + Err(e) => { + error!(target: LOG_TARGET, "failed to convert `s` into native string {:#?}", e); + ptr::replace( + error_ptr, + LibWalletError::from(InterfaceError::PointerError("invalid string".to_string())).code, + ); + return; + }, + }; + + // appending new value + // NOTE: relying on native vector's re-allocation + v.push(s); + + let mut v = ManuallyDrop::new( + v.into_iter() + .map(|x| CString::new(x.as_str()).unwrap().into_raw()) + .collect::>(), + ); + + (*tv).len = v.len(); + (*tv).cap = v.capacity(); + (*tv).ptr = v.as_mut_ptr() as *mut c_void; + ptr::replace(error_ptr, 0); +} + +/// Frees memory allocated for `TariVector`. +/// +/// ## Arguments +/// `v` - The pointer to `TariVector` +/// +/// ## Returns +/// `()` - Does not return a value, equivalent to void in C +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn destroy_tari_vector(v: *mut TariVector) { + if !v.is_null() { + let x = Box::from_raw(v); + let _ = x.ptr; } } @@ -4011,7 +4116,7 @@ pub unsafe extern "C" fn wallet_create( .to_str() .expect("A non-null network should be able to be converted to string"); error!(target: LOG_TARGET, "network set to {}", network); - eprintln!("network set to {}", network); + // eprintln!("network set to {}", network); match Network::from_str(&*network) { Ok(n) => n, Err(_) => { @@ -4364,24 +4469,6 @@ pub unsafe extern "C" fn destroy_tari_outputs(x: *mut TariOutputs) { } } -/// Frees memory for a `TariVector` -/// -/// ## Arguments -/// `x` - The pointer to `TariVector` -/// -/// ## Returns -/// `()` - Does not return a value, equivalent to void in C -/// -/// # Safety -/// None -#[no_mangle] -pub unsafe extern "C" fn destroy_tari_vector(x: *mut TariVector) { - if !x.is_null() { - let x = Box::from_raw(x); - _ = x.ptr; - } -} - /// This function will tell the wallet to do a coin split. /// /// ## Arguments @@ -4404,7 +4491,6 @@ pub unsafe extern "C" fn destroy_tari_vector(x: *mut TariVector) { pub unsafe extern "C" fn wallet_coin_split( wallet: *mut TariWallet, commitments: *mut TariVector, - amount_per_split: u64, number_of_splits: usize, fee_per_gram: u64, error_ptr: *mut i32, @@ -4437,9 +4523,8 @@ pub unsafe extern "C" fn wallet_coin_split( }, }; - match (*wallet).runtime.block_on((*wallet).wallet.coin_split( + match (*wallet).runtime.block_on((*wallet).wallet.coin_split_even( commitments, - MicroTari(amount_per_split), number_of_splits, MicroTari(fee_per_gram), String::new(), @@ -9307,7 +9392,7 @@ mod test { let commitments = Box::into_raw(Box::new(TariVector::from_string_vec(payload).unwrap())) as *mut TariVector; - let result = wallet_coin_split(alice_wallet, commitments, 20500, 3, 5, error_ptr); + let result = wallet_coin_split(alice_wallet, commitments, 3, 5, error_ptr); assert_eq!(error, 0); assert!(result > 0); @@ -9340,8 +9425,9 @@ mod test { assert_eq!(error, 0); assert_eq!(utxos.len(), 2); assert_eq!(unspent_outputs.len(), 2); - assert_eq!(new_pending_outputs.len(), 4); - assert!(new_pending_outputs[0..3].iter().all(|x| *x == 20500.into())); + assert_eq!(new_pending_outputs.len(), 3); + assert_eq!(new_pending_outputs[0], new_pending_outputs[1]); + assert_eq!(new_pending_outputs[2], new_pending_outputs[1] + MicroTari(1)); destroy_tari_outputs(outputs); destroy_tari_vector(commitments); @@ -9359,20 +9445,45 @@ mod test { #[test] fn test_tari_vector() { - let mut strings = ManuallyDrop::new(vec![ - CString::new("string0").unwrap().into_raw(), - CString::new("string1").unwrap().into_raw(), - CString::new("string2").unwrap().into_raw(), - ]); - - let v = TariVector { - tag: TariTypeTag::String, - len: strings.len(), - cap: strings.capacity(), - ptr: strings.as_mut_ptr() as *mut c_void, - }; + let mut error = 0; - eprintln!("v = {:#?}", v); - eprintln!("result = {:#?}", v.to_string_vec()); + unsafe { + let tv = create_tari_vector(TariTypeTag::Text); + assert_eq!((*tv).tag, TariTypeTag::Text); + assert_eq!((*tv).len, 0); + assert_eq!((*tv).cap, 2); + + tari_vector_push_string( + tv, + CString::new("test string 1").unwrap().into_raw() as *const c_char, + &mut error as *mut c_int, + ); + assert_eq!(error, 0); + assert_eq!((*tv).tag, TariTypeTag::Text); + assert_eq!((*tv).len, 1); + assert_eq!((*tv).cap, 1); + + tari_vector_push_string( + tv, + CString::new("test string 2").unwrap().into_raw() as *const c_char, + &mut error as *mut c_int, + ); + assert_eq!(error, 0); + assert_eq!((*tv).tag, TariTypeTag::Text); + assert_eq!((*tv).len, 2); + assert_eq!((*tv).cap, 2); + + tari_vector_push_string( + tv, + CString::new("test string 3").unwrap().into_raw() as *const c_char, + &mut error as *mut c_int, + ); + assert_eq!(error, 0); + assert_eq!((*tv).tag, TariTypeTag::Text); + assert_eq!((*tv).len, 3); + assert_eq!((*tv).cap, 3); + + destroy_tari_vector(tv); + } } } diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 69093ef02e..a907cfcd68 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -14,7 +14,7 @@ #define OutputFields_NUM_FIELDS 10 enum TariTypeTag { - String = 0, + Text = 0, Utxo = 1, Commitment = 2, }; @@ -163,6 +163,16 @@ struct TransactionSendStatus; struct TransportConfig; +/** + * -------------------------------- Vector ------------------------------------------------ /// + */ +struct TariVector { + enum TariTypeTag tag; + uintptr_t len; + uintptr_t cap; + void *ptr; +}; + typedef struct TransactionKernel TariTransactionKernel; /** @@ -296,13 +306,6 @@ struct TariOutputs { struct TariUtxo *ptr; }; -struct TariVector { - enum TariTypeTag tag; - uintptr_t len; - uintptr_t cap; - void *ptr; -}; - typedef struct FeePerGramStatsResponse TariFeePerGramStats; typedef struct FeePerGramStat TariFeePerGramStat; @@ -311,6 +314,48 @@ typedef struct FeePerGramStat TariFeePerGramStat; extern "C" { #endif // __cplusplus +/** + * Initialize a new `TariVector` + * + * ## Arguments + * `tag` - A predefined type-tag of the vector's payload. + * + * ## Returns + * `*mut TariVector` - Returns a pointer to a `TariVector`. + * + * # Safety + * `destroy_tari_vector()` must be called to free the allocated memory. + */ +struct TariVector *create_tari_vector(enum TariTypeTag tag); + +/** + * Appending a given value to the back of the vector. + * + * ## Arguments + * `s` - An item to push. + * + * ## Returns + * + * + * # Safety + * `destroy_tari_vector()` must be called to free the allocated memory. + */ +void tari_vector_push_string(struct TariVector *tv, const char *s, int32_t *error_ptr); + +/** + * Frees memory allocated for `TariVector`. + * + * ## Arguments + * `v` - The pointer to `TariVector` + * + * ## Returns + * `()` - Does not return a value, equivalent to void in C + * + * # Safety + * None + */ +void destroy_tari_vector(struct TariVector *v); + /** * -------------------------------- Strings ------------------------------------------------ /// * Frees memory for a char array @@ -2226,20 +2271,6 @@ struct TariOutputs *wallet_get_utxos(struct TariWallet *wallet, */ void destroy_tari_outputs(struct TariOutputs *x); -/** - * Frees memory for a `TariVector` - * - * ## Arguments - * `x` - The pointer to `TariVector` - * - * ## Returns - * `()` - Does not return a value, equivalent to void in C - * - * # Safety - * None - */ -void destroy_tari_vector(struct TariVector *x); - /** * This function will tell the wallet to do a coin split. * @@ -2262,7 +2293,6 @@ void destroy_tari_vector(struct TariVector *x); */ uint64_t wallet_coin_split(struct TariWallet *wallet, struct TariVector *commitments, - uint64_t amount_per_split, uintptr_t number_of_splits, uint64_t fee_per_gram, int32_t *error_ptr);