diff --git a/Cargo.lock b/Cargo.lock index feaa1927c5..219af9a951 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6673,6 +6673,7 @@ dependencies = [ "serial_test", "sha2 0.10.8", "sha3", + "static_assertions", "strum", "strum_macros", "tari-tiny-keccak", diff --git a/applications/minotari_app_grpc/proto/wallet.proto b/applications/minotari_app_grpc/proto/wallet.proto index 23498e4aac..d60b83c2e2 100644 --- a/applications/minotari_app_grpc/proto/wallet.proto +++ b/applications/minotari_app_grpc/proto/wallet.proto @@ -107,6 +107,7 @@ message CreateBurnTransactionRequest{ uint64 fee_per_gram = 2; string message = 3; bytes claim_public_key = 4; + string payment_id = 5; } diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index 60cd0fafdf..1ef997e880 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -158,6 +158,7 @@ pub(crate) const SPEND_STEP_2_SELF: &str = "step_2_for_self"; pub(crate) const SPEND_STEP_3_SELF: &str = "step_3_for_self"; pub(crate) const SPEND_STEP_3_PARTIES: &str = "step_3_for_parties"; pub(crate) const SPEND_STEP_4_LEADER: &str = "step_4_for_leader_from_"; +pub(crate) const NO_PAYMENT_ID: &str = ""; #[derive(Debug)] pub struct SentTransaction {} @@ -169,6 +170,7 @@ pub async fn send_tari( amount: MicroMinotari, destination: TariAddress, message: String, + payment_id: PaymentId, ) -> Result { wallet_transaction_service .send_transaction( @@ -178,6 +180,7 @@ pub async fn send_tari( OutputFeatures::default(), fee_per_gram * uT, message, + payment_id, ) .await .map_err(CommandError::TransactionServiceError) @@ -188,6 +191,7 @@ pub async fn burn_tari( fee_per_gram: u64, amount: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result<(TxId, BurntProof), CommandError> { wallet_transaction_service .burn_tari( @@ -195,6 +199,7 @@ pub async fn burn_tari( UtxoSelectionCriteria::default(), fee_per_gram * uT, message, + payment_id, None, ) .await @@ -216,6 +221,8 @@ async fn encumber_aggregate_utxo( recipient_address: TariAddress, original_maturity: u64, use_output: UseOutput, + message: String, + payment_id: PaymentId, ) -> Result<(TxId, Transaction, PublicKey, PublicKey, PublicKey, PublicKey), CommandError> { wallet_transaction_service .encumber_aggregate_utxo( @@ -229,6 +236,8 @@ async fn encumber_aggregate_utxo( recipient_address, original_maturity, use_output, + message, + payment_id, ) .await .map_err(CommandError::TransactionServiceError) @@ -240,9 +249,18 @@ async fn spend_backup_pre_mine_utxo( output_hash: HashOutput, expected_commitment: PedersenCommitment, recipient_address: TariAddress, + message: String, + payment_id: PaymentId, ) -> Result { wallet_transaction_service - .spend_backup_pre_mine_utxo(fee_per_gram, output_hash, expected_commitment, recipient_address) + .spend_backup_pre_mine_utxo( + fee_per_gram, + output_hash, + expected_commitment, + recipient_address, + message, + payment_id, + ) .await .map_err(CommandError::TransactionServiceError) } @@ -297,12 +315,13 @@ pub async fn finalise_sha_atomic_swap( pre_image: PublicKey, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result { let (tx_id, _fee, amount, tx) = output_service .create_claim_sha_atomic_swap_transaction(output_hash, pre_image, fee_per_gram) .await?; transaction_service - .submit_transaction(tx_id, tx, amount, message) + .submit_transaction(tx_id, tx, amount, message, payment_id) .await?; Ok(tx_id) } @@ -314,12 +333,13 @@ pub async fn claim_htlc_refund( output_hash: FixedHash, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result { let (tx_id, _fee, amount, tx) = output_service .create_htlc_refund_transaction(output_hash, fee_per_gram) .await?; transaction_service - .submit_transaction(tx_id, tx, amount, message) + .submit_transaction(tx_id, tx, amount, message, payment_id) .await?; Ok(tx_id) } @@ -374,6 +394,7 @@ pub async fn coin_split( num_splits: usize, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, output_service: &mut OutputManagerHandle, transaction_service: &mut TransactionServiceHandle, ) -> Result { @@ -381,7 +402,7 @@ pub async fn coin_split( .create_coin_split(vec![], amount_per_split, num_splits, fee_per_gram) .await?; transaction_service - .submit_transaction(tx_id, tx, amount, message) + .submit_transaction(tx_id, tx, amount, message, payment_id) .await?; Ok(tx_id) @@ -544,7 +565,7 @@ pub async fn make_it_rain( // Send transaction let tx_id = match transaction_type { MakeItRainTransactionType::Interactive => { - send_tari(tx_service, fee, amount, address.clone(), msg.clone()).await + send_tari(tx_service, fee, amount, address.clone(), msg.clone(), PaymentId::Empty).await }, MakeItRainTransactionType::StealthOneSided => { send_one_sided_to_stealth_address( @@ -558,9 +579,11 @@ pub async fn make_it_rain( ) .await }, - MakeItRainTransactionType::BurnTari => burn_tari(tx_service, fee, amount, msg.clone()) - .await - .map(|(tx_id, _)| tx_id), + MakeItRainTransactionType::BurnTari => { + burn_tari(tx_service, fee, amount, msg.clone(), PaymentId::Empty) + .await + .map(|(tx_id, _)| tx_id) + }, }; let submit_time = Instant::now(); @@ -784,6 +807,7 @@ pub async fn command_runner( config.fee_per_gram, args.amount, args.message, + get_payment_id(&args.payment_id), ) .await { @@ -969,6 +993,8 @@ pub async fn command_runner( output_hash, commitment.clone(), args.recipient_address, + args.message, + get_payment_id(&args.payment_id), ) .await { @@ -1334,6 +1360,8 @@ pub async fn command_runner( } else { UseOutput::FromBlockchain(embedded_output.hash()) }, + args.message.clone(), + get_payment_id(&args.payment_id), ) .await { @@ -1977,6 +2005,7 @@ pub async fn command_runner( args.amount, args.destination, args.message, + get_payment_id(&args.payment_id), ) .await { @@ -1995,7 +2024,7 @@ pub async fn command_runner( UtxoSelectionCriteria::default(), args.destination, args.message, - PaymentId::Empty, + get_payment_id(&args.payment_id), ) .await { @@ -2034,6 +2063,7 @@ pub async fn command_runner( args.num_splits, args.fee_per_gram, args.message, + get_payment_id(&args.payment_id), &mut output_service, &mut transaction_service.clone(), ) @@ -2257,6 +2287,7 @@ pub async fn command_runner( args.pre_image.into(), config.fee_per_gram.into(), args.message, + get_payment_id(&args.payment_id), ) .await { @@ -2277,6 +2308,7 @@ pub async fn command_runner( hash, config.fee_per_gram.into(), args.message, + get_payment_id(&args.payment_id), ) .await { @@ -2692,6 +2724,16 @@ pub async fn command_runner( Ok(unban_peer_manager_peers) } +fn get_payment_id(payment_id: &str) -> PaymentId { + match payment_id { + NO_PAYMENT_ID => PaymentId::Empty, + _ => { + let payment_id_arg = payment_id.as_bytes().to_vec(); + PaymentId::Open(payment_id_arg) + }, + } +} + async fn temp_ban_peers(wallet: &WalletSqlite, peer_list: &mut Vec) { for peer in peer_list { let _unused = wallet diff --git a/applications/minotari_console_wallet/src/cli.rs b/applications/minotari_console_wallet/src/cli.rs index b2abd7d513..6ab5928392 100644 --- a/applications/minotari_console_wallet/src/cli.rs +++ b/applications/minotari_console_wallet/src/cli.rs @@ -41,6 +41,8 @@ use tari_utilities::{ }; use thiserror::Error; +use crate::automation::commands::NO_PAYMENT_ID; + #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] #[clap(propagate_version = true)] @@ -177,6 +179,8 @@ pub struct SendMinotariArgs { pub destination: TariAddress, #[clap(short, long, default_value = "")] pub message: String, + #[clap(short, long, default_value = NO_PAYMENT_ID)] + pub payment_id: String, } #[derive(Debug, Args, Clone)] @@ -184,6 +188,8 @@ pub struct BurnMinotariArgs { pub amount: MicroMinotari, #[clap(short, long, default_value = "Burn funds")] pub message: String, + #[clap(short, long, default_value = NO_PAYMENT_ID)] + pub payment_id: String, } #[derive(Debug, Args, Clone)] @@ -265,6 +271,10 @@ pub struct PreMineSpendEncumberAggregateUtxoArgs { pub input_file_names: Vec, #[clap(long)] pub pre_mine_file_path: Option, + #[clap(short, long, default_value = "Spend pre-mine encumber aggregate UTXO")] + pub message: String, + #[clap(short, long, default_value = NO_PAYMENT_ID)] + pub payment_id: String, } #[derive(Debug, Args, Clone)] @@ -293,6 +303,10 @@ pub struct PreMineSpendBackupUtxoArgs { pub recipient_address: TariAddress, #[clap(long)] pub pre_mine_file_path: Option, + #[clap(short, long, default_value = "Spend pre-mine backup UTXO")] + pub message: String, + #[clap(short, long, default_value = NO_PAYMENT_ID)] + pub payment_id: String, } #[derive(Debug, Args, Clone)] @@ -362,6 +376,8 @@ pub struct CoinSplitArgs { pub fee_per_gram: MicroMinotari, #[clap(short, long, default_value = "Coin split")] pub message: String, + #[clap(short, long, default_value = NO_PAYMENT_ID)] + pub payment_id: String, } #[derive(Debug, Args, Clone)] @@ -431,6 +447,8 @@ pub struct FinaliseShaAtomicSwapArgs { pub pre_image: UniPublicKey, #[clap(short, long, default_value = "Claimed HTLC atomic swap")] pub message: String, + #[clap(short, long, default_value = NO_PAYMENT_ID)] + pub payment_id: String, } fn parse_hex(s: &str) -> Result, CliParseError> { @@ -443,6 +461,8 @@ pub struct ClaimShaAtomicSwapRefundArgs { pub output_hash: Vec>, #[clap(short, long, default_value = "Claimed HTLC atomic swap refund")] pub message: String, + #[clap(short, long, default_value = NO_PAYMENT_ID)] + pub payment_id: String, } #[derive(Debug, Args, Clone)] diff --git a/applications/minotari_console_wallet/src/grpc/mod.rs b/applications/minotari_console_wallet/src/grpc/mod.rs index fa2c761687..bcdb568781 100644 --- a/applications/minotari_console_wallet/src/grpc/mod.rs +++ b/applications/minotari_console_wallet/src/grpc/mod.rs @@ -29,7 +29,7 @@ pub fn convert_to_transaction_event(event: String, source: TransactionWrapper) - direction: completed.direction.to_string(), amount: completed.amount.as_u64(), message: completed.message.to_string(), - payment_id: completed.payment_id.map(|id| id.to_bytes()).unwrap_or_default(), + payment_id: completed.payment_id.to_bytes(), }, TransactionWrapper::Outbound(outbound) => TransactionEvent { event, diff --git a/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs index 59707a0c2f..a23b84916b 100644 --- a/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -415,6 +415,7 @@ impl wallet_server::Wallet for WalletGrpcServer { tx, amount, "Claiming HTLC transaction with pre-image".to_string(), + PaymentId::Empty, ) .await { @@ -465,7 +466,13 @@ impl wallet_server::Wallet for WalletGrpcServer { { Ok((tx_id, _fee, amount, tx)) => { match transaction_service - .submit_transaction(tx_id, tx, amount, "Creating HTLC refund transaction".to_string()) + .submit_transaction( + tx_id, + tx, + amount, + "Creating HTLC refund transaction".to_string(), + PaymentId::Empty, + ) .await { Ok(()) => TransferResult { @@ -522,9 +529,7 @@ impl wallet_server::Wallet for WalletGrpcServer { let mut transfers = Vec::new(); for (hex_address, address, amount, fee_per_gram, message, payment_type, payment_id) in recipients { - let payment_id = PaymentId::from_bytes(&payment_id) - .map_err(|_| "Invalid payment id".to_string()) - .map_err(Status::invalid_argument)?; + let payment_id = PaymentId::from_bytes(&payment_id); let mut transaction_service = self.get_transaction_service(); transfers.push(async move { ( @@ -538,6 +543,7 @@ impl wallet_server::Wallet for WalletGrpcServer { OutputFeatures::default(), fee_per_gram.into(), message, + payment_id, ) .await } else if payment_type == PaymentType::OneSided as i32 { @@ -612,6 +618,7 @@ impl wallet_server::Wallet for WalletGrpcServer { UtxoSelectionCriteria::default(), message.fee_per_gram.into(), message.message, + PaymentId::Open(message.payment_id.as_bytes().to_vec()), if message.claim_public_key.is_empty() { None } else { @@ -790,7 +797,7 @@ impl wallet_server::Wallet for WalletGrpcServer { .get_signature() .to_vec(), message: txn.message.clone(), - payment_id: txn.payment_id.as_ref().map(|id| id.to_bytes()).unwrap_or_default(), + payment_id: txn.payment_id.to_bytes(), }), }; match sender.send(Ok(response)).await { @@ -833,6 +840,7 @@ impl wallet_server::Wallet for WalletGrpcServer { .map_err(|_| Status::internal("Count not convert u64 to usize".to_string()))?, MicroMinotari::from(message.fee_per_gram), message.message, + PaymentId::Empty, ) .await .map_err(|e| Status::internal(format!("{:?}", e)))?; @@ -985,7 +993,13 @@ impl wallet_server::Wallet for WalletGrpcServer { output = output.with_script(script![Nop].map_err(|e| Status::invalid_argument(e.to_string()))?); let (tx_id, transaction) = output_manager - .create_send_to_self_with_output(vec![output], fee_per_gram.into(), UtxoSelectionCriteria::default()) + .create_send_to_self_with_output( + vec![output], + fee_per_gram.into(), + UtxoSelectionCriteria::default(), + message.clone(), + PaymentId::Empty, + ) .await .map_err(|e| Status::internal(e.to_string()))?; @@ -1003,7 +1017,7 @@ impl wallet_server::Wallet for WalletGrpcServer { let template_address = reg_output.hash(); transaction_service - .submit_transaction(tx_id, transaction, 0.into(), message) + .submit_transaction(tx_id, transaction, 0.into(), message, PaymentId::Empty) .await .map_err(|e| Status::internal(e.to_string()))?; @@ -1161,7 +1175,7 @@ fn convert_wallet_transaction_into_transaction_info( .map(|s| s.get_signature().to_vec()) .unwrap_or_default(), message: tx.message, - payment_id: tx.payment_id.map(|id| id.to_bytes()).unwrap_or_default(), + payment_id: tx.payment_id.to_bytes(), }, } } diff --git a/applications/minotari_console_wallet/src/notifier/mod.rs b/applications/minotari_console_wallet/src/notifier/mod.rs index c79404e3e2..6d32f2b1e3 100644 --- a/applications/minotari_console_wallet/src/notifier/mod.rs +++ b/applications/minotari_console_wallet/src/notifier/mod.rs @@ -37,7 +37,6 @@ use minotari_wallet::{ WalletSqlite, }; use tari_common_types::transaction::TxId; -use tari_core::transactions::transaction_components::encrypted_data::PaymentId; use tari_utilities::hex::Hex; use tokio::{runtime::Handle, sync::broadcast::Sender}; @@ -289,7 +288,7 @@ fn args_from_complete(tx: &CompletedTransaction, event: &str, confirmations: Opt let amount = format!("{}", tx.amount); let status = format!("{}", tx.status); let direction = format!("{}", tx.direction); - let payment_id = format!("{}", tx.payment_id.clone().unwrap_or(PaymentId::Empty)); + let payment_id = format!("{}", tx.payment_id.clone()); let kernel = tx.transaction.body.kernels().first(); let (excess, public_nonce, signature) = match kernel { diff --git a/applications/minotari_console_wallet/src/ui/components/burn_tab.rs b/applications/minotari_console_wallet/src/ui/components/burn_tab.rs index e90d97c87b..622b8ad3e9 100644 --- a/applications/minotari_console_wallet/src/ui/components/burn_tab.rs +++ b/applications/minotari_console_wallet/src/ui/components/burn_tab.rs @@ -5,7 +5,7 @@ use std::fs; use log::*; use minotari_wallet::output_manager_service::UtxoSelectionCriteria; -use tari_core::transactions::tari_amount::MicroMinotari; +use tari_core::transactions::{tari_amount::MicroMinotari, transaction_components::encrypted_data::PaymentId}; use tokio::{runtime::Handle, sync::watch}; use tui::{ backend::Backend, @@ -35,6 +35,7 @@ pub struct BurnTab { amount_field: String, fee_field: String, message_field: String, + payment_id_field: String, error_message: Option, success_message: Option, offline_message: Option, @@ -54,6 +55,7 @@ impl BurnTab { amount_field: String::new(), fee_field: app_state.get_default_fee_per_gram().as_u64().to_string(), message_field: String::new(), + payment_id_field: String::new(), error_message: None, success_message: None, offline_message: None, @@ -92,22 +94,22 @@ impl BurnTab { let instructions = Paragraph::new(vec![ Spans::from(vec![ Span::raw("Press "), - Span::styled("P", Style::default().add_modifier(Modifier::BOLD)), + Span::styled("V", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to edit "), Span::styled("Burn Proof Filepath", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" field, "), + Span::raw(", "), Span::styled("C", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to edit "), Span::styled("Claim Public Key", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" field, "), + Span::raw(", "), Span::styled("A", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to edit "), Span::styled("Amount", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" and "), + Span::raw(", "), Span::styled("F", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to edit "), Span::styled("Fee-Per-Gram", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" field,"), + Span::raw(" and "), Span::styled("B", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to view burnt proofs."), ]), @@ -129,7 +131,7 @@ impl BurnTab { .block( Block::default() .borders(Borders::ALL) - .title("Save burn proof to file(p)ath:"), + .title("Sa(v)e burn proof to file path:"), ); f.render_widget(burnt_proof_filepath_input, vert_chunks[1]); @@ -162,13 +164,26 @@ impl BurnTab { .block(Block::default().borders(Borders::ALL).title("(F)ee-per-gram (uT):")); f.render_widget(fee_input, amount_fee_layout[1]); + let message_payment_id_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .split(vert_chunks[4]); + let message_input = Paragraph::new(self.message_field.as_ref()) .style(match self.burn_input_mode { BurnInputMode::Message => Style::default().fg(Color::Magenta), _ => Style::default(), }) .block(Block::default().borders(Borders::ALL).title("(M)essage:")); - f.render_widget(message_input, vert_chunks[4]); + f.render_widget(message_input, message_payment_id_layout[0]); + + let payment_id_input = Paragraph::new(self.payment_id_field.as_ref()) + .style(match self.burn_input_mode { + BurnInputMode::PaymentId => Style::default().fg(Color::Magenta), + _ => Style::default(), + }) + .block(Block::default().borders(Borders::ALL).title("(P)ayment-id:")); + f.render_widget(payment_id_input, message_payment_id_layout[1]); match self.burn_input_mode { BurnInputMode::None => (), @@ -200,9 +215,15 @@ impl BurnTab { ), BurnInputMode::Message => f.set_cursor( // Put cursor past the end of the input text - vert_chunks[4].x + self.message_field.width() as u16 + 1, + message_payment_id_layout[0].x + self.message_field.width() as u16 + 1, + // Move one line down, from the border to the input line + message_payment_id_layout[0].y + 1, + ), + BurnInputMode::PaymentId => f.set_cursor( + // Put cursor past the end of the input text + message_payment_id_layout[1].x + self.payment_id_field.width() as u16 + 1, // Move one line down, from the border to the input line - vert_chunks[4].y + 1, + message_payment_id_layout[1].y + 1, ), } } @@ -312,6 +333,12 @@ impl BurnTab { UtxoSelectionCriteria::default(), fee_per_gram, self.message_field.clone(), + if self.payment_id_field.is_empty() { + PaymentId::Empty + } else { + let bytes = self.payment_id_field.as_bytes().to_vec(); + PaymentId::Open(bytes) + }, tx, )) { Err(e) => { @@ -356,6 +383,7 @@ impl BurnTab { self.amount_field = "".to_string(); self.fee_field = app_state.get_default_fee_per_gram().as_u64().to_string(); self.message_field = "".to_string(); + self.payment_id_field = "".to_string(); self.burn_input_mode = BurnInputMode::None; self.burn_result_watch = Some(rx); } @@ -408,12 +436,19 @@ impl BurnTab { }, }, BurnInputMode::Message => match c { - '\n' => self.burn_input_mode = BurnInputMode::None, + '\n' => self.burn_input_mode = BurnInputMode::PaymentId, c => { self.message_field.push(c); return KeyHandled::Handled; }, }, + BurnInputMode::PaymentId => match c { + '\n' => self.burn_input_mode = BurnInputMode::None, + c => { + self.payment_id_field.push(c); + return KeyHandled::Handled; + }, + }, } } @@ -546,7 +581,7 @@ impl Component for BurnTab { area, "Confirm Burning Transaction".to_string(), format!( - "Are you sure you want to burn {} Minotari with a Claim Public Key {}?\n(Y)es / (N)o", + "Are you sure you want to burn {} Minotari with a Claim Public Key\n{}?\n(Y)es / (N)o", self.amount_field, self.claim_public_key_field ), Color::Red, @@ -608,13 +643,14 @@ impl Component for BurnTab { } match c { - 'p' => self.burn_input_mode = BurnInputMode::BurntProofPath, + 'v' => self.burn_input_mode = BurnInputMode::BurntProofPath, 'c' => self.burn_input_mode = BurnInputMode::ClaimPublicKey, 'a' => { self.burn_input_mode = BurnInputMode::Amount; }, 'f' => self.burn_input_mode = BurnInputMode::Fee, 'm' => self.burn_input_mode = BurnInputMode::Message, + 'p' => self.burn_input_mode = BurnInputMode::PaymentId, 'b' => { self.show_proofs = !self.show_proofs; }, @@ -672,6 +708,9 @@ impl Component for BurnTab { BurnInputMode::Message => { let _ = self.message_field.pop(); }, + BurnInputMode::PaymentId => { + let _ = self.payment_id_field.pop(); + }, BurnInputMode::None => {}, } } @@ -684,6 +723,7 @@ pub enum BurnInputMode { ClaimPublicKey, Amount, Message, + PaymentId, Fee, } diff --git a/applications/minotari_console_wallet/src/ui/components/send_tab.rs b/applications/minotari_console_wallet/src/ui/components/send_tab.rs index 9f634120e3..166b238153 100644 --- a/applications/minotari_console_wallet/src/ui/components/send_tab.rs +++ b/applications/minotari_console_wallet/src/ui/components/send_tab.rs @@ -4,7 +4,7 @@ use log::*; use minotari_wallet::output_manager_service::UtxoSelectionCriteria; use tari_common_types::wallet_types::WalletType; -use tari_core::transactions::tari_amount::MicroMinotari; +use tari_core::transactions::{tari_amount::MicroMinotari, transaction_components::encrypted_data::PaymentId}; use tari_utilities::hex::Hex; use tokio::{runtime::Handle, sync::watch}; use tui::{ @@ -300,7 +300,12 @@ impl SendTab { UtxoSelectionCriteria::default(), fee_per_gram, self.message_field.clone(), - self.payment_id_field.clone(), + if self.payment_id_field.is_empty() { + PaymentId::Empty + } else { + let bytes = self.payment_id_field.as_bytes().to_vec(); + PaymentId::Open(bytes) + }, tx, ), ) { @@ -321,6 +326,12 @@ impl SendTab { UtxoSelectionCriteria::default(), fee_per_gram, self.message_field.clone(), + if self.payment_id_field.is_empty() { + PaymentId::Empty + } else { + let bytes = self.payment_id_field.as_bytes().to_vec(); + PaymentId::Open(bytes) + }, tx, )) { Err(e) => { diff --git a/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs b/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs index c7ea9d74ff..eb11666161 100644 --- a/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs +++ b/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs @@ -440,16 +440,7 @@ impl TransactionsTab { let maturity = Span::styled(maturity, Style::default().fg(Color::White)); let payment_id = match tx.payment_id.clone() { - Some(v) => { - let bytes = v.get_data(); - if bytes.is_empty() { - format!("#{}", v) - } else { - String::from_utf8(bytes) - .unwrap_or_else(|_| "Invalid string".to_string()) - .to_string() - } - }, + Some(v) => format!("#{}", v.compact_display()), None => "None".to_string(), }; let payment_id = Span::styled(payment_id, Style::default().fg(Color::White)); diff --git a/applications/minotari_console_wallet/src/ui/state/app_state.rs b/applications/minotari_console_wallet/src/ui/state/app_state.rs index 9fe1cfb3d5..5fa104ad93 100644 --- a/applications/minotari_console_wallet/src/ui/state/app_state.rs +++ b/applications/minotari_console_wallet/src/ui/state/app_state.rs @@ -295,6 +295,7 @@ impl AppState { selection_criteria: UtxoSelectionCriteria, fee_per_gram: u64, message: String, + payment_id: PaymentId, result_tx: watch::Sender, ) -> Result<(), UiError> { let inner = self.inner.write().await; @@ -310,6 +311,7 @@ impl AppState { selection_criteria, output_features, message, + payment_id, fee_per_gram, tx_service_handle, result_tx, @@ -325,17 +327,11 @@ impl AppState { selection_criteria: UtxoSelectionCriteria, fee_per_gram: u64, message: String, - payment_id_str: String, + payment_id: PaymentId, result_tx: watch::Sender, ) -> Result<(), UiError> { let inner = self.inner.write().await; let address = TariAddress::from_str(&address).map_err(|_| UiError::PublicKeyParseError)?; - let payment_id = if payment_id_str.is_empty() { - PaymentId::Empty - } else { - let bytes = payment_id_str.as_bytes().to_vec(); - PaymentId::Open(bytes) - }; let output_features = OutputFeatures { ..Default::default() }; @@ -364,6 +360,7 @@ impl AppState { selection_criteria: UtxoSelectionCriteria, fee_per_gram: u64, message: String, + payment_id: PaymentId, result_tx: watch::Sender, ) -> Result<(), UiError> { let inner = self.inner.write().await; @@ -397,6 +394,7 @@ impl AppState { MicroMinotari::from(amount), selection_criteria, message, + payment_id, fee_per_gram, tx_service_handle, inner.wallet.db.clone(), @@ -1242,7 +1240,7 @@ impl CompletedTransactionInfo { weight, inputs_count, outputs_count, - payment_id: tx.payment_id, + payment_id: Some(tx.payment_id), coinbase, burn, }) diff --git a/applications/minotari_console_wallet/src/ui/state/tasks.rs b/applications/minotari_console_wallet/src/ui/state/tasks.rs index b0fb66f47f..47ce243977 100644 --- a/applications/minotari_console_wallet/src/ui/state/tasks.rs +++ b/applications/minotari_console_wallet/src/ui/state/tasks.rs @@ -62,6 +62,7 @@ pub async fn send_transaction_task( selection_criteria: UtxoSelectionCriteria, output_features: OutputFeatures, message: String, + payment_id: PaymentId, fee_per_gram: MicroMinotari, mut transaction_service_handle: TransactionServiceHandle, result_tx: watch::Sender, @@ -77,6 +78,7 @@ pub async fn send_transaction_task( output_features, fee_per_gram, message, + payment_id, ) .await { @@ -194,6 +196,7 @@ pub async fn send_burn_transaction_task( amount: MicroMinotari, selection_criteria: UtxoSelectionCriteria, message: String, + payment_id: PaymentId, fee_per_gram: MicroMinotari, mut transaction_service_handle: TransactionServiceHandle, db: WalletDatabase, @@ -207,7 +210,14 @@ pub async fn send_burn_transaction_task( // ---------------------------------------------------------------------------- let (burn_tx_id, original_proof) = match transaction_service_handle - .burn_tari(amount, selection_criteria, fee_per_gram, message, claim_public_key) + .burn_tari( + amount, + selection_criteria, + fee_per_gram, + message, + payment_id, + claim_public_key, + ) .await { Ok((burn_tx_id, original_proof)) => (burn_tx_id, original_proof), diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index 0b7a7b11bb..9aa83b929b 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -109,6 +109,7 @@ tempfile = "3.1.0" toml = { version = "0.5" } quickcheck = "1.0" serial_test = "0.5" +static_assertions = "1.1.0" [build-dependencies] tari_common = { path = "../../common", features = [ diff --git a/base_layer/core/src/transactions/aggregated_body.rs b/base_layer/core/src/transactions/aggregated_body.rs index c6c31498b0..7fa873e578 100644 --- a/base_layer/core/src/transactions/aggregated_body.rs +++ b/base_layer/core/src/transactions/aggregated_body.rs @@ -19,16 +19,19 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#[cfg(feature = "base_node")] +use std::convert::TryFrom; use std::{ cmp::max, - convert::TryFrom, fmt::{Display, Error, Formatter}, }; use borsh::{BorshDeserialize, BorshSerialize}; use log::*; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{ComAndPubSignature, Commitment, FixedHash, PrivateKey}; +#[cfg(feature = "base_node")] +use tari_common_types::types::FixedHash; +use tari_common_types::types::{ComAndPubSignature, Commitment, PrivateKey}; use tari_crypto::commitment::HomomorphicCommitmentFactory; #[cfg(feature = "base_node")] use tari_mmr::pruned_hashset::PrunedHashSet; diff --git a/base_layer/core/src/transactions/transaction_components/encrypted_data.rs b/base_layer/core/src/transactions/transaction_components/encrypted_data.rs index 070d2c45aa..16d1e4c142 100644 --- a/base_layer/core/src/transactions/transaction_components/encrypted_data.rs +++ b/base_layer/core/src/transactions/transaction_components/encrypted_data.rs @@ -42,6 +42,7 @@ use chacha20poly1305::{ XNonce, }; use digest::{consts::U32, generic_array::GenericArray, FixedOutput}; +use num_traits::ToBytes; use primitive_types::U256; use serde::{Deserialize, Serialize}; use tari_common_types::{ @@ -67,6 +68,7 @@ const SIZE_NONCE: usize = size_of::(); const SIZE_VALUE: usize = size_of::(); const SIZE_MASK: usize = PrivateKey::KEY_LEN; const SIZE_TAG: usize = size_of::(); +const SIZE_U256: usize = size_of::(); pub const STATIC_ENCRYPTED_DATA_SIZE_TOTAL: usize = SIZE_NONCE + SIZE_VALUE + SIZE_MASK + SIZE_TAG; const MAX_ENCRYPTED_DATA_SIZE: usize = 256 + STATIC_ENCRYPTED_DATA_SIZE_TOTAL; @@ -79,33 +81,65 @@ pub struct EncryptedData { data: MaxSizeBytes, } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Default)] pub enum PaymentId { + /// No payment ID. + #[default] Empty, + /// A u64 number. U64(u64), + /// A u256 number. U256(U256), + /// A Tari address, usually the sender address. Address(TariAddress), + /// Open - the user optionally specifies a human-readable string. Open(Vec), - AddressAndData(TariAddress, Vec), + /// This payment ID is automatically generated by the system for output UTXOs. The optional user specified + /// `Open(Vec)` payment ID will be assigned to `id`; the system adds in the sender address. + AddressAndData { sender_address: TariAddress, data: Vec }, + /// This payment ID is automatically generated by the system for change outputs. The optional user specified + /// `Open(Vec)` payment ID will be assigned to `id`; the system adds in the transaction amount and recipient + /// address. + ChangeInfo { + recipient_address: TariAddress, + amount: MicroMinotari, + data: Vec, + }, } impl PaymentId { pub fn get_size(&self) -> usize { match self { PaymentId::Empty => 0, - PaymentId::U64(_) => size_of::(), - PaymentId::U256(_) => size_of::(), + PaymentId::U64(_) => SIZE_VALUE, + PaymentId::U256(_) => SIZE_U256, PaymentId::Address(a) => a.get_size(), PaymentId::Open(v) => v.len(), - PaymentId::AddressAndData(a, v) => a.get_size() + v.len(), + PaymentId::AddressAndData { + sender_address, + data: id, + } => sender_address.get_size() + id.len(), + PaymentId::ChangeInfo { + recipient_address, + amount: _amount, + data: msg_and_id, + } => recipient_address.get_size() + SIZE_VALUE + msg_and_id.len(), } } pub fn get_data(&self) -> Vec { match &self { - PaymentId::Empty | PaymentId::U64(_) | PaymentId::U256(_) | PaymentId::Address(_) => Vec::new(), + PaymentId::Empty => vec![], + PaymentId::U64(v) => v.to_le_bytes().to_vec(), + PaymentId::U256(v) => { + let bytes: &mut [u8] = &mut [0; SIZE_U256]; + v.to_little_endian(bytes); + bytes.to_vec() + }, + PaymentId::Address(v) => v.to_vec(), PaymentId::Open(v) => v.clone(), - PaymentId::AddressAndData(_v, d) => d.clone(), + PaymentId::AddressAndData { data, .. } => data.clone(), + PaymentId::ChangeInfo { data, .. } => data.clone(), } } @@ -120,62 +154,157 @@ impl PaymentId { }, PaymentId::Address(v) => v.to_vec(), PaymentId::Open(v) => v.clone(), - PaymentId::AddressAndData(v, d) => { - let mut bytes = v.to_vec(); - bytes.extend_from_slice(d); + PaymentId::AddressAndData { + sender_address, + data: id, + } => { + let mut bytes = sender_address.to_vec(); + bytes.extend_from_slice(id); + bytes + }, + PaymentId::ChangeInfo { + recipient_address, + amount, + data: msg_and_id, + } => { + let mut bytes = amount.as_u64().to_le_bytes().to_vec(); + bytes.extend_from_slice(&recipient_address.to_vec()); + bytes.extend_from_slice(msg_and_id); bytes }, } } - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Self { match bytes.len() { - 0 => Ok(PaymentId::Empty), - 8 => { - let bytes: [u8; 8] = bytes.try_into().expect("Cannot fail, as we already test the length"); + 0 => PaymentId::Empty, + SIZE_VALUE => { + let bytes: [u8; SIZE_VALUE] = bytes.try_into().expect("Cannot fail, as we already test the length"); let v = u64::from_le_bytes(bytes); - Ok(PaymentId::U64(v)) + PaymentId::U64(v) }, - 32 => { + SIZE_U256 => { let v = U256::from_little_endian(bytes); - Ok(PaymentId::U256(v)) - }, - TARI_ADDRESS_INTERNAL_DUAL_SIZE => { - let v = - TariAddress::from_bytes(bytes).map_err(|e| EncryptedDataError::ByteArrayError(e.to_string()))?; - Ok(PaymentId::Address(v)) - }, - TARI_ADDRESS_INTERNAL_SINGLE_SIZE => { - let v = - TariAddress::from_bytes(bytes).map_err(|e| EncryptedDataError::ByteArrayError(e.to_string()))?; - Ok(PaymentId::Address(v)) + PaymentId::U256(v) }, - len if len < TARI_ADDRESS_INTERNAL_SINGLE_SIZE => Ok(PaymentId::Open(bytes.to_vec())), - len if len < TARI_ADDRESS_INTERNAL_DUAL_SIZE => { - if let Ok(address) = TariAddress::from_bytes(&bytes[0..TARI_ADDRESS_INTERNAL_SINGLE_SIZE]) { - Ok(PaymentId::AddressAndData( - address, - bytes[TARI_ADDRESS_INTERNAL_SINGLE_SIZE..].to_vec(), - )) + len if len <= TARI_ADDRESS_INTERNAL_SINGLE_SIZE => { + if let Ok(v) = TariAddress::from_bytes(bytes) { + // Single + PaymentId::Address(v) } else { - Ok(PaymentId::Open(bytes.to_vec())) + // data + PaymentId::Open(bytes.to_vec()) } }, _ => { - if let Ok(address) = TariAddress::from_bytes(&bytes[0..TARI_ADDRESS_INTERNAL_SINGLE_SIZE]) { - Ok(PaymentId::AddressAndData( - address, - bytes[TARI_ADDRESS_INTERNAL_SINGLE_SIZE..].to_vec(), - )) - } else if let Ok(address) = TariAddress::from_bytes(&bytes[0..TARI_ADDRESS_INTERNAL_DUAL_SIZE]) { - Ok(PaymentId::AddressAndData( - address, - bytes[TARI_ADDRESS_INTERNAL_DUAL_SIZE..].to_vec(), - )) - } else { - Ok(PaymentId::Open(bytes.to_vec())) + // PaymentId::Address + if let Ok(v) = TariAddress::from_bytes(bytes) { + // Dual + return PaymentId::Address(v); + } + // PaymentId::AddressAndData + if bytes.len() > TARI_ADDRESS_INTERNAL_DUAL_SIZE { + // Dual + data + if let Ok(sender_address) = TariAddress::from_bytes(&bytes[0..TARI_ADDRESS_INTERNAL_DUAL_SIZE]) { + return PaymentId::AddressAndData { + sender_address, + data: bytes[TARI_ADDRESS_INTERNAL_DUAL_SIZE..].to_vec(), + }; + } + } + if bytes.len() > TARI_ADDRESS_INTERNAL_SINGLE_SIZE { + // Single + data + if let Ok(sender_address) = TariAddress::from_bytes(&bytes[0..TARI_ADDRESS_INTERNAL_SINGLE_SIZE]) { + return PaymentId::AddressAndData { + sender_address, + data: bytes[TARI_ADDRESS_INTERNAL_SINGLE_SIZE..].to_vec(), + }; + } + } + // PaymentId::ChangeData + let mut le_bytes = [0u8; SIZE_VALUE]; + le_bytes.copy_from_slice(&bytes[0..SIZE_VALUE]); + let amount = u64::from_le_bytes(le_bytes); + // Amount + Single/Dual + if let Ok(recipient_address) = TariAddress::from_bytes(&bytes[SIZE_VALUE..]) { + return PaymentId::ChangeInfo { + recipient_address, + amount: MicroMinotari::from(amount), + data: Vec::new(), + }; } + if bytes.len() > SIZE_VALUE + TARI_ADDRESS_INTERNAL_DUAL_SIZE { + if let Ok(recipient_address) = + TariAddress::from_bytes(&bytes[SIZE_VALUE..SIZE_VALUE + TARI_ADDRESS_INTERNAL_DUAL_SIZE]) + { + // Amount + Dual + data + return PaymentId::ChangeInfo { + recipient_address, + amount: MicroMinotari::from(amount), + data: bytes[SIZE_VALUE + TARI_ADDRESS_INTERNAL_DUAL_SIZE..].to_vec(), + }; + } + } + if bytes.len() > SIZE_VALUE + TARI_ADDRESS_INTERNAL_SINGLE_SIZE { + if let Ok(recipient_address) = + TariAddress::from_bytes(&bytes[SIZE_VALUE..SIZE_VALUE + TARI_ADDRESS_INTERNAL_SINGLE_SIZE]) + { + // Amount + Single + data + return PaymentId::ChangeInfo { + recipient_address, + amount: MicroMinotari::from(amount), + data: bytes[SIZE_VALUE + TARI_ADDRESS_INTERNAL_SINGLE_SIZE..].to_vec(), + }; + } + } + // Single + PaymentId::Open(bytes.to_vec()) + }, + } + } + + /// Helper function to convert a byte slice to a string for the open and data variants + pub fn stringify_bytes(bytes: &[u8]) -> String { + String::from_utf8_lossy(bytes).to_string() + } + + /// Helper function to display the payment id in a compact form + pub fn compact_display(&self) -> String { + fn short_address(s: &str, start_end_display: usize) -> String { + if s.len() > 2 * start_end_display { + format!( + "{}..{}", + &s[0..start_end_display], + &s[s.len() - start_end_display..s.len()] + ) + } else { + s.to_string() + } + } + match self { + PaymentId::Empty | PaymentId::U64(_) | PaymentId::U256(_) | PaymentId::Address(_) | PaymentId::Open(_) => { + self.to_string() }, + PaymentId::AddressAndData { + sender_address, + data: id, + } => { + format!( + "address({}), data({})", + short_address(&sender_address.to_base58(), 12), + PaymentId::stringify_bytes(id) + ) + }, + PaymentId::ChangeInfo { + recipient_address, + amount, + data: msg_and_id, + } => format!( + "address({}), amount({}), data({})", + short_address(&recipient_address.to_base58(), 12), + amount, + PaymentId::stringify_bytes(msg_and_id), + ), } } } @@ -187,8 +316,27 @@ impl Display for PaymentId { PaymentId::U64(v) => write!(f, "u64({v})"), PaymentId::U256(v) => write!(f, "u256({v})"), PaymentId::Address(v) => write!(f, "address({})", v.to_base58()), - PaymentId::Open(v) => write!(f, "data({})", v.to_hex()), - PaymentId::AddressAndData(v, d) => write!(f, "address_and_data({},{})", v.to_base58(), d.to_hex()), + PaymentId::Open(v) => write!(f, "data({})", PaymentId::stringify_bytes(v)), + PaymentId::AddressAndData { + sender_address, + data: id, + } => write!( + f, + "address({}), data({})", + sender_address.to_base58(), + PaymentId::stringify_bytes(id) + ), + PaymentId::ChangeInfo { + recipient_address, + amount, + data: msg_and_id, + } => write!( + f, + "address({}), amount({}), data({})", + recipient_address.to_base58(), + amount, + PaymentId::stringify_bytes(msg_and_id), + ), } } } @@ -272,7 +420,7 @@ impl EncryptedData { Ok(( u64::from_le_bytes(value_bytes).into(), PrivateKey::from_canonical_bytes(&bytes[SIZE_VALUE..SIZE_VALUE + SIZE_MASK])?, - PaymentId::from_bytes(&bytes[SIZE_VALUE + SIZE_MASK..])?, + PaymentId::from_bytes(&bytes[SIZE_VALUE + SIZE_MASK..]), )) } @@ -388,11 +536,118 @@ fn kdf_aead(encryption_key: &PrivateKey, commitment: &Commitment) -> EncryptedDa #[cfg(test)] mod test { + use static_assertions::const_assert; use tari_common_types::types::CommitmentFactory; use tari_crypto::commitment::HomomorphicCommitmentFactory; use super::*; + #[test] + fn address_sizes_increase_as_expected() { + const_assert!(SIZE_VALUE < SIZE_U256); + const_assert!(SIZE_U256 < TARI_ADDRESS_INTERNAL_SINGLE_SIZE); + const_assert!(TARI_ADDRESS_INTERNAL_SINGLE_SIZE < TARI_ADDRESS_INTERNAL_DUAL_SIZE); + } + + fn encrypt_decrypt_payment_id(payment_id: PaymentId) -> PaymentId { + let (value, mask) = (u64::MAX, PrivateKey::random(&mut OsRng)); + let commitment = CommitmentFactory::default().commit(&mask, &PrivateKey::from(value)); + let encryption_key = PrivateKey::random(&mut OsRng); + let amount = MicroMinotari::from(value); + let encrypted_data = + EncryptedData::encrypt_data(&encryption_key, &commitment, amount, &mask, payment_id.clone()).unwrap(); + let (_decrypted_value, _decrypted_mask, decrypted_payment_id) = + EncryptedData::decrypt_data(&encryption_key, &commitment, &encrypted_data).unwrap(); + decrypted_payment_id + } + + #[test] + fn it_encrypts_and_decrypts_equivalent_structures() { + // Address vs. AddressAndData with zero data + + // Single + let payment_id_1 = + PaymentId::Address(TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap()); + let payment_id_2 = PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: vec![], + }; + + let decrypted_payment_id_1 = encrypt_decrypt_payment_id(payment_id_1.clone()); + let decrypted_payment_id_2 = encrypt_decrypt_payment_id(payment_id_2); + // Decrypted bytes are the same + assert_eq!(decrypted_payment_id_1.to_bytes(), decrypted_payment_id_2.to_bytes()); + // But the types are different + assert_eq!(payment_id_1, decrypted_payment_id_1); + assert_eq!(payment_id_1, decrypted_payment_id_2); + + // Dual + let payment_id_1 = PaymentId::Address( + TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + ); + let payment_id_2 = PaymentId::AddressAndData { + sender_address: TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + data: vec![], + }; + + let decrypted_payment_id_1 = encrypt_decrypt_payment_id(payment_id_1.clone()); + let decrypted_payment_id_2 = encrypt_decrypt_payment_id(payment_id_2); + // Decrypted bytes are the same + assert_eq!(decrypted_payment_id_1.to_bytes(), decrypted_payment_id_2.to_bytes()); + // But the types are different + assert_eq!(payment_id_1, decrypted_payment_id_1); + assert_eq!(payment_id_1, decrypted_payment_id_2); + } + + fn test_edge_case_address(payment_id: PaymentId) { + if let PaymentId::Address(address) = payment_id.clone() { + let payment_id_bytes = payment_id.to_bytes(); + // Manipulate the last byte to invalidate the address + let mut payment_id_2_bytes = payment_id_bytes.clone(); + let payment_id_2_bytes_len = payment_id_2_bytes.len(); + payment_id_2_bytes[payment_id_2_bytes_len - 1] += 1; + + // The original payment_id deserializes to 'PaymentId::Address' + let payment_id_from_bytes = PaymentId::from_bytes(&payment_id_bytes); + assert_eq!(payment_id_from_bytes, payment_id); + + // The manipulated payment_id deserializes to 'PaymentId::Open' + let payment_id_2_from_bytes = PaymentId::from_bytes(&payment_id_2_bytes); + assert_eq!(payment_id_2_from_bytes, PaymentId::Open(payment_id_2_bytes)); + + // All the bytes except for the last byte of the two payment ids should be the same + let address_vec = address.to_vec(); + let payment_id_2_vec = payment_id_2_from_bytes.get_data(); + assert_eq!( + address_vec[..address_vec.len() - 1], + payment_id_2_vec[..payment_id_2_vec.len() - 1] + ); + } else { + panic!("payment_id_1 should be an address"); + } + } + + #[test] + fn it_encrypts_and_decrypts_edge_cases() { + let payment_id = + PaymentId::Address(TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap()); + test_edge_case_address(payment_id); + + let payment_id = PaymentId::Address( + TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + ); + test_edge_case_address(payment_id); + } + #[test] fn it_encrypts_and_decrypts_correctly() { for payment_id in [ @@ -411,28 +666,58 @@ mod test { PaymentId::Address(TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap()), PaymentId::Open(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), PaymentId::Open(vec![1; 256]), - PaymentId::AddressAndData( - TariAddress::from_base58( + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58( "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", ) .unwrap(), - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - ), - PaymentId::AddressAndData( - TariAddress::from_base58( + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58( "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", ) .unwrap(), - vec![1; 189], - ), - PaymentId::AddressAndData( - TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - ), - PaymentId::AddressAndData( - TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), - vec![1; 189], - ), + data: vec![1; 189], + }, + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: vec![1; 189], + }, + // Single + amount + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(123456), + data: vec![], + }, + // Single + amount + data + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(123456), + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, + // Dual + amount + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + amount: MicroMinotari::from(123456), + data: vec![], + }, + // Dual + amount + data + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + amount: MicroMinotari::from(123456), + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, ] { for (value, mask) in [ (0, PrivateKey::default()), @@ -481,28 +766,58 @@ mod test { PaymentId::Address(TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap()), PaymentId::Open(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), PaymentId::Open(vec![1; 256]), - PaymentId::AddressAndData( - TariAddress::from_base58( + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58( "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", ) .unwrap(), - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - ), - PaymentId::AddressAndData( - TariAddress::from_base58( + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58( "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", ) .unwrap(), - vec![1; 189], - ), - PaymentId::AddressAndData( - TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - ), - PaymentId::AddressAndData( - TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), - vec![1; 189], - ), + data: vec![1; 189], + }, + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: vec![1; 189], + }, + // Single + amount + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(123456), + data: vec![], + }, + // Single + amount + data + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(123456), + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, + // Dual + amount + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + amount: MicroMinotari::from(123456), + data: vec![], + }, + // Dual + amount + data + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + amount: MicroMinotari::from(123456), + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, ] { for (value, mask) in [ (0, PrivateKey::default()), @@ -541,16 +856,90 @@ mod test { "address(f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk)" ); assert_eq!( - PaymentId::Open(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).to_string(), - "data(0102030405060708090a)" + PaymentId::Open(vec![0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64]).to_string(), + "data(Hello World)" ); assert_eq!( - PaymentId::AddressAndData( - TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), - vec![0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64] - ) + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: vec![0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64] + } + .to_string(), + "address(f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk), data(Hello World)" + ); + assert_eq!( + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(123456), + data: vec![0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64] + } .to_string(), - "address_and_data(f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk,48656c6c6f20576f726c64)" + "address(f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk), amount(123456 µT), data(Hello World)" + ); + assert_eq!( + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(1234), + data: { + let mut msg_and_id = "Hello World!!!".as_bytes().to_vec(); + let mut separator = " | ".as_bytes().to_vec(); + msg_and_id.append(&mut separator); + let mut id = "11-22-33".as_bytes().to_vec(); + msg_and_id.append(&mut id); + msg_and_id + } + } + .to_string(), + "address(f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk), amount(1234 µT), data(Hello World!!! | \ + 11-22-33)" + ); + } + + #[test] + fn it_gets_useable_data() { + let payment_id = PaymentId::Empty; + assert_eq!("", PaymentId::stringify_bytes(&payment_id.get_data())); + + let payment_id = PaymentId::U64(12345); + assert_eq!( + "12345", + u64::from_le_bytes(payment_id.get_data().try_into().unwrap()).to_string() + ); + + let payment_id = PaymentId::U256(U256::from_dec_str("123456789").unwrap()); + assert_eq!( + "123456789", + U256::from_little_endian(&payment_id.get_data()).to_string() + ); + + let payment_id = + PaymentId::Address(TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap()); + assert_eq!( + "f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk", + TariAddress::from_bytes(&payment_id.get_data()).unwrap().to_base58() + ); + + let payment_id = PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: "Hello World!!!".as_bytes().to_vec(), + }; + assert_eq!("Hello World!!!", PaymentId::stringify_bytes(&payment_id.get_data())); + + let payment_id = PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(1234), + data: { + let mut msg_and_id = "Hello World!!!".as_bytes().to_vec(); + let mut separator = " | ".as_bytes().to_vec(); + msg_and_id.append(&mut separator); + let mut id = "11-22-33".as_bytes().to_vec(); + msg_and_id.append(&mut id); + msg_and_id + }, + }; + assert_eq!( + "Hello World!!! | 11-22-33", + PaymentId::stringify_bytes(&payment_id.get_data()) ); } } diff --git a/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.proto b/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.proto index 3679e27711..18255a1993 100644 --- a/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.proto +++ b/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.proto @@ -40,6 +40,8 @@ message SingleRoundSenderData { uint32 kernel_version = 14; // the sender address string sender_address = 15; + // The payment id to receiver + bytes payment_id = 16; } message TransactionSenderMessage { diff --git a/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.rs b/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.rs index 1217f45e18..dcd39b71b7 100644 --- a/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.rs @@ -32,7 +32,10 @@ use tari_script::TariScript; use tari_utilities::ByteArray; use super::{protocol as proto, protocol::transaction_sender_message::Message as ProtoTransactionSenderMessage}; -use crate::transactions::transaction_protocol::sender::{SingleRoundSenderData, TransactionSenderMessage}; +use crate::transactions::{ + transaction_components::encrypted_data::PaymentId, + transaction_protocol::sender::{SingleRoundSenderData, TransactionSenderMessage}, +}; impl proto::TransactionSenderMessage { pub fn none() -> Self { @@ -103,6 +106,7 @@ impl TryFrom for SingleRoundSenderData { .map(TryInto::try_into) .ok_or_else(|| "Transaction metadata not provided".to_string())??; let message = data.message; + let payment_id = PaymentId::from_bytes(&data.payment_id); let ephemeral_public_nonce = PublicKey::from_canonical_bytes(&data.ephemeral_public_nonce).map_err(|err| err.to_string())?; let features = data @@ -128,6 +132,7 @@ impl TryFrom for SingleRoundSenderData { public_nonce, metadata, message, + payment_id, features, script: TariScript::from_bytes(&data.script).map_err(|err| err.to_string())?, sender_offset_public_key, @@ -156,6 +161,7 @@ impl TryFrom for proto::SingleRoundSenderData { public_nonce: sender_data.public_nonce.to_vec(), metadata: Some(sender_data.metadata.into()), message: sender_data.message, + payment_id: sender_data.payment_id.to_bytes(), features: Some(sender_data.features.into()), script: sender_data.script.to_bytes(), sender_offset_public_key: sender_data.sender_offset_public_key.to_vec(), diff --git a/base_layer/core/src/transactions/transaction_protocol/recipient.rs b/base_layer/core/src/transactions/transaction_protocol/recipient.rs index d5c4ea1d4d..53e6c80af2 100644 --- a/base_layer/core/src/transactions/transaction_protocol/recipient.rs +++ b/base_layer/core/src/transactions/transaction_protocol/recipient.rs @@ -178,7 +178,12 @@ mod test { key_manager::{create_memory_db_key_manager, TransactionKeyManagerInterface}, tari_amount::*, test_helpers::{TestParams, UtxoTestParams}, - transaction_components::{OutputFeatures, TransactionKernelVersion, TransactionOutputVersion}, + transaction_components::{ + encrypted_data::PaymentId, + OutputFeatures, + TransactionKernelVersion, + TransactionOutputVersion, + }, transaction_protocol::{ sender::{SingleRoundSenderData, TransactionSenderMessage}, TransactionMetadata, @@ -203,6 +208,7 @@ mod test { public_nonce: sender_test_params.public_nonce_key_pk, // any random key will do metadata: m.clone(), message: "".to_string(), + payment_id: PaymentId::Empty, features, script, sender_offset_public_key: sender_test_params.sender_offset_key_pk, diff --git a/base_layer/core/src/transactions/transaction_protocol/sender.rs b/base_layer/core/src/transactions/transaction_protocol/sender.rs index ef9ef19256..f342fc31b4 100644 --- a/base_layer/core/src/transactions/transaction_protocol/sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/sender.rs @@ -39,6 +39,7 @@ use crate::{ key_manager::{TariKeyId, TransactionKeyManagerInterface, TxoStage}, tari_amount::*, transaction_components::{ + encrypted_data::PaymentId, KernelBuilder, OutputFeatures, Transaction, @@ -102,6 +103,8 @@ pub(super) struct RawTransactionInfo { pub metadata: TransactionMetadata, /// A user message sent to the receiver pub text_message: String, + /// A user payment ID for the sender/receiver + pub payment_id: PaymentId, /// The senders address pub sender_address: TariAddress, } @@ -134,6 +137,8 @@ pub struct SingleRoundSenderData { pub metadata: TransactionMetadata, /// Plain text message to receiver pub message: String, + /// A user payment ID for the sender/receiver + pub payment_id: PaymentId, /// The output's features pub features: OutputFeatures, /// Script @@ -444,6 +449,7 @@ impl SenderTransactionProtocol { public_excess, metadata: info.metadata.clone(), message: info.text_message.clone(), + payment_id: info.payment_id.clone(), features: recipient_output_features, script: recipient_script, sender_offset_public_key, @@ -1130,6 +1136,7 @@ mod test { Covenant::default(), 0.into(), MicroMinotari(1200) - fee - MicroMinotari(10), + TariAddress::default(), ) .await .unwrap() @@ -1255,6 +1262,7 @@ mod test { Covenant::default(), 0.into(), MicroMinotari(5000), + TariAddress::default(), ) .await .unwrap(); @@ -1367,6 +1375,7 @@ mod test { Covenant::default(), 0.into(), MicroMinotari(5000), + TariAddress::default(), ) .await .unwrap(); @@ -1467,6 +1476,7 @@ mod test { Covenant::default(), 0.into(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -1507,6 +1517,7 @@ mod test { Covenant::default(), 0.into(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -1552,6 +1563,7 @@ mod test { Covenant::default(), 0.into(), MicroMinotari(5000), + TariAddress::default(), ) .await .unwrap(); diff --git a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs index 4b45fa3b68..8e1922a6c2 100644 --- a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs +++ b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs @@ -286,6 +286,7 @@ mod test { public_nonce: pub_rs.clone(), metadata: m.clone(), message: "".to_string(), + payment_id: PaymentId::Empty, features: OutputFeatures::default(), script: script.clone(), sender_offset_public_key, diff --git a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs index ff0a54f002..5d28db5007 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -79,6 +79,7 @@ pub(super) struct RecipientDetails { pub recipient_covenant: Covenant, pub recipient_minimum_value_promise: MicroMinotari, pub recipient_ephemeral_public_key_nonce: TariKeyId, + pub recipient_address: TariAddress, } /// The SenderTransactionProtocolBuilder is a Builder that helps set up the initial state for the Sender party of a new @@ -98,6 +99,7 @@ pub struct SenderTransactionInitializer { change: Option, recipient: Option, recipient_text_message: Option, + payment_id: Option, prevent_fee_gt_amount: bool, tx_id: Option, kernel_features: KernelFeatures, @@ -130,6 +132,7 @@ where KM: TransactionKeyManagerInterface sender_custom_outputs: Vec::new(), change: None, recipient_text_message: None, + payment_id: None, prevent_fee_gt_amount: true, recipient: None, kernel_features: KernelFeatures::empty(), @@ -162,6 +165,7 @@ where KM: TransactionKeyManagerInterface recipient_covenant: Covenant, recipient_minimum_value_promise: MicroMinotari, amount: MicroMinotari, + recipient_address: TariAddress, ) -> Result<&mut Self, KeyManagerServiceError> { let recipient_ephemeral_public_key_nonce = self .key_manager @@ -179,6 +183,7 @@ where KM: TransactionKeyManagerInterface recipient_minimum_value_promise, recipient_ephemeral_public_key_nonce: recipient_ephemeral_public_key_nonce.key_id, amount, + recipient_address, }; self.recipient = Some(recipient_details); Ok(self) @@ -253,6 +258,54 @@ where KM: TransactionKeyManagerInterface self } + /// Provide a text message for receiver + pub fn with_payment_id(&mut self, payment_id: PaymentId) -> &mut Self { + self.payment_id = Some(payment_id); + self + } + + /// Append message and payment id as bytes + pub fn append_message_and_id(&self) -> Option> { + if let Some(mut message) = self.recipient_text_message.clone() { + if let Some(payment_id) = self.payment_id.clone() { + message.push_str(" | "); + let mut msg_and_id_bytes = message.into_bytes(); + match payment_id { + PaymentId::Empty => { + let mut empty = "*no payment id*".to_string().as_bytes().to_vec(); + msg_and_id_bytes.append(&mut empty); + }, + PaymentId::U64(val) => { + let mut data = val.to_string().as_bytes().to_vec(); + msg_and_id_bytes.append(&mut data); + }, + PaymentId::U256(val) => { + let mut data = val.to_string().as_bytes().to_vec(); + msg_and_id_bytes.append(&mut data); + }, + PaymentId::Address(val) => { + let mut data = val.to_base58().as_bytes().to_vec(); + msg_and_id_bytes.append(&mut data); + }, + PaymentId::Open(mut val) => { + msg_and_id_bytes.append(&mut val); + }, + PaymentId::AddressAndData { data: mut id, .. } => { + msg_and_id_bytes.append(&mut id); + }, + PaymentId::ChangeInfo { + data: mut msg_and_id, .. + } => { + msg_and_id_bytes.append(&mut msg_and_id); + }, + } + return Some(msg_and_id_bytes); + } + return Some(message.into_bytes()); + } + None + } + /// This will select the desired kernel features to be signed by the receiver pub fn with_kernel_features(&mut self, features: KernelFeatures) -> &mut Self { self.kernel_features = features; @@ -402,7 +455,19 @@ where KM: TransactionKeyManagerInterface .own_address .clone(); - let payment_id = PaymentId::Address(address); + let payment_id = if let Some(recipient) = self.recipient.clone() { + PaymentId::ChangeInfo { + recipient_address: recipient.recipient_address, + amount: recipient.amount, + data: self.append_message_and_id().unwrap_or_default(), + } + } else { + PaymentId::ChangeInfo { + recipient_address: address, + amount: total_to_self, + data: self.append_message_and_id().unwrap_or_default(), + } + }; let encrypted_data = self .key_manager @@ -590,6 +655,7 @@ where KM: TransactionKeyManagerInterface inputs: self.inputs, outputs: self.sender_custom_outputs, text_message: self.recipient_text_message.unwrap_or_default(), + payment_id: self.payment_id.unwrap_or_default(), sender_address: self.sender_address.clone(), }; @@ -878,6 +944,7 @@ mod test { Default::default(), 0.into(), MicroMinotari(500), + TariAddress::default(), ) .await .unwrap(); @@ -927,6 +994,7 @@ mod test { Default::default(), 0.into(), MicroMinotari::zero(), + TariAddress::default(), ) .await .unwrap(); @@ -994,6 +1062,7 @@ mod test { Default::default(), 0.into(), MicroMinotari(2500), + TariAddress::default(), ) .await .unwrap(); diff --git a/base_layer/wallet/migrations/2024-12-05-110700-payment_id/down.sql b/base_layer/wallet/migrations/2024-12-05-110700-payment_id/down.sql new file mode 100644 index 0000000000..291a97c5ce --- /dev/null +++ b/base_layer/wallet/migrations/2024-12-05-110700-payment_id/down.sql @@ -0,0 +1 @@ +-- This file should undo anything in `up.sql` \ No newline at end of file diff --git a/base_layer/wallet/migrations/2024-12-05-110700-payment_id/up.sql b/base_layer/wallet/migrations/2024-12-05-110700-payment_id/up.sql new file mode 100644 index 0000000000..29f74d6bad --- /dev/null +++ b/base_layer/wallet/migrations/2024-12-05-110700-payment_id/up.sql @@ -0,0 +1,6 @@ + +ALTER TABLE inbound_transactions + ADD payment_id BLOB NULL; + +ALTER TABLE outbound_transactions + ADD payment_id BLOB NULL; diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index 7f32a2f147..da628bcdea 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -31,7 +31,14 @@ use tari_core::{ covenants::Covenant, transactions::{ tari_amount::MicroMinotari, - transaction_components::{OutputFeatures, Transaction, TransactionOutput, WalletOutput, WalletOutputBuilder}, + transaction_components::{ + encrypted_data::PaymentId, + OutputFeatures, + Transaction, + TransactionOutput, + WalletOutput, + WalletOutputBuilder, + }, transaction_protocol::{sender::TransactionSenderMessage, TransactionMetadata}, ReceiverTransactionProtocol, SenderTransactionProtocol, @@ -73,6 +80,8 @@ pub enum OutputManagerRequest { recipient_address: TariAddress, original_maturity: u64, use_output: UseOutput, + message: String, + payment_id: PaymentId, }, SpendBackupPreMineUtxo { tx_id: TxId, @@ -92,6 +101,8 @@ pub enum OutputManagerRequest { script: TariScript, covenant: Covenant, minimum_value_promise: MicroMinotari, + recipient_address: TariAddress, + payment_id: PaymentId, }, CreatePayToSelfTransaction { tx_id: TxId, @@ -105,6 +116,8 @@ pub enum OutputManagerRequest { outputs: Vec, fee_per_gram: MicroMinotari, selection_criteria: UtxoSelectionCriteria, + message: String, + payment_id: PaymentId, }, CancelTransaction(TxId), GetSpentOutputs, @@ -119,6 +132,7 @@ pub enum OutputManagerRequest { ScrapeWallet { tx_id: TxId, fee_per_gram: MicroMinotari, + recipient_address: TariAddress, }, CreateCoinJoin { commitments: Vec, @@ -167,8 +181,16 @@ impl fmt::Display for OutputManagerRequest { v.metadata_signature.u_y().to_hex(), v.metadata_signature.u_a().to_hex(), ), - ScrapeWallet { tx_id, fee_per_gram } => { - write!(f, "ScrapeWallet (tx_id: {}, fee_per_gram: {})", tx_id, fee_per_gram) + ScrapeWallet { + tx_id, + fee_per_gram, + recipient_address, + } => { + write!( + f, + "ScrapeWallet (tx_id: {}, fee_per_gram: {}, recipient_address {})", + tx_id, fee_per_gram, recipient_address + ) }, EncumberAggregateUtxo { tx_id, @@ -509,6 +531,8 @@ impl OutputManagerHandle { script: TariScript, covenant: Covenant, minimum_value_promise: MicroMinotari, + recipient_address: TariAddress, + payment_id: PaymentId, ) -> Result { match self .handle @@ -523,6 +547,8 @@ impl OutputManagerHandle { script, covenant, minimum_value_promise, + recipient_address, + payment_id, }) .await?? { @@ -535,10 +561,15 @@ impl OutputManagerHandle { &mut self, tx_id: TxId, fee_per_gram: MicroMinotari, + recipient_address: TariAddress, ) -> Result { match self .handle - .call(OutputManagerRequest::ScrapeWallet { tx_id, fee_per_gram }) + .call(OutputManagerRequest::ScrapeWallet { + tx_id, + fee_per_gram, + recipient_address, + }) .await?? { OutputManagerResponse::TransactionToSend(stp) => Ok(stp), @@ -795,6 +826,8 @@ impl OutputManagerHandle { outputs: Vec, fee_per_gram: MicroMinotari, input_selection: UtxoSelectionCriteria, + message: String, + payment_id: PaymentId, ) -> Result<(TxId, Transaction), OutputManagerError> { match self .handle @@ -802,6 +835,8 @@ impl OutputManagerHandle { outputs, fee_per_gram, selection_criteria: input_selection, + message, + payment_id, }) .await?? { @@ -824,6 +859,8 @@ impl OutputManagerHandle { recipient_address: TariAddress, original_maturity: u64, use_output: UseOutput, + message: String, + payment_id: PaymentId, ) -> Result< ( Transaction, @@ -850,6 +887,8 @@ impl OutputManagerHandle { recipient_address, original_maturity, use_output, + message, + payment_id, }) .await?? { diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 3b14cb59a1..6480167fcc 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -258,6 +258,8 @@ where recipient_address, original_maturity, use_output, + message, + payment_id, } => self .encumber_aggregate_utxo( tx_id, @@ -269,7 +271,8 @@ where metadata_ephemeral_public_key_shares, dh_shared_secret_shares, recipient_address, - PaymentId::Empty, + message, + payment_id, original_maturity, RangeProofType::BulletProofPlus, 0.into(), @@ -290,7 +293,7 @@ where output_hash, expected_commitment, recipient_address, - PaymentId::Empty, + PaymentId::Open(output_hash.to_vec()), 0, RangeProofType::BulletProofPlus, 0.into(), @@ -327,6 +330,8 @@ where script, covenant, minimum_value_promise, + recipient_address, + payment_id, } => self .prepare_transaction_to_send( tx_id, @@ -339,6 +344,8 @@ where script, covenant, minimum_value_promise, + recipient_address, + payment_id, ) .await .map(OutputManagerResponse::TransactionToSend), @@ -400,8 +407,12 @@ where .await?, )) }, - OutputManagerRequest::ScrapeWallet { tx_id, fee_per_gram } => self - .scrape_wallet(tx_id, fee_per_gram) + OutputManagerRequest::ScrapeWallet { + tx_id, + fee_per_gram, + recipient_address, + } => self + .scrape_wallet(tx_id, fee_per_gram, recipient_address) .await .map(OutputManagerResponse::TransactionToSend), @@ -472,9 +483,17 @@ where outputs, fee_per_gram, selection_criteria, + message, + payment_id, } => { let (tx_id, transaction) = self - .create_pay_to_self_containing_outputs(outputs, selection_criteria, fee_per_gram) + .create_pay_to_self_containing_outputs( + outputs, + selection_criteria, + fee_per_gram, + message, + payment_id, + ) .await?; Ok(OutputManagerResponse::CreatePayToSelfWithOutputs { transaction: Box::new(transaction), @@ -965,6 +984,8 @@ where recipient_script: TariScript, recipient_covenant: Covenant, recipient_minimum_value_promise: MicroMinotari, + recipient_address: TariAddress, + payment_id: PaymentId, ) -> Result { debug!( target: LOG_TARGET, @@ -1011,10 +1032,12 @@ where recipient_covenant, recipient_minimum_value_promise, amount, + recipient_address, ) .await? .with_sender_address(self.resources.interactive_tari_address.clone()) .with_message(message) + .with_payment_id(payment_id) .with_prevent_fee_gt_amount(self.resources.config.prevent_fee_gt_amount) .with_lock_height(tx_meta.lock_height) .with_kernel_features(tx_meta.kernel_features) @@ -1087,6 +1110,8 @@ where outputs: Vec, selection_criteria: UtxoSelectionCriteria, fee_per_gram: MicroMinotari, + message: String, + payment_id: PaymentId, ) -> Result<(TxId, Transaction), OutputManagerError> { let total_value = outputs.iter().map(|o| o.value()).sum(); let nop_script = script![Nop]?; @@ -1131,7 +1156,9 @@ where .with_lock_height(0) .with_fee_per_gram(fee_per_gram) .with_prevent_fee_gt_amount(false) - .with_kernel_features(KernelFeatures::empty()); + .with_kernel_features(KernelFeatures::empty()) + .with_message(message) + .with_payment_id(payment_id); for uo in input_selection.iter() { builder.with_input(uo.wallet_output.clone()).await?; @@ -1249,7 +1276,8 @@ where metadata_ephemeral_public_key_shares: Vec, dh_shared_secret_shares: Vec, recipient_address: TariAddress, - payment_id: PaymentId, + tx_message: String, + tx_payment_id: PaymentId, original_maturity: u64, range_proof_type: RangeProofType, minimum_value_promise: MicroMinotari, @@ -1299,7 +1327,7 @@ where let mut aggregated_script_public_key_shares = PublicKey::default(); trace!(target: LOG_TARGET, "encumber_aggregate_utxo: created deterministic encryption key"); // Decrypt the output secrets and create a new input as WalletOutput (unblinded) - let input = if let Ok((amount, commitment_mask, payment_id)) = + let (input, payment_id) = if let Ok((amount, commitment_mask, payment_id)) = EncryptedData::decrypt_data(&encryption_private_key, &output.commitment, &output.encrypted_data) { if output.verify_mask(&self.resources.factories.range_proof, &commitment_mask, amount.as_u64())? { @@ -1335,21 +1363,24 @@ where ))); } let commitment_mask_key_id = self.resources.key_manager.import_key(commitment_mask).await?; - WalletOutput::new_with_rangeproof( - output.version, - amount, - commitment_mask_key_id, - output.features, - output.script, - ExecutionStack::new(script_signatures), - script_key.key_id.clone(), // Only of the master wallet - output.sender_offset_public_key, - output.metadata_signature, - 0, - output.covenant, - output.encrypted_data, - output.minimum_value_promise, - output.proof, + ( + WalletOutput::new_with_rangeproof( + output.version, + amount, + commitment_mask_key_id, + output.features, + output.script, + ExecutionStack::new(script_signatures), + script_key.key_id.clone(), // Only of the master wallet + output.sender_offset_public_key, + output.metadata_signature, + 0, + output.covenant, + output.encrypted_data, + output.minimum_value_promise, + output.proof, + payment_id.clone(), + ), payment_id, ) } else { @@ -1408,6 +1439,7 @@ where Covenant::default(), minimum_value_promise, amount, + recipient_address.clone(), ) .await? .with_change_data( @@ -1417,7 +1449,9 @@ where TariKeyId::default(), Covenant::default(), self.resources.interactive_tari_address.clone(), - ); + ) + .with_message(tx_message.clone()) + .with_payment_id(payment_id); let mut stp = builder .build() .await @@ -1519,7 +1553,7 @@ where .encrypt_data_for_recovery( &self.resources.key_manager, Some(&encryption_key_id), - payment_id.clone(), + tx_payment_id.clone(), ) .await? .with_input_data(ExecutionStack::default()) // Just a placeholder in the wallet @@ -1740,6 +1774,7 @@ where Covenant::default(), minimum_value_promise, amount, + recipient_address.clone(), ) .await? .with_change_data( @@ -1749,7 +1784,9 @@ where TariKeyId::default(), Covenant::default(), self.resources.one_sided_tari_address.clone(), - ); + ) + .with_message("Spend backup pre-mine UTXO".to_string()) + .with_payment_id(payment_id.clone()); let mut stp = builder .build() .await @@ -1818,7 +1855,10 @@ where .await?; let script = push_pubkey_script(&script_spending_key); let payment_id = match payment_id { - PaymentId::Open(v) => PaymentId::AddressAndData(self.resources.interactive_tari_address.clone(), v), + PaymentId::Open(v) => PaymentId::AddressAndData { + sender_address: self.resources.interactive_tari_address.clone(), + data: v, + }, PaymentId::Empty => PaymentId::Address(self.resources.one_sided_tari_address.clone()), _ => payment_id, }; @@ -1944,14 +1984,16 @@ where .key_manager .get_next_commitment_mask_and_script_key() .await?; - builder.with_change_data( - script!(PushPubKey(Box::new(change_script_public_key.pub_key.clone())))?, - ExecutionStack::default(), - change_script_public_key.key_id.clone(), - change_commitment_mask_key_id.key_id, - Covenant::default(), - self.resources.interactive_tari_address.clone(), - ); + builder + .with_change_data( + script!(PushPubKey(Box::new(change_script_public_key.pub_key.clone())))?, + ExecutionStack::default(), + change_script_public_key.key_id.clone(), + change_commitment_mask_key_id.key_id, + Covenant::default(), + self.resources.interactive_tari_address.clone(), + ) + .with_message("Pay to self transaction".to_string()); let mut stp = builder .build() @@ -2381,6 +2423,10 @@ where self.resources.key_manager.clone(), ); tx_builder + .with_message(format!( + "Coin split transaction, {} into {} outputs", + accumulated_amount, number_of_splits + )) .with_lock_height(0) .with_fee_per_gram(fee_per_gram) .with_kernel_features(KernelFeatures::empty()); @@ -2540,6 +2586,10 @@ where self.resources.key_manager.clone(), ); tx_builder + .with_message(format!( + "Coin split transaction, {} into {} outputs", + accumulated_amount, number_of_splits + )) .with_lock_height(0) .with_fee_per_gram(fee_per_gram) .with_kernel_features(KernelFeatures::empty()); @@ -2772,6 +2822,11 @@ where self.resources.key_manager.clone(), ); tx_builder + .with_message(format!( + "Coin join transaction, {} outputs{} into", + src_outputs.len(), + accumulated_amount + )) .with_lock_height(0) .with_fee_per_gram(fee_per_gram) .with_kernel_features(KernelFeatures::empty()); @@ -2831,6 +2886,7 @@ where &mut self, tx_id: TxId, fee_per_gram: MicroMinotari, + recipient_address: TariAddress, ) -> Result { let default_features_and_scripts_size = self .default_features_and_scripts_size() @@ -2862,6 +2918,7 @@ where Default::default(), MicroMinotari::zero(), accumulated_amount, + recipient_address, ) .await? .with_sender_address(self.resources.interactive_tari_address.clone()) @@ -2969,7 +3026,7 @@ where payment_id, ); - let message = "SHA-XTR atomic swap".to_string(); + let tx_message = "SHA-XTR atomic swap".to_string(); // Create builder with no recipients (other than ourselves) let mut builder = SenderTransactionProtocol::builder( @@ -2979,7 +3036,7 @@ where builder .with_lock_height(0) .with_fee_per_gram(fee_per_gram) - .with_message(message) + .with_message(tx_message) .with_kernel_features(KernelFeatures::empty()) .with_prevent_fee_gt_amount(self.resources.config.prevent_fee_gt_amount) .with_input(rewound_output) 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 0fff2dc300..449b95cac8 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 @@ -676,15 +676,7 @@ impl OutputSql { let encrypted_data = EncryptedData::from_bytes(&self.encrypted_data)?; let payment_id = match self.payment_id { - Some(bytes) => PaymentId::from_bytes(&bytes).map_err(|_| { - error!( - target: LOG_TARGET, - "Could not create payment id from stored bytes" - ); - OutputManagerStorageError::ConversionError { - reason: "payment id could not be converted from bytes".to_string(), - } - })?, + Some(bytes) => PaymentId::from_bytes(&bytes), None => PaymentId::Empty, }; diff --git a/base_layer/wallet/src/schema.rs b/base_layer/wallet/src/schema.rs index 3421bcc2b0..aae0d58aa3 100644 --- a/base_layer/wallet/src/schema.rs +++ b/base_layer/wallet/src/schema.rs @@ -53,6 +53,7 @@ diesel::table! { direct_send_success -> Integer, send_count -> Integer, last_send_timestamp -> Nullable, + payment_id -> Nullable, } } @@ -79,6 +80,7 @@ diesel::table! { direct_send_success -> Integer, send_count -> Integer, last_send_timestamp -> Nullable, + payment_id -> Nullable, } } diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index c270e18435..f10b7fb109 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -94,12 +94,14 @@ pub enum TransactionServiceRequest { output_features: Box, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, }, BurnTari { amount: MicroMinotari, selection_criteria: UtxoSelectionCriteria, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, claim_public_key: Option, }, EncumberAggregateUtxo { @@ -113,12 +115,16 @@ pub enum TransactionServiceRequest { recipient_address: TariAddress, original_maturity: u64, use_output: UseOutput, + message: String, + payment_id: PaymentId, }, SpendBackupPreMineUtxo { fee_per_gram: MicroMinotari, output_hash: HashOutput, expected_commitment: PedersenCommitment, recipient_address: TariAddress, + message: String, + payment_id: PaymentId, }, FetchUnspentOutputs { output_hashes: Vec, @@ -170,7 +176,14 @@ pub enum TransactionServiceRequest { destination: TariAddress, fee_per_gram: MicroMinotari, }, - SendShaAtomicSwapTransaction(TariAddress, MicroMinotari, UtxoSelectionCriteria, MicroMinotari, String), + SendShaAtomicSwapTransaction( + TariAddress, + MicroMinotari, + UtxoSelectionCriteria, + MicroMinotari, + String, + PaymentId, + ), CancelTransaction(TxId), ImportUtxoWithStatus { amount: MicroMinotari, @@ -183,7 +196,7 @@ pub enum TransactionServiceRequest { scanned_output: TransactionOutput, payment_id: PaymentId, }, - SubmitTransactionToSelf(TxId, Transaction, MicroMinotari, MicroMinotari, String), + SubmitTransactionToSelf(TxId, Transaction, MicroMinotari, MicroMinotari, String, PaymentId), SetLowPowerMode, SetNormalPowerMode, RestartTransactionProtocols, @@ -224,25 +237,35 @@ impl fmt::Display for TransactionServiceRequest { destination, amount, message, + payment_id, .. } => write!( f, - "SendTransaction (amount: {}, to: {}, message: {})", - amount, destination, message + "SendTransaction (amount: {}, to: {}, message: {}, payment_id: {})", + amount, destination, message, payment_id ), - Self::BurnTari { amount, message, .. } => write!(f, "Burning Tari ({}, {})", amount, message), + Self::BurnTari { + amount, + message, + payment_id, + .. + } => write!(f, "Burning Tari ({}, {}, {})", amount, message, payment_id), Self::SpendBackupPreMineUtxo { fee_per_gram, output_hash, expected_commitment, recipient_address, + message, + payment_id, } => f.write_str(&format!( "Spending backup pre-mine utxo with: fee_per_gram = {}, output_hash = {}, commitment = {}, recipient \ - = {}", + = {}, message = {}, payment_id = {}", fee_per_gram, output_hash, expected_commitment.to_hex(), recipient_address, + message, + payment_id, )), Self::EncumberAggregateUtxo { fee_per_gram, @@ -255,6 +278,8 @@ impl fmt::Display for TransactionServiceRequest { recipient_address, original_maturity, use_output, + message, + payment_id, .. } => { let output_hash = match use_output { @@ -265,7 +290,7 @@ impl fmt::Display for TransactionServiceRequest { "Creating encumber n-of-m utxo with: fee_per_gram = {}, output_hash = {}, commitment = {}, \ script_input_shares = {:?}, script_signature_shares = {:?}, sender_offset_public_key_shares = \ {:?}, metadata_ephemeral_public_key_shares = {:?}, dh_shared_secret_shares = {:?}, \ - recipient_address = {}, original_maturity: {}", + recipient_address = {}, original_maturity: {}, message: {}, payment_id: {}", fee_per_gram, output_hash, expected_commitment.to_hex(), @@ -296,6 +321,8 @@ impl fmt::Display for TransactionServiceRequest { .collect::>(), recipient_address, original_maturity, + message, + payment_id, )) }, Self::FetchUnspentOutputs { output_hashes } => { @@ -345,7 +372,7 @@ impl fmt::Display for TransactionServiceRequest { "SendOneSidedToStealthAddressTransaction (to {}, {}, {})", destination, amount, message ), - Self::SendShaAtomicSwapTransaction(k, _, v, _, msg) => { + Self::SendShaAtomicSwapTransaction(k, _, v, _, msg, _) => { write!(f, "SendShaAtomicSwapTransaction (to {}, {}, {})", k, v, msg) }, Self::CancelTransaction(t) => write!(f, "CancelTransaction ({})", t), @@ -364,7 +391,7 @@ impl fmt::Display for TransactionServiceRequest { {:?}, mined at: {:?}", amount, source_address, message, import_status, tx_id, current_height, mined_timestamp ), - Self::SubmitTransactionToSelf(tx_id, _, _, _, _) => write!(f, "SubmitTransaction ({})", tx_id), + Self::SubmitTransactionToSelf(tx_id, _, _, _, _, _) => write!(f, "SubmitTransaction ({})", tx_id), Self::SetLowPowerMode => write!(f, "SetLowPowerMode "), Self::SetNormalPowerMode => write!(f, "SetNormalPowerMode"), Self::RestartTransactionProtocols => write!(f, "RestartTransactionProtocols"), @@ -609,6 +636,7 @@ impl TransactionServiceHandle { output_features: OutputFeatures, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result { match self .handle @@ -619,6 +647,7 @@ impl TransactionServiceHandle { output_features: Box::new(output_features), fee_per_gram, message, + payment_id, }) .await?? { @@ -738,6 +767,7 @@ impl TransactionServiceHandle { selection_criteria: UtxoSelectionCriteria, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, claim_public_key: Option, ) -> Result<(TxId, BurntProof), TransactionServiceError> { match self @@ -747,6 +777,7 @@ impl TransactionServiceHandle { selection_criteria, fee_per_gram, message, + payment_id, claim_public_key, }) .await?? @@ -769,6 +800,8 @@ impl TransactionServiceHandle { recipient_address: TariAddress, original_maturity: u64, use_output: UseOutput, + message: String, + payment_id: PaymentId, ) -> Result<(TxId, Transaction, PublicKey, PublicKey, PublicKey, PublicKey), TransactionServiceError> { match self .handle @@ -783,6 +816,8 @@ impl TransactionServiceHandle { recipient_address, original_maturity, use_output, + message, + payment_id, }) .await?? { @@ -811,6 +846,8 @@ impl TransactionServiceHandle { output_hash: HashOutput, expected_commitment: PedersenCommitment, recipient_address: TariAddress, + message: String, + payment_id: PaymentId, ) -> Result { match self .handle @@ -819,6 +856,8 @@ impl TransactionServiceHandle { output_hash, expected_commitment, recipient_address, + message, + payment_id, }) .await?? { @@ -1057,12 +1096,13 @@ impl TransactionServiceHandle { tx: Transaction, amount: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result<(), TransactionServiceError> { let fee = tx.body.get_total_fee()?; match self .handle .call(TransactionServiceRequest::SubmitTransactionToSelf( - tx_id, tx, fee, amount, message, + tx_id, tx, fee, amount, message, payment_id, )) .await?? { @@ -1171,6 +1211,7 @@ impl TransactionServiceHandle { selection_criteria, fee_per_gram, message, + PaymentId::Empty, )) .await?? { diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs index 37b03fa6fb..8acffd1a29 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs @@ -156,6 +156,7 @@ where rtp, TransactionStatus::Pending, data.message.clone(), + data.payment_id.clone(), Utc::now().naive_utc(), ); @@ -447,7 +448,7 @@ where TransactionDirection::Inbound, None, None, - None, + inbound_tx.payment_id.clone(), ) .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs index e6c0a95f23..9af47cdda0 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs @@ -39,7 +39,7 @@ use tari_core::{ transactions::{ key_manager::TransactionKeyManagerInterface, tari_amount::MicroMinotari, - transaction_components::OutputFeatures, + transaction_components::{encrypted_data::PaymentId, OutputFeatures}, transaction_protocol::{ proto::protocol as proto, recipient::RecipientSignedMessage, @@ -93,6 +93,7 @@ pub struct TransactionSendProtocol>>, stage: TransactionSendProtocolStage, resources: TransactionServiceResources, @@ -118,6 +119,7 @@ where amount: MicroMinotari, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, tx_meta: TransactionMetadata, service_request_reply_channel: Option< oneshot::Sender>, @@ -134,6 +136,7 @@ where amount, fee_per_gram, message, + payment_id, service_request_reply_channel, stage, tx_meta, @@ -228,6 +231,8 @@ where TariScript::default(), Covenant::default(), MicroMinotari::zero(), + self.dest_address.clone(), + self.payment_id.clone(), ) .await { @@ -290,6 +295,7 @@ where sender_protocol.clone(), TransactionStatus::Pending, // This does not matter for the check self.message.clone(), + self.payment_id.clone(), Utc::now().naive_utc(), true, // This does not matter for the check ); @@ -344,6 +350,7 @@ where sender_protocol.clone(), initial_send.transaction_status.clone(), self.message.clone(), + self.payment_id.clone(), Utc::now().naive_utc(), initial_send.direct_send_result, ); @@ -604,7 +611,7 @@ where TransactionDirection::Outbound, None, None, - None, + outbound_tx.payment_id.clone(), ) .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 9988c774ff..017b61c230 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -614,6 +614,7 @@ where output_features, fee_per_gram, message, + payment_id, } => { let rp = reply_channel.take().expect("Cannot be missing"); self.send_transaction( @@ -623,6 +624,7 @@ where *output_features, fee_per_gram, message, + payment_id, TransactionMetadata::default(), send_transaction_join_handles, transaction_broadcast_join_handles, @@ -686,6 +688,7 @@ where selection_criteria, fee_per_gram, message, + payment_id, claim_public_key, } => self .burn_tari( @@ -693,6 +696,7 @@ where selection_criteria, fee_per_gram, message, + payment_id, claim_public_key, transaction_broadcast_join_handles, ) @@ -712,6 +716,8 @@ where recipient_address, original_maturity, use_output, + message, + payment_id, } => self .encumber_aggregate_tx( fee_per_gram, @@ -724,6 +730,8 @@ where recipient_address, original_maturity, use_output, + message, + payment_id, ) .await .map( @@ -750,8 +758,17 @@ where output_hash, expected_commitment, recipient_address, + message, + payment_id, } => self - .spend_backup_pre_mine_utxo(fee_per_gram, output_hash, expected_commitment, recipient_address) + .spend_backup_pre_mine_utxo( + fee_per_gram, + output_hash, + expected_commitment, + recipient_address, + message, + payment_id, + ) .await .map(TransactionServiceResponse::TransactionSent), TransactionServiceRequest::FetchUnspentOutputs { output_hashes } => { @@ -835,6 +852,7 @@ where selection_criteria, fee_per_gram, message, + payment_id, ) => Ok(TransactionServiceResponse::ShaAtomicSwapTransactionSent( self.send_sha_atomic_swap_transaction( destination, @@ -842,6 +860,7 @@ where selection_criteria, fee_per_gram, message, + payment_id, transaction_broadcast_join_handles, ) .await?, @@ -926,8 +945,16 @@ where ) .await .map(TransactionServiceResponse::UtxoImported), - TransactionServiceRequest::SubmitTransactionToSelf(tx_id, tx, fee, amount, message) => self - .submit_transaction_to_self(transaction_broadcast_join_handles, tx_id, tx, fee, amount, message) + TransactionServiceRequest::SubmitTransactionToSelf(tx_id, tx, fee, amount, message, payment_id) => self + .submit_transaction_to_self( + transaction_broadcast_join_handles, + tx_id, + tx, + fee, + amount, + message, + payment_id, + ) .await .map(|_| TransactionServiceResponse::TransactionSubmitted), TransactionServiceRequest::SetLowPowerMode => { @@ -1077,6 +1104,7 @@ where output_features: OutputFeatures, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, tx_meta: TransactionMetadata, join_handles: &mut FuturesUnordered< JoinHandle>>, @@ -1135,7 +1163,7 @@ where TransactionDirection::Inbound, None, None, - None, + payment_id, )?, ) .await?; @@ -1164,6 +1192,7 @@ where amount, fee_per_gram, message, + payment_id, tx_meta, Some(reply_channel), TransactionSendProtocolStage::Initial, @@ -1217,6 +1246,8 @@ where recipient_address: TariAddress, original_maturity: u64, use_output: UseOutput, + message: String, + payment_id: PaymentId, ) -> Result<(TxId, Transaction, PublicKey, PublicKey, PublicKey, PublicKey), TransactionServiceError> { let tx_id = TxId::new_random(); @@ -1235,6 +1266,8 @@ where recipient_address.clone(), original_maturity, use_output, + message.clone(), + payment_id.clone(), ) .await { @@ -1255,12 +1288,16 @@ where fee, transaction.clone(), TransactionStatus::Pending, - "claimed n-of-m utxo".to_string(), + if message.is_empty() { + "claimed n-of-m utxo".to_string() + } else { + message.clone() + }, Utc::now().naive_utc(), TransactionDirection::Outbound, None, None, - None, + payment_id.clone(), ) .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; self.db.insert_completed_transaction(tx_id, completed_tx)?; @@ -1283,6 +1320,8 @@ where output_hash: HashOutput, expected_commitment: PedersenCommitment, recipient_address: TariAddress, + message: String, + payment_id: PaymentId, ) -> Result { let tx_id = TxId::new_random(); @@ -1307,12 +1346,16 @@ where fee, transaction.clone(), TransactionStatus::Pending, - "claimed n-of-m utxo".to_string(), + if message.is_empty() { + "spend backup pre-mine utxo".to_string() + } else { + message + }, Utc::now().naive_utc(), TransactionDirection::Outbound, None, None, - None, + payment_id, ) .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; self.db.insert_completed_transaction(tx_id, completed_tx)?; @@ -1430,6 +1473,7 @@ where selection_criteria: UtxoSelectionCriteria, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, transaction_broadcast_join_handles: &mut FuturesUnordered< JoinHandle>>, >, @@ -1474,6 +1518,8 @@ where script.clone(), covenant.clone(), minimum_value_promise, + destination.clone(), + PaymentId::Empty, ) .await?; @@ -1643,7 +1689,7 @@ where TransactionDirection::Outbound, None, None, - None, + payment_id, )?, ) .await?; @@ -1672,8 +1718,14 @@ where ) -> Result { let tx_id = TxId::new_random(); let payment_id = match payment_id { - PaymentId::Open(v) => PaymentId::AddressAndData(self.resources.interactive_tari_address.clone(), v), - PaymentId::Empty => PaymentId::Address(self.resources.interactive_tari_address.clone()), + PaymentId::Open(v) => PaymentId::AddressAndData { + sender_address: self.resources.interactive_tari_address.clone(), + data: v, + }, + PaymentId::Empty => PaymentId::AddressAndData { + sender_address: self.resources.interactive_tari_address.clone(), + data: vec![], + }, _ => payment_id, }; self.verify_send(&dest_address, TariAddressFeatures::create_one_sided_only())?; @@ -1700,6 +1752,8 @@ where script.clone(), Covenant::default(), MicroMinotari::zero(), + dest_address.clone(), + payment_id.clone(), ) .await?; @@ -1880,7 +1934,7 @@ where TransactionDirection::Outbound, None, None, - Some(payment_id), + payment_id, )?, ) .await?; @@ -1914,7 +1968,7 @@ where let mut stp = self .resources .output_manager_service - .scrape_wallet(tx_id, fee_per_gram) + .scrape_wallet(tx_id, fee_per_gram, dest_address.clone()) .await?; // This call is needed to advance the state from `SingleRoundMessageReady` to `SingleRoundMessageReady`, @@ -2088,12 +2142,12 @@ where fee, tx.clone(), TransactionStatus::Completed, - "".to_string(), + "scrape wallet".to_string(), Utc::now().naive_utc(), TransactionDirection::Outbound, None, None, - Some(payment_id), + payment_id, )?, ) .await?; @@ -2147,6 +2201,7 @@ where selection_criteria: UtxoSelectionCriteria, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, claim_public_key: Option, transaction_broadcast_join_handles: &mut FuturesUnordered< JoinHandle>>, @@ -2176,6 +2231,8 @@ where script!(Nop)?, Covenant::default(), MicroMinotari::zero(), + self.resources.interactive_tari_address.clone(), + payment_id.clone(), ) .await?; @@ -2353,7 +2410,7 @@ where TransactionDirection::Outbound, None, None, - None, + payment_id, )?, ) .await?; @@ -2393,6 +2450,7 @@ where output_features, fee_per_gram, message, + PaymentId::Empty, TransactionMetadata::default(), join_handles, transaction_broadcast_join_handles, @@ -2422,6 +2480,7 @@ where OutputFeatures::for_template_registration(template_registration), fee_per_gram, message, + PaymentId::Empty, TransactionMetadata::default(), join_handles, transaction_broadcast_join_handles, @@ -2790,6 +2849,7 @@ where tx.amount, tx.fee, tx.message, + tx.payment_id, TransactionMetadata::default(), None, stage, @@ -3045,13 +3105,22 @@ where let mut amount = None; for ro in recovered { match &ro.output.payment_id { - PaymentId::AddressAndData(address, _) | PaymentId::Address(address) => { + PaymentId::AddressAndData { + sender_address: address, + data: _, + } | + PaymentId::Address(address) => { if source_address.is_none() { source_address = Some(address.clone()); payment_id = Some(ro.output.payment_id.clone()); amount = Some(ro.output.value); } }, + PaymentId::ChangeInfo { .. } => { + source_address = Some(self.resources.interactive_tari_address.clone()); + payment_id = Some(ro.output.payment_id.clone()); + amount = Some(ro.output.value); + }, _ => {}, }; } @@ -3068,7 +3137,7 @@ where TransactionDirection::Inbound, None, None, - payment_id, + payment_id.unwrap_or_default(), )?; self.db .insert_completed_transaction(tx_id, completed_transaction.clone())?; @@ -3613,6 +3682,7 @@ where fee: MicroMinotari, amount: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result<(), TransactionServiceError> { self.submit_transaction( transaction_broadcast_join_handles, @@ -3629,7 +3699,7 @@ where TransactionDirection::Inbound, None, None, - None, + payment_id, )?, ) .await?; diff --git a/base_layer/wallet/src/transaction_service/storage/database.rs b/base_layer/wallet/src/transaction_service/storage/database.rs index 08e9f16d1b..4028c9f008 100644 --- a/base_layer/wallet/src/transaction_service/storage/database.rs +++ b/base_layer/wallet/src/transaction_service/storage/database.rs @@ -697,10 +697,6 @@ where T: TransactionBackend + 'static scanned_output: TransactionOutput, payment_id: PaymentId, ) -> Result<(), TransactionStorageError> { - let payment_id = match payment_id { - PaymentId::Empty => None, - v => Some(v), - }; let transaction = CompletedTransaction::new( tx_id, source_address, diff --git a/base_layer/wallet/src/transaction_service/storage/models.rs b/base_layer/wallet/src/transaction_service/storage/models.rs index fa81435234..cc64c521a5 100644 --- a/base_layer/wallet/src/transaction_service/storage/models.rs +++ b/base_layer/wallet/src/transaction_service/storage/models.rs @@ -49,6 +49,7 @@ pub struct InboundTransaction { pub receiver_protocol: ReceiverTransactionProtocol, pub status: TransactionStatus, pub message: String, + pub payment_id: PaymentId, pub timestamp: NaiveDateTime, pub cancelled: bool, pub direct_send_success: bool, @@ -64,6 +65,7 @@ impl InboundTransaction { receiver_protocol: ReceiverTransactionProtocol, status: TransactionStatus, message: String, + payment_id: PaymentId, timestamp: NaiveDateTime, ) -> Self { Self { @@ -73,6 +75,7 @@ impl InboundTransaction { receiver_protocol, status, message, + payment_id, timestamp, cancelled: false, direct_send_success: false, @@ -91,6 +94,7 @@ pub struct OutboundTransaction { pub sender_protocol: SenderTransactionProtocol, pub status: TransactionStatus, pub message: String, + pub payment_id: PaymentId, pub timestamp: NaiveDateTime, pub cancelled: bool, pub direct_send_success: bool, @@ -107,6 +111,7 @@ impl OutboundTransaction { sender_protocol: SenderTransactionProtocol, status: TransactionStatus, message: String, + payment_id: PaymentId, timestamp: NaiveDateTime, direct_send_success: bool, ) -> Self { @@ -118,6 +123,7 @@ impl OutboundTransaction { sender_protocol, status, message, + payment_id, timestamp, cancelled: false, direct_send_success, @@ -147,7 +153,7 @@ pub struct CompletedTransaction { pub mined_height: Option, pub mined_in_block: Option, pub mined_timestamp: Option, - pub payment_id: Option, + pub payment_id: PaymentId, } impl CompletedTransaction { @@ -164,7 +170,7 @@ impl CompletedTransaction { direction: TransactionDirection, mined_height: Option, mined_timestamp: Option, - payment_id: Option, + payment_id: PaymentId, ) -> Result { if status == TransactionStatus::Coinbase { return Err(TransactionStorageError::CoinbaseNotSupported); @@ -207,6 +213,7 @@ impl From for InboundTransaction { receiver_protocol: ReceiverTransactionProtocol::new_placeholder(), status: ct.status, message: ct.message, + payment_id: ct.payment_id, timestamp: ct.timestamp, cancelled: ct.cancelled.is_some(), direct_send_success: false, @@ -226,6 +233,7 @@ impl From for OutboundTransaction { sender_protocol: SenderTransactionProtocol::new_placeholder(), status: ct.status, message: ct.message, + payment_id: ct.payment_id, timestamp: ct.timestamp, cancelled: ct.cancelled.is_some(), direct_send_success: false, @@ -273,7 +281,7 @@ impl From for CompletedTransaction { mined_height: None, mined_in_block: None, mined_timestamp: None, - payment_id: None, + payment_id: tx.payment_id, } } } @@ -303,7 +311,7 @@ impl From for CompletedTransaction { mined_height: None, mined_in_block: None, mined_timestamp: None, - payment_id: None, + payment_id: tx.payment_id, } } } diff --git a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs index 14cc8ad353..09a4ff2f25 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -1171,6 +1171,7 @@ struct InboundTransactionSql { direct_send_success: i32, send_count: i32, last_send_timestamp: Option, + payment_id: Option>, } impl InboundTransactionSql { @@ -1356,6 +1357,7 @@ impl InboundTransactionSql { direct_send_success: i32::from(i.direct_send_success), send_count: i.send_count as i32, last_send_timestamp: i.last_send_timestamp, + payment_id: Some(i.payment_id.to_bytes()), }; i.encrypt(cipher).map_err(TransactionStorageError::AeadError) } @@ -1406,6 +1408,7 @@ impl InboundTransaction { direct_send_success: i.direct_send_success != 0, send_count: i.send_count as u32, last_send_timestamp: i.last_send_timestamp, + payment_id: PaymentId::from_bytes(&i.payment_id.unwrap_or_default()), }) } } @@ -1435,6 +1438,7 @@ struct OutboundTransactionSql { direct_send_success: i32, send_count: i32, last_send_timestamp: Option, + payment_id: Option>, } impl OutboundTransactionSql { @@ -1605,6 +1609,7 @@ impl OutboundTransactionSql { direct_send_success: i32::from(o.direct_send_success), send_count: o.send_count as i32, last_send_timestamp: o.last_send_timestamp, + payment_id: Some(o.payment_id.to_bytes()), }; outbound_tx.encrypt(cipher).map_err(TransactionStorageError::AeadError) @@ -1657,6 +1662,7 @@ impl OutboundTransaction { direct_send_success: o.direct_send_success != 0, send_count: o.send_count as u32, last_send_timestamp: o.last_send_timestamp, + payment_id: PaymentId::from_bytes(&o.payment_id.unwrap_or_default()), }; // zeroize decrypted data @@ -1964,10 +1970,6 @@ impl CompletedTransactionSql { fn try_from(c: CompletedTransaction, cipher: &XChaCha20Poly1305) -> Result { let transaction_bytes = bincode::serialize(&c.transaction).map_err(|e| TransactionStorageError::BincodeSerialize(e.to_string()))?; - let payment_id = match c.payment_id { - Some(id) => Some(id.to_bytes()), - None => Some(Vec::new()), - }; let output = Self { tx_id: c.tx_id.as_u64() as i64, source_address: c.source_address.to_vec(), @@ -1988,7 +1990,7 @@ impl CompletedTransactionSql { mined_timestamp: c.mined_timestamp, transaction_signature_nonce: c.transaction_signature.get_public_nonce().to_vec(), transaction_signature_key: c.transaction_signature.get_signature().to_vec(), - payment_id, + payment_id: Some(c.payment_id.to_bytes()), }; output.encrypt(cipher).map_err(TransactionStorageError::AeadError) @@ -2062,18 +2064,6 @@ impl CompletedTransaction { }, None => None, }; - let payment_id = match c.payment_id { - Some(bytes) => PaymentId::from_bytes(&bytes).map_err(|_| { - error!( - target: LOG_TARGET, - "Could not create payment id from stored bytes" - ); - CompletedTransactionConversionError::BincodeDeserialize( - "payment id could not be converted from bytes".to_string(), - ) - })?, - None => PaymentId::Empty, - }; let output = Self { tx_id: (c.tx_id as u64).into(), @@ -2098,7 +2088,7 @@ impl CompletedTransaction { mined_height: c.mined_height.map(|ic| ic as u64), mined_in_block, mined_timestamp: c.mined_timestamp, - payment_id: Some(payment_id), + payment_id: PaymentId::from_bytes(&c.payment_id.unwrap_or_default()), }; // zeroize sensitive data @@ -2309,6 +2299,7 @@ mod test { Default::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap() @@ -2334,6 +2325,7 @@ mod test { sender_protocol: stp.clone(), status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -2353,6 +2345,7 @@ mod test { sender_protocol: stp.clone(), status: TransactionStatus::Pending, message: "Hey!".to_string(), + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -2419,6 +2412,7 @@ mod test { receiver_protocol: rtp.clone(), status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: Default::default(), timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -2437,6 +2431,7 @@ mod test { receiver_protocol: rtp, status: TransactionStatus::Pending, message: "Hey!".to_string(), + payment_id: Default::default(), timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -2508,7 +2503,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, - payment_id: None, + payment_id: PaymentId::Empty, }; let source_address = TariAddress::new_dual_address_with_default_features( PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), @@ -2539,7 +2534,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, - payment_id: None, + payment_id: PaymentId::Empty, }; CompletedTransactionSql::try_from(completed_tx1.clone(), &cipher) @@ -2700,6 +2695,7 @@ mod test { receiver_protocol: ReceiverTransactionProtocol::new_placeholder(), status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: Default::default(), timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -2728,6 +2724,7 @@ mod test { sender_protocol: SenderTransactionProtocol::new_placeholder(), status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -2779,7 +2776,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, - payment_id: Some(PaymentId::Empty), + payment_id: PaymentId::Empty, }; let completed_tx_sql = CompletedTransactionSql::try_from(completed_tx.clone(), &cipher).unwrap(); @@ -2844,6 +2841,7 @@ mod test { receiver_protocol: ReceiverTransactionProtocol::new_placeholder(), status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: Default::default(), timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -2867,6 +2865,7 @@ mod test { sender_protocol: SenderTransactionProtocol::new_placeholder(), status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -2912,7 +2911,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, - payment_id: None, + payment_id: PaymentId::Empty, }; let completed_tx_sql = CompletedTransactionSql::try_from(completed_tx, &cipher).unwrap(); @@ -3055,7 +3054,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, - payment_id: None, + payment_id: PaymentId::Empty, }; let completed_tx_sql = CompletedTransactionSql::try_from(completed_tx.clone(), &cipher).unwrap(); diff --git a/base_layer/wallet/src/utxo_scanner_service/utxo_scanner_task.rs b/base_layer/wallet/src/utxo_scanner_service/utxo_scanner_task.rs index 41fb837de3..a096673de5 100644 --- a/base_layer/wallet/src/utxo_scanner_service/utxo_scanner_task.rs +++ b/base_layer/wallet/src/utxo_scanner_service/utxo_scanner_task.rs @@ -651,7 +651,11 @@ where self.resources.one_sided_tari_address.clone() } else { match &wo.payment_id { - PaymentId::AddressAndData(address, _) | PaymentId::Address(address) => address.clone(), + PaymentId::AddressAndData { + sender_address: address, + data: _, + } | + PaymentId::Address(address) => address.clone(), _ => TariAddress::default(), } }; diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index bbbfade4da..a13f9373f4 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -677,6 +677,7 @@ where split_count: usize, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result { let coin_split_tx = self .output_manager_service @@ -687,7 +688,7 @@ where Ok((tx_id, split_tx, amount)) => { let coin_tx = self .transaction_service - .submit_transaction(tx_id, split_tx, amount, message) + .submit_transaction(tx_id, split_tx, amount, message, payment_id) .await; match coin_tx { Ok(_) => Ok(tx_id), @@ -715,7 +716,7 @@ where Ok((tx_id, split_tx, amount)) => { let coin_tx = self .transaction_service - .submit_transaction(tx_id, split_tx, amount, message) + .submit_transaction(tx_id, split_tx, amount, message, PaymentId::Empty) .await; match coin_tx { Ok(_) => Ok(tx_id), @@ -743,7 +744,7 @@ where Ok((tx_id, split_tx, amount)) => { let coin_tx = self .transaction_service - .submit_transaction(tx_id, split_tx, amount, message) + .submit_transaction(tx_id, split_tx, amount, message, PaymentId::Empty) .await; match coin_tx { Ok(_) => Ok(tx_id), @@ -769,7 +770,7 @@ where Ok((tx_id, tx, output_value)) => { let coin_tx = self .transaction_service - .submit_transaction(tx_id, tx, output_value, msg.unwrap_or_default()) + .submit_transaction(tx_id, tx, output_value, msg.unwrap_or_default(), PaymentId::Empty) .await; match coin_tx { 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 8d82146257..a3f852a46e 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -269,6 +269,7 @@ async fn generate_sender_transaction_message( Covenant::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -404,6 +405,8 @@ async fn test_utxo_selection_no_chain_metadata() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap_err(); @@ -440,6 +443,8 @@ async fn test_utxo_selection_no_chain_metadata() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -534,6 +539,8 @@ async fn test_utxo_selection_with_chain_metadata() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap_err(); @@ -616,6 +623,8 @@ async fn test_utxo_selection_with_chain_metadata() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -644,6 +653,8 @@ async fn test_utxo_selection_with_chain_metadata() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -739,6 +750,8 @@ async fn test_utxo_selection_with_tx_priority() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -785,6 +798,8 @@ async fn send_not_enough_funds() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await { @@ -852,6 +867,8 @@ async fn send_no_change() { TariScript::default(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -918,6 +935,8 @@ async fn send_not_enough_for_change() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await { @@ -959,6 +978,8 @@ async fn cancel_transaction() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -1064,6 +1085,8 @@ async fn test_get_balance() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -1135,6 +1158,8 @@ async fn sending_transaction_persisted_while_offline() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -1168,6 +1193,8 @@ async fn sending_transaction_persisted_while_offline() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -1479,6 +1506,8 @@ async fn test_txo_validation() { TariScript::default(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); diff --git a/base_layer/wallet/tests/transaction_service_tests/service.rs b/base_layer/wallet/tests/transaction_service_tests/service.rs index 15fea49f58..f551ac90b2 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -609,7 +609,8 @@ async fn manage_single_transaction() { UtxoSelectionCriteria::default(), OutputFeatures::default(), MicroMinotari::from(4), - "".to_string() + "".to_string(), + PaymentId::Empty, ) .await .is_err()); @@ -628,6 +629,7 @@ async fn manage_single_transaction() { OutputFeatures::default(), MicroMinotari::from(4), message, + PaymentId::Empty, ) .await .expect("Alice sending tx"); @@ -786,6 +788,7 @@ async fn large_interactive_transaction() { OutputFeatures::default(), MicroMinotari::from(1), message, + PaymentId::Empty, ) .await .expect("Alice sending large tx"); @@ -948,6 +951,7 @@ async fn test_spend_dust_to_self_in_oversized_transaction() { OutputFeatures::default(), fee_per_gram, message.clone(), + PaymentId::Empty, ) .await .is_err()); @@ -1046,6 +1050,7 @@ async fn test_spend_dust_to_other_in_oversized_transaction() { OutputFeatures::default(), fee_per_gram, message.clone(), + PaymentId::Empty, ) .await .unwrap(); @@ -1162,6 +1167,7 @@ async fn test_spend_dust_happy_path() { OutputFeatures::default(), fee_per_gram, message.clone(), + PaymentId::Empty, ) .await .unwrap(); @@ -1207,6 +1213,7 @@ async fn test_spend_dust_happy_path() { OutputFeatures::default(), fee_per_gram, message.clone(), + PaymentId::Empty, ) .await .unwrap(); @@ -1308,6 +1315,7 @@ async fn single_transaction_to_self() { OutputFeatures::default(), 20.into(), message.clone(), + PaymentId::Empty, ) .await .expect("Alice sending tx"); @@ -1392,7 +1400,13 @@ async fn large_coin_split_transaction() { assert_eq!(coin_split_tx.body.outputs().len(), split_count + 1); alice_ts - .submit_transaction(tx_id, coin_split_tx, amount, "large coin-split".to_string()) + .submit_transaction( + tx_id, + coin_split_tx, + amount, + "large coin-split".to_string(), + PaymentId::Empty, + ) .await .expect("Alice sending coin-split tx"); @@ -1477,6 +1491,7 @@ async fn single_transaction_burn_tari() { UtxoSelectionCriteria::default(), 20.into(), message.clone(), + PaymentId::Empty, Some(claim_public_key.clone()), ) .await @@ -2063,7 +2078,7 @@ async fn test_htlc_send_and_claim() { bob_ts_interface .transaction_service_handle - .submit_transaction(tx_id_htlc, tx, htlc_amount, "".to_string()) + .submit_transaction(tx_id_htlc, tx, htlc_amount, "".to_string(), PaymentId::Empty) .await .unwrap(); assert_eq!( @@ -2260,6 +2275,7 @@ async fn manage_multiple_transactions() { OutputFeatures::default(), MicroMinotari::from(20), "a to b 1".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -2277,6 +2293,7 @@ async fn manage_multiple_transactions() { OutputFeatures::default(), MicroMinotari::from(20), "a to c 1".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -2296,6 +2313,7 @@ async fn manage_multiple_transactions() { OutputFeatures::default(), MicroMinotari::from(20), "b to a 1".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -2307,6 +2325,7 @@ async fn manage_multiple_transactions() { OutputFeatures::default(), MicroMinotari::from(20), "a to b 2".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -2452,6 +2471,7 @@ async fn test_accepting_unknown_tx_id_and_malformed_reply() { OutputFeatures::default(), MicroMinotari::from(20), "".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -2556,6 +2576,8 @@ async fn finalize_tx_with_incorrect_pubkey() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -2685,6 +2707,8 @@ async fn finalize_tx_with_missing_output() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -2883,6 +2907,7 @@ async fn discovery_async_return_test() { OutputFeatures::default(), MicroMinotari::from(20), "Discovery Tx!".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -2922,6 +2947,7 @@ async fn discovery_async_return_test() { OutputFeatures::default(), MicroMinotari::from(20), "Discovery Tx2!".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -3023,7 +3049,7 @@ async fn test_power_mode_updates() { mined_height: None, mined_in_block: None, mined_timestamp: None, - payment_id: None, + payment_id: PaymentId::Empty, }; let source_address = TariAddress::new_dual_address_with_default_features( @@ -3055,7 +3081,7 @@ async fn test_power_mode_updates() { mined_height: None, mined_in_block: None, mined_timestamp: None, - payment_id: None, + payment_id: PaymentId::Empty, }; tx_backend @@ -3230,6 +3256,7 @@ async fn test_transaction_cancellation() { OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -3353,6 +3380,7 @@ async fn test_transaction_cancellation() { Covenant::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -3439,6 +3467,7 @@ async fn test_transaction_cancellation() { Covenant::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -3578,6 +3607,7 @@ async fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -3780,6 +3810,7 @@ async fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -3969,6 +4000,7 @@ async fn test_tx_direct_send_behaviour() { OutputFeatures::default(), 100 * uT, "Testing Message1".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -4013,6 +4045,7 @@ async fn test_tx_direct_send_behaviour() { OutputFeatures::default(), 100 * uT, "Testing Message2".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -4062,6 +4095,7 @@ async fn test_tx_direct_send_behaviour() { OutputFeatures::default(), 100 * uT, "Testing Message3".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -4111,6 +4145,7 @@ async fn test_tx_direct_send_behaviour() { OutputFeatures::default(), 100 * uT, "Testing Message4".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -4203,6 +4238,7 @@ async fn test_restarting_transaction_protocols() { Covenant::default(), MicroMinotari::zero(), MicroMinotari(2000) - fee - MicroMinotari(10), + TariAddress::default(), ) .await .unwrap() @@ -4251,6 +4287,7 @@ async fn test_restarting_transaction_protocols() { receiver_protocol, status: TransactionStatus::Pending, message: msg.message.clone(), + payment_id: Default::default(), timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -4278,6 +4315,7 @@ async fn test_restarting_transaction_protocols() { sender_protocol: bob_pre_finalize, status: TransactionStatus::Pending, message: msg.message, + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -4437,6 +4475,7 @@ async fn test_transaction_resending() { OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -4642,6 +4681,7 @@ async fn test_resend_on_startup() { Covenant::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -4664,6 +4704,7 @@ async fn test_resend_on_startup() { sender_protocol: stp, status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -4797,6 +4838,7 @@ async fn test_resend_on_startup() { receiver_protocol: rtp, status: TransactionStatus::Pending, message: "Yo2".to_string(), + payment_id: Default::default(), timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -4959,6 +5001,7 @@ async fn test_replying_to_cancelled_tx() { OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -5099,6 +5142,7 @@ async fn test_transaction_timeout_cancellation() { OutputFeatures::default(), 20 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -5171,6 +5215,7 @@ async fn test_transaction_timeout_cancellation() { Covenant::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -5193,6 +5238,7 @@ async fn test_transaction_timeout_cancellation() { sender_protocol: stp, status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: PaymentId::Empty, timestamp: Utc::now() .naive_utc() .checked_sub_signed(ChronoDuration::seconds(20)) @@ -5396,6 +5442,7 @@ async fn transaction_service_tx_broadcast() { OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -5457,6 +5504,7 @@ async fn transaction_service_tx_broadcast() { OutputFeatures::default(), 20 * uT, "Testing Message2".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -5748,7 +5796,7 @@ async fn broadcast_all_completed_transactions_on_startup() { mined_height: None, mined_in_block: None, mined_timestamp: None, - payment_id: None, + payment_id: PaymentId::Empty, }; let completed_tx2 = CompletedTransaction { diff --git a/base_layer/wallet/tests/transaction_service_tests/storage.rs b/base_layer/wallet/tests/transaction_service_tests/storage.rs index 41e3401691..339c4973bd 100644 --- a/base_layer/wallet/tests/transaction_service_tests/storage.rs +++ b/base_layer/wallet/tests/transaction_service_tests/storage.rs @@ -102,6 +102,7 @@ pub async fn test_db_backend(backend: T) { Covenant::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -141,6 +142,7 @@ pub async fn test_db_backend(backend: T) { sender_protocol: stp.clone(), status: TransactionStatus::Pending, message: messages[i].clone(), + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -254,6 +256,7 @@ pub async fn test_db_backend(backend: T) { receiver_protocol: rtp.clone(), status: TransactionStatus::Pending, message: messages[i].clone(), + payment_id: Default::default(), timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -345,7 +348,7 @@ pub async fn test_db_backend(backend: T) { mined_height: None, mined_in_block: None, mined_timestamp: None, - payment_id: Some(PaymentId::Empty), + payment_id: PaymentId::Empty, }); db.complete_outbound_transaction(outbound_txs[i].tx_id, completed_txs[i].clone()) .unwrap(); @@ -446,6 +449,7 @@ pub async fn test_db_backend(backend: T) { rtp, TransactionStatus::Pending, "To be cancelled".to_string(), + PaymentId::Empty, Utc::now().naive_utc(), ), ) @@ -499,6 +503,7 @@ pub async fn test_db_backend(backend: T) { stp, TransactionStatus::Pending, "To be cancelled".to_string(), + PaymentId::Empty, Utc::now().naive_utc(), false, ), @@ -596,7 +601,7 @@ async fn import_tx_and_read_it_from_db() { TransactionDirection::Inbound, Some(5), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap()), - None, + PaymentId::Empty, ) .unwrap(); @@ -626,7 +631,7 @@ async fn import_tx_and_read_it_from_db() { TransactionDirection::Inbound, Some(6), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap()), - None, + PaymentId::Empty, ) .unwrap(); @@ -656,7 +661,7 @@ async fn import_tx_and_read_it_from_db() { TransactionDirection::Inbound, Some(7), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap()), - None, + PaymentId::Empty, ) .unwrap(); diff --git a/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs b/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs index 3a077193a7..fef5d8c7fa 100644 --- a/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs +++ b/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs @@ -81,7 +81,7 @@ use tari_core::{ key_manager::{create_memory_db_key_manager, MemoryDbKeyManager, TransactionKeyManagerInterface}, tari_amount::{uT, MicroMinotari, T}, test_helpers::schema_to_transaction, - transaction_components::OutputFeatures, + transaction_components::{encrypted_data::PaymentId, OutputFeatures}, CryptoFactories, }, txn_schema, @@ -236,7 +236,7 @@ pub async fn add_transaction_to_database( TransactionDirection::Outbound, None, None, - None, + PaymentId::Empty, ) .unwrap(); db.insert_completed_transaction(tx_id, completed_tx1).unwrap(); diff --git a/base_layer/wallet_ffi/src/callback_handler_tests.rs b/base_layer/wallet_ffi/src/callback_handler_tests.rs index c913d985b3..172f5f4ae7 100644 --- a/base_layer/wallet_ffi/src/callback_handler_tests.rs +++ b/base_layer/wallet_ffi/src/callback_handler_tests.rs @@ -49,7 +49,7 @@ mod test { }; use tari_core::transactions::{ tari_amount::{uT, MicroMinotari}, - transaction_components::Transaction, + transaction_components::{encrypted_data::PaymentId, Transaction}, ReceiverTransactionProtocol, SenderTransactionProtocol, }; @@ -320,6 +320,7 @@ mod test { rtp, TransactionStatus::Pending, "1".to_string(), + PaymentId::Empty, Utc::now().naive_utc(), ); db.add_pending_inbound_transaction(1u64.into(), inbound_tx.clone()) @@ -354,7 +355,7 @@ mod test { TransactionDirection::Inbound, None, None, - None, + PaymentId::Empty, ) .unwrap(); db.insert_completed_transaction(2u64.into(), completed_tx.clone()) @@ -374,6 +375,7 @@ mod test { stp, TransactionStatus::Pending, "3".to_string(), + PaymentId::Empty, Utc::now().naive_utc(), false, ); @@ -426,7 +428,7 @@ mod test { TransactionDirection::Inbound, Some(2), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap_or(NaiveDateTime::MIN)), - None, + PaymentId::Empty, ) .unwrap(); db.insert_completed_transaction(6u64.into(), faux_unconfirmed_tx.clone()) @@ -461,7 +463,7 @@ mod test { TransactionDirection::Inbound, Some(5), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap()), - None, + PaymentId::Empty, ) .unwrap(); db.insert_completed_transaction(7u64.into(), faux_confirmed_tx.clone()) diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index b522120bb7..5e54e4e1ec 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -4243,18 +4243,13 @@ pub unsafe extern "C" fn completed_transaction_get_payment_id( ptr::swap(error_out, &mut error as *mut c_int); return result.into_raw(); } - let payment_id_str = match payment_id { - None => "".to_string(), - Some(v) => { - let bytes = v.get_data(); - if bytes.is_empty() { - format!("#{}", v) - } else { - String::from_utf8(bytes) - .unwrap_or_else(|_| "Invalid string".to_string()) - .to_string() - } - }, + let payment_id_str = { + let bytes = payment_id.get_data(); + if bytes.is_empty() { + format!("#{}", payment_id) + } else { + PaymentId::stringify_bytes(&bytes) + } }; match CString::new(payment_id_str) { Ok(v) => result = v, @@ -7394,23 +7389,24 @@ pub unsafe extern "C" fn wallet_send_transaction( } }; + let payment_id = if payment_id_string.is_null() { + PaymentId::Empty + } else { + match CStr::from_ptr(payment_id_string).to_str() { + Ok(v) => { + let rust_str = v.to_owned(); + let bytes = rust_str.as_bytes().to_vec(); + PaymentId::Open(bytes) + }, + _ => { + error = LibWalletError::from(InterfaceError::NullError("payment_id".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + }, + } + }; + if one_sided { - let payment_id = if payment_id_string.is_null() { - PaymentId::Empty - } else { - match CStr::from_ptr(payment_id_string).to_str() { - Ok(v) => { - let rust_str = v.to_owned(); - let bytes = rust_str.as_bytes().to_vec(); - PaymentId::Open(bytes) - }, - _ => { - error = LibWalletError::from(InterfaceError::NullError("payment_id".to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return 0; - }, - } - }; match (*wallet).runtime.block_on( (*wallet) .wallet @@ -7442,6 +7438,7 @@ pub unsafe extern "C" fn wallet_send_transaction( OutputFeatures::default(), MicroMinotari::from(fee_per_gram), message_string, + payment_id, )) { Ok(tx_id) => tx_id.as_u64(), Err(e) => { diff --git a/integration_tests/tests/steps/wallet_cli_steps.rs b/integration_tests/tests/steps/wallet_cli_steps.rs index 6620ffb9e2..08ed832204 100644 --- a/integration_tests/tests/steps/wallet_cli_steps.rs +++ b/integration_tests/tests/steps/wallet_cli_steps.rs @@ -154,6 +154,7 @@ async fn send_from_cli(world: &mut TariWorld, amount: u64, wallet_a: String, wal amount: MicroMinotari(amount), message: format!("Send amount {} from {} to {}", amount, wallet_a, wallet_b), destination: wallet_b_address, + payment_id: "123456".to_string(), }; cli.command2 = Some(CliCommands::SendMinotari(args)); @@ -175,6 +176,7 @@ async fn create_burn_tx_via_cli(world: &mut TariWorld, amount: u64, wallet: Stri let args = BurnMinotariArgs { amount: MicroMinotari(amount), message: format!("Burn, burn amount {} !!!", amount,), + payment_id: "123456".to_string(), }; cli.command2 = Some(CliCommands::BurnMinotari(args)); @@ -251,6 +253,7 @@ async fn coin_split_via_cli(world: &mut TariWorld, wallet: String, amount: u64, num_splits: usize::try_from(splits).unwrap(), fee_per_gram: MicroMinotari(20), message: format!("coin split amount {} with splits {}", amount, splits), + payment_id: "".to_string(), }; cli.command2 = Some(CliCommands::CoinSplit(args)); diff --git a/integration_tests/tests/steps/wallet_steps.rs b/integration_tests/tests/steps/wallet_steps.rs index c7747f3936..375040e580 100644 --- a/integration_tests/tests/steps/wallet_steps.rs +++ b/integration_tests/tests/steps/wallet_steps.rs @@ -2792,6 +2792,7 @@ async fn burn_transaction(world: &mut TariWorld, amount: u64, wallet: String, fe fee_per_gram: fee, message: "Burning some tari".to_string(), claim_public_key: identity.public_key, + payment_id: "123456".to_string(), }; let result = client.create_burn_transaction(req).await.unwrap();