Skip to content

Commit

Permalink
Add scanned transaction handling
Browse files Browse the repository at this point in the history
- Added separate statuses for scanned one-sided transactions to enable unique
events and validation.
- Added validation for one-sided payments updating their mined height and
confirmations.
- Added callbacks for scanned one-sided transactions (unconfirmed and confirmed)
- Updated the wallet FFI with the callbacks.
  • Loading branch information
hansieodendaal committed Feb 4, 2022
1 parent 3495e85 commit a6ecf90
Show file tree
Hide file tree
Showing 23 changed files with 880 additions and 156 deletions.
4 changes: 4 additions & 0 deletions applications/tari_app_grpc/proto/wallet.proto
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ enum TransactionStatus {
TRANSACTION_STATUS_NOT_FOUND = 7;
// The transaction was rejected by the mempool
TRANSACTION_STATUS_REJECTED = 8;
// This transaction has been imported by scanning the blockchain for one-sided transactions
TRANSACTION_STATUS_SCANNED_UNCONFIRMED = 9;
// This transaction has been imported and confirmed by scanning the blockchain for one-sided transactions
TRANSACTION_STATUS_SCANNED_CONFIRMED = 10;
}

message GetCompletedTransactionsRequest { }
Expand Down
2 changes: 2 additions & 0 deletions applications/tari_app_grpc/src/conversions/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ impl From<TransactionStatus> for grpc::TransactionStatus {
Pending => grpc::TransactionStatus::Pending,
Coinbase => grpc::TransactionStatus::Coinbase,
Rejected => grpc::TransactionStatus::Rejected,
ScannedUnconfirmed => grpc::TransactionStatus::ScannedUnconfirmed,
ScannedConfirmed => grpc::TransactionStatus::ScannedConfirmed,
}
}
}
Expand Down
12 changes: 10 additions & 2 deletions applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ use tari_app_grpc::{
TransferResult,
},
};
use tari_common_types::types::{BlockHash, PublicKey, Signature};
use tari_common_types::{
transaction::ImportStatus,
types::{BlockHash, PublicKey, Signature},
};
use tari_comms::{types::CommsPublicKey, CommsNode};
use tari_core::transactions::{
tari_amount::MicroTari,
Expand Down Expand Up @@ -568,7 +571,12 @@ impl wallet_server::Wallet for WalletGrpcServer {
for o in unblinded_outputs.iter() {
tx_ids.push(
wallet
.import_unblinded_utxo(o.clone(), &CommsPublicKey::default(), "Imported via gRPC".to_string())
.import_unblinded_utxo(
o.clone(),
&CommsPublicKey::default(),
"Imported via gRPC".to_string(),
ImportStatus::Imported,
)
.await
.map_err(|e| Status::internal(format!("{:?}", e)))?
.into(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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::TransactionScannedUnconfirmed{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::TransactionScanned{tx_id, is_valid: _}=> {
self.trigger_confirmations_cleanup(tx_id).await;
self.trigger_tx_state_refresh(tx_id).await;
self.trigger_balance_refresh();
Expand All @@ -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();
},
Expand Down
52 changes: 52 additions & 0 deletions base_layer/common_types/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ pub enum TransactionStatus {
MinedConfirmed,
/// This transaction was Rejected by the mempool
Rejected,
/// This transaction has been imported by scanning the blockchain for one-sided transactions
ScannedUnconfirmed,
/// This transaction has been imported and confirmed by scanning the blockchain for one-sided transactions
ScannedConfirmed,
}

#[derive(Debug, Error)]
Expand All @@ -48,6 +52,8 @@ impl TryFrom<i32> for TransactionStatus {
5 => Ok(TransactionStatus::Coinbase),
6 => Ok(TransactionStatus::MinedConfirmed),
7 => Ok(TransactionStatus::Rejected),
8 => Ok(TransactionStatus::ScannedUnconfirmed),
9 => Ok(TransactionStatus::ScannedConfirmed),
code => Err(TransactionConversionError { code }),
}
}
Expand All @@ -71,10 +77,56 @@ impl Display for TransactionStatus {
TransactionStatus::Pending => write!(f, "Pending"),
TransactionStatus::Coinbase => write!(f, "Coinbase"),
TransactionStatus::Rejected => write!(f, "Rejected"),
TransactionStatus::ScannedUnconfirmed => write!(f, "ScannedUnconfirmed"),
TransactionStatus::ScannedConfirmed => write!(f, "ScannedConfirmed"),
}
}
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ImportStatus {
/// This is an invalid transaction import status that will result in an error
Invalid,
/// 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
ScannedUnconfirmed,
/// This transaction import status is used when a one-sided transaction has been scanned and confirmed
ScannedConfirmed,
}

impl TryFrom<ImportStatus> for TransactionStatus {
type Error = TransactionConversionError;

fn try_from(value: ImportStatus) -> Result<Self, Self::Error> {
match value {
ImportStatus::Imported => Ok(TransactionStatus::Imported),
ImportStatus::ScannedUnconfirmed => Ok(TransactionStatus::ScannedUnconfirmed),
ImportStatus::ScannedConfirmed => Ok(TransactionStatus::ScannedConfirmed),
_ => Err(TransactionConversionError { code: i32::MAX }),
}
}
}

impl TryFrom<TransactionStatus> for ImportStatus {
type Error = TransactionConversionError;

fn try_from(value: TransactionStatus) -> Result<Self, Self::Error> {
match value {
TransactionStatus::Imported => Ok(ImportStatus::Imported),
TransactionStatus::ScannedUnconfirmed => Ok(ImportStatus::ScannedUnconfirmed),
TransactionStatus::ScannedConfirmed => Ok(ImportStatus::ScannedConfirmed),
_ => Err(TransactionConversionError { code: i32::MAX }),
}
}
}

impl Default for ImportStatus {
fn default() -> Self {
ImportStatus::Invalid
}
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TransactionDirection {
Inbound,
Expand Down
43 changes: 36 additions & 7 deletions base_layer/wallet/src/transaction_service/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -72,7 +75,7 @@ pub enum TransactionServiceRequest {
},
SendShaAtomicSwapTransaction(CommsPublicKey, MicroTari, MicroTari, String),
CancelTransaction(TxId),
ImportUtxo(MicroTari, CommsPublicKey, String, Option<u64>),
ImportUtxoWithStatus(MicroTari, CommsPublicKey, String, Option<u64>, ImportStatus),
SubmitTransactionToSelf(TxId, Transaction, MicroTari, MicroTari, String),
SetLowPowerMode,
SetNormalPowerMode,
Expand Down Expand Up @@ -123,12 +126,13 @@ 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: {})",
Self::ImportUtxoWithStatus(v, k, msg, maturity, import_statsus) => f.write_str(&format!(
"ImportUtxo (from {}, {}, {} with maturity: {} as {:?})",
k,
v,
msg,
maturity.unwrap_or(0)
maturity.unwrap_or(0),
import_statsus
)),
Self::SubmitTransactionToSelf(tx_id, _, _, _, _) => f.write_str(&format!("SubmitTransaction ({})", tx_id)),
Self::SetLowPowerMode => f.write_str("SetLowPowerMode "),
Expand Down Expand Up @@ -189,6 +193,15 @@ pub enum TransactionEvent {
TransactionCancelled(TxId, TxRejection),
TransactionBroadcast(TxId),
TransactionImported(TxId),
TransactionScannedUnconfirmed {
tx_id: TxId,
num_confirmations: u64,
is_valid: bool,
},
TransactionScanned {
tx_id: TxId,
is_valid: bool,
},
TransactionMined {
tx_id: TxId,
is_valid: bool,
Expand Down Expand Up @@ -242,6 +255,20 @@ impl fmt::Display for TransactionEvent {
TransactionEvent::TransactionImported(tx) => {
write!(f, "TransactionImported for {}", tx)
},
TransactionEvent::TransactionScannedUnconfirmed {
tx_id,
num_confirmations,
is_valid,
} => {
write!(
f,
"TransactionScannedUnconfirmed for {} with num confirmations: {}. is_valid: {}",
tx_id, num_confirmations, is_valid
)
},
TransactionEvent::TransactionScanned { tx_id, is_valid } => {
write!(f, "TransactionScanned for {}. is_valid: {}", tx_id, is_valid)
},
TransactionEvent::TransactionMined { tx_id, is_valid } => {
write!(f, "TransactionMined for {}. is_valid: {}", tx_id, is_valid)
},
Expand Down Expand Up @@ -517,20 +544,22 @@ 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<u64>,
import_status: ImportStatus,
) -> Result<TxId, TransactionServiceError> {
match self
.handle
.call(TransactionServiceRequest::ImportUtxo(
.call(TransactionServiceRequest::ImportUtxoWithStatus(
amount,
source_public_key,
message,
maturity,
import_status,
))
.await??
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,8 @@ where
mined_height: u64,
num_confirmations: u64,
) -> Result<(), TransactionServiceProtocolError> {
let is_scanned =
*status == TransactionStatus::ScannedUnconfirmed || *status == TransactionStatus::ScannedConfirmed;
self.db
.set_transaction_mined_height(
tx_id,
Expand All @@ -396,12 +398,23 @@ where
mined_in_block.clone(),
num_confirmations,
num_confirmations >= self.config.num_confirmations_required,
is_scanned,
)
.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 is_scanned {
self.publish_event(TransactionEvent::TransactionScanned { tx_id, is_valid: true })
} else {
self.publish_event(TransactionEvent::TransactionMined { tx_id, is_valid: true })
}
} else if is_scanned {
self.publish_event(TransactionEvent::TransactionScannedUnconfirmed {
tx_id,
num_confirmations,
is_valid: true,
})
} else {
self.publish_event(TransactionEvent::TransactionMinedUnconfirmed {
tx_id,
Expand Down Expand Up @@ -441,6 +454,7 @@ where
mined_in_block.clone(),
num_confirmations,
num_confirmations >= self.config.num_confirmations_required,
false,
)
.await
.for_protocol(self.operation_id.as_u64())?;
Expand All @@ -465,8 +479,10 @@ where
tx_id: TxId,
status: &TransactionStatus,
) -> Result<(), TransactionServiceProtocolError> {
let is_scanned =
*status == TransactionStatus::ScannedUnconfirmed || *status == TransactionStatus::ScannedConfirmed;
self.db
.set_transaction_as_unmined(tx_id)
.set_transaction_as_unmined(tx_id, is_scanned)
.await
.for_protocol(self.operation_id.as_u64())?;

Expand Down
49 changes: 34 additions & 15 deletions base_layer/wallet/src/transaction_service/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use log::*;
use rand::rngs::OsRng;
use sha2::Sha256;
use tari_common_types::{
transaction::{TransactionDirection, TransactionStatus, TxId},
transaction::{ImportStatus, TransactionConversionError, TransactionDirection, TransactionStatus, TxId},
types::{PrivateKey, PublicKey},
};
use tari_comms::{peer_manager::NodeIdentity, types::CommsPublicKey};
Expand Down Expand Up @@ -644,8 +644,14 @@ 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(
value,
source_public_key,
message,
maturity,
import_status,
) => self
.add_utxo_import_transaction_with_status(value, source_public_key, message, maturity, import_status)
.await
.map(TransactionServiceResponse::UtxoImported),
TransactionServiceRequest::SubmitTransactionToSelf(tx_id, tx, fee, amount, message) => self
Expand Down Expand Up @@ -2023,35 +2029,48 @@ 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<u64>,
import_status: ImportStatus,
) -> Result<TxId, TransactionServiceError> {
let tx_id = 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(),
)
.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::Invalid => {
return Err(TransactionServiceError::ConversionError(TransactionConversionError {
code: i32::MAX,
}))
},
ImportStatus::Imported => TransactionEvent::TransactionImported(tx_id),
ImportStatus::ScannedUnconfirmed => TransactionEvent::TransactionScannedUnconfirmed {
tx_id,
num_confirmations: 0,
is_valid: true,
},
ImportStatus::ScannedConfirmed => TransactionEvent::TransactionScanned { 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)
}

Expand Down
Loading

0 comments on commit a6ecf90

Please sign in to comment.