From 63d08a9f120e842dcc5a34a1db6b39957c643b30 Mon Sep 17 00:00:00 2001 From: Yigit Sever Date: Fri, 23 Apr 2021 01:41:18 +0300 Subject: [WIP] Done, untested --- src/handlers.rs | 200 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 114 insertions(+), 86 deletions(-) (limited to 'src/handlers.rs') diff --git a/src/handlers.rs b/src/handlers.rs index ca41b61..123f70e 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -4,6 +4,7 @@ use askama::Template; use blake2::{Blake2s, Digest}; use block_modes::block_padding::Pkcs7; use block_modes::{BlockMode, Cbc}; +use chrono::Utc; use jsonwebtoken::errors::ErrorKind; use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; use log::{debug, warn}; @@ -259,8 +260,7 @@ pub async fn authenticate_user( } } - // We're using this as the validator - // I hate myself + // We're using this as the validator instead of anything reasonable if DecodingKey::from_rsa_pem(request.public_key.as_bytes()).is_err() { let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, @@ -311,7 +311,6 @@ pub async fn authenticate_user( /// GET /transaction /// Returns JSON array of transactions -/// Cannot fail pub async fn list_transactions(db: Db) -> Result { debug!("GET /transaction, list_transactions() is handling"); let mut result = HashMap::new(); @@ -341,8 +340,6 @@ pub async fn propose_block( ) -> Result { debug!("POST /block, propose_block() is handling"); - let users_store = db.users.upgradable_read(); - warn!("New block proposal: {:?}", &new_block); if new_block.transaction_list.len() < BLOCK_TRANSACTION_COUNT as usize { @@ -363,7 +360,30 @@ pub async fn propose_block( } // proposer (first transaction fingerprint) checks - let internal_user = match users_store.get(&new_block.transaction_list[0]) { + + let pending_transactions = db.pending_transactions.upgradable_read(); + + let internal_user_fingerprint = match pending_transactions.get(&new_block.transaction_list[0]) { + Some(coinbase) => &coinbase.source, + None => { + debug!( + "User with public key signature {:?} is not found in the database", + new_block.transaction_list[0] + ); + + let res_json = warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: "User with that public key signature is not found in the database" + .to_owned(), + }); + + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + } + }; + + let users_store = db.users.upgradable_read(); + + let internal_user = match users_store.get(internal_user_fingerprint) { Some(existing_user) => existing_user, None => { debug!( @@ -433,19 +453,16 @@ pub async fn propose_block( } // Scope the RwLocks, there are hashing stuff below - { - let pending_transactions = db.pending_transactions.read(); - // Are transactions in the block valid? - for transaction_hash in new_block.transaction_list.iter() { - if !pending_transactions.contains_key(transaction_hash) { - let res_json = warp::reply::json(&GradeCoinResponse { - res: ResponseType::Error, - message: "Block contains unknown transaction".to_owned(), - }); + // Are transactions in the block valid? + for transaction_hash in new_block.transaction_list.iter() { + if !pending_transactions.contains_key(transaction_hash) { + let res_json = warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: "Block contains unknown transaction".to_owned(), + }); - return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); - } + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); } } @@ -487,9 +504,9 @@ pub async fn propose_block( // All clear, block accepted! warn!("ACCEPTED BLOCK {:?}", new_block); - // Scope the pending_transactions + // Scope the read guards { - let mut pending_transactions = db.pending_transactions.write(); + let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions); let mut users_store = RwLockUpgradableReadGuard::upgrade(users_store); for fingerprint in new_block.transaction_list.iter() { @@ -502,7 +519,22 @@ pub async fn propose_block( } if let Some(to) = users_store.get_mut(target) { - to.balance += transaction.amount; + to.balance += transaction.amount + 1; + } + + // if the receiver is a bot, they will reciprocate + if users_store.get(target).unwrap().is_bot { + let transaction_id = + calculate_transaction_id(&transaction.target, &transaction.source); + pending_transactions.insert( + transaction_id, + Transaction { + source: target.to_owned(), + target: source.to_owned(), + amount: transaction.amount, + timestamp: Utc::now().naive_local(), + }, + ); } } } @@ -557,12 +589,12 @@ pub async fn propose_transaction( let users_store = db.users.read(); // Is this transaction from an authorized source? - let internal_user = match users_store.get(&new_transaction.by) { + let internal_user = match users_store.get(&new_transaction.source) { Some(existing_user) => existing_user, None => { debug!( "User with public key signature {:?} is not found in the database", - new_transaction.by + new_transaction.source ); return Ok(warp::reply::with_status( @@ -578,11 +610,41 @@ pub async fn propose_transaction( // `internal_user` is an authenticated student, can propose - // Does this user have a pending transaction? + // This public key was already written to the database, we can panic if it's not valid at + // *this* point + let proposer_public_key = &internal_user.public_key; + + let token_payload = match authorize_proposer(token, &proposer_public_key) { + Ok(data) => data, + Err(below) => { + debug!("JWT Error: {:?}", below); + return Ok(warp::reply::with_status( + warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: below, + }), + StatusCode::BAD_REQUEST, + )); + } + }; + + let transaction_id = calculate_transaction_id(&new_transaction.source, &new_transaction.target); + + // OLD: Does this user have a pending transaction? + // NEW: Is this source:target pair unqiue? { let transactions = db.pending_transactions.read(); - if transactions.contains_key(&*new_transaction.by.to_owned()) { - debug!("{:?} already has a pending transaction", new_transaction.by); + debug!( + "This is a transaction from {} to {}", + new_transaction.source, new_transaction.target, + ); + + if transactions.contains_key(&transaction_id) { + debug!( + "this source/target combination {} already has a pending transaction", + transaction_id + ); + return Ok(warp::reply::with_status( warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, @@ -593,6 +655,18 @@ pub async fn propose_transaction( } } + if new_transaction.source == new_transaction.target { + debug!("transaction source and target are the same",); + + return Ok(warp::reply::with_status( + warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: "transaction to yourself, you had to try didn't you? :)".to_owned(), + }), + StatusCode::BAD_REQUEST, + )); + } + // Is transaction amount within bounds if new_transaction.amount > TX_UPPER_LIMIT { debug!( @@ -608,76 +682,22 @@ pub async fn propose_transaction( )); } - if new_transaction.by == new_transaction.source { - // check if user can afford the transaction - if internal_user.balance < new_transaction.amount { - debug!( - "User does not have enough balance ({}) for this TX {}", - internal_user.balance, new_transaction.amount - ); - return Ok(warp::reply::with_status( - warp::reply::json(&GradeCoinResponse { - res: ResponseType::Error, - message: - "User does not have enough balance in their account for this transaction" - .to_owned(), - }), - StatusCode::BAD_REQUEST, - )); - } - } else if new_transaction.by == new_transaction.target { - // Only transactions FROM bank could appear here - - if new_transaction.source - != "31415926535897932384626433832795028841971693993751058209749445923" - { - debug!( - "Extortion attempt - between {} and {}", - new_transaction.source, new_transaction.target - ); - return Ok(warp::reply::with_status( - warp::reply::json(&GradeCoinResponse { - res: ResponseType::Error, - message: "Transactions cannot extort Gradecoin from unsuspecting users" - .to_owned(), - }), - StatusCode::BAD_REQUEST, - )); - } - } else { + // check if user can afford the transaction + if internal_user.balance < new_transaction.amount { debug!( - "Attempt to transact between two unrelated parties - {} and {}", - new_transaction.source, new_transaction.target + "User does not have enough balance ({}) for this TX {}", + internal_user.balance, new_transaction.amount ); return Ok(warp::reply::with_status( warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "Transactions cannot be proposed on behalf of someone else".to_owned(), + message: "User does not have enough balance in their account for this transaction" + .to_owned(), }), StatusCode::BAD_REQUEST, )); } - // This public key was already written to the database, we can panic if it's not valid at - // *this* point - let proposer_public_key = &internal_user.public_key; - - let token_payload = match authorize_proposer(token, &proposer_public_key) { - Ok(data) => data, - Err(below) => { - debug!("Something went wrong at JWT {:?}", below); - return Ok(warp::reply::with_status( - warp::reply::json(&GradeCoinResponse { - res: ResponseType::Error, - message: below, - }), - StatusCode::BAD_REQUEST, - )); - } - }; - - // authorized for transaction proposal - // this transaction was already checked for correctness at custom_filters, we can panic here if // it has been changed since @@ -701,7 +721,7 @@ pub async fn propose_transaction( let mut transactions = db.pending_transactions.write(); - transactions.insert(new_transaction.by.to_owned(), new_transaction); + transactions.insert(transaction_id, new_transaction); Ok(warp::reply::with_status( warp::reply::json(&GradeCoinResponse { @@ -772,6 +792,12 @@ fn authorize_proposer(jwt_token: String, user_pem: &str) -> Result String { + let long_fingerprint = format!("{}{}", source, target); + let id = format!("{:x}", Sha256::digest(long_fingerprint.as_bytes())); + id +} + #[derive(Template)] #[template(path = "list.html")] struct UserTemplate<'a> { @@ -781,6 +807,7 @@ struct UserTemplate<'a> { struct DisplayUsers { fingerprint: String, balance: u16, + is_bot: bool, } pub async fn user_list_handler(db: Db) -> Result { @@ -791,6 +818,7 @@ pub async fn user_list_handler(db: Db) -> Result