diff --git a/applications/ffi_client/index.js b/applications/ffi_client/index.js index 20aeaf6c39..c7a7c81f39 100644 --- a/applications/ffi_client/index.js +++ b/applications/ffi_client/index.js @@ -69,6 +69,18 @@ try { console.log("txMinedUnconfirmed: ", ptr, confirmations); } ); + // callback_faux_transaction_confirmed: unsafe extern "C" fn(*mut TariCompletedTransaction), + const txFauxConfirmed = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("txFauxConfirmed: ", ptr); + }); + // callback_faux_transaction_unconfirmed: unsafe extern "C" fn(*mut TariCompletedTransaction, u64), + const txFauxUnconfirmed = ffi.Callback( + "void", + ["pointer"], + function (ptr, confirmations) { + console.log("txFauxUnconfirmed: ", ptr, confirmations); + } + ); // callback_direct_send_result: unsafe extern "C" fn(c_ulonglong, bool), const directSendResult = ffi.Callback("void", [u64, bool], function (i, j) { console.log("directSendResult: ", i, j); @@ -112,6 +124,8 @@ try { txBroadcast, txMined, txMinedUnconfirmed, + txFauxConfirmed, + txFauxUnconfirmed, directSendResult, safResult, txCancelled, diff --git a/applications/ffi_client/lib/index.js b/applications/ffi_client/lib/index.js index db922de1f0..b9e8b34da7 100644 --- a/applications/ffi_client/lib/index.js +++ b/applications/ffi_client/lib/index.js @@ -66,6 +66,9 @@ const libWallet = ffi.Library("./libtari_wallet_ffi.dylib", { fn, fn, fn, + fn, + fn, + bool, errPtr, ], ], diff --git a/applications/ffi_client/recovery.js b/applications/ffi_client/recovery.js index 0c51d3daa3..bf82225ca2 100644 --- a/applications/ffi_client/recovery.js +++ b/applications/ffi_client/recovery.js @@ -80,6 +80,18 @@ try { console.log("txMinedUnconfirmed: ", ptr, confirmations); } ); + // callback_faux_transaction_confirmed: unsafe extern "C" fn(*mut TariCompletedTransaction), + const txFauxConfirmed = ffi.Callback("void", ["pointer"], function (ptr) { + console.log("txFauxConfirmed: ", ptr); + }); + // callback_faux_transaction_unconfirmed: unsafe extern "C" fn(*mut TariCompletedTransaction, u64), + const txFauxUnconfirmed = ffi.Callback( + "void", + ["pointer"], + function (ptr, confirmations) { + console.log("txFauxUnconfirmed: ", ptr, confirmations); + } + ); // callback_direct_send_result: unsafe extern "C" fn(c_ulonglong, bool), const directSendResult = ffi.Callback("void", [u64, bool], function (i, j) { console.log("directSendResult: ", i, j); diff --git a/applications/tari_app_grpc/proto/wallet.proto b/applications/tari_app_grpc/proto/wallet.proto index 3ba81bbf0f..edb4a6957d 100644 --- a/applications/tari_app_grpc/proto/wallet.proto +++ b/applications/tari_app_grpc/proto/wallet.proto @@ -192,6 +192,10 @@ enum TransactionStatus { TRANSACTION_STATUS_NOT_FOUND = 7; // The transaction was rejected by the mempool TRANSACTION_STATUS_REJECTED = 8; + // This is faux transaction mainly for one-sided transaction outputs or wallet recovery outputs have been found + TRANSACTION_STATUS_FAUX_UNCONFIRMED = 9; + // All Imported and FauxUnconfirmed transactions will end up with this status when the outputs have been confirmed + TRANSACTION_STATUS_FAUX_CONFIRMED = 10; } message GetCompletedTransactionsRequest { } diff --git a/applications/tari_app_grpc/src/conversions/transaction.rs b/applications/tari_app_grpc/src/conversions/transaction.rs index fffb851ef7..f74dc1e66f 100644 --- a/applications/tari_app_grpc/src/conversions/transaction.rs +++ b/applications/tari_app_grpc/src/conversions/transaction.rs @@ -98,6 +98,8 @@ impl From for grpc::TransactionStatus { Pending => grpc::TransactionStatus::Pending, Coinbase => grpc::TransactionStatus::Coinbase, Rejected => grpc::TransactionStatus::Rejected, + FauxUnconfirmed => grpc::TransactionStatus::FauxUnconfirmed, + FauxConfirmed => grpc::TransactionStatus::FauxConfirmed, } } } diff --git a/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs b/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs index 8e341008c6..d01aadd520 100644 --- a/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs +++ b/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs @@ -94,13 +94,15 @@ impl WalletEventMonitor { self.trigger_balance_refresh(); notifier.transaction_received(tx_id); }, - TransactionEvent::TransactionMinedUnconfirmed{tx_id, num_confirmations, is_valid: _} => { + TransactionEvent::TransactionMinedUnconfirmed{tx_id, num_confirmations, is_valid: _} | + TransactionEvent::FauxTransactionUnconfirmed{tx_id, num_confirmations, is_valid: _}=> { self.trigger_confirmations_refresh(tx_id, num_confirmations).await; self.trigger_tx_state_refresh(tx_id).await; self.trigger_balance_refresh(); notifier.transaction_mined_unconfirmed(tx_id, num_confirmations); }, - TransactionEvent::TransactionMined{tx_id, is_valid: _} => { + TransactionEvent::TransactionMined{tx_id, is_valid: _} | + TransactionEvent::FauxTransactionConfirmed{tx_id, is_valid: _}=> { self.trigger_confirmations_cleanup(tx_id).await; self.trigger_tx_state_refresh(tx_id).await; self.trigger_balance_refresh(); @@ -114,7 +116,8 @@ impl WalletEventMonitor { TransactionEvent::ReceivedTransaction(tx_id) | TransactionEvent::ReceivedTransactionReply(tx_id) | TransactionEvent::TransactionBroadcast(tx_id) | - TransactionEvent::TransactionMinedRequestTimedOut(tx_id) | TransactionEvent::TransactionImported(tx_id) => { + TransactionEvent::TransactionMinedRequestTimedOut(tx_id) | + TransactionEvent::TransactionImported(tx_id) => { self.trigger_tx_state_refresh(tx_id).await; self.trigger_balance_refresh(); }, diff --git a/base_layer/common_types/src/transaction.rs b/base_layer/common_types/src/transaction.rs index 0e33ca237e..45908a7c89 100644 --- a/base_layer/common_types/src/transaction.rs +++ b/base_layer/common_types/src/transaction.rs @@ -17,7 +17,7 @@ pub enum TransactionStatus { Broadcast, /// This transaction has been mined and included in a block. MinedUnconfirmed, - /// This transaction was generated as part of importing a spendable UTXO + /// This transaction was generated as part of importing a spendable unblinded UTXO Imported, /// This transaction is still being negotiated by the parties Pending, @@ -27,6 +27,27 @@ pub enum TransactionStatus { MinedConfirmed, /// This transaction was Rejected by the mempool Rejected, + /// This is faux transaction mainly for one-sided transaction outputs or wallet recovery outputs have been found + FauxUnconfirmed, + /// All Imported and FauxUnconfirmed transactions will end up with this status when the outputs have been confirmed + FauxConfirmed, +} + +impl TransactionStatus { + pub fn is_faux(&self) -> bool { + match self { + TransactionStatus::Completed => false, + TransactionStatus::Broadcast => false, + TransactionStatus::MinedUnconfirmed => false, + TransactionStatus::Imported => true, + TransactionStatus::Pending => false, + TransactionStatus::Coinbase => false, + TransactionStatus::MinedConfirmed => false, + TransactionStatus::Rejected => false, + TransactionStatus::FauxUnconfirmed => true, + TransactionStatus::FauxConfirmed => true, + } + } } #[derive(Debug, Error)] @@ -48,6 +69,8 @@ impl TryFrom for TransactionStatus { 5 => Ok(TransactionStatus::Coinbase), 6 => Ok(TransactionStatus::MinedConfirmed), 7 => Ok(TransactionStatus::Rejected), + 8 => Ok(TransactionStatus::FauxUnconfirmed), + 9 => Ok(TransactionStatus::FauxConfirmed), code => Err(TransactionConversionError { code }), } } @@ -71,6 +94,43 @@ impl Display for TransactionStatus { TransactionStatus::Pending => write!(f, "Pending"), TransactionStatus::Coinbase => write!(f, "Coinbase"), TransactionStatus::Rejected => write!(f, "Rejected"), + TransactionStatus::FauxUnconfirmed => write!(f, "FauxUnconfirmed"), + TransactionStatus::FauxConfirmed => write!(f, "FauxConfirmed"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ImportStatus { + /// This transaction import status is used when importing a spendable UTXO + Imported, + /// This transaction import status is used when a one-sided transaction has been scanned but is unconfirmed + FauxUnconfirmed, + /// This transaction import status is used when a one-sided transaction has been scanned and confirmed + FauxConfirmed, +} + +impl TryFrom for TransactionStatus { + type Error = TransactionConversionError; + + fn try_from(value: ImportStatus) -> Result { + match value { + ImportStatus::Imported => Ok(TransactionStatus::Imported), + ImportStatus::FauxUnconfirmed => Ok(TransactionStatus::FauxUnconfirmed), + ImportStatus::FauxConfirmed => Ok(TransactionStatus::FauxConfirmed), + } + } +} + +impl TryFrom for ImportStatus { + type Error = TransactionConversionError; + + fn try_from(value: TransactionStatus) -> Result { + match value { + TransactionStatus::Imported => Ok(ImportStatus::Imported), + TransactionStatus::FauxUnconfirmed => Ok(ImportStatus::FauxUnconfirmed), + TransactionStatus::FauxConfirmed => Ok(ImportStatus::FauxConfirmed), + _ => Err(TransactionConversionError { code: i32::MAX }), } } } diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index 315c931a6a..9bfd99a350 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -25,7 +25,7 @@ use std::{fmt, fmt::Formatter, sync::Arc}; use aes_gcm::Aes256Gcm; use tari_common_types::{ transaction::TxId, - types::{HashOutput, PublicKey}, + types::{BlockHash, HashOutput, PublicKey}, }; use tari_core::{ covenants::Covenant, @@ -106,8 +106,14 @@ pub enum OutputManagerRequest { num_kernels: usize, num_outputs: usize, }, - ScanForRecoverableOutputs(Vec), - ScanOutputs(Vec), + ScanForRecoverableOutputs { + outputs: Vec, + tx_id: TxId, + }, + ScanOutputs { + outputs: Vec, + tx_id: TxId, + }, AddKnownOneSidedPaymentScript(KnownOneSidedPaymentScript), CreateOutputWithFeatures { value: MicroTari, @@ -165,8 +171,8 @@ impl fmt::Display for OutputManagerRequest { "FeeEstimate(amount: {}, fee_per_gram: {}, num_kernels: {}, num_outputs: {})", amount, fee_per_gram, num_kernels, num_outputs ), - ScanForRecoverableOutputs(_) => write!(f, "ScanForRecoverableOutputs"), - ScanOutputs(_) => write!(f, "ScanOutputs"), + ScanForRecoverableOutputs { .. } => write!(f, "ScanForRecoverableOutputs"), + ScanOutputs { .. } => write!(f, "ScanOutputs"), AddKnownOneSidedPaymentScript(_) => write!(f, "AddKnownOneSidedPaymentScript"), CreateOutputWithFeatures { value, features } => { write!(f, "CreateOutputWithFeatures({}, {})", value, features,) @@ -220,12 +226,21 @@ pub enum OutputManagerResponse { RewoundOutputs(Vec), ScanOutputs(Vec), AddKnownOneSidedPaymentScript, - CreateOutputWithFeatures { output: Box }, - CreatePayToSelfWithOutputs { transaction: Box, tx_id: TxId }, + CreateOutputWithFeatures { + output: Box, + }, + CreatePayToSelfWithOutputs { + transaction: Box, + tx_id: TxId, + }, ReinstatedCancelledInboundTx, CoinbaseAbandonedSet, ClaimHtlcTransaction((TxId, MicroTari, MicroTari, Transaction)), - OutputStatusesByTxId(Vec), + OutputStatusesByTxId { + statuses: Vec, + mined_height: Option, + block_hash: Option, + }, } pub type OutputManagerEventSender = broadcast::Sender>; @@ -642,10 +657,11 @@ impl OutputManagerHandle { pub async fn scan_for_recoverable_outputs( &mut self, outputs: Vec, + tx_id: TxId, ) -> Result, OutputManagerError> { match self .handle - .call(OutputManagerRequest::ScanForRecoverableOutputs(outputs)) + .call(OutputManagerRequest::ScanForRecoverableOutputs { outputs, tx_id }) .await?? { OutputManagerResponse::RewoundOutputs(outputs) => Ok(outputs), @@ -656,8 +672,13 @@ impl OutputManagerHandle { pub async fn scan_outputs_for_one_sided_payments( &mut self, outputs: Vec, + tx_id: TxId, ) -> Result, OutputManagerError> { - match self.handle.call(OutputManagerRequest::ScanOutputs(outputs)).await?? { + match self + .handle + .call(OutputManagerRequest::ScanOutputs { outputs, tx_id }) + .await?? + { OutputManagerResponse::ScanOutputs(outputs) => Ok(outputs), _ => Err(OutputManagerError::UnexpectedApiResponse), } @@ -749,13 +770,20 @@ impl OutputManagerHandle { } } - pub async fn get_output_statuses_by_tx_id(&mut self, tx_id: TxId) -> Result, OutputManagerError> { + pub async fn get_output_statuses_by_tx_id( + &mut self, + tx_id: TxId, + ) -> Result<(Vec, Option, Option), OutputManagerError> { match self .handle .call(OutputManagerRequest::GetOutputStatusesByTxId(tx_id)) .await?? { - OutputManagerResponse::OutputStatusesByTxId(s) => Ok(s), + OutputManagerResponse::OutputStatusesByTxId { + statuses, + mined_height, + block_hash, + } => Ok((statuses, mined_height, block_hash)), _ => Err(OutputManagerError::UnexpectedApiResponse), } } diff --git a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs index d6f4200e0c..afa80139f6 100644 --- a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs +++ b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs @@ -24,7 +24,10 @@ use std::{sync::Arc, time::Instant}; use log::*; use rand::rngs::OsRng; -use tari_common_types::types::{PrivateKey, PublicKey, RangeProof}; +use tari_common_types::{ + transaction::TxId, + types::{PrivateKey, PublicKey, RangeProof}, +}; use tari_core::transactions::{ transaction::{TransactionOutput, UnblindedOutput}, CryptoFactories, @@ -73,6 +76,7 @@ where TBackend: OutputManagerBackend + 'static pub async fn scan_and_recover_outputs( &mut self, outputs: Vec, + tx_id: TxId, ) -> Result, OutputManagerError> { let start = Instant::now(); let outputs_length = outputs.len(); @@ -133,7 +137,7 @@ where TBackend: OutputManagerBackend + 'static Some(proof), )?; let output_hex = db_output.commitment.to_hex(); - if let Err(e) = self.db.add_unspent_output(db_output).await { + if let Err(e) = self.db.add_unspent_output_with_tx_id(tx_id, db_output).await { match e { OutputManagerStorageError::DuplicateOutput => { info!( diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 3dfb07ecc1..cef6150185 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -29,7 +29,7 @@ use log::*; use rand::{rngs::OsRng, RngCore}; use tari_common_types::{ transaction::TxId, - types::{HashOutput, PrivateKey, PublicKey}, + types::{BlockHash, HashOutput, PrivateKey, PublicKey}, }; use tari_comms::{types::CommsPublicKey, NodeIdentity}; use tari_core::{ @@ -358,16 +358,16 @@ where OutputManagerRequest::GetPublicRewindKeys => Ok(OutputManagerResponse::PublicRewindKeys(Box::new( self.resources.master_key_manager.get_rewind_public_keys(), ))), - OutputManagerRequest::ScanForRecoverableOutputs(outputs) => StandardUtxoRecoverer::new( + OutputManagerRequest::ScanForRecoverableOutputs { outputs, tx_id } => StandardUtxoRecoverer::new( self.resources.master_key_manager.clone(), self.resources.factories.clone(), self.resources.db.clone(), ) - .scan_and_recover_outputs(outputs) + .scan_and_recover_outputs(outputs, tx_id) .await .map(OutputManagerResponse::RewoundOutputs), - OutputManagerRequest::ScanOutputs(outputs) => self - .scan_outputs_for_one_sided_payments(outputs) + OutputManagerRequest::ScanOutputs { outputs, tx_id } => self + .scan_outputs_for_one_sided_payments(outputs, tx_id) .await .map(OutputManagerResponse::ScanOutputs), OutputManagerRequest::AddKnownOneSidedPaymentScript(known_script) => self @@ -415,16 +415,36 @@ where .create_htlc_refund_transaction(output, fee_per_gram) .await .map(OutputManagerResponse::ClaimHtlcTransaction), - OutputManagerRequest::GetOutputStatusesByTxId(tx_id) => self - .get_output_status_by_tx_id(tx_id) - .await - .map(OutputManagerResponse::OutputStatusesByTxId), + OutputManagerRequest::GetOutputStatusesByTxId(tx_id) => { + let (statuses, mined_height, block_hash) = self.get_output_status_by_tx_id(tx_id).await?; + Ok(OutputManagerResponse::OutputStatusesByTxId { + statuses, + mined_height, + block_hash, + }) + }, } } - async fn get_output_status_by_tx_id(&self, tx_id: TxId) -> Result, OutputManagerError> { + async fn get_output_status_by_tx_id( + &self, + tx_id: TxId, + ) -> Result<(Vec, Option, Option), OutputManagerError> { let outputs = self.resources.db.fetch_outputs_by_tx_id(tx_id).await?; - Ok(outputs.into_iter().map(|uo| uo.status).collect()) + let statuses = outputs.clone().into_iter().map(|uo| uo.status).collect(); + // We need the maximum mined height and corresponding block hash (faux transactions outputs can have different + // mined heights) + let (mut last_height, mut max_mined_height, mut block_hash) = (0u64, None, None); + for uo in outputs { + if let Some(height) = uo.mined_height { + if last_height < height { + last_height = height; + max_mined_height = uo.mined_height; + block_hash = uo.mined_in_block.clone(); + } + } + } + Ok((statuses, max_mined_height, block_hash)) } async fn claim_sha_atomic_swap_with_hash( @@ -1893,6 +1913,7 @@ where async fn scan_outputs_for_one_sided_payments( &mut self, outputs: Vec, + tx_id: TxId, ) -> Result, OutputManagerError> { let known_one_sided_payment_scripts: Vec = self.resources.db.get_all_known_one_sided_payment_scripts().await?; @@ -1945,7 +1966,7 @@ where )?; let output_hex = output.commitment.to_hex(); - match self.resources.db.add_unspent_output(db_output).await { + match self.resources.db.add_unspent_output_with_tx_id(tx_id, db_output).await { Ok(_) => { rewound_outputs.push(rewound_output); }, diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 8a9fde45dd..d996d7cf74 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -23,7 +23,10 @@ use std::{collections::HashMap, fmt, fmt::Formatter, sync::Arc}; use aes_gcm::Aes256Gcm; -use tari_common_types::{transaction::TxId, types::PublicKey}; +use tari_common_types::{ + transaction::{ImportStatus, TxId}, + types::PublicKey, +}; use tari_comms::types::CommsPublicKey; use tari_core::transactions::{ tari_amount::MicroTari, @@ -72,7 +75,15 @@ pub enum TransactionServiceRequest { }, SendShaAtomicSwapTransaction(CommsPublicKey, MicroTari, MicroTari, String), CancelTransaction(TxId), - ImportUtxo(MicroTari, CommsPublicKey, String, Option), + ImportUtxoWithStatus { + amount: MicroTari, + source_public_key: CommsPublicKey, + message: String, + maturity: Option, + import_status: ImportStatus, + tx_id: Option, + current_height: Option, + }, SubmitTransactionToSelf(TxId, Transaction, MicroTari, MicroTari, String), SetLowPowerMode, SetNormalPowerMode, @@ -123,12 +134,23 @@ impl fmt::Display for TransactionServiceRequest { f.write_str(&format!("SendShaAtomicSwapTransaction (to {}, {}, {})", k, v, msg)) }, Self::CancelTransaction(t) => f.write_str(&format!("CancelTransaction ({})", t)), - Self::ImportUtxo(v, k, msg, maturity) => f.write_str(&format!( - "ImportUtxo (from {}, {}, {} with maturity: {})", - k, - v, - msg, - maturity.unwrap_or(0) + Self::ImportUtxoWithStatus { + amount, + source_public_key, + message, + maturity, + import_status, + tx_id, + current_height, + } => f.write_str(&format!( + "ImportUtxo (from {}, {}, {} with maturity {} and {:?} and {:?} and {:?})", + source_public_key, + amount, + message, + maturity.unwrap_or(0), + import_status, + tx_id, + current_height, )), Self::SubmitTransactionToSelf(tx_id, _, _, _, _) => f.write_str(&format!("SubmitTransaction ({})", tx_id)), Self::SetLowPowerMode => f.write_str("SetLowPowerMode "), @@ -189,6 +211,15 @@ pub enum TransactionEvent { TransactionCancelled(TxId, TxRejection), TransactionBroadcast(TxId), TransactionImported(TxId), + FauxTransactionUnconfirmed { + tx_id: TxId, + num_confirmations: u64, + is_valid: bool, + }, + FauxTransactionConfirmed { + tx_id: TxId, + is_valid: bool, + }, TransactionMined { tx_id: TxId, is_valid: bool, @@ -242,6 +273,20 @@ impl fmt::Display for TransactionEvent { TransactionEvent::TransactionImported(tx) => { write!(f, "TransactionImported for {}", tx) }, + TransactionEvent::FauxTransactionUnconfirmed { + tx_id, + num_confirmations, + is_valid, + } => { + write!( + f, + "FauxTransactionUnconfirmed for {} with num confirmations: {}. is_valid: {}", + tx_id, num_confirmations, is_valid + ) + }, + TransactionEvent::FauxTransactionConfirmed { tx_id, is_valid } => { + write!(f, "FauxTransactionConfirmed for {}. is_valid: {}", tx_id, is_valid) + }, TransactionEvent::TransactionMined { tx_id, is_valid } => { write!(f, "TransactionMined for {}. is_valid: {}", tx_id, is_valid) }, @@ -517,21 +562,27 @@ impl TransactionServiceHandle { } } - pub async fn import_utxo( + pub async fn import_utxo_with_status( &mut self, amount: MicroTari, source_public_key: CommsPublicKey, message: String, maturity: Option, + import_status: ImportStatus, + tx_id: Option, + current_height: Option, ) -> Result { match self .handle - .call(TransactionServiceRequest::ImportUtxo( + .call(TransactionServiceRequest::ImportUtxoWithStatus { amount, source_public_key, message, maturity, - )) + import_status, + tx_id, + current_height, + }) .await?? { TransactionServiceResponse::UtxoImported(tx_id) => Ok(tx_id), 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 d32efc4900..3d5f8189b5 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 @@ -451,6 +451,7 @@ where inbound_tx.timestamp, TransactionDirection::Inbound, None, + None, ); self.resources 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 4007eeaa77..c9e92680ac 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 @@ -511,6 +511,7 @@ where Utc::now().naive_utc(), TransactionDirection::Outbound, None, + None, ); self.resources diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs index b348c29758..3572317408 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs @@ -396,12 +396,23 @@ where mined_in_block.clone(), num_confirmations, num_confirmations >= self.config.num_confirmations_required, + status.is_faux(), ) .await .for_protocol(self.operation_id.as_u64())?; if num_confirmations >= self.config.num_confirmations_required { - self.publish_event(TransactionEvent::TransactionMined { tx_id, is_valid: true }) + if status.is_faux() { + self.publish_event(TransactionEvent::FauxTransactionConfirmed { tx_id, is_valid: true }) + } else { + self.publish_event(TransactionEvent::TransactionMined { tx_id, is_valid: true }) + } + } else if status.is_faux() { + self.publish_event(TransactionEvent::FauxTransactionUnconfirmed { + tx_id, + num_confirmations, + is_valid: true, + }) } else { self.publish_event(TransactionEvent::TransactionMinedUnconfirmed { tx_id, @@ -441,6 +452,7 @@ where mined_in_block.clone(), num_confirmations, num_confirmations >= self.config.num_confirmations_required, + false, ) .await .for_protocol(self.operation_id.as_u64())?; diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 7ce9c6a77b..4389a4c955 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -34,7 +34,7 @@ use log::*; use rand::rngs::OsRng; use sha2::Sha256; use tari_common_types::{ - transaction::{TransactionDirection, TransactionStatus, TxId}, + transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, types::{PrivateKey, PublicKey}, }; use tari_comms::{peer_manager::NodeIdentity, types::CommsPublicKey}; @@ -93,7 +93,7 @@ use crate::{ models::CompletedTransaction, }, tasks::{ - check_imported_transaction_status::check_imported_transactions, + check_faux_transaction_status::check_faux_transactions, send_finalized_transaction::send_finalized_transaction_message, send_transaction_cancelled::send_transaction_cancelled_message, send_transaction_reply::send_transaction_reply, @@ -644,8 +644,24 @@ where TransactionServiceRequest::GetAnyTransaction(tx_id) => Ok(TransactionServiceResponse::AnyTransaction( Box::new(self.db.get_any_transaction(tx_id).await?), )), - TransactionServiceRequest::ImportUtxo(value, source_public_key, message, maturity) => self - .add_utxo_import_transaction(value, source_public_key, message, maturity) + TransactionServiceRequest::ImportUtxoWithStatus { + amount, + source_public_key, + message, + maturity, + import_status, + tx_id, + current_height, + } => self + .add_utxo_import_transaction_with_status( + amount, + source_public_key, + message, + maturity, + import_status, + tx_id, + current_height, + ) .await .map(TransactionServiceResponse::UtxoImported), TransactionServiceRequest::SubmitTransactionToSelf(tx_id, tx, fee, amount, message) => self @@ -748,7 +764,21 @@ where if let OutputManagerEvent::TxoValidationSuccess(_) = (*event).clone() { let db = self.db.clone(); let output_manager_handle = self.output_manager_service.clone(); - tokio::spawn(check_imported_transactions(output_manager_handle, db)); + let metadata = match self.wallet_db.get_chain_metadata().await { + Ok(data) => data, + Err(_) => None, + }; + let tip_height = match metadata { + Some(val) => val.height_of_longest_chain(), + None => 0u64, + }; + let event_publisher = self.event_publisher.clone(); + tokio::spawn(check_faux_transactions( + output_manager_handle, + db, + event_publisher, + tip_height, + )); } } @@ -812,6 +842,7 @@ where Utc::now().naive_utc(), TransactionDirection::Inbound, None, + None, ), ) .await?; @@ -1016,6 +1047,7 @@ where Utc::now().naive_utc(), TransactionDirection::Outbound, None, + None, ), ) .await?; @@ -1159,6 +1191,7 @@ where Utc::now().naive_utc(), TransactionDirection::Outbound, None, + None, ), ) .await?; @@ -2023,35 +2056,46 @@ where } /// Add a completed transaction to the Transaction Manager to record directly importing a spendable UTXO. - pub async fn add_utxo_import_transaction( + pub async fn add_utxo_import_transaction_with_status( &mut self, value: MicroTari, source_public_key: CommsPublicKey, message: String, maturity: Option, + import_status: ImportStatus, + tx_id: Option, + current_height: Option, ) -> Result { - let tx_id = TxId::new_random(); + let tx_id = if let Some(id) = tx_id { id } else { TxId::new_random() }; self.db - .add_utxo_import_transaction( + .add_utxo_import_transaction_with_status( tx_id, value, source_public_key, self.node_identity.public_key().clone(), message, maturity, + import_status.clone(), + current_height, ) .await?; - let _ = self - .event_publisher - .send(Arc::new(TransactionEvent::TransactionImported(tx_id))) - .map_err(|e| { - trace!( - target: LOG_TARGET, - "Error sending event, usually because there are no subscribers: {:?}", - e - ); + let transaction_event = match import_status { + ImportStatus::Imported => TransactionEvent::TransactionImported(tx_id), + ImportStatus::FauxUnconfirmed => TransactionEvent::FauxTransactionUnconfirmed { + tx_id, + num_confirmations: 0, + is_valid: true, + }, + ImportStatus::FauxConfirmed => TransactionEvent::FauxTransactionConfirmed { tx_id, is_valid: true }, + }; + let _ = self.event_publisher.send(Arc::new(transaction_event)).map_err(|e| { + trace!( + target: LOG_TARGET, + "Error sending event, usually because there are no subscribers: {:?}", e - }); + ); + e + }); Ok(tx_id) } @@ -2105,6 +2149,7 @@ where Utc::now().naive_utc(), TransactionDirection::Inbound, None, + None, ), ) .await?; @@ -2165,6 +2210,7 @@ where Utc::now().naive_utc(), TransactionDirection::Inbound, Some(block_height), + None, ), ) .await?; diff --git a/base_layer/wallet/src/transaction_service/storage/database.rs b/base_layer/wallet/src/transaction_service/storage/database.rs index aede6aad88..d55a794272 100644 --- a/base_layer/wallet/src/transaction_service/storage/database.rs +++ b/base_layer/wallet/src/transaction_service/storage/database.rs @@ -22,6 +22,7 @@ use std::{ collections::HashMap, + convert::TryFrom, fmt, fmt::{Display, Error, Formatter}, sync::Arc, @@ -31,7 +32,7 @@ use aes_gcm::Aes256Gcm; use chrono::Utc; use log::*; use tari_common_types::{ - transaction::{TransactionDirection, TransactionStatus, TxId}, + transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, types::{BlindingFactor, BlockHash}, }; use tari_comms::types::CommsPublicKey; @@ -132,6 +133,7 @@ pub trait TransactionBackend: Send + Sync + Clone { mined_in_block: BlockHash, num_confirmations: u64, is_confirmed: bool, + is_faux: bool, ) -> Result<(), TransactionStorageError>; /// Clears the mined block and height of a transaction fn set_transaction_as_unmined(&self, tx_id: TxId) -> Result<(), TransactionStorageError>; @@ -142,6 +144,11 @@ pub trait TransactionBackend: Send + Sync + Clone { &self, ) -> Result, TransactionStorageError>; fn fetch_imported_transactions(&self) -> Result, TransactionStorageError>; + fn fetch_unconfirmed_faux_transactions(&self) -> Result, TransactionStorageError>; + fn fetch_confirmed_faux_transactions_from_height( + &self, + height: u64, + ) -> Result, TransactionStorageError>; } #[derive(Clone, PartialEq)] @@ -442,6 +449,27 @@ where T: TransactionBackend + 'static Ok(t) } + pub async fn get_unconfirmed_faux_transactions( + &self, + ) -> Result, TransactionStorageError> { + let db_clone = self.db.clone(); + let t = tokio::task::spawn_blocking(move || db_clone.fetch_unconfirmed_faux_transactions()) + .await + .map_err(|err| TransactionStorageError::BlockingTaskSpawnError(err.to_string()))??; + Ok(t) + } + + pub async fn get_confirmed_faux_transactions_from_height( + &self, + height: u64, + ) -> Result, TransactionStorageError> { + let db_clone = self.db.clone(); + let t = tokio::task::spawn_blocking(move || db_clone.fetch_confirmed_faux_transactions_from_height(height)) + .await + .map_err(|err| TransactionStorageError::BlockingTaskSpawnError(err.to_string()))??; + Ok(t) + } + pub async fn fetch_last_mined_transaction(&self) -> Result, TransactionStorageError> { self.db.fetch_last_mined_transaction() } @@ -702,7 +730,8 @@ where T: TransactionBackend + 'static .and_then(|inner_result| inner_result) } - pub async fn add_utxo_import_transaction( + /// Faux transaction added to the database with imported status + pub async fn add_utxo_import_transaction_with_status( &self, tx_id: TxId, amount: MicroTari, @@ -710,6 +739,8 @@ where T: TransactionBackend + 'static comms_public_key: CommsPublicKey, message: String, maturity: Option, + import_status: ImportStatus, + current_height: Option, ) -> Result<(), TransactionStorageError> { let transaction = CompletedTransaction::new( tx_id, @@ -724,11 +755,12 @@ where T: TransactionBackend + 'static BlindingFactor::default(), BlindingFactor::default(), ), - TransactionStatus::Imported, + TransactionStatus::try_from(import_status)?, message, Utc::now().naive_utc(), TransactionDirection::Inbound, maturity, + current_height, ); let db_clone = self.db.clone(); @@ -816,6 +848,7 @@ where T: TransactionBackend + 'static mined_in_block: BlockHash, num_confirmations: u64, is_confirmed: bool, + is_faux: bool, ) -> Result<(), TransactionStorageError> { let db_clone = self.db.clone(); tokio::task::spawn_blocking(move || { @@ -826,6 +859,7 @@ where T: TransactionBackend + 'static mined_in_block, num_confirmations, is_confirmed, + is_faux, ) }) .await diff --git a/base_layer/wallet/src/transaction_service/storage/models.rs b/base_layer/wallet/src/transaction_service/storage/models.rs index d0c0702571..756ed3312b 100644 --- a/base_layer/wallet/src/transaction_service/storage/models.rs +++ b/base_layer/wallet/src/transaction_service/storage/models.rs @@ -159,6 +159,7 @@ impl CompletedTransaction { timestamp: NaiveDateTime, direction: TransactionDirection, coinbase_block_height: Option, + mined_height: Option, ) -> Self { let transaction_signature = if let Some(excess_sig) = transaction.first_kernel_excess_sig() { excess_sig.clone() @@ -183,7 +184,7 @@ impl CompletedTransaction { valid: true, transaction_signature, confirmations: None, - mined_height: None, + mined_height, mined_in_block: None, } } 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 8dc318d5e8..a3a6566a86 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -1009,6 +1009,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { mined_in_block: BlockHash, num_confirmations: u64, is_confirmed: bool, + is_faux: bool, ) -> Result<(), TransactionStorageError> { let start = Instant::now(); let conn = self.database_connection.get_pooled_connection()?; @@ -1022,6 +1023,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { num_confirmations, is_confirmed, &conn, + is_faux, )?; }, Err(TransactionStorageError::DieselError(DieselError::NotFound)) => { @@ -1048,6 +1050,8 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let tx = completed_transactions::table + // Note: Check 'mined_in_block' as well as 'mined_height' is populated for faux transactions before it is confirmed + .filter(completed_transactions::mined_in_block.is_not_null()) .filter(completed_transactions::mined_height.is_not_null()) .filter(completed_transactions::mined_height.gt(0)) .order_by(completed_transactions::mined_height.desc()) @@ -1072,6 +1076,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { Ok(result) } + // This method returns completed but unconfirmed transactions that were not imported fn fetch_unconfirmed_transactions_info(&self) -> Result, TransactionStorageError> { let start = Instant::now(); let conn = self.database_connection.get_pooled_connection()?; @@ -1226,6 +1231,40 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }) .collect::, TransactionStorageError>>() } + + fn fetch_unconfirmed_faux_transactions(&self) -> Result, TransactionStorageError> { + let conn = self.database_connection.get_pooled_connection()?; + CompletedTransactionSql::index_by_status_and_cancelled(TransactionStatus::FauxUnconfirmed, false, &conn)? + .into_iter() + .map(|mut ct: CompletedTransactionSql| { + if let Err(e) = self.decrypt_if_necessary(&mut ct) { + return Err(e); + } + CompletedTransaction::try_from(ct).map_err(TransactionStorageError::from) + }) + .collect::, TransactionStorageError>>() + } + + fn fetch_confirmed_faux_transactions_from_height( + &self, + height: u64, + ) -> Result, TransactionStorageError> { + let conn = self.database_connection.get_pooled_connection()?; + CompletedTransactionSql::index_by_status_and_cancelled_from_block_height( + TransactionStatus::FauxConfirmed, + false, + height as i64, + &conn, + )? + .into_iter() + .map(|mut ct: CompletedTransactionSql| { + if let Err(e) = self.decrypt_if_necessary(&mut ct) { + return Err(e); + } + CompletedTransaction::try_from(ct).map_err(TransactionStorageError::from) + }) + .collect::, TransactionStorageError>>() + } } #[derive(Debug, PartialEq)] @@ -1674,6 +1713,19 @@ impl CompletedTransactionSql { .load::(conn)?) } + pub fn index_by_status_and_cancelled_from_block_height( + status: TransactionStatus, + cancelled: bool, + block_height: i64, + conn: &SqliteConnection, + ) -> Result, TransactionStorageError> { + Ok(completed_transactions::table + .filter(completed_transactions::cancelled.eq(cancelled as i32)) + .filter(completed_transactions::status.eq(status as i32)) + .filter(completed_transactions::mined_height.ge(block_height)) + .load::(conn)?) + } + pub fn index_coinbase_at_block_height( block_height: i64, conn: &SqliteConnection, @@ -1753,6 +1805,8 @@ impl CompletedTransactionSql { pub fn set_as_unmined(&self, conn: &SqliteConnection) -> Result<(), TransactionStorageError> { let status = if self.coinbase_block_height.is_some() { Some(TransactionStatus::Coinbase as i32) + } else if self.status == TransactionStatus::FauxConfirmed as i32 { + Some(TransactionStatus::FauxUnconfirmed as i32) } else if self.status == TransactionStatus::Broadcast as i32 { Some(TransactionStatus::Broadcast as i32) } else { @@ -1798,11 +1852,18 @@ impl CompletedTransactionSql { num_confirmations: u64, is_confirmed: bool, conn: &SqliteConnection, + is_faux: bool, ) -> Result<(), TransactionStorageError> { let status = if self.coinbase_block_height.is_some() && !is_valid { TransactionStatus::Coinbase as i32 } else if is_confirmed { - TransactionStatus::MinedConfirmed as i32 + if is_faux { + TransactionStatus::FauxConfirmed as i32 + } else { + TransactionStatus::MinedConfirmed as i32 + } + } else if is_faux { + TransactionStatus::FauxUnconfirmed as i32 } else { TransactionStatus::MinedUnconfirmed as i32 }; @@ -1983,7 +2044,7 @@ pub struct UnconfirmedTransactionInfoSql { } impl UnconfirmedTransactionInfoSql { - /// This method returns completed but unconfirmed transactions + /// This method returns completed but unconfirmed transactions that were not imported or scanned pub fn fetch_unconfirmed_transactions_info( conn: &SqliteConnection, ) -> Result, TransactionStorageError> { @@ -1999,6 +2060,8 @@ impl UnconfirmedTransactionInfoSql { .filter( completed_transactions::status .ne(TransactionStatus::Imported as i32) + .and(completed_transactions::status.ne(TransactionStatus::FauxUnconfirmed as i32)) + .and(completed_transactions::status.ne(TransactionStatus::FauxConfirmed as i32)) .and( completed_transactions::mined_height .is_null() diff --git a/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs b/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs new file mode 100644 index 0000000000..6a5a776fe4 --- /dev/null +++ b/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs @@ -0,0 +1,167 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// 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. + +use std::sync::Arc; + +use log::*; +use tari_common_types::types::BlockHash; + +use crate::{ + output_manager_service::{handle::OutputManagerHandle, storage::OutputStatus}, + transaction_service::{ + config::TransactionServiceConfig, + handle::{TransactionEvent, TransactionEventSender}, + storage::{ + database::{TransactionBackend, TransactionDatabase}, + models::CompletedTransaction, + }, + }, +}; + +const LOG_TARGET: &str = "wallet::transaction_service::service"; + +pub async fn check_faux_transactions( + mut output_manager: OutputManagerHandle, + db: TransactionDatabase, + event_publisher: TransactionEventSender, + tip_height: u64, +) { + let mut all_faux_transactions: Vec = match db.get_imported_transactions().await { + Ok(txs) => txs, + Err(e) => { + error!(target: LOG_TARGET, "Problem retrieving imported transactions: {}", e); + return; + }, + }; + let mut unconfirmed_faux = match db.get_unconfirmed_faux_transactions().await { + Ok(txs) => txs, + Err(e) => { + error!( + target: LOG_TARGET, + "Problem retrieving unconfirmed faux transactions: {}", e + ); + return; + }, + }; + all_faux_transactions.append(&mut unconfirmed_faux); + // Reorged faux transactions cannot be detected by excess signature, thus use last known confirmed transaction + // height or current tip height with safety margin to determine if these should be returned + let last_mined_transaction = match db.fetch_last_mined_transaction().await { + Ok(tx) => tx, + Err(_) => None, + }; + let height_with_margin = tip_height.saturating_sub(100); + let check_height = if let Some(tx) = last_mined_transaction { + tx.mined_height.unwrap_or(height_with_margin) + } else { + height_with_margin + }; + let mut confirmed_faux = match db.get_confirmed_faux_transactions_from_height(check_height).await { + Ok(txs) => txs, + Err(e) => { + error!( + target: LOG_TARGET, + "Problem retrieving confirmed faux transactions: {}", e + ); + return; + }, + }; + all_faux_transactions.append(&mut confirmed_faux); + + debug!( + target: LOG_TARGET, + "Checking {} faux transaction statuses", + all_faux_transactions.len() + ); + for tx in all_faux_transactions.into_iter() { + let (status, mined_height, block_hash) = match output_manager.get_output_statuses_by_tx_id(tx.tx_id).await { + Ok(s) => s, + Err(e) => { + error!(target: LOG_TARGET, "Problem retrieving output statuses: {}", e); + return; + }, + }; + if !status.iter().any(|s| s != &OutputStatus::Unspent) { + let mined_height = if let Some(height) = mined_height { + height + } else { + tip_height + }; + let mined_in_block: BlockHash = if let Some(hash) = block_hash { + hash + } else { + vec![0u8; 32] + }; + let is_valid = tip_height >= mined_height; + let is_confirmed = tip_height.saturating_sub(mined_height) >= + TransactionServiceConfig::default().num_confirmations_required; + let num_confirmations = tip_height - mined_height; + debug!( + target: LOG_TARGET, + "Updating faux transaction: TxId({}), mined_height({}), is_confirmed({}), num_confirmations({}), \ + is_valid({})", + tx.tx_id, + mined_height, + is_confirmed, + num_confirmations, + is_valid, + ); + let result = db + .set_transaction_mined_height( + tx.tx_id, + true, + mined_height, + mined_in_block, + num_confirmations, + is_confirmed, + is_valid, + ) + .await; + if let Err(e) = result { + error!( + target: LOG_TARGET, + "Error setting faux transaction to mined confirmed: {}", e + ); + } else { + let transaction_event = match is_confirmed { + false => TransactionEvent::FauxTransactionUnconfirmed { + tx_id: tx.tx_id, + num_confirmations: 0, + is_valid, + }, + true => TransactionEvent::FauxTransactionConfirmed { + tx_id: tx.tx_id, + is_valid, + }, + }; + let _ = event_publisher.send(Arc::new(transaction_event)).map_err(|e| { + trace!( + target: LOG_TARGET, + "Error sending event, usually because there are no subscribers: {:?}", + e + ); + e + }); + } + } + } +} diff --git a/base_layer/wallet/src/transaction_service/tasks/check_imported_transaction_status.rs b/base_layer/wallet/src/transaction_service/tasks/check_imported_transaction_status.rs deleted file mode 100644 index fe1ea992b2..0000000000 --- a/base_layer/wallet/src/transaction_service/tasks/check_imported_transaction_status.rs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2021. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// 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. - -use log::*; - -use crate::{ - output_manager_service::{handle::OutputManagerHandle, storage::OutputStatus}, - transaction_service::{ - config::TransactionServiceConfig, - storage::database::{TransactionBackend, TransactionDatabase}, - }, -}; - -const LOG_TARGET: &str = "wallet::transaction_service::service"; - -pub async fn check_imported_transactions( - mut output_manager: OutputManagerHandle, - db: TransactionDatabase, -) { - let imported_transactions = match db.get_imported_transactions().await { - Ok(txs) => txs, - Err(e) => { - error!(target: LOG_TARGET, "Problem retrieving imported transactions: {}", e); - return; - }, - }; - - for tx in imported_transactions.into_iter() { - let status = match output_manager.get_output_statuses_by_tx_id(tx.tx_id).await { - Ok(s) => s, - Err(e) => { - error!(target: LOG_TARGET, "Problem retrieving output statuses: {}", e); - return; - }, - }; - if !status.iter().any(|s| s != &OutputStatus::Unspent) { - debug!( - target: LOG_TARGET, - "Faux Transaction (TxId: {}) updated to confirmed", tx.tx_id - ); - if let Err(e) = db - .set_transaction_mined_height( - tx.tx_id, - true, - 0, - vec![0u8; 32], - TransactionServiceConfig::default().num_confirmations_required, - true, - ) - .await - { - error!( - target: LOG_TARGET, - "Error setting faux transaction to mined confirmed: {}", e - ); - } - } - } -} diff --git a/base_layer/wallet/src/transaction_service/tasks/mod.rs b/base_layer/wallet/src/transaction_service/tasks/mod.rs index dce38a144c..292932156e 100644 --- a/base_layer/wallet/src/transaction_service/tasks/mod.rs +++ b/base_layer/wallet/src/transaction_service/tasks/mod.rs @@ -20,7 +20,7 @@ // 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. -pub mod check_imported_transaction_status; +pub mod check_faux_transaction_status; pub mod send_finalized_transaction; pub mod send_transaction_cancelled; pub mod send_transaction_reply; 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 a56bb36e4d..5eec755347 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 @@ -28,7 +28,10 @@ use std::{ use chrono::Utc; use futures::StreamExt; use log::*; -use tari_common_types::{transaction::TxId, types::HashOutput}; +use tari_common_types::{ + transaction::{ImportStatus, TxId}, + types::HashOutput, +}; use tari_comms::{peer_manager::NodeId, types::CommsPublicKey, PeerConnection}; use tari_core::{ base_node::rpc::BaseNodeWalletRpcClient, @@ -46,6 +49,7 @@ use tokio::sync::broadcast; use crate::{ error::WalletError, storage::database::WalletBackend, + transaction_service::error::{TransactionServiceError, TransactionStorageError}, utxo_scanner_service::{ error::UtxoScannerError, handle::UtxoScannerEvent, @@ -427,10 +431,12 @@ where TBackend: WalletBackend + 'static total_scanned += outputs.len(); let start = Instant::now(); - let found_outputs = self.scan_for_outputs(outputs).await?; + let (tx_id, found_outputs) = self.scan_for_outputs(outputs).await?; scan_for_outputs_profiling.push(start.elapsed()); - let (count, amount) = self.import_utxos_to_transaction_service(found_outputs).await?; + let (count, amount) = self + .import_utxos_to_transaction_service(found_outputs, tx_id, current_height) + .await?; self.resources .db @@ -481,17 +487,18 @@ where TBackend: WalletBackend + 'static async fn scan_for_outputs( &mut self, outputs: Vec, - ) -> Result, UtxoScannerError> { + ) -> Result<(TxId, Vec<(UnblindedOutput, String)>), UtxoScannerError> { let mut found_outputs: Vec<(UnblindedOutput, String)> = Vec::new(); + let tx_id = TxId::new_random(); if self.mode == UtxoScannerMode::Recovery { found_outputs.append( &mut self .resources .output_manager_service - .scan_for_recoverable_outputs(outputs.clone()) + .scan_for_recoverable_outputs(outputs.clone(), tx_id) .await? .into_iter() - .map(|v| (v, format!("Recovered on {}.", Utc::now().naive_utc()))) + .map(|uo| (uo, format!("Recovered output on {}.", Utc::now().naive_utc()))) .collect(), ); }; @@ -499,36 +506,54 @@ where TBackend: WalletBackend + 'static &mut self .resources .output_manager_service - .scan_outputs_for_one_sided_payments(outputs.clone()) + .scan_outputs_for_one_sided_payments(outputs.clone(), tx_id) .await? .into_iter() - .map(|v| { + .map(|uo| { ( - v, - format!("Detected one-sided transaction on {}.", Utc::now().naive_utc()), + uo, + format!("Detected one-sided transaction output on {}.", Utc::now().naive_utc()), ) }) .collect(), ); - Ok(found_outputs) + Ok((tx_id, found_outputs)) } async fn import_utxos_to_transaction_service( &mut self, utxos: Vec<(UnblindedOutput, String)>, + tx_id: TxId, + current_height: u64, ) -> Result<(u64, MicroTari), UtxoScannerError> { let mut num_recovered = 0u64; let mut total_amount = MicroTari::from(0); let source_public_key = self.resources.node_identity.public_key().clone(); - for uo in utxos { + for (uo, message) in utxos { match self - .import_unblinded_utxo_to_transaction_service(uo.0.clone(), &source_public_key, uo.1) + .import_unblinded_utxo_to_transaction_service( + uo.clone(), + &source_public_key, + message, + tx_id, + current_height, + ) .await { Ok(_) => { num_recovered = num_recovered.saturating_add(1); - total_amount += uo.0.value; + total_amount += uo.value; + }, + Err(WalletError::TransactionServiceError(TransactionServiceError::TransactionStorageError( + TransactionStorageError::DuplicateOutput, + ))) => { + info!( + target: LOG_TARGET, + "Recoverer attempted to add a duplicate output to the database for faux transaction ({}); \ + ignoring it as this is not a real error", + tx_id + ); }, Err(e) => return Err(UtxoScannerError::UtxoImportError(e.to_string())), } @@ -565,33 +590,38 @@ where TBackend: WalletBackend + 'static let _ = self.event_sender.send(event); } - /// A faux incoming transaction will be created to provide a record of the event of importing a UTXO. The TxId of - /// the generated transaction is returned. + /// A faux incoming transaction will be created to provide a record of the event of importing a scanned UTXO. The + /// TxId of the generated transaction is returned. pub async fn import_unblinded_utxo_to_transaction_service( &mut self, unblinded_output: UnblindedOutput, source_public_key: &CommsPublicKey, message: String, + tx_id: TxId, + current_height: u64, ) -> Result { let tx_id = self .resources .transaction_service - .import_utxo( + .import_utxo_with_status( unblinded_output.value, source_public_key.clone(), message, Some(unblinded_output.features.maturity), + ImportStatus::FauxUnconfirmed, + Some(tx_id), + Some(current_height), ) .await?; info!( target: LOG_TARGET, - "UTXO (Commitment: {}) imported into wallet", + "UTXO (Commitment: {}) imported into wallet as 'ImportStatus::FauxUnconfirmed'", unblinded_output .as_transaction_input(&self.resources.factories.commitment)? .commitment() .map_err(WalletError::TransactionError)? - .to_hex() + .to_hex(), ); Ok(tx_id) diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 46801d53ab..47ec10a00e 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -26,7 +26,7 @@ use digest::Digest; use log::*; use tari_common::configuration::bootstrap::ApplicationType; use tari_common_types::{ - transaction::TxId, + transaction::{ImportStatus, TxId}, types::{ComSignature, PrivateKey, PublicKey}, }; use tari_comms::{ @@ -401,7 +401,15 @@ where let tx_id = self .transaction_service - .import_utxo(amount, source_public_key.clone(), message, Some(features.maturity)) + .import_utxo_with_status( + amount, + source_public_key.clone(), + message, + Some(features.maturity), + ImportStatus::Imported, + None, + None, + ) .await?; let commitment_hex = unblinded_output @@ -416,7 +424,7 @@ where info!( target: LOG_TARGET, - "UTXO (Commitment: {}) imported into wallet", commitment_hex + "UTXO (Commitment: {}) imported into wallet as 'ImportStatus::Imported'", commitment_hex ); Ok(tx_id) @@ -433,11 +441,14 @@ where ) -> Result { let tx_id = self .transaction_service - .import_utxo( + .import_utxo_with_status( unblinded_output.value, source_public_key.clone(), message, Some(unblinded_output.features.maturity), + ImportStatus::Imported, + None, + None, ) .await?; @@ -447,12 +458,12 @@ where info!( target: LOG_TARGET, - "UTXO (Commitment: {}) imported into wallet", + "UTXO (Commitment: {}) imported into wallet as 'ImportStatus::Imported'", unblinded_output .as_transaction_input(&self.factories.commitment)? .commitment() .map_err(WalletError::TransactionError)? - .to_hex() + .to_hex(), ); Ok(tx_id) 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 dbe7909342..10de433c8d 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -1904,12 +1904,12 @@ async fn test_get_status_by_tx_id() { let (mut oms, _, _shutdown, _, _, _, _, _) = setup_output_manager_service(backend, true).await; let (_ti, uo1) = make_input(&mut OsRng.clone(), MicroTari::from(10000), &factories.commitment); - oms.add_unvalidated_output(TxId::from(1), uo1, None).await.unwrap(); + oms.add_unvalidated_output(TxId::from(1u64), uo1, None).await.unwrap(); let (_ti, uo2) = make_input(&mut OsRng.clone(), MicroTari::from(10000), &factories.commitment); - oms.add_unvalidated_output(TxId::from(2), uo2, None).await.unwrap(); + oms.add_unvalidated_output(TxId::from(2u64), uo2, None).await.unwrap(); - let status = oms.get_output_statuses_by_tx_id(TxId::from(1)).await.unwrap(); + let (status, _, _) = oms.get_output_statuses_by_tx_id(TxId::from(1u64)).await.unwrap(); assert_eq!(status.len(), 1); assert_eq!(status[0], OutputStatus::EncumberedToBeReceived); diff --git a/base_layer/wallet/tests/support/output_manager_service_mock.rs b/base_layer/wallet/tests/support/output_manager_service_mock.rs index a409dd2c4c..1837418439 100644 --- a/base_layer/wallet/tests/support/output_manager_service_mock.rs +++ b/base_layer/wallet/tests/support/output_manager_service_mock.rs @@ -96,7 +96,10 @@ impl OutputManagerServiceMock { ) { info!(target: LOG_TARGET, "Handling Request: {}", request); match request { - OutputManagerRequest::ScanForRecoverableOutputs(requested_outputs) => { + OutputManagerRequest::ScanForRecoverableOutputs { + outputs: requested_outputs, + tx_id: _tx_id, + } => { let lock = acquire_lock!(self.state.recoverable_outputs); let outputs = (*lock) .clone() @@ -117,7 +120,10 @@ impl OutputManagerServiceMock { e }); }, - OutputManagerRequest::ScanOutputs(_to) => { + OutputManagerRequest::ScanOutputs { + outputs: _to, + tx_id: _tx_id, + } => { let lock = acquire_lock!(self.state.one_sided_payments); let outputs = (*lock).clone(); let _ = reply_tx diff --git a/base_layer/wallet/tests/support/transaction_service_mock.rs b/base_layer/wallet/tests/support/transaction_service_mock.rs index cce11b5707..f4bb390242 100644 --- a/base_layer/wallet/tests/support/transaction_service_mock.rs +++ b/base_layer/wallet/tests/support/transaction_service_mock.rs @@ -95,9 +95,9 @@ impl TransactionServiceMock { info!(target: LOG_TARGET, "Handling Request: {}", request); match request { - TransactionServiceRequest::ImportUtxo(_, _, _, _) => { + TransactionServiceRequest::ImportUtxoWithStatus { .. } => { let _ = reply_tx - .send(Ok(TransactionServiceResponse::UtxoImported(TxId::from(42)))) + .send(Ok(TransactionServiceResponse::UtxoImported(TxId::from(42u64)))) .map_err(|e| { warn!(target: LOG_TARGET, "Failed to send reply"); e diff --git a/base_layer/wallet/tests/transaction_service_tests/service.rs b/base_layer/wallet/tests/transaction_service_tests/service.rs index b145f8ef8c..b2632638d7 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -38,7 +38,7 @@ use prost::Message; use rand::rngs::OsRng; use tari_common_types::{ chain_metadata::ChainMetadata, - transaction::{TransactionDirection, TransactionStatus, TxId}, + transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, types::{PrivateKey, PublicKey, Signature}, }; use tari_comms::{ @@ -908,7 +908,7 @@ fn recover_one_sided_transaction() { let outputs = completed_tx.transaction.body.outputs().clone(); let unblinded = bob_oms - .scan_outputs_for_one_sided_payments(outputs.clone()) + .scan_outputs_for_one_sided_payments(outputs.clone(), TxId::new_random()) .await .unwrap(); // Bob should be able to claim 1 output. @@ -916,7 +916,10 @@ fn recover_one_sided_transaction() { assert_eq!(value, unblinded[0].value); // Should ignore already existing outputs - let unblinded = bob_oms.scan_outputs_for_one_sided_payments(outputs).await.unwrap(); + let unblinded = bob_oms + .scan_outputs_for_one_sided_payments(outputs, TxId::new_random()) + .await + .unwrap(); assert!(unblinded.is_empty()); }); } @@ -5394,52 +5397,115 @@ fn test_update_faux_tx_on_oms_validation() { let mut alice_ts_interface = setup_transaction_service_no_comms(&mut runtime, factories.clone(), connection, None); - let tx_id = runtime - .block_on(alice_ts_interface.transaction_service_handle.import_utxo( + let tx_id_1 = runtime + .block_on(alice_ts_interface.transaction_service_handle.import_utxo_with_status( MicroTari::from(10000), alice_ts_interface.base_node_identity.public_key().clone(), "blah".to_string(), None, + ImportStatus::Imported, + None, + None, )) .unwrap(); - - let (_ti, uo) = make_input(&mut OsRng.clone(), MicroTari::from(10000), &factories.commitment); - runtime - .block_on( - alice_ts_interface - .output_manager_service_handle - .add_output_with_tx_id(tx_id, uo, None), - ) + let tx_id_2 = runtime + .block_on(alice_ts_interface.transaction_service_handle.import_utxo_with_status( + MicroTari::from(20000), + alice_ts_interface.base_node_identity.public_key().clone(), + "one-sided 1".to_string(), + None, + ImportStatus::FauxUnconfirmed, + None, + None, + )) .unwrap(); - - let transaction = runtime - .block_on(alice_ts_interface.transaction_service_handle.get_any_transaction(tx_id)) - .unwrap() + let tx_id_3 = runtime + .block_on(alice_ts_interface.transaction_service_handle.import_utxo_with_status( + MicroTari::from(30000), + alice_ts_interface.base_node_identity.public_key().clone(), + "one-sided 2".to_string(), + None, + ImportStatus::FauxConfirmed, + None, + None, + )) .unwrap(); - if let WalletTransaction::Completed(tx) = transaction { - assert_eq!(tx.status, TransactionStatus::Imported); - } else { - panic!("Should find a complete transaction"); + + let (_ti, uo_1) = make_input(&mut OsRng.clone(), MicroTari::from(10000), &factories.commitment); + let (_ti, uo_2) = make_input(&mut OsRng.clone(), MicroTari::from(20000), &factories.commitment); + let (_ti, uo_3) = make_input(&mut OsRng.clone(), MicroTari::from(30000), &factories.commitment); + for (tx_id, uo) in [(tx_id_1, uo_1), (tx_id_2, uo_2), (tx_id_3, uo_3)] { + runtime + .block_on( + alice_ts_interface + .output_manager_service_handle + .add_output_with_tx_id(tx_id, uo, None), + ) + .unwrap(); } + for tx_id in [tx_id_1, tx_id_2, tx_id_3] { + let transaction = runtime + .block_on(alice_ts_interface.transaction_service_handle.get_any_transaction(tx_id)) + .unwrap() + .unwrap(); + if tx_id == tx_id_1 { + if let WalletTransaction::Completed(tx) = &transaction { + assert_eq!(tx.status, TransactionStatus::Imported); + } else { + panic!("Should find a complete Imported transaction"); + } + } + if tx_id == tx_id_2 { + if let WalletTransaction::Completed(tx) = &transaction { + assert_eq!(tx.status, TransactionStatus::FauxUnconfirmed); + } else { + panic!("Should find a complete FauxUnconfirmed transaction"); + } + } + if tx_id == tx_id_3 { + if let WalletTransaction::Completed(tx) = &transaction { + assert_eq!(tx.status, TransactionStatus::FauxConfirmed); + } else { + panic!("Should find a complete FauxConfirmed transaction"); + } + } + } + + // This will change the status of the imported transaction alice_ts_interface .output_manager_service_event_publisher - .send(Arc::new(OutputManagerEvent::TxoValidationSuccess(1))) + .send(Arc::new(OutputManagerEvent::TxoValidationSuccess(1u64))) .unwrap(); - let mut found = false; + let mut found_imported = false; + let mut found_faux_unconfirmed = false; + let mut found_faux_confirmed = false; for _ in 0..20 { runtime.block_on(async { sleep(Duration::from_secs(1)).await }); - let transaction = runtime - .block_on(alice_ts_interface.transaction_service_handle.get_any_transaction(tx_id)) - .unwrap() - .unwrap(); - if let WalletTransaction::Completed(tx) = transaction { - if tx.status == TransactionStatus::MinedConfirmed { - found = true; - break; + for tx_id in [tx_id_1, tx_id_2, tx_id_3] { + let transaction = runtime + .block_on(alice_ts_interface.transaction_service_handle.get_any_transaction(tx_id)) + .unwrap() + .unwrap(); + if let WalletTransaction::Completed(tx) = transaction { + if tx_id == tx_id_1 && tx.status == TransactionStatus::FauxUnconfirmed && !found_imported { + found_imported = true; + } + if tx_id == tx_id_2 && tx.status == TransactionStatus::FauxUnconfirmed && !found_faux_unconfirmed { + found_faux_unconfirmed = true; + } + if tx_id == tx_id_3 && tx.status == TransactionStatus::FauxConfirmed && !found_faux_confirmed { + found_faux_confirmed = true; + } } } + if found_imported && found_faux_unconfirmed && found_faux_confirmed { + break; + } } - assert!(found, "Should have found the updated status"); + assert!( + found_imported && found_faux_unconfirmed && found_faux_confirmed, + "Should have found the updated statuses" + ); } diff --git a/base_layer/wallet/tests/transaction_service_tests/storage.rs b/base_layer/wallet/tests/transaction_service_tests/storage.rs index 72b2cf2052..b23b8876ab 100644 --- a/base_layer/wallet/tests/transaction_service_tests/storage.rs +++ b/base_layer/wallet/tests/transaction_service_tests/storage.rs @@ -323,7 +323,7 @@ pub fn test_db_backend(backend: T) { assert!(runtime.block_on(db.fetch_last_mined_transaction()).unwrap().is_none()); runtime - .block_on(db.set_transaction_mined_height(completed_txs[0].tx_id, true, 10, [0u8; 16].to_vec(), 5, true)) + .block_on(db.set_transaction_mined_height(completed_txs[0].tx_id, true, 10, [0u8; 16].to_vec(), 5, true, false)) .unwrap(); assert_eq!( @@ -596,7 +596,7 @@ async fn import_tx_and_read_it_from_db() { let sqlite_db = TransactionServiceSqliteDatabase::new(connection, Some(cipher)); let transaction = CompletedTransaction::new( - TxId::from(1), + TxId::from(1u64), PublicKey::default(), PublicKey::default(), MicroTari::from(100000), @@ -605,25 +605,94 @@ async fn import_tx_and_read_it_from_db() { Vec::new(), Vec::new(), Vec::new(), - PrivateKey::default(), - PrivateKey::default(), + PrivateKey::random(&mut OsRng), + PrivateKey::random(&mut OsRng), ), TransactionStatus::Imported, "message".to_string(), Utc::now().naive_utc(), TransactionDirection::Inbound, Some(0), + Some(5), ); sqlite_db .write(WriteOperation::Insert(DbKeyValuePair::CompletedTransaction( - TxId::from(1), + TxId::from(1u64), Box::new(transaction), ))) .unwrap(); - let db_tx = sqlite_db.fetch_imported_transactions().unwrap(); + let transaction = CompletedTransaction::new( + TxId::from(2u64), + PublicKey::default(), + PublicKey::default(), + MicroTari::from(100000), + MicroTari::from(0), + Transaction::new( + Vec::new(), + Vec::new(), + Vec::new(), + PrivateKey::random(&mut OsRng), + PrivateKey::random(&mut OsRng), + ), + TransactionStatus::FauxUnconfirmed, + "message".to_string(), + Utc::now().naive_utc(), + TransactionDirection::Inbound, + Some(0), + Some(6), + ); + + sqlite_db + .write(WriteOperation::Insert(DbKeyValuePair::CompletedTransaction( + TxId::from(2u64), + Box::new(transaction), + ))) + .unwrap(); + let transaction = CompletedTransaction::new( + TxId::from(3u64), + PublicKey::default(), + PublicKey::default(), + MicroTari::from(100000), + MicroTari::from(0), + Transaction::new( + Vec::new(), + Vec::new(), + Vec::new(), + PrivateKey::random(&mut OsRng), + PrivateKey::random(&mut OsRng), + ), + TransactionStatus::FauxConfirmed, + "message".to_string(), + Utc::now().naive_utc(), + TransactionDirection::Inbound, + Some(0), + Some(7), + ); + + sqlite_db + .write(WriteOperation::Insert(DbKeyValuePair::CompletedTransaction( + TxId::from(3u64), + Box::new(transaction), + ))) + .unwrap(); + + let db_tx = sqlite_db.fetch_imported_transactions().unwrap(); assert_eq!(db_tx.len(), 1); assert_eq!(db_tx.first().unwrap().tx_id, TxId::from(1)); + assert_eq!(db_tx.first().unwrap().mined_height, Some(5)); + + let db_tx = sqlite_db.fetch_unconfirmed_faux_transactions().unwrap(); + assert_eq!(db_tx.len(), 1); + assert_eq!(db_tx.first().unwrap().tx_id, TxId::from(2)); + assert_eq!(db_tx.first().unwrap().mined_height, Some(6)); + + let db_tx = sqlite_db.fetch_confirmed_faux_transactions_from_height(10).unwrap(); + assert_eq!(db_tx.len(), 0); + let db_tx = sqlite_db.fetch_confirmed_faux_transactions_from_height(4).unwrap(); + assert_eq!(db_tx.len(), 1); + assert_eq!(db_tx.first().unwrap().tx_id, TxId::from(3)); + assert_eq!(db_tx.first().unwrap().mined_height, Some(7)); } 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 97f55d1c1e..c84666355f 100644 --- a/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs +++ b/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs @@ -197,6 +197,7 @@ pub async fn add_transaction_to_database( Utc::now().naive_local(), TransactionDirection::Outbound, coinbase_block_height, + None, ); completed_tx1.valid = valid; db.insert_completed_transaction(tx_id, completed_tx1).await.unwrap(); diff --git a/base_layer/wallet/tests/wallet.rs b/base_layer/wallet/tests/wallet.rs index f122812223..4c56387e0b 100644 --- a/base_layer/wallet/tests/wallet.rs +++ b/base_layer/wallet/tests/wallet.rs @@ -45,8 +45,10 @@ use std::{panic, path::Path, sync::Arc, time::Duration}; use rand::rngs::OsRng; +use support::{comms_and_services::get_next_memory_address, utils::make_input}; use tari_common_types::{ chain_metadata::ChainMetadata, + transaction::TransactionStatus, types::{PrivateKey, PublicKey}, }; use tari_comms::{ @@ -55,11 +57,14 @@ use tari_comms::{ types::CommsPublicKey, }; use tari_comms_dht::{store_forward::SafConfig, DhtConfig}; -use tari_core::transactions::{ - tari_amount::{uT, MicroTari}, - test_helpers::{create_unblinded_output, TestParams}, - transaction::OutputFeatures, - CryptoFactories, +use tari_core::{ + covenants::Covenant, + transactions::{ + tari_amount::{uT, MicroTari}, + test_helpers::{create_unblinded_output, TestParams}, + transaction::OutputFeatures, + CryptoFactories, + }, }; use tari_crypto::{ inputs, @@ -97,11 +102,7 @@ use tari_wallet::{ }; use tempfile::tempdir; use tokio::{runtime::Runtime, time::sleep}; - pub mod support; -use support::{comms_and_services::get_next_memory_address, utils::make_input}; -use tari_common_types::transaction::TransactionStatus; -use tari_core::covenants::Covenant; use tari_wallet::output_manager_service::storage::database::OutputManagerDatabase; fn create_peer(public_key: CommsPublicKey, net_address: Multiaddr) -> Peer { @@ -763,10 +764,10 @@ async fn test_import_utxo() { .import_utxo( utxo.value, &utxo.spending_key, - script, - input, + script.clone(), + input.clone(), base_node_identity.public_key(), - features, + features.clone(), "Testing".to_string(), utxo.metadata_signature.clone(), &p.script_private_key, diff --git a/base_layer/wallet_ffi/src/callback_handler.rs b/base_layer/wallet_ffi/src/callback_handler.rs index f3a4c7d3b5..4cbaaa5232 100644 --- a/base_layer/wallet_ffi/src/callback_handler.rs +++ b/base_layer/wallet_ffi/src/callback_handler.rs @@ -40,6 +40,12 @@ //! `callback_transaction_mined` - This will be called when a Broadcast transaction is detected as mined via a base //! node request //! +//! `callback_faux_transaction_confirmed` - This will be called when an imported output, recovered output or one-sided +//! transaction is detected as mined +//! +//! `callback_faux_transaction_unconfirmed` - This will be called when a recovered output or one-sided transaction is +//! freshly imported or when an imported transaction transitions from Imported to FauxUnconfirmed +//! //! `callback_discovery_process_complete` - This will be called when a `send_transacion(..)` call is made to a peer //! whose address is not known and a discovery process must be conducted. The outcome of the discovery process is //! relayed via this callback @@ -80,6 +86,8 @@ where TBackend: TransactionBackend + 'static callback_transaction_broadcast: unsafe extern "C" fn(*mut CompletedTransaction), callback_transaction_mined: unsafe extern "C" fn(*mut CompletedTransaction), callback_transaction_mined_unconfirmed: unsafe extern "C" fn(*mut CompletedTransaction, u64), + callback_faux_transaction_confirmed: unsafe extern "C" fn(*mut CompletedTransaction), + callback_faux_transaction_unconfirmed: unsafe extern "C" fn(*mut CompletedTransaction, u64), callback_direct_send_result: unsafe extern "C" fn(u64, bool), callback_store_and_forward_send_result: unsafe extern "C" fn(u64, bool), callback_transaction_cancellation: unsafe extern "C" fn(*mut CompletedTransaction, u64), @@ -118,6 +126,8 @@ where TBackend: TransactionBackend + 'static callback_transaction_broadcast: unsafe extern "C" fn(*mut CompletedTransaction), callback_transaction_mined: unsafe extern "C" fn(*mut CompletedTransaction), callback_transaction_mined_unconfirmed: unsafe extern "C" fn(*mut CompletedTransaction, u64), + callback_faux_transaction_confirmed: unsafe extern "C" fn(*mut CompletedTransaction), + callback_faux_transaction_unconfirmed: unsafe extern "C" fn(*mut CompletedTransaction, u64), callback_direct_send_result: unsafe extern "C" fn(u64, bool), callback_store_and_forward_send_result: unsafe extern "C" fn(u64, bool), callback_transaction_cancellation: unsafe extern "C" fn(*mut CompletedTransaction, u64), @@ -151,6 +161,14 @@ where TBackend: TransactionBackend + 'static target: LOG_TARGET, "TransactionMinedUnconfirmedCallback -> Assigning Fn: {:?}", callback_transaction_mined_unconfirmed ); + info!( + target: LOG_TARGET, + "FauxTransactionConfirmedCallback -> Assigning Fn: {:?}", callback_faux_transaction_confirmed + ); + info!( + target: LOG_TARGET, + "FauxTransactionUnconfirmedCallback -> Assigning Fn: {:?}", callback_faux_transaction_unconfirmed + ); info!( target: LOG_TARGET, "DirectSendResultCallback -> Assigning Fn: {:?}", callback_direct_send_result @@ -191,6 +209,8 @@ where TBackend: TransactionBackend + 'static callback_transaction_broadcast, callback_transaction_mined, callback_transaction_mined_unconfirmed, + callback_faux_transaction_confirmed, + callback_faux_transaction_unconfirmed, callback_direct_send_result, callback_store_and_forward_send_result, callback_transaction_cancellation, @@ -262,6 +282,14 @@ where TBackend: TransactionBackend + 'static self.receive_transaction_mined_unconfirmed_event(tx_id, num_confirmations).await; self.trigger_balance_refresh().await; }, + TransactionEvent::FauxTransactionConfirmed{tx_id, is_valid: _} => { + self.receive_faux_transaction_confirmed_event(tx_id).await; + self.trigger_balance_refresh().await; + }, + TransactionEvent::FauxTransactionUnconfirmed{tx_id, num_confirmations, is_valid: _} => { + self.receive_faux_transaction_unconfirmed_event(tx_id, num_confirmations).await; + self.trigger_balance_refresh().await; + }, TransactionEvent::TransactionValidationStateChanged(_request_key) => { self.trigger_balance_refresh().await; }, @@ -272,7 +300,7 @@ where TBackend: TransactionBackend + 'static self.transaction_validation_complete_event(request_key.as_u64(), false); }, TransactionEvent::TransactionMinedRequestTimedOut(_tx_id) | - TransactionEvent::TransactionImported(_tx_id) | + TransactionEvent::TransactionImported(_tx_id)| TransactionEvent::TransactionCompletedImmediately(_tx_id) => { self.trigger_balance_refresh().await; @@ -493,7 +521,7 @@ where TBackend: TransactionBackend + 'static Ok(tx) => { debug!( target: LOG_TARGET, - "Calling Received Transaction Mined callback function for TxId: {}", tx_id + "Calling Received Transaction Mined Unconfirmed callback function for TxId: {}", tx_id ); let boxing = Box::into_raw(Box::new(tx)); unsafe { @@ -504,6 +532,38 @@ where TBackend: TransactionBackend + 'static } } + async fn receive_faux_transaction_confirmed_event(&mut self, tx_id: TxId) { + match self.db.get_completed_transaction(tx_id).await { + Ok(tx) => { + debug!( + target: LOG_TARGET, + "Calling Received Faux Transaction Confirmed callback function for TxId: {}", tx_id + ); + let boxing = Box::into_raw(Box::new(tx)); + unsafe { + (self.callback_faux_transaction_confirmed)(boxing); + } + }, + Err(e) => error!(target: LOG_TARGET, "Error retrieving Completed Transaction: {:?}", e), + } + } + + async fn receive_faux_transaction_unconfirmed_event(&mut self, tx_id: TxId, confirmations: u64) { + match self.db.get_completed_transaction(tx_id).await { + Ok(tx) => { + debug!( + target: LOG_TARGET, + "Calling Received Faux Transaction Unconfirmed callback function for TxId: {}", tx_id + ); + let boxing = Box::into_raw(Box::new(tx)); + unsafe { + (self.callback_faux_transaction_unconfirmed)(boxing, confirmations); + } + }, + Err(e) => error!(target: LOG_TARGET, "Error retrieving Completed Transaction: {:?}", e), + } + } + fn transaction_validation_complete_event(&mut self, request_key: u64, success: bool) { debug!( target: LOG_TARGET, diff --git a/base_layer/wallet_ffi/src/callback_handler_tests.rs b/base_layer/wallet_ffi/src/callback_handler_tests.rs index 64732fc9b1..6d38f44146 100644 --- a/base_layer/wallet_ffi/src/callback_handler_tests.rs +++ b/base_layer/wallet_ffi/src/callback_handler_tests.rs @@ -77,6 +77,8 @@ mod test { pub broadcast_tx_callback_called: bool, pub mined_tx_callback_called: bool, pub mined_tx_unconfirmed_callback_called: u64, + pub faux_tx_confirmed_callback_called: bool, + pub faux_tx_unconfirmed_callback_called: u64, pub direct_send_callback_called: bool, pub store_and_forward_send_callback_called: bool, pub tx_cancellation_callback_called_completed: bool, @@ -98,6 +100,8 @@ mod test { broadcast_tx_callback_called: false, mined_tx_callback_called: false, mined_tx_unconfirmed_callback_called: 0, + faux_tx_confirmed_callback_called: false, + faux_tx_unconfirmed_callback_called: 0, direct_send_callback_called: false, store_and_forward_send_callback_called: false, callback_txo_validation_complete: 0, @@ -158,6 +162,20 @@ mod test { Box::from_raw(tx); } + unsafe extern "C" fn faux_confirmed_callback(tx: *mut CompletedTransaction) { + let mut lock = CALLBACK_STATE.lock().unwrap(); + lock.faux_tx_confirmed_callback_called = true; + drop(lock); + Box::from_raw(tx); + } + + unsafe extern "C" fn faux_unconfirmed_callback(tx: *mut CompletedTransaction, confirmations: u64) { + let mut lock = CALLBACK_STATE.lock().unwrap(); + lock.faux_tx_unconfirmed_callback_called = confirmations; + drop(lock); + Box::from_raw(tx); + } + unsafe extern "C" fn direct_send_callback(_tx_id: u64, _result: bool) { let mut lock = CALLBACK_STATE.lock().unwrap(); lock.direct_send_callback_called = true; @@ -230,6 +248,10 @@ mod test { "1".to_string(), Utc::now().naive_utc(), ); + runtime + .block_on(db.add_pending_inbound_transaction(1u64.into(), inbound_tx.clone())) + .unwrap(); + let completed_tx = CompletedTransaction::new( 2u64.into(), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), @@ -248,7 +270,12 @@ mod test { Utc::now().naive_utc(), TransactionDirection::Inbound, None, + None, ); + runtime + .block_on(db.insert_completed_transaction(2u64.into(), completed_tx.clone())) + .unwrap(); + let stp = SenderTransactionProtocol::new_placeholder(); let outbound_tx = OutboundTransaction::new( 3u64.into(), @@ -261,33 +288,76 @@ mod test { Utc::now().naive_utc(), false, ); + runtime + .block_on(db.add_pending_outbound_transaction(3u64.into(), outbound_tx.clone())) + .unwrap(); + runtime.block_on(db.cancel_pending_transaction(3u64.into())).unwrap(); + let inbound_tx_cancelled = InboundTransaction { tx_id: 4u64.into(), ..inbound_tx.clone() }; - let completed_tx_cancelled = CompletedTransaction { - tx_id: 5u64.into(), - ..completed_tx.clone() - }; - - runtime - .block_on(db.add_pending_inbound_transaction(1u64.into(), inbound_tx.clone())) - .unwrap(); - runtime - .block_on(db.insert_completed_transaction(2u64.into(), completed_tx.clone())) - .unwrap(); runtime .block_on(db.add_pending_inbound_transaction(4u64.into(), inbound_tx_cancelled)) .unwrap(); runtime.block_on(db.cancel_pending_transaction(4u64.into())).unwrap(); + + let completed_tx_cancelled = CompletedTransaction { + tx_id: 5u64.into(), + ..completed_tx.clone() + }; runtime .block_on(db.insert_completed_transaction(5u64.into(), completed_tx_cancelled.clone())) .unwrap(); runtime.block_on(db.reject_completed_transaction(5u64.into())).unwrap(); + + let faux_unconfirmed_tx = CompletedTransaction::new( + 6u64.into(), + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), + MicroTari::from(100), + MicroTari::from(2000), + Transaction::new( + Vec::new(), + Vec::new(), + Vec::new(), + BlindingFactor::default(), + BlindingFactor::default(), + ), + TransactionStatus::FauxUnconfirmed, + "6".to_string(), + Utc::now().naive_utc(), + TransactionDirection::Inbound, + None, + Some(2), + ); runtime - .block_on(db.add_pending_outbound_transaction(3u64.into(), outbound_tx.clone())) + .block_on(db.insert_completed_transaction(6u64.into(), faux_unconfirmed_tx.clone())) + .unwrap(); + + let faux_confirmed_tx = CompletedTransaction::new( + 7u64.into(), + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), + MicroTari::from(100), + MicroTari::from(2000), + Transaction::new( + Vec::new(), + Vec::new(), + Vec::new(), + BlindingFactor::default(), + BlindingFactor::default(), + ), + TransactionStatus::FauxConfirmed, + "7".to_string(), + Utc::now().naive_utc(), + TransactionDirection::Inbound, + None, + Some(5), + ); + runtime + .block_on(db.insert_completed_transaction(7u64.into(), faux_confirmed_tx.clone())) .unwrap(); - runtime.block_on(db.cancel_pending_transaction(3u64.into())).unwrap(); let (transaction_event_sender, transaction_event_receiver) = broadcast::channel(20); let (oms_event_sender, oms_event_receiver) = broadcast::channel(20); @@ -330,6 +400,8 @@ mod test { broadcast_callback, mined_callback, mined_unconfirmed_callback, + faux_confirmed_callback, + faux_unconfirmed_callback, direct_send_callback, store_and_forward_send_callback, tx_cancellation_callback, @@ -478,7 +550,7 @@ mod test { .unwrap(); balance.available_balance -= completed_tx_cancelled.amount; - mock_output_manager_service_state.set_balance(balance); + mock_output_manager_service_state.set_balance(balance.clone()); // Balance updated should be detected with following event, total = 5 times oms_event_sender .send(Arc::new(OutputManagerEvent::TxoValidationSuccess(1u64))) @@ -520,6 +592,51 @@ mod test { .send(Arc::new(TransactionEvent::TransactionValidationCompleted(4u64.into()))) .unwrap(); + balance.pending_incoming_balance += faux_unconfirmed_tx.amount; + mock_output_manager_service_state.set_balance(balance.clone()); + // Balance updated should be detected with following event, total = 6 times + transaction_event_sender + .send(Arc::new(TransactionEvent::FauxTransactionUnconfirmed { + tx_id: 6u64.into(), + num_confirmations: 2, + is_valid: true, + })) + .unwrap(); + let start = Instant::now(); + while start.elapsed().as_secs() < 10 { + { + let lock = CALLBACK_STATE.lock().unwrap(); + if lock.callback_balance_updated == 6 { + callback_balance_updated = 6; + break; + } + } + thread::sleep(Duration::from_millis(100)); + } + assert_eq!(callback_balance_updated, 6); + + balance.available_balance += faux_confirmed_tx.amount; + mock_output_manager_service_state.set_balance(balance.clone()); + // Balance updated should be detected with following event, total = 7 times + transaction_event_sender + .send(Arc::new(TransactionEvent::FauxTransactionConfirmed { + tx_id: 7u64.into(), + is_valid: true, + })) + .unwrap(); + let start = Instant::now(); + while start.elapsed().as_secs() < 10 { + { + let lock = CALLBACK_STATE.lock().unwrap(); + if lock.callback_balance_updated == 7 { + callback_balance_updated = 7; + break; + } + } + thread::sleep(Duration::from_millis(100)); + } + assert_eq!(callback_balance_updated, 7); + dht_event_sender .send(Arc::new(DhtEvent::StoreAndForwardMessagesReceived)) .unwrap(); @@ -541,6 +658,8 @@ mod test { assert!(lock.broadcast_tx_callback_called); assert!(lock.mined_tx_callback_called); assert_eq!(lock.mined_tx_unconfirmed_callback_called, 22u64); + assert!(lock.faux_tx_confirmed_callback_called); + assert_eq!(lock.faux_tx_unconfirmed_callback_called, 2u64); assert!(lock.direct_send_callback_called); assert!(lock.store_and_forward_send_callback_called); assert!(lock.tx_cancellation_callback_called_inbound); @@ -548,7 +667,7 @@ mod test { assert!(lock.tx_cancellation_callback_called_outbound); assert!(lock.saf_messages_received); assert_eq!(lock.callback_txo_validation_complete, 3); - assert_eq!(lock.callback_balance_updated, 5); + assert_eq!(lock.callback_balance_updated, 7); assert_eq!(lock.callback_transaction_validation_complete, 7); assert_eq!(lock.connectivity_status_callback_called, 7); diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 5041dd5084..e957cfc596 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -3231,7 +3231,11 @@ unsafe fn init_logging( /// `callback_transaction_mined` - The callback function pointer matching the function signature. This will be called /// when a Broadcast transaction is detected as mined AND confirmed. /// `callback_transaction_mined_unconfirmed` - The callback function pointer matching the function signature. This will -/// be called when a Broadcast transaction is detected as mined but not yet confirmed. +/// be called when a Broadcast transaction is detected as mined but not yet confirmed. +/// `callback_faux_transaction_confirmed` - The callback function pointer matching the function signature. This will be +/// called when a one-sided transaction is detected as mined AND confirmed. +/// `callback_faux_transaction_unconfirmed` - The callback function pointer matching the function signature. This +/// will be called when a one-sided transaction is detected as mined but not yet confirmed. /// `callback_direct_send_result` - The callback function pointer matching the function signature. This is called /// when a direct send is completed. The first parameter is the transaction id and the second is whether if was /// successful or not. @@ -3293,6 +3297,8 @@ pub unsafe extern "C" fn wallet_create( callback_transaction_broadcast: unsafe extern "C" fn(*mut TariCompletedTransaction), callback_transaction_mined: unsafe extern "C" fn(*mut TariCompletedTransaction), callback_transaction_mined_unconfirmed: unsafe extern "C" fn(*mut TariCompletedTransaction, u64), + callback_faux_transaction_confirmed: unsafe extern "C" fn(*mut TariCompletedTransaction), + callback_faux_transaction_unconfirmed: unsafe extern "C" fn(*mut TariCompletedTransaction, u64), callback_direct_send_result: unsafe extern "C" fn(c_ulonglong, bool), callback_store_and_forward_send_result: unsafe extern "C" fn(c_ulonglong, bool), callback_transaction_cancellation: unsafe extern "C" fn(*mut TariCompletedTransaction, u64), @@ -3499,6 +3505,8 @@ pub unsafe extern "C" fn wallet_create( callback_transaction_broadcast, callback_transaction_mined, callback_transaction_mined_unconfirmed, + callback_faux_transaction_confirmed, + callback_faux_transaction_unconfirmed, callback_direct_send_result, callback_store_and_forward_send_result, callback_transaction_cancellation, @@ -6053,6 +6061,8 @@ mod test { pub broadcast_tx_callback_called: bool, pub mined_tx_callback_called: bool, pub mined_tx_unconfirmed_callback_called: bool, + pub scanned_tx_callback_called: bool, + pub scanned_tx_unconfirmed_callback_called: bool, pub direct_send_callback_called: bool, pub store_and_forward_send_callback_called: bool, pub tx_cancellation_callback_called: bool, @@ -6070,6 +6080,8 @@ mod test { broadcast_tx_callback_called: false, mined_tx_callback_called: false, mined_tx_unconfirmed_callback_called: false, + scanned_tx_callback_called: false, + scanned_tx_unconfirmed_callback_called: false, direct_send_callback_called: false, store_and_forward_send_callback_called: false, tx_cancellation_callback_called: false, @@ -6177,6 +6189,48 @@ mod test { completed_transaction_destroy(tx); } + unsafe extern "C" fn scanned_callback(tx: *mut TariCompletedTransaction) { + assert!(!tx.is_null()); + assert_eq!( + type_of((*tx).clone()), + std::any::type_name::() + ); + assert_eq!((*tx).status, TransactionStatus::FauxConfirmed); + let mut lock = CALLBACK_STATE_FFI.lock().unwrap(); + lock.scanned_tx_callback_called = true; + drop(lock); + completed_transaction_destroy(tx); + } + + unsafe extern "C" fn scanned_unconfirmed_callback(tx: *mut TariCompletedTransaction, _confirmations: u64) { + assert!(!tx.is_null()); + assert_eq!( + type_of((*tx).clone()), + std::any::type_name::() + ); + assert_eq!((*tx).status, TransactionStatus::FauxUnconfirmed); + let mut lock = CALLBACK_STATE_FFI.lock().unwrap(); + lock.scanned_tx_unconfirmed_callback_called = true; + let mut error = 0; + let error_ptr = &mut error as *mut c_int; + let kernel = completed_transaction_get_transaction_kernel(tx, error_ptr); + let excess_hex_ptr = transaction_kernel_get_excess_hex(kernel, error_ptr); + let excess_hex = CString::from_raw(excess_hex_ptr).to_str().unwrap().to_owned(); + assert!(!excess_hex.is_empty()); + let nonce_hex_ptr = transaction_kernel_get_excess_public_nonce_hex(kernel, error_ptr); + let nonce_hex = CString::from_raw(nonce_hex_ptr).to_str().unwrap().to_owned(); + assert!(!nonce_hex.is_empty()); + let sig_hex_ptr = transaction_kernel_get_excess_signature_hex(kernel, error_ptr); + let sig_hex = CString::from_raw(sig_hex_ptr).to_str().unwrap().to_owned(); + assert!(!sig_hex.is_empty()); + string_destroy(excess_hex_ptr as *mut c_char); + string_destroy(sig_hex_ptr as *mut c_char); + string_destroy(nonce_hex_ptr); + transaction_kernel_destroy(kernel); + drop(lock); + completed_transaction_destroy(tx); + } + unsafe extern "C" fn direct_send_callback(_tx_id: c_ulonglong, _result: bool) { // assert!(true); //optimized out by compiler } @@ -6579,6 +6633,8 @@ mod test { broadcast_callback, mined_callback, mined_unconfirmed_callback, + scanned_callback, + scanned_unconfirmed_callback, direct_send_callback, store_and_forward_send_callback, tx_cancellation_callback, @@ -6616,6 +6672,8 @@ mod test { broadcast_callback, mined_callback, mined_unconfirmed_callback, + scanned_callback, + scanned_unconfirmed_callback, direct_send_callback, store_and_forward_send_callback, tx_cancellation_callback, @@ -6719,6 +6777,8 @@ mod test { broadcast_callback, mined_callback, mined_unconfirmed_callback, + scanned_callback, + scanned_unconfirmed_callback, direct_send_callback, store_and_forward_send_callback, tx_cancellation_callback, @@ -6767,6 +6827,8 @@ mod test { broadcast_callback, mined_callback, mined_unconfirmed_callback, + scanned_callback, + scanned_unconfirmed_callback, direct_send_callback, store_and_forward_send_callback, tx_cancellation_callback, @@ -6798,6 +6860,8 @@ mod test { broadcast_callback, mined_callback, mined_unconfirmed_callback, + scanned_callback, + scanned_unconfirmed_callback, direct_send_callback, store_and_forward_send_callback, tx_cancellation_callback, @@ -6824,6 +6888,8 @@ mod test { broadcast_callback, mined_callback, mined_unconfirmed_callback, + scanned_callback, + scanned_unconfirmed_callback, direct_send_callback, store_and_forward_send_callback, tx_cancellation_callback, @@ -6871,6 +6937,8 @@ mod test { broadcast_callback, mined_callback, mined_unconfirmed_callback, + scanned_callback, + scanned_unconfirmed_callback, direct_send_callback, store_and_forward_send_callback, tx_cancellation_callback, @@ -6947,6 +7015,8 @@ mod test { broadcast_callback, mined_callback, mined_unconfirmed_callback, + scanned_callback, + scanned_unconfirmed_callback, direct_send_callback, store_and_forward_send_callback, tx_cancellation_callback, @@ -7154,6 +7224,8 @@ mod test { broadcast_callback, mined_callback, mined_unconfirmed_callback, + scanned_callback, + scanned_unconfirmed_callback, direct_send_callback, store_and_forward_send_callback, tx_cancellation_callback, @@ -7209,6 +7281,8 @@ mod test { broadcast_callback, mined_callback, mined_unconfirmed_callback, + scanned_callback, + scanned_unconfirmed_callback, direct_send_callback, store_and_forward_send_callback, tx_cancellation_callback, diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 889aaea741..cf66c19cbf 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -263,14 +263,19 @@ unsigned long long completed_transaction_get_fee(struct TariCompletedTransaction const char *completed_transaction_get_message(struct TariCompletedTransaction *transaction, int *error_out); // Gets the status of a TariCompletedTransaction -// | Value | Interpretation | +// | Value | Interpretation | // |---|---| -// | -1 | TxNullError | -// | 0 | Completed | -// | 1 | Broadcast | -// | 2 | Mined | -// | 3 | Imported | -// | 4 | Pending | +// | -1 | TxNullError | +// | 0 | Completed | +// | 1 | Broadcast | +// | 2 | MinedUnconfirmed | +// | 3 | Imported | +// | 4 | Pending | +// | 5 | Coinbase | +// | 6 | MinedConfirmed | +// | 7 | Rejected | +// | 8 | FauxUnconfirmed | +// | 9 | FauxConfirmed | int completed_transaction_get_status(struct TariCompletedTransaction *transaction, int *error_out); // Gets the TransactionID of a TariCompletedTransaction @@ -341,14 +346,19 @@ const char *pending_outbound_transaction_get_message(struct TariPendingOutboundT unsigned long long pending_outbound_transaction_get_timestamp(struct TariPendingOutboundTransaction *transaction, int *error_out); // Gets the status of a TariPendingOutboundTransaction -// | Value | Interpretation | +// | Value | Interpretation | // |---|---| -// | -1 | TxNullError | -// | 0 | Completed | -// | 1 | Broadcast | -// | 2 | Mined | -// | 3 | Imported | -// | 4 | Pending | +// | -1 | TxNullError | +// | 0 | Completed | +// | 1 | Broadcast | +// | 2 | MinedUnconfirmed | +// | 3 | Imported | +// | 4 | Pending | +// | 5 | Coinbase | +// | 6 | MinedConfirmed | +// | 7 | Rejected | +// | 8 | FauxUnconfirmed | +// | 9 | FauxConfirmed | int pending_outbound_transaction_get_status(struct TariPendingOutboundTransaction *transaction, int *error_out); // Frees memory for a TariPendingOutboundTactions @@ -383,14 +393,19 @@ unsigned long long pending_inbound_transaction_get_amount(struct TariPendingInbo unsigned long long pending_inbound_transaction_get_timestamp(struct TariPendingInboundTransaction *transaction, int *error_out); // Gets the status of a TariPendingInboundTransaction -// | Value | Interpretation | +// | Value | Interpretation | // |---|---| -// | -1 | TxNullError | -// | 0 | Completed | -// | 1 | Broadcast | -// | 2 | Mined | -// | 3 | Imported | -// | 4 | Pending | +// | -1 | TxNullError | +// | 0 | Completed | +// | 1 | Broadcast | +// | 2 | MinedUnconfirmed | +// | 3 | Imported | +// | 4 | Pending | +// | 5 | Coinbase | +// | 6 | MinedConfirmed | +// | 7 | Rejected | +// | 8 | FauxUnconfirmed | +// | 9 | FauxConfirmed | int pending_inbound_transaction_get_status(struct TariPendingInboundTransaction *transaction, int *error_out); // Frees memory for a TariPendingInboundTransaction @@ -451,6 +466,10 @@ struct TariPublicKeys *comms_list_connected_public_keys(struct TariWallet *walle /// when a Broadcast transaction is detected as mined AND confirmed. /// `callback_transaction_mined_unconfirmed` - The callback function pointer matching the function signature. This will /// be called when a Broadcast transaction is detected as mined but not yet confirmed. +/// `callback_faux_transaction_confirmed` - The callback function pointer matching the function signature. This will be called +/// when a one-sided transaction is detected as mined AND confirmed. +/// `callback_faux_transaction_unconfirmed` - The callback function pointer matching the function signature. This will +/// be called when a one-sided transaction is detected as mined but not yet confirmed. /// `callback_direct_send_result` - The callback function pointer matching the function signature. This is called /// when a direct send is completed. The first parameter is the transaction id and the second is whether if was successful or not. /// `callback_store_and_forward_send_result` - The callback function pointer matching the function signature. This is called @@ -515,6 +534,8 @@ struct TariWallet *wallet_create(struct TariCommsConfig *config, void (*callback_transaction_broadcast)(struct TariCompletedTransaction *), void (*callback_transaction_mined)(struct TariCompletedTransaction *), void (*callback_transaction_mined_unconfirmed)(struct TariCompletedTransaction *, unsigned long long), + void (*callback_faux_transaction_confirmed)(struct TariCompletedTransaction *), + void (*callback_faux_transaction_unconfirmed)(struct TariCompletedTransaction *, unsigned long long), void (*callback_direct_send_result)(unsigned long long, bool), void (*callback_store_and_forward_send_result)(unsigned long long, bool), void (*callback_transaction_cancellation)(struct TariCompletedTransaction *, unsigned long long), diff --git a/integration_tests/features/WalletFFI.feature b/integration_tests/features/WalletFFI.feature index 9e96c20e7d..a00b643873 100644 --- a/integration_tests/features/WalletFFI.feature +++ b/integration_tests/features/WalletFFI.feature @@ -61,7 +61,7 @@ Feature: Wallet FFI And mining node MINER mines 10 blocks Then I wait for wallet SENDER to have at least 1000000 uT And I send 2000000 uT without waiting for broadcast from wallet SENDER to wallet FFI_WALLET at fee 20 - Then ffi wallet FFI_WALLET detects AT_LEAST 1 ffi transactions to be Broadcast + Then ffi wallet FFI_WALLET detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_BROADCAST And wallet SENDER detects all transactions are at least Broadcast And mining node MINER mines 10 blocks Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT @@ -94,11 +94,11 @@ Feature: Wallet FFI And mining node MINER mines 10 blocks Then I wait for wallet SENDER to have at least 1000000 uT And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 20 - Then ffi wallet FFI_WALLET detects AT_LEAST 1 ffi transactions to be Broadcast + Then ffi wallet FFI_WALLET detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_BROADCAST And mining node MINER mines 10 blocks Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 20 - Then ffi wallet FFI_WALLET detects AT_LEAST 2 ffi transactions to be Broadcast + Then ffi wallet FFI_WALLET detects AT_LEAST 2 ffi transactions to be TRANSACTION_STATUS_BROADCAST # The broadcast check does not include delivery; create some holding points to ensure it was received And mining node MINER mines 2 blocks Then all nodes are at height 22 @@ -134,6 +134,7 @@ Feature: Wallet FFI Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT And I stop ffi wallet FFI_WALLET + @critical Scenario: As a client I want to send a one-sided transaction Given I have a seed node SEED And I have a base node BASE1 connected to all seed nodes @@ -143,21 +144,43 @@ Feature: Wallet FFI And I have wallet RECEIVER connected to base node BASE2 And I have mining node MINER connected to base node BASE1 and wallet SENDER And mining node MINER mines 10 blocks - Then I wait for wallet SENDER to have at least 1000000 uT - And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 20 - Then ffi wallet FFI_WALLET detects AT_LEAST 1 ffi transactions to be Broadcast + Then I wait for wallet SENDER to have at least 5000000 uT + And I send 2400000 uT from wallet SENDER to wallet FFI_WALLET at fee 20 + And I send 2400000 uT from wallet SENDER to wallet FFI_WALLET at fee 20 + Then ffi wallet FFI_WALLET detects AT_LEAST 2 ffi transactions to be TRANSACTION_STATUS_BROADCAST And mining node MINER mines 10 blocks - Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT + Then I wait for ffi wallet FFI_WALLET to have at least 4000000 uT And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 20 via one-sided transactions + Then ffi wallet FFI_WALLET detects AT_LEAST 2 ffi transactions to be TRANSACTION_STATUS_BROADCAST And mining node MINER mines 2 blocks Then all nodes are at height 22 - And mining node MINER mines 2 blocks - Then all nodes are at height 24 - And mining node MINER mines 6 blocks - Then I wait for wallet RECEIVER to have at least 1000000 uT - Then I wait for ffi wallet FFI_WALLET to receive 2 mined + Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_FAUX_UNCONFIRMED and valid + And mining node MINER mines 5 blocks + Then all nodes are at height 27 + Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_FAUX_CONFIRMED and valid And I stop ffi wallet FFI_WALLET + @critical + Scenario: As a client I want to receive a one-sided transaction + Given I have a seed node SEED + And I have a base node BASE1 connected to all seed nodes + And I have a base node BASE2 connected to all seed nodes + And I have wallet SENDER connected to base node BASE1 + And I have a ffi wallet FFI_RECEIVER connected to base node BASE2 + And I have mining node MINER connected to base node BASE1 and wallet SENDER + And mining node MINER mines 10 blocks + Then I wait for wallet SENDER to have at least 5000000 uT + Then I send a one-sided transaction of 1000000 uT from SENDER to FFI_RECEIVER at fee 20 + And mining node MINER mines 2 blocks + Then all nodes are at height 12 + Then ffi wallet FFI_RECEIVER detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_FAUX_UNCONFIRMED + And I send 1000000 uT from wallet SENDER to wallet FFI_RECEIVER at fee 20 + Then ffi wallet FFI_RECEIVER detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_BROADCAST + And mining node MINER mines 5 blocks + Then all nodes are at height 17 + Then ffi wallet FFI_RECEIVER detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_FAUX_CONFIRMED + And I stop ffi wallet FFI_RECEIVER + # Scenario: As a client I want to get my balance # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" diff --git a/integration_tests/features/support/ffi_steps.js b/integration_tests/features/support/ffi_steps.js index 1853b77eda..7ecb6459ed 100644 --- a/integration_tests/features/support/ffi_steps.js +++ b/integration_tests/features/support/ffi_steps.js @@ -3,16 +3,6 @@ const expect = require("chai").expect; const { sleep, waitForIterate } = require("../../helpers/util"); -When( - "I have ffi wallet {word} connected to base node {word}", - { timeout: 20 * 1000 }, - async function (name, node) { - let wallet = await this.createAndAddFFIWallet(name); - let peer = this.nodes[node].peerAddress().split("::"); - wallet.addBaseNodePeer(peer[0], peer[1]); - } -); - Then("I want to get emoji id of ffi wallet {word}", async function (name) { let wallet = this.getWallet(name); let emoji_id = wallet.identifyEmoji(); @@ -479,13 +469,21 @@ Then( ); Then( - "ffi wallet {word} detects {word} {int} ffi transactions to be Broadcast", + "ffi wallet {word} detects {word} {int} ffi transactions to be {word}", { timeout: 125 * 1000 }, - async function (walletName, comparison, amount) { + async function (walletName, comparison, amount, status) { // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed const atLeast = "AT_LEAST"; const exactly = "EXACTLY"; expect(comparison === atLeast || comparison === exactly).to.equal(true); + const broadcast = "TRANSACTION_STATUS_BROADCAST"; + const fauxUnconfirmed = "TRANSACTION_STATUS_FAUX_UNCONFIRMED"; + const fauxConfirmed = "TRANSACTION_STATUS_FAUX_CONFIRMED"; + expect( + status === broadcast || + status === fauxUnconfirmed || + status === fauxConfirmed + ).to.equal(true); const wallet = this.getWallet(walletName); console.log("\n"); @@ -496,27 +494,53 @@ Then( comparison + " " + amount + - " broadcast transaction(s)" + " " + + status + + " transaction(s)" ); await waitForIterate( () => { - return wallet.getCounters().broadcast >= amount; + switch (status) { + case broadcast: + return wallet.getCounters().broadcast >= amount; + case fauxUnconfirmed: + return wallet.getCounters().fauxUnconfirmed >= amount; + case fauxConfirmed: + return wallet.getCounters().fauxConfirmed >= amount; + default: + expect(status).to.equal("please add this<< TransactionStatus"); + } }, true, 1000, 120 ); - if (!(wallet.getCounters().broadcast >= amount)) { - console.log("Counter not adequate!"); + let amountOfCallbacks; + switch (status) { + case broadcast: + amountOfCallbacks = wallet.getCounters().broadcast; + break; + case fauxUnconfirmed: + amountOfCallbacks = wallet.getCounters().fauxUnconfirmed; + break; + case fauxConfirmed: + amountOfCallbacks = wallet.getCounters().fauxConfirmed; + break; + default: + expect(status).to.equal("please add this<< TransactionStatus"); + } + + if (!(amountOfCallbacks >= amount)) { + console.log("\nCounter not adequate!", wallet.getCounters()); } else { console.log(wallet.getCounters()); } if (comparison === atLeast) { - expect(wallet.getCounters().broadcast >= amount).to.equal(true); + expect(amountOfCallbacks >= amount).to.equal(true); } else { - expect(wallet.getCounters().broadcast === amount).to.equal(true); + expect(amountOfCallbacks === amount).to.equal(true); } } ); diff --git a/integration_tests/features/support/wallet_steps.js b/integration_tests/features/support/wallet_steps.js index 1828e32fc4..16e80af104 100644 --- a/integration_tests/features/support/wallet_steps.js +++ b/integration_tests/features/support/wallet_steps.js @@ -2034,7 +2034,9 @@ Then( 1 + " has " + transactions[i]["status"] + - " and is valid(" + + " (need " + + transactionStatus + + ") and is valid(" + transactions[i]["valid"] + ")" ); diff --git a/integration_tests/helpers/ffi/ffiInterface.js b/integration_tests/helpers/ffi/ffiInterface.js index 2f2f455fa2..cb98d17bf4 100644 --- a/integration_tests/helpers/ffi/ffiInterface.js +++ b/integration_tests/helpers/ffi/ffiInterface.js @@ -292,6 +292,8 @@ class InterfaceFFI { this.ptr, this.ptr, this.ptr, + this.ptr, + this.ptr, this.boolPtr, this.intPtr, ], @@ -1136,6 +1138,14 @@ class InterfaceFFI { return ffi.Callback(this.void, [this.ptr, this.ulonglong], fn); } + static createCallbackFauxTransactionConfirmed(fn) { + return ffi.Callback(this.void, [this.ptr], fn); + } + + static createCallbackFauxTransactionUnconfirmed(fn) { + return ffi.Callback(this.void, [this.ptr, this.ulonglong], fn); + } + static createCallbackDirectSendResult(fn) { return ffi.Callback(this.void, [this.ulonglong, this.bool], fn); } @@ -1184,6 +1194,8 @@ class InterfaceFFI { callback_transaction_broadcast, callback_transaction_mined, callback_transaction_mined_unconfirmed, + callback_faux_transaction_confirmed, + callback_faux_transaction_unconfirmed, callback_direct_send_result, callback_store_and_forward_send_result, callback_transaction_cancellation, @@ -1209,6 +1221,8 @@ class InterfaceFFI { callback_transaction_broadcast, callback_transaction_mined, callback_transaction_mined_unconfirmed, + callback_faux_transaction_confirmed, + callback_faux_transaction_unconfirmed, callback_direct_send_result, callback_store_and_forward_send_result, callback_transaction_cancellation, diff --git a/integration_tests/helpers/ffi/wallet.js b/integration_tests/helpers/ffi/wallet.js index d307660dd2..fc19fda279 100644 --- a/integration_tests/helpers/ffi/wallet.js +++ b/integration_tests/helpers/ffi/wallet.js @@ -22,11 +22,16 @@ class Wallet { ptr; balance = new WalletBalance(); log_path = ""; - receivedTransaction = 0; - receivedTransactionReply = 0; + transactionReceived = 0; + transactionReplyReceived = 0; transactionBroadcast = 0; transactionMined = 0; - saf_messages = 0; + transactionMinedUnconfirmed = 0; + transactionFauxConfirmed = 0; + transactionFauxUnconfirmed = 0; + transactionSafMessageReceived = 0; + transactionCancelled = 0; + transactionFinalized = 0; txo_validation_complete = false; txo_validation_result = 0; tx_validation_complete = false; @@ -37,6 +42,8 @@ class Wallet { callback_transaction_broadcast; callback_transaction_mined; callback_transaction_mined_unconfirmed; + callback_faux_transaction_confirmed; + callback_faux_transaction_unconfirmed; callback_direct_send_result; callback_store_and_forward_send_result; callback_transaction_cancellation; @@ -61,27 +68,31 @@ class Wallet { } clearCallbackCounters() { - this.receivedTransaction = - this.receivedTransactionReply = + this.transactionReceived = + this.transactionReplyReceived = this.transactionBroadcast = this.transactionMined = - this.saf_messages = - this.cancelled = - this.minedunconfirmed = - this.finalized = + this.transactionFauxConfirmed = + this.transactionSafMessageReceived = + this.transactionCancelled = + this.transactionMinedUnconfirmed = + this.transactionFauxUnconfirmed = + this.transactionFinalized = 0; } getCounters() { return { - received: this.receivedTransaction, - replyreceived: this.receivedTransactionReply, + received: this.transactionReceived, + replyReceived: this.transactionReplyReceived, broadcast: this.transactionBroadcast, - finalized: this.finalized, - minedunconfirmed: this.minedunconfirmed, - cancelled: this.cancelled, + finalized: this.transactionFinalized, + minedUnconfirmed: this.transactionMinedUnconfirmed, + fauxUnconfirmed: this.transactionFauxUnconfirmed, + cancelled: this.transactionCancelled, mined: this.transactionMined, - saf: this.saf_messages, + fauxConfirmed: this.transactionFauxConfirmed, + saf: this.transactionSafMessageReceived, }; } @@ -116,6 +127,14 @@ class Wallet { InterfaceFFI.createCallbackTransactionMinedUnconfirmed( this.onTransactionMinedUnconfirmed ); + this.callback_faux_transaction_confirmed = + InterfaceFFI.createCallbackFauxTransactionConfirmed( + this.onFauxTransactionConfirmed + ); + this.callback_faux_transaction_unconfirmed = + InterfaceFFI.createCallbackFauxTransactionUnconfirmed( + this.onFauxTransactionUnconfirmed + ); this.callback_direct_send_result = InterfaceFFI.createCallbackDirectSendResult(this.onDirectSendResult); this.callback_store_and_forward_send_result = @@ -148,14 +167,16 @@ class Wallet { ); //endregion - this.receivedTransaction = 0; - this.receivedTransactionReply = 0; + this.transactionReceived = 0; + this.transactionReplyReceived = 0; this.transactionBroadcast = 0; this.transactionMined = 0; - this.saf_messages = 0; - this.cancelled = 0; - this.minedunconfirmed = 0; - this.finalized = 0; + this.transactionFauxConfirmed = 0; + this.transactionSafMessageReceived = 0; + this.transactionCancelled = 0; + this.transactionMinedUnconfirmed = 0; + this.transactionFauxUnconfirmed = 0; + this.transactionFinalized = 0; this.recoveryFinished = true; let sanitize = null; let words = null; @@ -179,6 +200,8 @@ class Wallet { this.callback_transaction_broadcast, this.callback_transaction_mined, this.callback_transaction_mined_unconfirmed, + this.callback_faux_transaction_confirmed, + this.callback_faux_transaction_unconfirmed, this.callback_direct_send_result, this.callback_store_and_forward_send_result, this.callback_transaction_cancellation, @@ -198,7 +221,7 @@ class Wallet { `${new Date().toISOString()} received Transaction with txID ${tx.getTransactionID()}` ); tx.destroy(); - this.receivedTransaction += 1; + this.transactionReceived += 1; }; onReceivedTransactionReply = (ptr) => { @@ -208,7 +231,7 @@ class Wallet { `${new Date().toISOString()} received reply for Transaction with txID ${tx.getTransactionID()}.` ); tx.destroy(); - this.receivedTransactionReply += 1; + this.transactionReplyReceived += 1; }; onReceivedFinalizedTransaction = (ptr) => { @@ -218,7 +241,7 @@ class Wallet { `${new Date().toISOString()} received finalization for Transaction with txID ${tx.getTransactionID()}.` ); tx.destroy(); - this.finalized += 1; + this.transactionFinalized += 1; }; onTransactionBroadcast = (ptr) => { @@ -248,7 +271,27 @@ class Wallet { `${new Date().toISOString()} Transaction with txID ${tx.getTransactionID()} is mined unconfirmed with ${confirmations} confirmations.` ); tx.destroy(); - this.minedunconfirmed += 1; + this.transactionMinedUnconfirmed += 1; + }; + + onFauxTransactionConfirmed = (ptr) => { + let tx = new CompletedTransaction(); + tx.pointerAssign(ptr); + console.log( + `${new Date().toISOString()} Faux transaction with txID ${tx.getTransactionID()} was confirmed.` + ); + tx.destroy(); + this.transactionFauxConfirmed += 1; + }; + + onFauxTransactionUnconfirmed = (ptr, confirmations) => { + let tx = new CompletedTransaction(); + tx.pointerAssign(ptr); + console.log( + `${new Date().toISOString()} Faux transaction with txID ${tx.getTransactionID()} is unconfirmed with ${confirmations} confirmations.` + ); + tx.destroy(); + this.transactionFauxUnconfirmed += 1; }; onTransactionCancellation = (ptr, reason) => { @@ -258,7 +301,7 @@ class Wallet { `${new Date().toISOString()} Transaction with txID ${tx.getTransactionID()} was cancelled with reason code ${reason}.` ); tx.destroy(); - this.cancelled += 1; + this.transactionCancelled += 1; }; onDirectSendResult = (id, success) => { @@ -308,7 +351,7 @@ class Wallet { onSafMessageReceived = () => { console.log(`${new Date().toISOString()} callbackSafMessageReceived()`); - this.saf_messages += 1; + this.transactionSafMessageReceived += 1; }; onRecoveryProgress = (a, b, c) => { @@ -465,6 +508,8 @@ class Wallet { this.callback_transaction_broadcast = this.callback_transaction_mined = this.callback_transaction_mined_unconfirmed = + this.callback_faux_transaction_confirmed = + this.callback_faux_transaction_unconfirmed = this.callback_direct_send_result = this.callback_store_and_forward_send_result = this.callback_transaction_cancellation = diff --git a/integration_tests/helpers/walletFFIClient.js b/integration_tests/helpers/walletFFIClient.js index 6bfd907f12..bc1b230d0c 100644 --- a/integration_tests/helpers/walletFFIClient.js +++ b/integration_tests/helpers/walletFFIClient.js @@ -157,7 +157,7 @@ class WalletFFIClient { seed_words_text, pass_phrase, rolling_log_files = 50, - byte_size_per_log = 102400 + byte_size_per_log = 1048576 ) { this.pass_phrase = pass_phrase; if (seed_words_text) { diff --git a/integration_tests/package-lock.json b/integration_tests/package-lock.json index 868ccd2cda..63d7981e34 100644 --- a/integration_tests/package-lock.json +++ b/integration_tests/package-lock.json @@ -653,7 +653,7 @@ }, "any-promise": { "version": "1.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", "dev": true }, @@ -747,7 +747,7 @@ }, "assert-plus": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, @@ -759,7 +759,7 @@ }, "assertion-error-formatter": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/assertion-error-formatter/-/assertion-error-formatter-3.0.0.tgz", "integrity": "sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==", "dev": true, "requires": { @@ -993,7 +993,7 @@ }, "colors": { "version": "1.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" }, "commander": { @@ -1088,7 +1088,7 @@ }, "d": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", "dev": true, "requires": { @@ -1152,7 +1152,7 @@ }, "diff": { "version": "4.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, @@ -1167,7 +1167,7 @@ }, "duration": { "version": "0.2.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz", "integrity": "sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg==", "dev": true, "requires": { @@ -1259,7 +1259,7 @@ }, "es5-ext": { "version": "0.10.53", - "resolved": false, + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", "dev": true, "requires": { @@ -1270,7 +1270,7 @@ }, "es6-iterator": { "version": "2.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { @@ -1281,7 +1281,7 @@ }, "es6-symbol": { "version": "3.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { @@ -1704,7 +1704,7 @@ }, "ext": { "version": "1.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", "dev": true, "requires": { @@ -1713,7 +1713,7 @@ "dependencies": { "type": { "version": "2.5.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==", "dev": true } @@ -1765,7 +1765,7 @@ }, "figures": { "version": "3.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "requires": { "escape-string-regexp": "^1.0.5" @@ -1946,7 +1946,7 @@ }, "globals": { "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "resolved": false, "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, @@ -2042,7 +2042,7 @@ }, "indent-string": { "version": "4.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, @@ -2175,7 +2175,7 @@ }, "is-stream": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, @@ -2252,7 +2252,7 @@ }, "jsesc": { "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "resolved": false, "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, @@ -2328,7 +2328,7 @@ }, "knuth-shuffle-seeded": { "version": "1.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz", "integrity": "sha1-AfG2VzOqdUDuCNiwF0Fk0iCB5OE=", "dev": true, "requires": { @@ -2504,7 +2504,7 @@ }, "mz": { "version": "2.7.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, "requires": { @@ -2521,7 +2521,7 @@ }, "next-tick": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, @@ -2574,7 +2574,7 @@ }, "object-assign": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-inspect": { @@ -2669,7 +2669,7 @@ }, "pad-right": { "version": "0.2.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/pad-right/-/pad-right-0.2.2.tgz", "integrity": "sha1-b7ySQEXSRPKiokRQMGDTv8YAl3Q=", "dev": true, "requires": { @@ -2877,7 +2877,7 @@ }, "repeat-string": { "version": "1.6.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, @@ -2942,7 +2942,7 @@ }, "seed-random": { "version": "2.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=", "dev": true }, @@ -3038,7 +3038,7 @@ }, "source-map": { "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "resolved": false, "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, @@ -3068,7 +3068,7 @@ }, "stack-chain": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-2.0.0.tgz", "integrity": "sha512-GGrHXePi305aW7XQweYZZwiRwR7Js3MWoK/EHzzB9ROdc75nCnjSJVi21rdAGxFl+yCx2L2qdfl5y7NO4lTyqg==", "dev": true }, @@ -3118,7 +3118,7 @@ }, "string-argv": { "version": "0.3.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", "dev": true }, @@ -3263,7 +3263,7 @@ }, "thenify": { "version": "3.3.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, "requires": { @@ -3272,7 +3272,7 @@ }, "thenify-all": { "version": "1.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", "dev": true, "requires": { @@ -3290,7 +3290,7 @@ }, "to-fast-properties": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "resolved": false, "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, @@ -3336,7 +3336,7 @@ }, "type": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", "dev": true }, @@ -3398,7 +3398,7 @@ }, "util-arity": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/util-arity/-/util-arity-1.1.0.tgz", "integrity": "sha1-WdAa8f2z/t4KxOYysKtfbOl8kzA=", "dev": true }, @@ -3440,104 +3440,68 @@ "dependencies": { "@grpc/grpc-js": { "version": "1.3.6", - "resolved": false, - "integrity": "sha512-v7+LQFbqZKmd/Tvf5/j1Xlbq6jXL/4d+gUtm2TNX4QiEC3ELWADmGr2dGlUyLl6aKTuYfsN72vAsO5zmavYkEg==", "requires": { "@types/node": ">=12.12.47" } }, "@grpc/proto-loader": { "version": "0.5.6", - "resolved": false, - "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", "requires": { "lodash.camelcase": "^4.3.0", "protobufjs": "^6.8.6" } }, "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": false, - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + "version": "1.1.2" }, "@protobufjs/base64": { - "version": "1.1.2", - "resolved": false, - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + "version": "1.1.2" }, "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": false, - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + "version": "2.0.4" }, "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": false, - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + "version": "1.1.0" }, "@protobufjs/fetch": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", "requires": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "@protobufjs/float": { - "version": "1.0.2", - "resolved": false, - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + "version": "1.0.2" }, "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": false, - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + "version": "1.1.0" }, "@protobufjs/path": { - "version": "1.1.2", - "resolved": false, - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + "version": "1.1.2" }, "@protobufjs/pool": { - "version": "1.1.0", - "resolved": false, - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + "version": "1.1.0" }, "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": false, - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + "version": "1.1.0" }, "@types/long": { - "version": "4.0.1", - "resolved": false, - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + "version": "4.0.1" }, "@types/node": { - "version": "16.3.2", - "resolved": false, - "integrity": "sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==" + "version": "16.3.2" }, "grpc-promise": { - "version": "1.4.0", - "resolved": false, - "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==" + "version": "1.4.0" }, "lodash.camelcase": { - "version": "4.3.0", - "resolved": false, - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + "version": "4.3.0" }, "long": { - "version": "4.0.0", - "resolved": false, - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "version": "4.0.0" }, "protobufjs": { "version": "6.11.2", - "resolved": false, - "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2",