Skip to content

Commit

Permalink
feat: add import tx method (#6132)
Browse files Browse the repository at this point in the history
Description
---
Allows users to import an exported transaction to their wallet. 

Motivation and Context
---
This allows true cold mining. A wallet can export any transaction, and
it can then be imported to any transaction which will monitor and ensure
the transaction is broadcasted to the network.

How Has This Been Tested?
---
unit tests and manual
  • Loading branch information
SWvheerden authored Feb 8, 2024
1 parent 9a7fcd6 commit f3d9121
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 9 deletions.
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions applications/minotari_console_wallet/src/automation/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pub enum WalletCommand {
Whois,
ExportUtxos,
ExportTx,
ImportTx,
ExportSpentUtxos,
CountUtxos,
SetBaseNode,
Expand Down Expand Up @@ -817,6 +818,19 @@ pub async fn command_runner(
},
Err(e) => eprintln!("ExportTx error! {}", e),
},
ImportTx(args) => {
match load_tx_from_csv_file(args.input_file) {
Ok(txs) => {
for tx in txs {
match transaction_service.import_transaction(tx).await {
Ok(id) => println!("imported tx: {}", id),
Err(e) => eprintln!("Could not import tx {}", e),
};
}
},
Err(e) => eprintln!("ImportTx error! {}", e),
};
},
ExportSpentUtxos(args) => match output_service.get_spent_outputs().await {
Ok(utxos) => {
let utxos: Vec<(WalletOutput, Commitment)> =
Expand Down Expand Up @@ -1107,6 +1121,19 @@ fn write_tx_to_csv_file(tx: WalletTransaction, file_path: PathBuf) -> Result<(),
Ok(())
}

fn load_tx_from_csv_file(file_path: PathBuf) -> Result<Vec<WalletTransaction>, CommandError> {
let file_contents = fs::read_to_string(file_path).map_err(|e| CommandError::CSVFile(e.to_string()))?;
let mut results = Vec::new();
for line in file_contents.lines() {
if let Ok(tx) = serde_json::from_str(line) {
results.push(tx);
} else {
return Err(CommandError::CSVFile("Could not read json file".to_string()));
}
}
Ok(results)
}

#[allow(dead_code)]
fn write_json_file<P: AsRef<Path>, T: Serialize>(path: P, data: &T) -> Result<(), CommandError> {
fs::create_dir_all(path.as_ref().parent().unwrap()).map_err(|e| CommandError::JsonFile(e.to_string()))?;
Expand Down
7 changes: 7 additions & 0 deletions applications/minotari_console_wallet/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ pub enum CliCommands {
Whois(WhoisArgs),
ExportUtxos(ExportUtxosArgs),
ExportTx(ExportTxArgs),
ImportTx(ImportTxArgs),
ExportSpentUtxos(ExportUtxosArgs),
CountUtxos,
SetBaseNode(SetBaseNodeArgs),
Expand Down Expand Up @@ -249,6 +250,12 @@ pub struct ExportTxArgs {
pub output_file: Option<PathBuf>,
}

#[derive(Debug, Args, Clone)]
pub struct ImportTxArgs {
#[clap(short, long)]
pub input_file: PathBuf,
}

#[derive(Debug, Args, Clone)]
pub struct SetBaseNodeArgs {
pub public_key: UniPublicKey,
Expand Down
19 changes: 18 additions & 1 deletion applications/minotari_console_wallet/src/wallet_modes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ async fn run_grpc(

#[cfg(test)]
mod test {
use std::path::Path;

use crate::{cli::CliCommands, wallet_modes::parse_command_file};

Expand All @@ -501,6 +502,8 @@ mod test {
export-tx 123456789 --output-file pie.txt
import-tx --input-file pie_this_message.txt
# End of script file
"
.to_string();
Expand All @@ -514,6 +517,7 @@ mod test {
let mut coin_split = false;
let mut discover_peer = false;
let mut export_tx = false;
let mut import_tx = false;
let mut whois = false;
for command in commands {
match command {
Expand All @@ -532,6 +536,11 @@ mod test {
export_tx = true
}
},
CliCommands::ImportTx(args) => {
if args.input_file == Path::new("pie_this_message.txt") {
import_tx = true
}
},
CliCommands::ExportSpentUtxos(_) => {},
CliCommands::CountUtxos => {},
CliCommands::SetBaseNode(_) => {},
Expand All @@ -546,7 +555,15 @@ mod test {
}
}
assert!(
get_balance && send_tari && burn_tari && make_it_rain && coin_split && discover_peer && whois && export_tx
get_balance &&
send_tari &&
burn_tari &&
make_it_rain &&
coin_split &&
discover_peer &&
whois &&
export_tx &&
import_tx
);
}
}
14 changes: 14 additions & 0 deletions base_layer/wallet/src/transaction_service/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub enum TransactionServiceRequest {
GetCancelledCompletedTransactions,
GetCompletedTransaction(TxId),
GetAnyTransaction(TxId),
ImportTransaction(WalletTransaction),
SendTransaction {
destination: TariAddress,
amount: MicroMinotari,
Expand Down Expand Up @@ -165,6 +166,7 @@ impl fmt::Display for TransactionServiceRequest {
Self::GetPendingInboundTransactions => write!(f, "GetPendingInboundTransactions"),
Self::GetPendingOutboundTransactions => write!(f, "GetPendingOutboundTransactions"),
Self::GetCompletedTransactions => write!(f, "GetCompletedTransactions"),
Self::ImportTransaction(tx) => write!(f, "ImportTransaction: {:?}", tx),
Self::GetCancelledPendingInboundTransactions => write!(f, "GetCancelledPendingInboundTransactions"),
Self::GetCancelledPendingOutboundTransactions => write!(f, "GetCancelledPendingOutboundTransactions"),
Self::GetCancelledCompletedTransactions => write!(f, "GetCancelledCompletedTransactions"),
Expand Down Expand Up @@ -243,6 +245,7 @@ impl fmt::Display for TransactionServiceRequest {
#[derive(Debug)]
pub enum TransactionServiceResponse {
TransactionSent(TxId),
TransactionImported(TxId),
BurntTransactionSent {
tx_id: TxId,
proof: Box<BurntProof>,
Expand Down Expand Up @@ -727,6 +730,17 @@ impl TransactionServiceHandle {
}
}

pub async fn import_transaction(&mut self, tx: WalletTransaction) -> Result<TxId, TransactionServiceError> {
match self
.handle
.call(TransactionServiceRequest::ImportTransaction(tx))
.await??
{
TransactionServiceResponse::TransactionImported(t) => Ok(t),
_ => Err(TransactionServiceError::UnexpectedApiResponse),
}
}

pub async fn import_utxo_with_status(
&mut self,
amount: MicroMinotari,
Expand Down
26 changes: 25 additions & 1 deletion base_layer/wallet/src/transaction_service/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ use crate::{
},
storage::{
database::{TransactionBackend, TransactionDatabase},
models::{CompletedTransaction, TxCancellationReason},
models::{
CompletedTransaction,
TxCancellationReason,
WalletTransaction::{Completed, PendingInbound, PendingOutbound},
},
},
tasks::{
check_faux_transaction_status::check_detected_transactions,
Expand Down Expand Up @@ -774,6 +778,26 @@ where
TransactionServiceRequest::GetAnyTransaction(tx_id) => Ok(TransactionServiceResponse::AnyTransaction(
Box::new(self.db.get_any_transaction(tx_id)?),
)),
TransactionServiceRequest::ImportTransaction(tx) => {
let tx_id = match tx {
PendingInbound(inbound_tx) => {
let tx_id = inbound_tx.tx_id;
self.db.insert_pending_inbound_transaction(tx_id, inbound_tx)?;
tx_id
},
PendingOutbound(outbound_tx) => {
let tx_id = outbound_tx.tx_id;
self.db.insert_pending_outbound_transaction(tx_id, outbound_tx)?;
tx_id
},
Completed(completed_tx) => {
let tx_id = completed_tx.tx_id;
self.db.insert_completed_transaction(tx_id, completed_tx)?;
tx_id
},
};
Ok(TransactionServiceResponse::TransactionImported(tx_id))
},
TransactionServiceRequest::ImportUtxoWithStatus {
amount,
source_address,
Expand Down
24 changes: 24 additions & 0 deletions base_layer/wallet/src/transaction_service/storage/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,30 @@ where T: TransactionBackend + 'static
)))
}

pub fn insert_pending_inbound_transaction(
&self,
tx_id: TxId,
transaction: InboundTransaction,
) -> Result<Option<DbValue>, TransactionStorageError> {
self.db
.write(WriteOperation::Insert(DbKeyValuePair::PendingInboundTransaction(
tx_id,
Box::new(transaction),
)))
}

pub fn insert_pending_outbound_transaction(
&self,
tx_id: TxId,
transaction: OutboundTransaction,
) -> Result<Option<DbValue>, TransactionStorageError> {
self.db
.write(WriteOperation::Insert(DbKeyValuePair::PendingOutboundTransaction(
tx_id,
Box::new(transaction),
)))
}

pub fn get_pending_outbound_transaction(
&self,
tx_id: TxId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ impl From<InboundTransaction> for CompletedTransaction {
}
}

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum WalletTransaction {
PendingInbound(InboundTransaction),
Expand Down

0 comments on commit f3d9121

Please sign in to comment.