bc_cli/
main.rs

1//!
2//! Main CLI runner for the Blockchain Application.<br>
3//! Function main.<br>
4//! List, transact and reset the blockchain sample application.
5//!
6
7use bc_db::mongo_utils;
8use bc_utils::utils;
9use block_chain::wallet::Wallet;
10use bson::Document;
11use mongodb::{Client, Collection};
12use std::collections::HashMap;
13use std::error::Error;
14use std::io::{self, Write};
15use tokio;
16
17///
18/// Function main for the CLI blockchain application.
19///
20#[tokio::main]
21async fn main() -> Result<(), Box<dyn Error>> {
22    // Initialize MongoDB client
23    let client = Client::with_uri_str("mongodb://localhost:27017").await?;
24    let db = client.database("blockchain_db");
25
26    // Initialize collections
27    let blocks_collection = db.collection("blocks");
28    let wallets_collection = db.collection("wallets");
29    let blockchain_collection = db.collection("blockchain");
30
31    // Load blockchain from database
32    let mut blockchain =
33        mongo_utils::load_blockchain(blocks_collection.clone(), blockchain_collection.clone())
34            .await?;
35    // Validate and print the blockchain validity
36    let output = utils::list_blockchain(&blockchain);
37    println!("{}", output);
38
39    loop {
40        // Display the menu options
41        print!("\n\n");
42        println!("*************** ITEMS ***************");
43        println!("*   1. List Blockchain              *");
44        println!("*   2. View Wallet Details          *");
45        println!("*   3. Send Crypto Currency         *");
46        println!("*   4. Mine Transactions            *");
47        println!("*   5. Reset Sample Data            *");
48        println!("*   6. Exit                         *");
49        println!("*************************************\n");
50        print!("Input Choice: ");
51
52        // Flush stdout to ensure the prompt is displayed
53        io::stdout().flush().unwrap();
54
55        // Read the user's input
56        let mut input = String::new();
57        io::stdin()
58            .read_line(&mut input)
59            .expect("Failed to read line");
60
61        // Trim and parse the input
62        let input = input.trim();
63        match input.parse::<u32>() {
64            Ok(choice) => match choice {
65                1 => {
66                    let blockchain = mongo_utils::load_blockchain(
67                        blocks_collection.clone(),
68                        blockchain_collection.clone(),
69                    )
70                    .await?;
71                    let output = utils::list_blockchain(&blockchain);
72                    println!("{}", output);
73                }
74                2 => {
75                    // Load wallets from database
76                    let wallet_map: HashMap<String, Wallet> =
77                        mongo_utils::load_wallets(wallets_collection.clone()).await?;
78                    list_wallets(&wallet_map);
79                    list_wallet(&wallet_map);
80                }
81                3 => {
82                    // // Load wallets from database
83                    // let mut wallet_map: HashMap<String, Wallet> =
84                    //     mongo_utils::load_wallets(wallets_collection.clone()).await?;
85                    // list_wallets(&wallet_map);
86                    send_cc(
87                        // &mut wallet_map,
88                        // &mut blockchain,
89                        blocks_collection.clone(),
90                        wallets_collection.clone(),
91                        blockchain_collection.clone(),
92                    )
93                    .await;
94                }
95                4 => {
96                    mine_transactions(
97                        blocks_collection.clone(),
98                        blockchain_collection.clone(),
99                        wallets_collection.clone(),
100                    )
101                    .await;
102                }
103                5 => {
104                    let result = utils::reset_sample_data(
105                        &blocks_collection,
106                        &wallets_collection,
107                        &blockchain_collection,
108                    )
109                    .await;
110
111                    // Update the in-memory state
112                    let new_blockchain = mongo_utils::load_blockchain(
113                        blocks_collection.clone(),
114                        blockchain_collection.clone(),
115                    )
116                    .await?;
117                    blockchain = new_blockchain;
118
119                    // Validate and print the blockchain validity
120                    print!("{:?}", result);
121                    let output = utils::list_blockchain(&blockchain);
122                    println!("{}", output);
123                }
124                6 => {
125                    println!("Exiting...");
126                    break;
127                }
128                _ => println!("Invalid choice. Please enter a number between 1 and 7."),
129            },
130            Err(_) => println!("Invalid input. Please enter a number."),
131        }
132    }
133
134    Ok(())
135}
136
137fn list_wallets(wallet_map: &HashMap<String, Wallet>) {
138    println!("\nAvaiable Wallets:\n");
139    for (address, wallet) in wallet_map {
140        println!("Wallet address: {} and PK: {}", address, wallet.public_key);
141    }
142}
143
144fn list_wallet(wallet_map: &HashMap<String, Wallet>) {
145    print!("\nInput Address of Wallet to display: ");
146    io::stdout().flush().unwrap(); // Ensure the prompt is displayed
147    let mut selected_address = String::new();
148    io::stdin()
149        .read_line(&mut selected_address)
150        .expect("Failed to read line");
151    println!("\nWallet selected {}", selected_address);
152    let selected_address = selected_address.trim();
153    if let Some(wallet) = wallet_map.get(selected_address) {
154        let output = utils::view_wallet(&wallet, &wallet.address);
155        println!("{}", output);
156    } else {
157        println!("Address Not Found!")
158    }
159}
160
161async fn send_cc(
162    // wallet_map: &mut HashMap<String, Wallet>,
163    // blockchain: &mut Blockchain,
164    blocks_collection: Collection<Document>,
165    wallets_collection: Collection<Document>,
166    blockchain_collection: Collection<Document>,
167) {
168    // Load blockchain from database
169    let mut blockchain =
170        mongo_utils::load_blockchain(blocks_collection.clone(), blockchain_collection.clone())
171            .await
172            .unwrap();
173
174    // Load wallets from database
175    let mut wallet_map: HashMap<String, Wallet> =
176        mongo_utils::load_wallets(wallets_collection.clone())
177            .await
178            .unwrap()
179            .clone();
180    list_wallets(&wallet_map);
181
182    print!("\nInput Sender Wallet Address: ");
183    io::stdout().flush().unwrap(); // Ensure the prompt is displayed
184    let mut sender_address = String::new();
185    io::stdin()
186        .read_line(&mut sender_address)
187        .expect("Failed to read line");
188    println!("\nWallet selected: {}", sender_address);
189    let sender_address = sender_address.trim();
190
191    print!("Input Receiver Wallet Address: ");
192    io::stdout().flush().unwrap(); // Ensure the prompt is displayed
193    let mut receiver_address = String::new();
194    io::stdin()
195        .read_line(&mut receiver_address)
196        .expect("Failed to read line");
197    println!("\nWallet selected: {}", receiver_address);
198    let receiver_address = receiver_address.trim();
199
200    let mut sender_wallet = wallet_map
201        .remove(sender_address)
202        .expect("Sender wallet not found");
203    let mut receiver_wallet = wallet_map
204        .remove(receiver_address)
205        .expect("Receiver wallet not found");
206
207    let (amount, fee) = get_amount_fees();
208    let msg: String = Result::expect(
209        utils::send_crypto(
210            amount,
211            fee,
212            &mut blockchain,
213            &mut sender_wallet,
214            &mut receiver_wallet,
215            blocks_collection.clone(),
216            wallets_collection.clone(),
217            blockchain_collection.clone(),
218        )
219        .await,
220        "Could not complete Transaction!",
221    );
222    print!("{msg}");
223}
224
225fn get_amount_fees() -> (f64, f64) {
226    let amount = get_input("Please enter the amount to send: ");
227    let fee = get_input("Please enter the fee for mining: ");
228
229    (amount, fee)
230}
231
232fn get_input(msg: &str) -> f64 {
233    loop {
234        // Prompt the user to input a value
235        print!("{msg}");
236        io::stdout().flush().unwrap(); // Ensure the prompt is displayed
237
238        let mut input = String::new();
239        io::stdin()
240            .read_line(&mut input)
241            .expect("Failed to read line");
242
243        // Try to parse the input
244        match input.trim().parse::<f64>() {
245            Ok(amt_value) => return amt_value, // Return the value if it's valid
246            Err(_) => println!("Invalid input, please enter a valid number"), // Prompt the user to try again
247        }
248    }
249}
250
251async fn mine_transactions(
252    blocks_collection: Collection<Document>,
253    blockchain_collection: Collection<Document>,
254    wallets_collection: Collection<Document>,
255) {
256    println!("\nList of Pending Transactions from Mempool\n");
257    let result = utils::get_pending_txrn(&wallets_collection.clone()).await;
258
259    let mut gbl_index = 0;
260    for (_address, transactions) in &result {
261        for txn in transactions.iter() {
262            println!(
263                "No#:-{} tx_id: {:?} timestamp: {} sender: {} receiver: {} amount: {} fee: {}",
264                gbl_index, txn.tx_id, txn.timestamp, txn.sender, txn.receiver, txn.amount, txn.fee
265            );
266            gbl_index += 1;
267        }
268    }
269
270    if gbl_index == 0 {
271        println!("\nNo Pending transactions to mine!\n");
272        return ();
273    }
274
275    println!("Select No# to mine: ");
276    io::stdout().flush().unwrap(); // Ensure the prompt is displayed
277    let mut selected_number = String::new();
278    io::stdin()
279        .read_line(&mut selected_number)
280        .expect("Failed to read line");
281
282    let selected_index: usize = selected_number
283        .trim()
284        .parse()
285        .expect("Please enter a valid number");
286
287    // Retrieve the tx_id from the existing map
288    gbl_index = 0;
289    let mut selected_tx_id = None;
290    for (_address, transactions) in &result {
291        for txn in transactions.iter() {
292            if gbl_index == selected_index {
293                selected_tx_id = Some(txn.tx_id.clone());
294                break;
295            }
296            gbl_index += 1;
297        }
298        if selected_tx_id.is_some() {
299            break;
300        }
301    }
302
303    let tx_id = selected_tx_id.unwrap();
304
305    utils::mine_crypto(blocks_collection.clone(), blockchain_collection.clone()).await;
306    let response = utils::commit_mine_crypto(
307        tx_id,
308        blocks_collection,
309        blockchain_collection,
310        wallets_collection,
311    )
312    .await;
313    println!("{response}");
314}