Skip to content

feat: import transactions via grpc#7078

Merged
SWvheerden merged 9 commits intotari-project:developmentfrom
mmrrnn:mp_import_transactions_via_grpc
May 22, 2025
Merged

feat: import transactions via grpc#7078
SWvheerden merged 9 commits intotari-project:developmentfrom
mmrrnn:mp_import_transactions_via_grpc

Conversation

@mmrrnn
Copy link
Copy Markdown
Contributor

@mmrrnn mmrrnn commented May 21, 2025

Description

In Tari Universe we want to import tx when view wallet instance is running. It's only possible via grpc due to exclusive file lock.

  • also print out tx_id when send-one-sided completed to export the tx easily

Motivation and Context

How Has This Been Tested?

https://drive.google.com/file/d/1iV-cLxqvnTBX0QSs6lAzqg55Zn5aGOu2/view

What process can a PR reviewer use to test or verify this change?

Breaking Changes

  • None
  • Requires data directory on base node to be deleted
  • Requires hard fork
  • Other - Please specify

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Added the ability to import transactions into the wallet via a new gRPC method.
    • Transaction IDs of sent one-sided transactions to stealth addresses are now displayed in the console.
    • Users receive notifications and balance updates when transactions are imported.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented May 21, 2025

"""

Walkthrough

A new gRPC method, ImportTransactions, was introduced to the wallet service, enabling the import of transactions from a JSON string. Supporting request and response messages were defined in the proto file. The server-side logic for this method was implemented, handling JSON deserialization and transaction importing. Transaction import events now trigger UI updates and notifications. Additionally, transaction IDs are printed after sending one-sided transactions to stealth addresses. A new transaction event variant was added to signal imported transactions.

Changes

File(s) Change Summary
applications/minotari_app_grpc/proto/wallet.proto Added ImportTransactions RPC to the Wallet service; defined ImportTransactionsRequest and ImportTransactionsResponse messages.
applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs Implemented import_transactions method for the Wallet gRPC server; handles JSON parsing and transaction importing.
applications/minotari_console_wallet/src/automation/commands.rs Added a println! to output transaction ID after sending a one-sided transaction to a stealth address.
applications/minotari_console_wallet/src/ui/state/wallet_event_monitor.rs Added handling for TransactionImported events to refresh transaction state, update balance, and notify user.
base_layer/wallet/src/transaction_service/handle.rs Added TransactionImported(TxId) variant to TransactionEvent enum and updated its Display implementation.
base_layer/wallet/src/transaction_service/service.rs Emitted TransactionImported event after importing a transaction in the service layer.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant WalletGrpcServer
    participant TransactionService
    participant EventPublisher
    participant UI

    Client->>WalletGrpcServer: ImportTransactions(JSON string)
    WalletGrpcServer->>WalletGrpcServer: Deserialize JSON to WalletTransaction list
    loop For each WalletTransaction
        WalletGrpcServer->>TransactionService: Import transaction
        TransactionService->>EventPublisher: Publish TransactionImported event
        EventPublisher->>UI: Notify TransactionImported(tx_id)
        TransactionService-->>WalletGrpcServer: Result (success/failure)
    end
    WalletGrpcServer->>Client: ImportTransactionsResponse(tx_ids)
Loading

Possibly related PRs

Poem

🐇
A file of transactions, now easy to bring,
With gRPC magic, wallets can sing!
IDs are printed, for all to see,
Imported with care, refreshed instantly.
Hop, hop, hooray—new features today!

"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e952975 and e51d48a.

📒 Files selected for processing (2)
  • applications/minotari_app_grpc/proto/wallet.proto (13 hunks)
  • applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs
  • applications/minotari_app_grpc/proto/wallet.proto
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: Cucumber tests / FFI
  • GitHub Check: Cucumber tests / Base Layer
  • GitHub Check: test (nextnet, nextnet)
  • GitHub Check: test (testnet, esmeralda)
  • GitHub Check: test (mainnet, stagenet)
  • GitHub Check: cargo check with stable
  • GitHub Check: ci
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Copy Markdown

github-actions bot commented May 21, 2025

Test Results (CI)

1 090 tests   1 086 ✅  16m 18s ⏱️
   30 suites      0 💤
    1 files        4 ❌

For more details on these failures, see this check.

Results for commit e952975.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions bot commented May 21, 2025

Test Results (Integration tests)

 2 files  + 2  11 suites  +11   1h 14m 5s ⏱️ + 1h 14m 5s
36 tests +36  31 ✅ +31  0 💤 ±0   5 ❌ + 5 
44 runs  +44  33 ✅ +33  0 💤 ±0  11 ❌ +11 

For more details on these failures, see this check.

Results for commit e952975. ± Comparison against base commit 7ab4d16.

♻️ This comment has been updated with latest results.

@mmrrnn mmrrnn marked this pull request as ready for review May 21, 2025 15:54
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
applications/minotari_app_grpc/proto/wallet.proto (4)

1000-1002: Missing Documentation for New RPC
The new ImportTransactions RPC lacks a descriptive comment block. Please add a doc comment above it, outlining:

  • Purpose of the RPC
  • Details of the file_path parameter
  • Expected behavior and error codes
  • Example usage

1360-1363: Add Documentation for ImportTransactionsRequest
The ImportTransactionsRequest message is undocumented. Add a proto comment describing the file_path field, its expected format (e.g., newline-delimited JSON), any path restrictions, and validation rules.


1365-1367: Add Documentation for ImportTransactionsResponse
The ImportTransactionsResponse message is missing comments. Describe that tx_ids is the list of successfully imported transaction IDs and under what conditions it may be empty or partially populated.


1365-1367: Optional: Rename tx_ids for Consistency
Other RPCs use transaction_ids (e.g., GetTransactionInfoRequest). Consider renaming tx_ids to transaction_ids for clarity and consistency across the API.

applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs (2)

1222-1223: Consider using the logging framework instead of println for consistency.

Using println for logging successful transaction imports is inconsistent with the project's logging practices. The rest of the codebase uses the logging framework with the LOG_TARGET constant.

-                    println!("Imported tx: {}", id);
+                    info!(target: LOG_TARGET, "Imported tx: {}", id);

1225-1226: Replace eprintln with proper error logging.

Using eprintln for error logging is inconsistent with the project's logging practices and doesn't provide the proper context information.

-                Err(e) => eprintln!("Could not import tx {}", e),
+                Err(e) => error!(target: LOG_TARGET, "Could not import tx: {}", e),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 5ed9861 and a88cc6e.

📒 Files selected for processing (3)
  • applications/minotari_app_grpc/proto/wallet.proto (2 hunks)
  • applications/minotari_console_wallet/src/automation/commands.rs (1 hunks)
  • applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: check-title
  • GitHub Check: test (testnet, esmeralda)
  • GitHub Check: test (mainnet, stagenet)
  • GitHub Check: test (nextnet, nextnet)
  • GitHub Check: cargo check with stable
  • GitHub Check: ci
  • GitHub Check: Cucumber tests / Base Layer
  • GitHub Check: Cucumber tests / FFI
🔇 Additional comments (2)
applications/minotari_console_wallet/src/automation/commands.rs (1)

2064-2067: Provide immediate console feedback for stealth sends
Adding println!("Transaction ID: {}", tx_id); gives end users clear confirmation of the generated transaction ID when using the SendOneSidedToStealthAddress command.

applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs (1)

36-36: Appropriately updated imports to include the new transaction request/response types.

The import statement now includes the new ImportTransactionsRequest and ImportTransactionsResponse types necessary for the new gRPC endpoint.

Comment on lines +1209 to +1210
let file_contents = std::fs::read_to_string(request.file_path).map_err(|_| Status::invalid_argument("File path is malformed!"))?;
let mut txs: Vec<WalletTransaction> = Vec::new();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Add security validation for file path access.

The method reads a file path from user input without validation, which could potentially lead to directory traversal attacks. Consider implementing path validation or restricting file access to a specific directory.

You could add validation like this:

-        let file_contents = std::fs::read_to_string(request.file_path).map_err(|_| Status::invalid_argument("File path is malformed!"))?;
+        // Validate file path to prevent directory traversal attacks
+        let path = std::path::Path::new(&request.file_path);
+        if !path.is_file() {
+            return Err(Status::invalid_argument(format!("'{}' is not a valid file", request.file_path)));
+        }
+        
+        // Optionally, restrict to a specific directory
+        let allowed_dir = std::path::Path::new("/path/to/allowed/directory");
+        if !path.starts_with(allowed_dir) {
+            return Err(Status::permission_denied("Access to this file path is not allowed"));
+        }
+        
+        let file_contents = std::fs::read_to_string(request.file_path)
+            .map_err(|e| Status::invalid_argument(format!("Failed to read file: {}", e)))?;

Run a security scan to check for similar file I/O operations across the codebase:


🏁 Script executed:

#!/bin/bash
# Search for potential file I/O operations without path validation
echo "Searching for file I/O operations that might need path validation"
rg "fs::(read|open|create)" --type=rust

Length of output: 5855


Enforce safe file path handling in gRPC file read

The RPC handler in applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs reads request.file_path without any validation, allowing directory traversal or unauthorized access. Please add canonicalization and a directory whitelist:

  • File: applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs (lines 1209–1210)
  • Ensure the path is canonicalized and resides under a known base directory
  • Verify the canonical path points to an existing regular file

Suggested diff:

         // Validate and restrict user-supplied file paths to prevent directory traversal
-        let file_contents = std::fs::read_to_string(request.file_path)
-            .map_err(|_| Status::invalid_argument("File path is malformed!"))?;
+        let requested = std::path::Path::new(&request.file_path);
+        let canonical = requested
+            .canonicalize()
+            .map_err(|_| Status::invalid_argument("Invalid file path"))?;
+        // Define and canonicalize your allowed directory
+        let base = std::path::Path::new("/var/lib/minotari/allowed");
+        let base_canonical = base
+            .canonicalize()
+            .map_err(|_| Status::internal("Server misconfiguration"))?;
+        if !canonical.starts_with(&base_canonical) {
+            return Err(Status::permission_denied("Access to this file is not allowed"));
+        }
+        if !canonical.is_file() {
+            return Err(Status::invalid_argument("Path is not a file"));
+        }
+        let file_contents = std::fs::read_to_string(&canonical)
+            .map_err(|e| Status::invalid_argument(format!("Failed to read file: {}", e)))?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let file_contents = std::fs::read_to_string(request.file_path).map_err(|_| Status::invalid_argument("File path is malformed!"))?;
let mut txs: Vec<WalletTransaction> = Vec::new();
// Validate and restrict user-supplied file paths to prevent directory traversal
let requested = std::path::Path::new(&request.file_path);
let canonical = requested
.canonicalize()
.map_err(|_| Status::invalid_argument("Invalid file path"))?;
// Define and canonicalize your allowed directory
let base = std::path::Path::new("/var/lib/minotari/allowed");
let base_canonical = base
.canonicalize()
.map_err(|_| Status::internal("Server misconfiguration"))?;
if !canonical.starts_with(&base_canonical) {
return Err(Status::permission_denied("Access to this file is not allowed"));
}
if !canonical.is_file() {
return Err(Status::invalid_argument("Path is not a file"));
}
let file_contents = std::fs::read_to_string(&canonical)
.map_err(|e| Status::invalid_argument(format!("Failed to read file: {}", e)))?;
let mut txs: Vec<WalletTransaction> = Vec::new();
🤖 Prompt for AI Agents
In applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs around
lines 1209 to 1210, the code reads a file path from user input without
validation, risking directory traversal attacks. Fix this by canonicalizing the
requested file path and verifying it is within a predefined base directory.
Also, check that the canonical path points to an existing regular file before
reading it. If validation fails, return an appropriate error status to prevent
unauthorized file access.

Comment on lines +1203 to +1229
async fn import_transactions(
&self,
request: Request<ImportTransactionsRequest>,
) -> Result<Response<ImportTransactionsResponse>, Status> {
let request = request.into_inner();
// Extract file contents
let file_contents = std::fs::read_to_string(request.file_path).map_err(|_| Status::invalid_argument("File path is malformed!"))?;
let mut txs: Vec<WalletTransaction> = Vec::new();
for line in file_contents.lines() {
serde_json::from_str(line)
.map(|tx| txs.push(tx))
.map_err(|_| Status::invalid_argument("File path is malformed!"))?;
}
let mut transaction_service = self.get_transaction_service();

let mut tx_ids = Vec::new();
for tx in txs {
match transaction_service.import_transaction(tx).await {
Ok(id) => {
println!("Imported tx: {}", id);
tx_ids.push(id.into());
},
Err(e) => eprintln!("Could not import tx {}", e),
};
}
Ok(Response::new(ImportTransactionsResponse { tx_ids }))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Improve error handling and file processing for the new import_transactions method.

The implementation of the new gRPC endpoint is functional but has several areas that could be improved:

  1. The file I/O error handling uses a generic "File path is malformed!" message for all errors, which is not very informative.
  2. Reading the entire file into memory at once could cause problems with large transaction files.
  3. Transaction import failures are only printed to stderr without returning this information to the client.

Here's a suggested implementation with improved error handling and streaming file processing:

    async fn import_transactions(
        &self,
        request: Request<ImportTransactionsRequest>,
    ) -> Result<Response<ImportTransactionsResponse>, Status> {
        let request = request.into_inner();
-        // Extract file contents
-        let file_contents = std::fs::read_to_string(request.file_path).map_err(|_| Status::invalid_argument("File path is malformed!"))?;
-        let mut txs: Vec<WalletTransaction> = Vec::new();
-        for line in file_contents.lines() {
-            serde_json::from_str(line)
-                .map(|tx| txs.push(tx))
-                .map_err(|_| Status::invalid_argument("File path is malformed!"))?;
-        }
+        // Open the file with proper error handling
+        let file = std::fs::File::open(&request.file_path).map_err(|e| 
+            Status::invalid_argument(format!("Failed to open file '{}': {}", request.file_path, e)))?;
+        let reader = std::io::BufReader::new(file);
+        
+        // Process the file line by line instead of loading it all into memory
+        let mut txs: Vec<WalletTransaction> = Vec::new();
+        for (line_num, line_result) in std::io::BufRead::lines(reader).enumerate() {
+            let line = line_result.map_err(|e| 
+                Status::invalid_argument(format!("Failed to read line {}: {}", line_num + 1, e)))?;
+            
+            serde_json::from_str(&line)
+                .map(|tx| txs.push(tx))
+                .map_err(|e| Status::invalid_argument(
+                    format!("Invalid JSON at line {}: {}", line_num + 1, e)
+                ))?;
+        }
+        
        let mut transaction_service = self.get_transaction_service();

        let mut tx_ids = Vec::new();
+        let mut failed_imports = Vec::new();
        for tx in txs {
            match transaction_service.import_transaction(tx).await {
                Ok(id) => {
                    println!("Imported tx: {}", id);
                    tx_ids.push(id.into());
                },
-                Err(e) => eprintln!("Could not import tx {}", e),
+                Err(e) => {
+                    error!(target: LOG_TARGET, "Failed to import transaction: {}", e);
+                    failed_imports.push(e.to_string());
+                }
            };
        }
+        
+        if !failed_imports.is_empty() && tx_ids.is_empty() {
+            // If all imports failed, return an error
+            return Err(Status::internal(format!("All transaction imports failed: {}", failed_imports.join(", "))));
+        }
+        
        Ok(Response::new(ImportTransactionsResponse { tx_ids }))
    }

Additionally, consider adding:

  1. A check to verify the file exists before opening it
  2. A size limit for the file to prevent denial of service attacks
  3. A method to return both successful and failed transaction imports in the response

Improve error handling and streaming in import_transactions

This endpoint currently reads the entire file into memory, hides I/O errors behind a generic message, and never surfaces import failures to the client. Let’s address all three:

• File: applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs
Method: import_transactions (around lines 1203–1229)

• Replace read_to_string with File::open + BufReader for streaming and detailed errors
• Add per‐line JSON parse errors with line numbers
• Collect failed imports and return an error or include failures in the response

Suggested diff:

     async fn import_transactions(
         &self,
         request: Request<ImportTransactionsRequest>,
     ) -> Result<Response<ImportTransactionsResponse>, Status> {
         let request = request.into_inner();
-        // Extract file contents
-        let file_contents = std::fs::read_to_string(request.file_path).map_err(|_| Status::invalid_argument("File path is malformed!"))?;
-        let mut txs: Vec<WalletTransaction> = Vec::new();
-        for line in file_contents.lines() {
-            serde_json::from_str(line)
-                .map(|tx| txs.push(tx))
-                .map_err(|_| Status::invalid_argument("File path is malformed!"))?;
-        }
+        // Open file with detailed error
+        let path = &request.file_path;
+        if !std::path::Path::new(path).exists() {
+            return Err(Status::invalid_argument(format!("File not found: {}", path)));
+        }
+        let file = std::fs::File::open(path).map_err(|e|
+            Status::invalid_argument(format!("Failed to open '{}': {}", path, e))
+        )?;
+        let reader = std::io::BufReader::new(file);
+
+        // Stream JSON lines with per‐line error reporting
+        let mut txs = Vec::new();
+        for (i, line_res) in reader.lines().enumerate() {
+            let line = line_res.map_err(|e|
+                Status::invalid_argument(format!("Read error at line {}: {}", i + 1, e))
+            )?;
+            let tx: WalletTransaction = serde_json::from_str(&line).map_err(|e|
+                Status::invalid_argument(format!("Invalid JSON at line {}: {}", i + 1, e))
+            )?;
+            txs.push(tx);
+        }
 
         let mut transaction_service = self.get_transaction_service();
 
         let mut tx_ids = Vec::new();
+        let mut failures = Vec::new();
         for tx in txs {
             match transaction_service.import_transaction(tx).await {
                 Ok(id) => {
                     println!("Imported tx: {}", id);
                     tx_ids.push(id.into());
                 }
-                Err(e) => eprintln!("Could not import tx {}", e),
+                Err(e) => {
+                    error!(target: LOG_TARGET, "Import failed: {}", e);
+                    failures.push(e.to_string());
+                }
             };
         }
+        // If every import failed, return an error
+        if !failures.is_empty() && tx_ids.is_empty() {
+            return Err(Status::internal(format!(
+                "All imports failed: {}",
+                failures.join("; ")
+            )));
+        }
 
         Ok(Response::new(ImportTransactionsResponse { tx_ids }))
     }

Additional considerations:

  1. Enforce a maximum file size before streaming to mitigate DoS.
  2. Extend the response (e.g. add a failed_tx_ids or error details field) so clients can reconcile partial failures.
  3. Log at appropriate levels and include LOG_TARGET constant if not already defined.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async fn import_transactions(
&self,
request: Request<ImportTransactionsRequest>,
) -> Result<Response<ImportTransactionsResponse>, Status> {
let request = request.into_inner();
// Extract file contents
let file_contents = std::fs::read_to_string(request.file_path).map_err(|_| Status::invalid_argument("File path is malformed!"))?;
let mut txs: Vec<WalletTransaction> = Vec::new();
for line in file_contents.lines() {
serde_json::from_str(line)
.map(|tx| txs.push(tx))
.map_err(|_| Status::invalid_argument("File path is malformed!"))?;
}
let mut transaction_service = self.get_transaction_service();
let mut tx_ids = Vec::new();
for tx in txs {
match transaction_service.import_transaction(tx).await {
Ok(id) => {
println!("Imported tx: {}", id);
tx_ids.push(id.into());
},
Err(e) => eprintln!("Could not import tx {}", e),
};
}
Ok(Response::new(ImportTransactionsResponse { tx_ids }))
}
async fn import_transactions(
&self,
request: Request<ImportTransactionsRequest>,
) -> Result<Response<ImportTransactionsResponse>, Status> {
let request = request.into_inner();
// Open file with detailed error
let path = &request.file_path;
if !std::path::Path::new(path).exists() {
return Err(Status::invalid_argument(format!("File not found: {}", path)));
}
let file = std::fs::File::open(path).map_err(|e| {
Status::invalid_argument(format!("Failed to open '{}': {}", path, e))
})?;
let reader = std::io::BufReader::new(file);
// Stream JSON lines with per-line error reporting
let mut txs = Vec::new();
for (i, line_res) in reader.lines().enumerate() {
let line = line_res.map_err(|e| {
Status::invalid_argument(format!("Read error at line {}: {}", i + 1, e))
})?;
let tx: WalletTransaction = serde_json::from_str(&line).map_err(|e| {
Status::invalid_argument(format!("Invalid JSON at line {}: {}", i + 1, e))
})?;
txs.push(tx);
}
let mut transaction_service = self.get_transaction_service();
let mut tx_ids = Vec::new();
let mut failures = Vec::new();
for tx in txs {
match transaction_service.import_transaction(tx).await {
Ok(id) => {
println!("Imported tx: {}", id);
tx_ids.push(id.into());
}
Err(e) => {
error!(target: LOG_TARGET, "Import failed: {}", e);
failures.push(e.to_string());
}
};
}
// If every import failed, return an error
if !failures.is_empty() && tx_ids.is_empty() {
return Err(Status::internal(format!(
"All imports failed: {}",
failures.join("; ")
)));
}
Ok(Response::new(ImportTransactionsResponse { tx_ids }))
}
🤖 Prompt for AI Agents
In applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs around
lines 1203 to 1229, improve import_transactions by replacing read_to_string with
streaming file reading using File::open and BufReader to handle large files
efficiently. Add a file existence check and enforce a maximum file size limit
before processing to prevent DoS attacks. Enhance error handling by providing
detailed I/O error messages instead of a generic "File path is malformed!"
message, and include per-line JSON parsing errors with line numbers. Collect
both successful and failed transaction imports, then extend the
ImportTransactionsResponse to return this information to the client, allowing
them to reconcile partial failures. Finally, ensure proper logging with
appropriate log levels and include a LOG_TARGET constant if missing.

@mmrrnn mmrrnn requested a review from a team as a code owner May 21, 2025 21:43
@mmrrnn
Copy link
Copy Markdown
Contributor Author

mmrrnn commented May 22, 2025

image

image

@SWvheerden SWvheerden changed the title chore: import transactions via grpc feat: import transactions via grpc May 22, 2025
@SWvheerden SWvheerden merged commit 12db85d into tari-project:development May 22, 2025
13 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants