bc_utils/
utils.rs

1//!
2//! Utilities runner for the Blockchain Application.<br>
3//! Functions used by the main menu.<br>
4//! List, transact and reset the blockchain sample application.
5//!
6
7use bc_db::mongo_utils;
8use block_chain::block::Block;
9use block_chain::blockchain::Blockchain;
10use block_chain::wallet::{Transaction, Wallet};
11use bson::Document;
12use chrono::Utc;
13use mongodb::Collection;
14use serde_json::json;
15use std::collections::{BTreeMap, HashMap, VecDeque};
16use std::error::Error;
17use uuid::Uuid;
18
19///
20/// List blockchain data.
21///
22pub fn list_blockchain(blockchain: &Blockchain) -> String {
23    // Build a JSON object that includes all the relevant information
24    let response = json!({
25        "message": "Listing Blockchain...",
26        "is_valid": blockchain.is_chain_valid(),
27        "length": blockchain.chain.len(),
28        "mempool_size": blockchain.mempool.len(),
29        "blockchain": blockchain // This will serialize the blockchain struct
30    });
31
32    // Serialize the JSON object into a string
33    serde_json::to_string_pretty(&response).expect("Failed to serialize response to JSON")
34}
35
36///
37/// View wallet data.
38///
39pub fn view_wallet(wallet: &Wallet, wallet_name: &str) -> String {
40    // Build a JSON object that includes all the relevant information
41    let response = json!({
42        "message": format!("Listing Wallet {}...", wallet_name),
43        "pending transactions": wallet.pending_transactions.len(),
44        "wallet": wallet // This will serialize the wallet struct
45    });
46
47    // Serialize the JSON object into a string
48    serde_json::to_string_pretty(&response).expect("Failed to serialize response to JSON")
49}
50
51///
52/// Load blockchain data.
53///
54pub async fn load_blockchain(
55    blocks_collection: &Collection<Document>,
56    blockchain_collection: &Collection<Document>,
57) -> Blockchain {
58    // Load blockchain from database
59    let result =
60        mongo_utils::load_blockchain(blocks_collection.clone(), blockchain_collection.clone())
61            .await;
62    result.unwrap()
63}
64
65///
66/// Get wallet data.
67///
68pub async fn get_wallet_addresses(wallets_collection: &Collection<Document>) -> Vec<String> {
69    let wallet_map: HashMap<String, Wallet> = Result::expect(
70        mongo_utils::load_wallets(wallets_collection.clone()).await,
71        "Could not get wallets data",
72    );
73
74    let mut addresses: Vec<String> = wallet_map
75        .iter()
76        .map(|(_key, wallet)| wallet.address.clone())
77        .collect();
78    addresses.sort();
79    addresses
80}
81
82///
83/// Get pending transactions.
84///
85pub async fn get_pending_transactions(wallets_collection: &Collection<Document>) -> Vec<String> {
86    let wallet_map: HashMap<String, Wallet> = Result::expect(
87        mongo_utils::load_wallets(wallets_collection.clone()).await,
88        "Could not get wallets data",
89    );
90
91    let pending_txrn_map: BTreeMap<String, VecDeque<Transaction>> = wallet_map
92        .into_iter()
93        .filter(|(_key, wallet)| !wallet.pending_transactions.is_empty())
94        .map(|(_key, wallet)| (wallet.address.clone(), wallet.pending_transactions.clone()))
95        .collect();
96
97    if pending_txrn_map.len() == 0 {
98        let mut no_trxns = Vec::new();
99        no_trxns.push(String::from(" No Pending Transactions! "));
100        return no_trxns;
101    }
102
103    pending_txrn_map
104        .values()
105        .flat_map(|deque| deque.iter())
106        .map(|transaction| format!("{:?}", transaction)) // Adjust this line to your actual string conversion
107        .collect()
108}
109
110///
111/// Retrieve the last hash in the chain.
112///
113pub async fn get_previous_hash<'a>(
114    blocks_collection: &'a Collection<Document>,
115    blockchain_collection: &'a Collection<Document>,
116) -> String {
117    // Extract last_hash as &str
118    let blockchain = load_blockchain(&blocks_collection, &blockchain_collection).await;
119    let previous_hash: String = blockchain.get_last_hash().unwrap().to_string();
120    previous_hash
121}
122
123///
124/// Retrieve the tx_id from the existing map.
125///
126pub async fn get_transaction(
127    tx_id: String,
128    wallets_collection: Collection<Document>,
129) -> Transaction {
130    let result = get_pending_txrn(&wallets_collection.clone()).await;
131
132    let mut transaction = None;
133    for (_address, transactions) in &result {
134        for txn in transactions.iter() {
135            println!("txn.tx_id {} and given tx_id is {}", txn.tx_id, tx_id);
136            println!("txn is {:?}", Some(txn));
137            if txn.tx_id.to_string() == tx_id.to_string() {
138                transaction = Some(txn);
139            }
140        }
141    }
142    transaction.unwrap().clone()
143}
144
145///
146/// Crypto transaction from one wallet to another.
147///
148pub async fn send_crypto(
149    amount: f64,
150    fee: f64,
151    blockchain: &mut Blockchain,
152    sender_wallet: &mut Wallet,
153    receiver_wallet: &mut Wallet,
154    blocks_collection: Collection<Document>,
155    wallets_collection: Collection<Document>,
156    blockchain_collection: Collection<Document>,
157) -> Result<String, Box<dyn Error>> {
158    // Check amounts are valid
159    let (is_valid, msg) = validate_txn(blockchain, sender_wallet, amount, fee).await;
160    if is_valid == -1 {
161        return Ok(msg);
162    }
163
164    // Process transaction
165    sender_wallet.update_balance(-amount - fee);
166    // receiver_wallet.update_balance(amount);
167
168    let transaction = Transaction {
169        tx_id: Uuid::new_v4().to_string(),
170        sender: sender_wallet.address.clone(),
171        receiver: receiver_wallet.address.clone(),
172        amount,
173        fee,
174        timestamp: Utc::now().timestamp() as u64,
175    };
176
177    let mut transactions: Vec<Transaction> = Vec::new();
178    transactions.push(transaction.clone());
179
180    sender_wallet.add_pending_transaction(transaction.clone());
181
182    blockchain.mempool.push(transaction);
183
184    save_to_db(
185        blockchain,
186        sender_wallet,
187        receiver_wallet,
188        blocks_collection,
189        wallets_collection,
190        blockchain_collection,
191    )
192    .await?;
193
194    Ok(String::from(
195        "Transaction successful, blockchain and wallets updated",
196    ))
197}
198
199///
200/// Get pending transactions.
201///
202pub async fn get_pending_txrn(
203    wallets_collection: &Collection<Document>,
204) -> BTreeMap<String, VecDeque<Transaction>> {
205    let wallet_map: HashMap<String, Wallet> = mongo_utils::load_wallets(wallets_collection.clone())
206        .await
207        .expect("Could not get wallets data");
208
209    let pending_txrn_map: BTreeMap<String, VecDeque<Transaction>> = wallet_map
210        .into_iter()
211        .filter(|(_key, wallet)| !wallet.pending_transactions.is_empty())
212        .map(|(_key, wallet)| (wallet.address.clone(), wallet.pending_transactions.clone()))
213        .collect();
214
215    pending_txrn_map
216}
217
218///
219/// Mine a pending transaction.
220///
221pub async fn mine_crypto(
222    blocks_collection: Collection<Document>,
223    blockchain_collection: Collection<Document>,
224) {
225    // Extract last_hash as &str
226    let blockchain = load_blockchain(&blocks_collection, &blockchain_collection).await;
227    let previous_hash: &str = blockchain.get_last_hash().unwrap();
228
229    // call mine_tranasaction
230    let result = mine_transactions("The", previous_hash, 1).await;
231    let (nonce, hash, msg) = match result {
232        Ok((nonce, hash, formatted_message)) => (nonce, hash, formatted_message),
233        Err(e) => {
234            println!("Error occurred: {}", e);
235            return;
236        }
237    };
238    println!("Nonce: {nonce}, hash: {hash}\n{msg}");
239}
240
241///
242/// Commit the mined transaction to the database.
243///
244pub async fn commit_mine_crypto(
245    tx_id: String,
246    blocks_collection: Collection<Document>,
247    blockchain_collection: Collection<Document>,
248    wallets_collection: Collection<Document>,
249) -> String {
250    // Extract last_hash as &str
251    let mut blockchain = load_blockchain(&blocks_collection, &blockchain_collection).await;
252
253    let transaction = get_transaction(tx_id.clone(), wallets_collection.clone()).await;
254    let data = serde_json::to_value(&transaction).expect("Failed to serialize transaction");
255    let mut transaction_data: Vec<Transaction> = Vec::new();
256    transaction_data.push(transaction.clone());
257
258    // create new block with txn
259    let block = Block::new(
260        blockchain.chain.len() as u64,
261        Utc::now().timestamp() as u64,
262        blockchain
263            .chain
264            .last()
265            .map_or(String::from("0"), |block| block.hash.clone()),
266        data,
267        String::from("Miner1"),
268        transaction_data,
269    );
270
271    // insert block in block_chain
272    blockchain.chain.push(block.clone());
273
274    // remove txn from mempool[]
275    let mut index_to_remove = 0;
276    for mp in blockchain.mempool.clone() {
277        if mp.tx_id == tx_id {
278            blockchain.mempool.remove(index_to_remove);
279            break;
280        }
281        index_to_remove += 1;
282    }
283
284    // Load the wallets for txn updates.
285    let mut sender_wallet =
286        mongo_utils::load_wallet(wallets_collection.clone(), transaction.sender.as_str())
287            .await
288            .unwrap();
289    let mut receiver_wallet =
290        mongo_utils::load_wallet(wallets_collection.clone(), transaction.receiver.as_str())
291            .await
292            .unwrap();
293
294    // sender wallet moving txn from pending[] to transactions[]
295    sender_wallet.transfer_txn(tx_id);
296
297    // update receiver wallet by
298    receiver_wallet.add_transaction(transaction.clone());
299    receiver_wallet.update_balance(transaction.amount);
300
301    // save to db
302    let result = save_to_db(
303        &mut blockchain,
304        &mut sender_wallet,
305        &mut receiver_wallet,
306        blocks_collection,
307        wallets_collection,
308        blockchain_collection,
309    )
310    .await;
311
312    println!("Save db = {:?}", result);
313
314    let response = json!({
315        "message": "Listing New Block...",
316        "block": block.clone()
317    });
318
319    // Serialize the JSON object into a string
320    serde_json::to_string_pretty(&response).expect("Failed to serialize response to JSON")
321}
322
323///
324/// Reset Sample Data.
325///
326pub async fn reset_sample_data(
327    blocks_collection: &Collection<Document>,
328    wallets_collection: &Collection<Document>,
329    blockchain_collection: &Collection<Document>,
330) -> Result<(), Box<dyn Error>> {
331    let _ = mongo_utils::reset_sample_data(
332        blockchain_collection.clone(),
333        blocks_collection.clone(),
334        wallets_collection.clone(),
335    )
336    .await;
337
338    Ok(())
339}
340
341///
342/// Validate the transaction details.
343///
344async fn validate_txn(
345    blockchain: &Blockchain,
346    wallet1: &mut Wallet,
347    amount: f64,
348    fee: f64,
349) -> (i8, String) {
350    let mut msg = String::from("All transaction checks OK");
351    let mut is_valid = 0;
352    let txn_amount = amount + fee;
353
354    if wallet1.get_balance() < txn_amount {
355        msg = String::from("Transaction failed: Insufficient balance\n");
356        is_valid = -1;
357    }
358
359    if blockchain.chain.len() >= 10 {
360        msg = String::from(
361            "Due to system constraints no more than 10 blocks allowed\nPlease reset data.",
362        );
363        is_valid = -1;
364    }
365
366    if blockchain.mempool.len() >= 6 {
367        msg = String::from(
368            "Due to system constraints no more than 10 blocks allowed\nPlease reset data.",
369        );
370        is_valid = -1;
371    }
372
373    if wallet1.get_balance() < txn_amount {
374        msg = String::from("Insufficient balance");
375        is_valid = -1;
376    }
377
378    if txn_amount <= 0.0 || txn_amount.is_nan() || amount <= 0.0 || fee <= 0.0 {
379        msg = String::from("Not a valid amount.");
380        is_valid = -1;
381    }
382
383    (is_valid, msg)
384}
385
386///
387/// Mine Transactions from Mempool.
388///
389async fn mine_transactions(
390    data: &str,
391    previous_hash: &str,
392    difficulty: usize,
393) -> Result<(u64, String, String), Box<dyn Error>> {
394    let (nonce, hash) = crate::mining::mine_block(data, previous_hash, difficulty, None);
395    let result = (
396        nonce,
397        hash.clone(),
398        format!("\nMining completed!\nNonce: {:>2}, Hash: {}", nonce, hash),
399    );
400
401    Ok(result)
402}
403
404///
405/// Save to mongoDB.
406///
407async fn save_to_db(
408    blockchain: &mut Blockchain,
409    wallet1: &mut Wallet,
410    wallet2: &mut Wallet,
411    blocks_collection: Collection<Document>,
412    wallets_collection: Collection<Document>,
413    blockchain_collection: Collection<Document>,
414) -> Result<(), Box<dyn Error>> {
415    // Save updated blockchain and wallets to MongoDB
416    mongo_utils::save_blockchain(blockchain, blocks_collection, blockchain_collection).await?;
417    mongo_utils::save_wallet(wallet1.clone(), wallets_collection.clone()).await?;
418    mongo_utils::save_wallet(wallet2.clone(), wallets_collection).await?;
419
420    Ok(())
421}