From 7eeeff4bbd5a147bd35e9ae7af75dba1da87383b Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Wed, 18 Jan 2023 18:20:43 +0200 Subject: [PATCH] feat: add tx_id_to export (#5126) Description --- Adds the tx_id to export unspent utxo request. Motivation and Context --- This allows a callee to ask the TMS service using the tx_id for more details about how the output was received How Has This Been Tested? --- Unit tests. Fixes: https://github.com/tari-project/tari/issues/5117 --- .../src/automation/commands.rs | 2 + .../src/grpc/wallet_grpc_server.rs | 2 +- .../src/output_manager_service/handle.rs | 6 +- .../recovery/standard_outputs_recoverer.rs | 2 + .../src/output_manager_service/service.rs | 41 ++++- .../output_manager_service/storage/models.rs | 15 +- .../storage/sqlite_db/mod.rs | 10 +- .../storage/sqlite_db/output_sql.rs | 2 + .../output_manager_service_tests/service.rs | 28 ++-- .../output_manager_service_tests/storage.rs | 18 ++- base_layer/wallet/tests/utxo_scanner.rs | 60 ++++++-- base_layer/wallet_ffi/src/lib.rs | 140 +++++++++++++++++- base_layer/wallet_ffi/wallet.h | 55 +++++++ 13 files changed, 333 insertions(+), 48 deletions(-) diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index ff69a0ee4c..e10468eaae 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -765,6 +765,7 @@ pub async fn command_runner( }, ExportUtxos(args) => match output_service.get_unspent_outputs().await { Ok(utxos) => { + let utxos: Vec = utxos.into_iter().map(|v| v.unblinded_output).collect(); let count = utxos.len(); let sum: MicroTari = utxos.iter().map(|utxo| utxo.value).sum(); if let Some(file) = args.output_file { @@ -801,6 +802,7 @@ pub async fn command_runner( }, CountUtxos => match output_service.get_unspent_outputs().await { Ok(utxos) => { + let utxos: Vec = utxos.into_iter().map(|v| v.unblinded_output).collect(); let count = utxos.len(); let values: Vec = utxos.iter().map(|utxo| utxo.value).collect(); let sum: MicroTari = values.iter().sum(); diff --git a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs index c3b5c1b3dd..ce9427df1c 100644 --- a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -274,7 +274,7 @@ impl wallet_server::Wallet for WalletGrpcServer { Ok(Response::new(GetUnspentAmountsResponse { amount: unspent_amounts .into_iter() - .map(|o| o.value.as_u64()) + .map(|o| o.unblinded_output.value.as_u64()) .filter(|&a| a > 0) .collect(), })) diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index cac04bbdad..4a6624b2f0 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -53,7 +53,7 @@ use crate::output_manager_service::{ service::{Balance, OutputStatusesByTxId}, storage::{ database::OutputBackendQuery, - models::{KnownOneSidedPaymentScript, SpendingPriority}, + models::{DbUnblindedOutput, KnownOneSidedPaymentScript, SpendingPriority}, }, UtxoSelectionCriteria, }; @@ -252,7 +252,7 @@ pub enum OutputManagerResponse { TransactionToSend(SenderTransactionProtocol), TransactionCancelled, SpentOutputs(Vec), - UnspentOutputs(Vec), + UnspentOutputs(Vec), Outputs(Vec), InvalidOutputs(Vec), BaseNodePublicKeySet, @@ -625,7 +625,7 @@ impl OutputManagerHandle { } /// Sorted from lowest value to highest - pub async fn get_unspent_outputs(&mut self) -> Result, OutputManagerError> { + pub async fn get_unspent_outputs(&mut self) -> Result, OutputManagerError> { match self.handle.call(OutputManagerRequest::GetUnspentOutputs).await?? { OutputManagerResponse::UnspentOutputs(s) => Ok(s), _ => Err(OutputManagerError::UnexpectedApiResponse), diff --git a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs index 00d81a9334..27215a546c 100644 --- a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs +++ b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs @@ -161,6 +161,8 @@ where None, Some(proof), output_source, + None, + None, )?; let tx_id = TxId::new_random(); let output_hex = db_output.commitment.to_hex(); diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 7499735f93..01da07f21b 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -351,7 +351,7 @@ where Ok(OutputManagerResponse::SpentOutputs(outputs)) }, OutputManagerRequest::GetUnspentOutputs => { - let outputs = self.fetch_unspent_outputs()?.into_iter().map(|v| v.into()).collect(); + let outputs = self.fetch_unspent_outputs()?; Ok(OutputManagerResponse::UnspentOutputs(outputs)) }, OutputManagerRequest::GetOutputsBy(q) => { @@ -650,6 +650,8 @@ where &self.resources.factories, spend_priority, OutputSource::default(), + tx_id, + None, )?; debug!( target: LOG_TARGET, @@ -688,6 +690,8 @@ where spend_priority, None, OutputSource::default(), + tx_id, + None, )?; debug!( target: LOG_TARGET, @@ -728,6 +732,8 @@ where &self.resources.factories, spend_priority, OutputSource::default(), + Some(tx_id), + None, )?; self.resources.db.add_unvalidated_output(tx_id, output)?; @@ -844,6 +850,8 @@ where None, None, OutputSource::default(), + Some(single_round_sender_data.tx_id), + None, )?; self.resources @@ -1046,6 +1054,8 @@ where None, None, OutputSource::default(), + Some(tx_id), + None, )?); } @@ -1110,6 +1120,8 @@ where None, None, OutputSource::Coinbase, + Some(tx_id), + None, )?; // If there is no existing output available, we store the one we produced. @@ -1203,13 +1215,15 @@ where None, None, OutputSource::default(), + None, + None, )?) } let mut stp = builder .build(&self.resources.factories, None, u64::MAX) .map_err(|e| OutputManagerError::BuildError(e.message))?; - + let tx_id = stp.get_tx_id()?; if let Some(unblinded_output) = stp.get_change_unblinded_output()? { db_outputs.push(DbUnblindedOutput::rewindable_from_unblinded_output( unblinded_output, @@ -1218,9 +1232,10 @@ where None, None, OutputSource::default(), + Some(tx_id), + None, )?); } - let tx_id = stp.get_tx_id()?; self.resources .db @@ -1325,6 +1340,8 @@ where None, None, OutputSource::default(), + Some(tx_id), + None, )?; builder .with_output(utxo.unblinded_output.clone(), sender_offset_private_key.clone()) @@ -1365,6 +1382,8 @@ where None, None, OutputSource::default(), + Some(tx_id), + None, )?; outputs.push(change_output); } @@ -1820,6 +1839,8 @@ where None, None, OutputSource::default(), + None, + None, )?; tx_builder @@ -2039,6 +2060,8 @@ where None, None, OutputSource::default(), + None, + None, )?; tx_builder @@ -2097,6 +2120,8 @@ where None, None, OutputSource::default(), + Some(tx_id), + None, )?); } @@ -2246,6 +2271,8 @@ where None, None, OutputSource::default(), + None, + None, )?; tx_builder @@ -2408,6 +2435,8 @@ where None, None, OutputSource::AtomicSwap, + Some(tx_id), + None, )?; outputs.push(change_output); @@ -2495,6 +2524,8 @@ where None, None, OutputSource::Refund, + Some(tx_id), + None, )?; outputs.push(change_output); @@ -2645,6 +2676,7 @@ where output.minimum_value_promise, ); + let tx_id = TxId::new_random(); let db_output = DbUnblindedOutput::rewindable_from_unblinded_output( rewound_output.clone(), &self.resources.factories, @@ -2655,10 +2687,11 @@ where None, Some(&output.proof), output_source, + Some(tx_id), + None, )?; let output_hex = output.commitment.to_hex(); - let tx_id = TxId::new_random(); match self.resources.db.add_unspent_output_with_tx_id(tx_id, db_output) { Ok(_) => { diff --git a/base_layer/wallet/src/output_manager_service/storage/models.rs b/base_layer/wallet/src/output_manager_service/storage/models.rs index 45ad665a7d..16ee1e0c4c 100644 --- a/base_layer/wallet/src/output_manager_service/storage/models.rs +++ b/base_layer/wallet/src/output_manager_service/storage/models.rs @@ -24,7 +24,10 @@ use std::cmp::Ordering; use chrono::NaiveDateTime; use derivative::Derivative; -use tari_common_types::types::{BlockHash, BulletRangeProof, Commitment, HashOutput, PrivateKey}; +use tari_common_types::{ + transaction::TxId, + types::{BlockHash, BulletRangeProof, Commitment, HashOutput, PrivateKey}, +}; use tari_core::transactions::{ transaction_components::UnblindedOutput, transaction_protocol::RewindData, @@ -51,6 +54,8 @@ pub struct DbUnblindedOutput { pub marked_deleted_in_block: Option, pub spending_priority: SpendingPriority, pub source: OutputSource, + pub received_in_tx_id: Option, + pub spent_in_tx_id: Option, } impl DbUnblindedOutput { @@ -59,6 +64,8 @@ impl DbUnblindedOutput { factory: &CryptoFactories, spend_priority: Option, source: OutputSource, + received_in_tx_id: Option, + spent_in_tx_id: Option, ) -> Result { let tx_out = output.as_transaction_output(factory)?; Ok(DbUnblindedOutput { @@ -74,6 +81,8 @@ impl DbUnblindedOutput { marked_deleted_in_block: None, spending_priority: spend_priority.unwrap_or(SpendingPriority::Normal), source, + received_in_tx_id, + spent_in_tx_id, }) } @@ -84,6 +93,8 @@ impl DbUnblindedOutput { spending_priority: Option, proof: Option<&BulletRangeProof>, source: OutputSource, + received_in_tx_id: Option, + spent_in_tx_id: Option, ) -> Result { let tx_out = output.as_rewindable_transaction_output(factory, rewind_data, proof)?; Ok(DbUnblindedOutput { @@ -99,6 +110,8 @@ impl DbUnblindedOutput { marked_deleted_in_block: None, spending_priority: spending_priority.unwrap_or(SpendingPriority::Normal), source, + received_in_tx_id, + spent_in_tx_id, }) } } diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs index 9144c889c1..1d75bed0af 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs @@ -1412,6 +1412,7 @@ mod test { (input, unblinded_output) } + #[allow(clippy::too_many_lines)] #[test] fn test_crud() { let db_name = format!("{}.sqlite3", random::string(8).as_str()); @@ -1439,7 +1440,8 @@ mod test { for _i in 0..2 { let (_, uo) = make_input(MicroTari::from(100 + OsRng.next_u64() % 1000)); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown, None, None) + .unwrap(); let o = NewOutputSql::new(uo, OutputStatus::Unspent, None, None, &cipher).unwrap(); outputs.push(o.clone()); outputs_unspent.push(o.clone()); @@ -1448,7 +1450,8 @@ mod test { for _i in 0..3 { let (_, uo) = make_input(MicroTari::from(100 + OsRng.next_u64() % 1000)); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown, None, None) + .unwrap(); let o = NewOutputSql::new(uo, OutputStatus::Spent, None, None, &cipher).unwrap(); outputs.push(o.clone()); outputs_spent.push(o.clone()); @@ -1556,7 +1559,8 @@ mod test { let (_, uo) = make_input(MicroTari::from(100 + OsRng.next_u64() % 1000)); let decrypted_spending_key = uo.spending_key.to_vec(); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown).unwrap(); + let uo = + DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown, None, None).unwrap(); let output = NewOutputSql::new(uo, OutputStatus::Unspent, None, None, &cipher).unwrap(); diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs index def955fc6a..b0a60d4552 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs @@ -795,6 +795,8 @@ impl OutputSql { marked_deleted_in_block, spending_priority, source: o.source.try_into()?, + received_in_tx_id: o.received_in_tx_id.map(|d| (d as u64).into()), + spent_in_tx_id: o.spent_in_tx_id.map(|d| (d as u64).into()), }) } } diff --git a/base_layer/wallet/tests/output_manager_service_tests/service.rs b/base_layer/wallet/tests/output_manager_service_tests/service.rs index 97384f27dd..e3922727d8 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -495,8 +495,8 @@ async fn test_utxo_selection_no_chain_metadata() { assert_eq!(utxos.len(), 8); for (index, utxo) in utxos.iter().enumerate() { let i = index as u64 + 3; - assert_eq!(utxo.features.maturity, i); - assert_eq!(utxo.value, i * amount); + assert_eq!(utxo.unblinded_output.features.maturity, i); + assert_eq!(utxo.unblinded_output.value, i * amount); } // test that we can get a fee estimate with no chain metadata @@ -532,8 +532,8 @@ async fn test_utxo_selection_no_chain_metadata() { assert_eq!(utxos.len(), 7); for (index, utxo) in utxos.iter().enumerate() { let i = index as u64 + 3; - assert_eq!(utxo.features.maturity, i); - assert_eq!(utxo.value, i * amount); + assert_eq!(utxo.unblinded_output.features.maturity, i); + assert_eq!(utxo.unblinded_output.value, i * amount); } } @@ -621,7 +621,7 @@ async fn test_utxo_selection_with_chain_metadata() { // test that largest spendable utxo was encumbered let utxos = oms.get_unspent_outputs().await.unwrap(); assert_eq!(utxos.len(), 9); - let found = utxos.iter().any(|u| u.value == 6 * amount); + let found = utxos.iter().any(|u| u.unblinded_output.value == 6 * amount); assert!(!found, "An unspendable utxo was selected"); // test transactions @@ -646,10 +646,10 @@ async fn test_utxo_selection_with_chain_metadata() { let utxos = oms.get_unspent_outputs().await.unwrap(); assert_eq!(utxos.len(), 7); for utxo in &utxos { - assert_ne!(utxo.features.maturity, 1); - assert_ne!(utxo.value, amount); - assert_ne!(utxo.features.maturity, 2); - assert_ne!(utxo.value, 2 * amount); + assert_ne!(utxo.unblinded_output.features.maturity, 1); + assert_ne!(utxo.unblinded_output.value, amount); + assert_ne!(utxo.unblinded_output.features.maturity, 2); + assert_ne!(utxo.unblinded_output.value, 2 * amount); } // when the amount is greater than the largest utxo, then "Largest" selection strategy is used @@ -674,10 +674,10 @@ async fn test_utxo_selection_with_chain_metadata() { let utxos = oms.get_unspent_outputs().await.unwrap(); assert_eq!(utxos.len(), 5); for utxo in &utxos { - assert_ne!(utxo.features.maturity, 4); - assert_ne!(utxo.value, 4 * amount); - assert_ne!(utxo.features.maturity, 5); - assert_ne!(utxo.value, 5 * amount); + assert_ne!(utxo.unblinded_output.features.maturity, 4); + assert_ne!(utxo.unblinded_output.value, 4 * amount); + assert_ne!(utxo.unblinded_output.features.maturity, 5); + assert_ne!(utxo.unblinded_output.value, 5 * amount); } } @@ -752,7 +752,7 @@ async fn test_utxo_selection_with_tx_priority() { let utxos = oms.get_unspent_outputs().await.unwrap(); assert_eq!(utxos.len(), 1); - assert_ne!(utxos[0].features.output_type, OutputType::Coinbase); + assert_ne!(utxos[0].unblinded_output.features.output_type, OutputType::Coinbase); } #[tokio::test] diff --git a/base_layer/wallet/tests/output_manager_service_tests/storage.rs b/base_layer/wallet/tests/output_manager_service_tests/storage.rs index cbec793036..80df47485d 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/storage.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/storage.rs @@ -55,7 +55,8 @@ pub fn test_db_backend(backend: T) { MicroTari::from(100 + OsRng.next_u64() % 1000), &factories.commitment, )); - let mut uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown).unwrap(); + let mut uo = + DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown, None, None).unwrap(); uo.unblinded_output.features.maturity = i; db.add_unspent_output(uo.clone()).unwrap(); unspent_outputs.push(uo); @@ -102,7 +103,8 @@ pub fn test_db_backend(backend: T) { MicroTari::from(100 + OsRng.next_u64() % 1000), &factories.commitment, )); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown, None, None) + .unwrap(); db.add_unspent_output(uo.clone()).unwrap(); pending_tx.outputs_to_be_spent.push(uo); } @@ -112,7 +114,8 @@ pub fn test_db_backend(backend: T) { MicroTari::from(100 + OsRng.next_u64() % 1000), &factories.commitment, )); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown, None, None) + .unwrap(); pending_tx.outputs_to_be_received.push(uo); } db.encumber_outputs( @@ -248,7 +251,7 @@ pub fn test_db_backend(backend: T) { &factories.commitment, )); let output_to_be_received = - DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown).unwrap(); + DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown, None, None).unwrap(); db.add_output_to_be_received(TxId::from(11u64), output_to_be_received.clone(), None) .unwrap(); pending_incoming_balance += output_to_be_received.unblinded_output.value; @@ -360,7 +363,8 @@ pub async fn test_short_term_encumberance() { &factories.commitment, ) .await; - let mut uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown).unwrap(); + let mut uo = + DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown, None, None).unwrap(); uo.unblinded_output.features.maturity = i; db.add_unspent_output(uo.clone()).unwrap(); unspent_outputs.push(uo); @@ -417,7 +421,7 @@ pub async fn test_no_duplicate_outputs() { // create an output let (_ti, uo) = make_input(&mut OsRng, MicroTari::from(1000), &factories.commitment).await; - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown, None, None).unwrap(); // add it to the database let result = db.add_unspent_output(uo.clone()); @@ -457,7 +461,7 @@ pub async fn test_mark_as_unmined() { // create an output let (_ti, uo) = make_input(&mut OsRng, MicroTari::from(1000), &factories.commitment).await; - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown, None, None).unwrap(); // add it to the database db.add_unspent_output(uo.clone()).unwrap(); diff --git a/base_layer/wallet/tests/utxo_scanner.rs b/base_layer/wallet/tests/utxo_scanner.rs index ddca1f3151..05711a13d4 100644 --- a/base_layer/wallet/tests/utxo_scanner.rs +++ b/base_layer/wallet/tests/utxo_scanner.rs @@ -325,8 +325,15 @@ async fn test_utxo_scanner_recovery() { let mut total_amount_to_recover = MicroTari::from(0); for (h, outputs) in &unblinded_outputs { for output in outputs.iter().skip(outputs.len() / 2) { - let dbo = DbUnblindedOutput::from_unblinded_output(output.clone(), &factories, None, OutputSource::Unknown) - .unwrap(); + let dbo = DbUnblindedOutput::from_unblinded_output( + output.clone(), + &factories, + None, + OutputSource::Unknown, + None, + None, + ) + .unwrap(); // Only the outputs in blocks after the birthday should be included in the recovered total if *h >= NUM_BLOCKS.saturating_sub(BIRTHDAY_OFFSET).saturating_sub(2) { total_outputs_to_recover += 1; @@ -417,8 +424,15 @@ async fn test_utxo_scanner_recovery_with_restart() { let mut total_amount_to_recover = MicroTari::from(0); for (h, outputs) in &unblinded_outputs { for output in outputs.iter().skip(outputs.len() / 2) { - let dbo = DbUnblindedOutput::from_unblinded_output(output.clone(), &factories, None, OutputSource::Unknown) - .unwrap(); + let dbo = DbUnblindedOutput::from_unblinded_output( + output.clone(), + &factories, + None, + OutputSource::Unknown, + None, + None, + ) + .unwrap(); // Only the outputs in blocks after the birthday should be included in the recovered total if *h >= NUM_BLOCKS.saturating_sub(BIRTHDAY_OFFSET).saturating_sub(2) { total_outputs_to_recover += 1; @@ -573,8 +587,15 @@ async fn test_utxo_scanner_recovery_with_restart_and_reorg() { let mut db_unblinded_outputs = Vec::new(); for outputs in unblinded_outputs.values() { for output in outputs.iter().skip(outputs.len() / 2) { - let dbo = DbUnblindedOutput::from_unblinded_output(output.clone(), &factories, None, OutputSource::Unknown) - .unwrap(); + let dbo = DbUnblindedOutput::from_unblinded_output( + output.clone(), + &factories, + None, + OutputSource::Unknown, + None, + None, + ) + .unwrap(); db_unblinded_outputs.push(dbo); } } @@ -642,8 +663,15 @@ async fn test_utxo_scanner_recovery_with_restart_and_reorg() { let mut total_amount_to_recover = MicroTari::from(0); for (h, outputs) in &unblinded_outputs { for output in outputs.iter().skip(outputs.len() / 2) { - let dbo = DbUnblindedOutput::from_unblinded_output(output.clone(), &factories, None, OutputSource::Unknown) - .unwrap(); + let dbo = DbUnblindedOutput::from_unblinded_output( + output.clone(), + &factories, + None, + OutputSource::Unknown, + None, + None, + ) + .unwrap(); // Only the outputs in blocks after the birthday should be included in the recovered total if *h >= 4 { total_outputs_to_recover += 1; @@ -847,8 +875,15 @@ async fn test_utxo_scanner_one_sided_payments() { let mut total_amount_to_recover = MicroTari::from(0); for (h, outputs) in &unblinded_outputs { for output in outputs.iter().skip(outputs.len() / 2) { - let dbo = DbUnblindedOutput::from_unblinded_output(output.clone(), &factories, None, OutputSource::Unknown) - .unwrap(); + let dbo = DbUnblindedOutput::from_unblinded_output( + output.clone(), + &factories, + None, + OutputSource::Unknown, + None, + None, + ) + .unwrap(); // Only the outputs in blocks after the birthday should be included in the recovered total if *h >= NUM_BLOCKS.saturating_sub(BIRTHDAY_OFFSET).saturating_sub(2) { total_outputs_to_recover += 1; @@ -921,8 +956,9 @@ async fn test_utxo_scanner_one_sided_payments() { utxos_by_block.push(block11); block_headers.insert(NUM_BLOCKS, block_header11); - db_unblinded_outputs - .push(DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown).unwrap()); + db_unblinded_outputs.push( + DbUnblindedOutput::from_unblinded_output(uo, &factories, None, OutputSource::Unknown, None, None).unwrap(), + ); test_interface .oms_mock_state .set_one_sided_payments(db_unblinded_outputs); diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 2c8958c1ef..fd8e83431f 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -203,7 +203,7 @@ pub type TariEncryptedValue = tari_core::transactions::transaction_components::E pub type TariComAndPubSignature = tari_common_types::types::ComAndPubSignature; pub type TariUnblindedOutput = tari_core::transactions::transaction_components::UnblindedOutput; -pub struct TariUnblindedOutputs(Vec); +pub struct TariUnblindedOutputs(Vec); pub struct TariContacts(Vec); @@ -1616,7 +1616,7 @@ pub unsafe extern "C" fn tari_unblinded_output_to_json( Ok(json_string) => match CString::new(json_string) { Ok(v) => hex_bytes = v, _ => { - error = LibWalletError::from(InterfaceError::PointerError("contact".to_string())).code; + error = LibWalletError::from(InterfaceError::PointerError("output".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); }, }, @@ -1747,7 +1747,48 @@ pub unsafe extern "C" fn unblinded_outputs_get_at( ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); } - Box::into_raw(Box::new((*outputs).0[position as usize].clone())) + Box::into_raw(Box::new((*outputs).0[position as usize].unblinded_output.clone())) +} + +/// Gets a TariUnblindedOutput from TariUnblindedOutputs at position +/// +/// ## Arguments +/// `outputs` - The pointer to a TariUnblindedOutputs +/// `position` - The integer position +/// `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 TariUnblindedOutput` - Returns a TariUnblindedOutput, note that it returns ptr::null_mut() if +/// TariUnblindedOutputs is null or position is invalid +/// +/// # Safety +/// The ```contact_destroy``` method must be called when finished with a TariContact to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn unblinded_outputs_received_tx_id_get_at( + outputs: *mut TariUnblindedOutputs, + position: c_uint, + error_out: *mut c_int, +) -> *mut c_ulonglong { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if outputs.is_null() { + error = LibWalletError::from(InterfaceError::NullError("outputs".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + let len = unblinded_outputs_get_length(outputs, error_out) as c_int - 1; + if len < 0 || position > len as c_uint { + error = LibWalletError::from(InterfaceError::PositionInvalidError).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + Box::into_raw(Box::new( + (*outputs).0[position as usize] + .received_in_tx_id + .unwrap_or_default() + .as_u64(), + )) } /// Frees memory for a TariUnblindedOutputs @@ -3679,6 +3720,99 @@ pub unsafe extern "C" fn completed_transaction_get_cancellation_reason( } } +/// returns the TariCompletedTransaction as a json string +/// +/// ## Arguments +/// `tx` - The pointer to a TariCompletedTransaction +/// +/// ## Returns +/// `*mut c_char` - Returns a pointer to a char array. Note that it returns an empty char array if +/// TariCompletedTransaction is null or the position is invalid +/// +/// # Safety +/// The ```completed_transaction_destroy``` function must be called when finished with a TariCompletedTransaction to +/// prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn tari_completed_transaction_to_json( + tx: *mut TariCompletedTransaction, + error_out: *mut c_int, +) -> *mut c_char { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let mut hex_bytes = CString::new("").expect("Blank CString will not fail."); + if tx.is_null() { + error = LibWalletError::from(InterfaceError::NullError("transaction".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + } else { + match serde_json::to_string(&*tx) { + Ok(json_string) => match CString::new(json_string) { + Ok(v) => hex_bytes = v, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("transaction".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + }, + }, + Err(_) => { + error = LibWalletError::from(HexError::HexConversionError).code; + ptr::swap(error_out, &mut error as *mut c_int); + }, + } + } + CString::into_raw(hex_bytes) +} + +/// Creates a TariUnblindedOutput from a char array +/// +/// ## Arguments +/// `tx_json` - The pointer to a char array which is json of the TariCompletedTransaction +/// `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 TariCompletedTransaction` - Returns a pointer to a TariCompletedTransaction. Note that it returns +/// ptr::null_mut() if key is null or if there was an error creating the TariCompletedTransaction from key +/// +/// # Safety +/// The ```completed_transaction_destroy``` function must be called when finished with a TariCompletedTransaction to +// /// prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn create_tari_completed_transaction_from_json( + tx_json: *const c_char, + error_out: *mut c_int, +) -> *mut TariCompletedTransaction { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let tx_json_str; + if tx_json.is_null() { + error = LibWalletError::from(InterfaceError::NullError("tx_json".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } else { + match CStr::from_ptr(tx_json).to_str() { + Ok(v) => { + tx_json_str = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("tx_json".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + } + let tx: Result = serde_json::from_str(&tx_json_str); + + match tx { + Ok(tx) => Box::into_raw(Box::new(tx)), + Err(e) => { + error!(target: LOG_TARGET, "Error creating a transaction from json: {:?}", e); + + error = LibWalletError::from(HexError::HexConversionError).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } +} + /// Frees memory for a TariCompletedTransaction /// /// ## Arguments diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 8b180c1c57..573ad59ba3 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -944,6 +944,26 @@ TariUnblindedOutput *unblinded_outputs_get_at(struct TariUnblindedOutputs *outpu unsigned int position, int *error_out); +/** + * Gets a TariUnblindedOutput from TariUnblindedOutputs at position + * + * ## Arguments + * `outputs` - The pointer to a TariUnblindedOutputs + * `position` - The integer position + * `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 TariUnblindedOutput` - Returns a TariUnblindedOutput, note that it returns ptr::null_mut() if + * TariUnblindedOutputs is null or position is invalid + * + * # Safety + * The ```contact_destroy``` method must be called when finished with a TariContact to prevent a memory leak + */ +unsigned long long *unblinded_outputs_received_tx_id_get_at(struct TariUnblindedOutputs *outputs, + unsigned int position, + int *error_out); + /** * Frees memory for a TariUnblindedOutputs * @@ -1972,6 +1992,41 @@ unsigned long long completed_transaction_get_confirmations(TariCompletedTransact int completed_transaction_get_cancellation_reason(TariCompletedTransaction *tx, int *error_out); +/** + * returns the TariCompletedTransaction as a json string + * + * ## Arguments + * `tx` - The pointer to a TariCompletedTransaction + * + * ## Returns + * `*mut c_char` - Returns a pointer to a char array. Note that it returns an empty char array if + * TariCompletedTransaction is null or the position is invalid + * + * # Safety + * The ```completed_transaction_destroy``` function must be called when finished with a TariCompletedTransaction to + * prevent a memory leak + */ +char *tari_completed_transaction_to_json(TariCompletedTransaction *tx, + int *error_out); + +/** + * Creates a TariUnblindedOutput from a char array + * + * ## Arguments + * `tx_json` - The pointer to a char array which is json of the TariCompletedTransaction + * `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 TariCompletedTransaction` - Returns a pointer to a TariCompletedTransaction. Note that it returns + * ptr::null_mut() if key is null or if there was an error creating the TariCompletedTransaction from key + * + * # Safety + * The ```completed_transaction_destroy``` function must be called when finished with a TariCompletedTransaction to + */ +TariCompletedTransaction *create_tari_completed_transaction_from_json(const char *tx_json, + int *error_out); + /** * Frees memory for a TariCompletedTransaction *