From 412116978169701479814d433a8bad73bfa54e02 Mon Sep 17 00:00:00 2001 From: Yigit Sever Date: Wed, 14 Apr 2021 21:28:37 +0300 Subject: Accepted block transactions play out now --- src/handlers.rs | 105 ++++++++++++++++++++++++++++++++++++++------------------ src/schema.rs | 9 ++--- 2 files changed, 75 insertions(+), 39 deletions(-) diff --git a/src/handlers.rs b/src/handlers.rs index 4a1edcc..420e82a 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -18,6 +18,7 @@ use std::fs; use warp::{http::StatusCode, reply}; use crate::PRIVATE_KEY; +const BLOCK_TRANSACTION_COUNT: u8 = 10; // Encryption primitive type Aes128Cbc = Cbc; @@ -116,7 +117,7 @@ pub async fn authenticate_user( let provided_id = request.student_id.clone(); - let privileged_student_id = match MetuId::new(request.student_id) { + let privileged_student_id = match MetuId::new(request.student_id, request.passwd) { Some(id) => id, None => { let res_json = warp::reply::json(&GradeCoinResponse { @@ -223,12 +224,10 @@ pub async fn list_transactions(db: Db) -> Result { /// Proposes a new block for the next round. /// Can reject the block /// -/// TODO: WHO IS PROPOSING THIS BLOCK OH GOD <13-04-21, yigit> // ok let's say the proposer has -/// to put their transaction as the first transaction of the transaction_list -/// that's not going to backfire in any way +/// The proposer has to put their transaction as the first transaction of the [`transaction_list`]. +/// This is the analogue of `coinbase` in Bitcoin works /// -/// TODO: after a block is accepted, it's transactions should play out and the proposer should -/// get something for their efforts <13-04-21, yigit> // +/// The `coinbase` transaction also gets something for their efforts. pub async fn authorized_propose_block( new_block: Block, token: String, @@ -236,10 +235,23 @@ pub async fn authorized_propose_block( ) -> Result { debug!("POST request to /block, authorized_propose_block"); - let users_store = db.users.read(); + let users_store = db.users.upgradable_read(); println!("{:?}", &new_block); + if new_block.transaction_list.len() < 1 { + let res_json = warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: format!( + "There should be {} transactions in the block", + BLOCK_TRANSACTION_COUNT + ), + }); + + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + } + + // proposer (first transaction fingerprint) checks let internal_user = match users_store.get(&new_block.transaction_list[0]) { Some(existing_user) => existing_user, None => { @@ -250,7 +262,7 @@ pub async fn authorized_propose_block( let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "User with the given public key signature is not found in the database" + message: "User with that public key signature is not found in the database" .to_owned(), }); @@ -260,6 +272,7 @@ pub async fn authorized_propose_block( let proposer_public_key = &internal_user.public_key; + // JWT Check let token_payload = match authorize_proposer(token, &proposer_public_key) { Ok(data) => data, Err(below) => { @@ -274,8 +287,7 @@ pub async fn authorized_propose_block( } }; - debug!("authorized for block proposal"); - + // Block hash check if token_payload.claims.tha != new_block.hash { debug!( "The Hash of the block {:?} did not match the hash given in jwt {:?}", @@ -289,18 +301,20 @@ pub async fn authorized_propose_block( return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); } - debug!("clear for block proposal"); - let pending_transactions = db.pending_transactions.upgradable_read(); - let blockchain = db.blockchain.upgradable_read(); + // Scope the RwLocks, there are hashing stuff below + { + let pending_transactions = db.pending_transactions.read(); - 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)); + } } } @@ -315,7 +329,7 @@ pub async fn authorized_propose_block( let hashvalue = Blake2s::digest(&naked_block_flat); let hash_string = format!("{:x}", hashvalue); - // Does the hash claimed in block matched with the actual hash? + // Does the hash claimed in block match with the actual hash? if hash_string != new_block.hash { debug!("request was not telling the truth, hash values do not match"); let res_json = warp::reply::json(&GradeCoinResponse { @@ -339,7 +353,32 @@ pub async fn authorized_propose_block( return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); } - let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain); + // All clear, block accepted! + debug!("We have a new block!"); + + { + let pending_transactions = db.pending_transactions.read(); + let mut users = db.users.write(); + + for fingerprint in new_block.transaction_list.iter() { + let transaction = pending_transactions.get(fingerprint).unwrap(); + let source = &transaction.source; + let target = &transaction.target; + + if let Some(from) = users.get_mut(source) { + from.balance -= transaction.amount; + } + + if let Some(to) = users.get_mut(target) { + to.balance += transaction.amount; + } + } + } + + { + let mut pending_transactions = db.pending_transactions.write(); + pending_transactions.clear(); + } let block_json = serde_json::to_string(&new_block).unwrap(); @@ -349,15 +388,15 @@ pub async fn authorized_propose_block( ) .unwrap(); - *blockchain = new_block; - - let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions); - pending_transactions.clear(); + { + let mut blockchain = db.blockchain.write(); + *blockchain = new_block; + } Ok(warp::reply::with_status( warp::reply::json(&GradeCoinResponse { res: ResponseType::Success, - message: "Block accepted".to_owned(), + message: "Block accepted coinbase reward awarded".to_owned(), }), StatusCode::CREATED, )) @@ -379,7 +418,6 @@ pub async fn authorized_propose_transaction( db: Db, ) -> Result { debug!("POST request to /transaction, authorized_propose_transaction"); - debug!("The transaction request: {:?}", new_transaction); let users_store = db.users.read(); @@ -403,7 +441,7 @@ pub async fn authorized_propose_transaction( } }; - // `user` is an authenticated student, can propose + // `internal_user` is an authenticated student, can propose // check if user can afford the transaction if new_transaction.by == new_transaction.source { @@ -418,7 +456,7 @@ pub async fn authorized_propose_transaction( )); } } else { - // todo: add bank mechanism + // TODO: add bank mechanism <14-04-21, keles> // return Ok(warp::reply::with_status( warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, @@ -446,9 +484,10 @@ pub async fn authorized_propose_transaction( } }; - // this transaction was already checked for correctness at custom_filters, we can panic - // here if it has been changed since - debug!("authorized for transaction proposal"); + // authorized for transaction proposal + + // this transaction was already checked for correctness at custom_filters, we can panic here if + // it has been changed since let hashed_transaction = Md5::digest(&serde_json::to_vec(&new_transaction).unwrap()); diff --git a/src/schema.rs b/src/schema.rs index 6402724..2a9e1db 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -37,7 +37,7 @@ fn last_block_exists() -> (bool, String) { } fn read_block_name() -> io::Result> { - let mut entries = fs::read_dir("./blocks")? + let entries = fs::read_dir("./blocks")? .map(|res| res.map(|e| e.path())) .collect::, io::Error>>()?; @@ -89,13 +89,10 @@ pub struct Claims { /// /// [`Db::users`] is the in memory representation of the users, with their public keys, metu_ids and /// gradecoin balances. -/// -/// TODO: Replace the pending_transactions HashMap with -/// HashMap #[derive(Debug, Clone)] pub struct Db { pub blockchain: Arc>, - pub pending_transactions: Arc>>, + pub pending_transactions: Arc>>, pub users: Arc>>, } @@ -166,7 +163,7 @@ pub struct User { pub balance: i32, } -/// The values will be hard coded so MetuId::new() can accept/reject values based on that +/// The values are hard coded in [`OUR_STUDENTS`] so MetuId::new() can accept/reject values based on that #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct MetuId { id: String, -- cgit v1.2.3-70-g09d2