diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/block.rs | 1 | ||||
| -rw-r--r-- | src/config.rs | 10 | ||||
| -rw-r--r-- | src/db.rs | 18 | ||||
| -rw-r--r-- | src/handlers.rs | 119 | ||||
| -rw-r--r-- | src/main.rs | 20 | ||||
| -rw-r--r-- | src/routes.rs | 25 | ||||
| -rw-r--r-- | src/student.rs | 8 |
7 files changed, 112 insertions, 89 deletions
diff --git a/src/block.rs b/src/block.rs index e707779..1a61d72 100644 --- a/src/block.rs +++ b/src/block.rs | |||
| @@ -9,7 +9,6 @@ | |||
| 9 | //! Users are held in memory and they're also backed up to text files | 9 | //! Users are held in memory and they're also backed up to text files |
| 10 | use chrono::{NaiveDate, NaiveDateTime}; | 10 | use chrono::{NaiveDate, NaiveDateTime}; |
| 11 | use serde::{Deserialize, Serialize}; | 11 | use serde::{Deserialize, Serialize}; |
| 12 | use std::{string::String, vec::Vec}; | ||
| 13 | 12 | ||
| 14 | pub type Fingerprint = String; | 13 | pub type Fingerprint = String; |
| 15 | pub type Id = String; | 14 | pub type Id = String; |
diff --git a/src/config.rs b/src/config.rs index 28c63be..112fb3c 100644 --- a/src/config.rs +++ b/src/config.rs | |||
| @@ -1,8 +1,8 @@ | |||
| 1 | //! # Configuration | 1 | //! # Configuration |
| 2 | //! | 2 | //! |
| 3 | //! This module holds the data structures for network configuration. | 3 | //! This module holds the data structures for network configuration. |
| 4 | use serde::{Deserialize, Serialize}; | ||
| 5 | use log::{error, info}; | 4 | use log::{error, info}; |
| 5 | use serde::{Deserialize, Serialize}; | ||
| 6 | 6 | ||
| 7 | /// Configuration for a single network | 7 | /// Configuration for a single network |
| 8 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] | 8 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] |
| @@ -29,7 +29,7 @@ pub struct Config { | |||
| 29 | 29 | ||
| 30 | /// Valid blocks should have this many transactions | 30 | /// Valid blocks should have this many transactions |
| 31 | pub block_transaction_count: u8, | 31 | pub block_transaction_count: u8, |
| 32 | 32 | ||
| 33 | /// How many zero hexadecimal characters should a correct hash start with? | 33 | /// How many zero hexadecimal characters should a correct hash start with? |
| 34 | pub hash_zeros: u8, | 34 | pub hash_zeros: u8, |
| 35 | 35 | ||
| @@ -58,15 +58,15 @@ impl Config { | |||
| 58 | error!("Cannot read config file: {}", filename); | 58 | error!("Cannot read config file: {}", filename); |
| 59 | error!("Error: {:?}", e); | 59 | error!("Error: {:?}", e); |
| 60 | return None; | 60 | return None; |
| 61 | }, | 61 | } |
| 62 | }; | 62 | }; |
| 63 | let config : Config = match serde_yaml::from_reader(file) { | 63 | let config: Config = match serde_yaml::from_reader(file) { |
| 64 | Ok(c) => c, | 64 | Ok(c) => c, |
| 65 | Err(e) => { | 65 | Err(e) => { |
| 66 | error!("Cannot parse config file: {}", filename); | 66 | error!("Cannot parse config file: {}", filename); |
| 67 | error!("Error: {:?}", e); | 67 | error!("Error: {:?}", e); |
| 68 | return None; | 68 | return None; |
| 69 | }, | 69 | } |
| 70 | }; | 70 | }; |
| 71 | // File closes automatically when it goes out of scope. | 71 | // File closes automatically when it goes out of scope. |
| 72 | info!("Config file read successfully: {}", filename); | 72 | info!("Config file read successfully: {}", filename); |
| @@ -9,9 +9,9 @@ | |||
| 9 | //! [`Db::users`] is the in memory representation of the users, | 9 | //! [`Db::users`] is the in memory representation of the users, |
| 10 | //! with their public keys, `metu_ids` and gradecoin balances. | 10 | //! with their public keys, `metu_ids` and gradecoin balances. |
| 11 | use crate::block::{Block, Fingerprint, Id, Transaction}; | 11 | use crate::block::{Block, Fingerprint, Id, Transaction}; |
| 12 | use crate::student::{MetuId, User, UserAtRest}; | ||
| 13 | use crate::config::Config; | 12 | use crate::config::Config; |
| 14 | use log::debug; | 13 | use crate::student::{MetuId, User, UserAtRest}; |
| 14 | use log::info; | ||
| 15 | use parking_lot::RwLock; | 15 | use parking_lot::RwLock; |
| 16 | use std::{collections::HashMap, fs, io, path::PathBuf, sync::Arc}; | 16 | use std::{collections::HashMap, fs, io, path::PathBuf, sync::Arc}; |
| 17 | 17 | ||
| @@ -32,7 +32,7 @@ impl Db { | |||
| 32 | // Load bots | 32 | // Load bots |
| 33 | let users: HashMap<Fingerprint, User> = get_friendly_users(); | 33 | let users: HashMap<Fingerprint, User> = get_friendly_users(); |
| 34 | 34 | ||
| 35 | // Read the list of users who can register | 35 | // Load the list of users who can register |
| 36 | let preapproved_users = read_approved_users(&config.preapproved_users); | 36 | let preapproved_users = read_approved_users(&config.preapproved_users); |
| 37 | 37 | ||
| 38 | let mut db = Db { | 38 | let mut db = Db { |
| @@ -43,21 +43,21 @@ impl Db { | |||
| 43 | preapproved_users, | 43 | preapproved_users, |
| 44 | }; | 44 | }; |
| 45 | 45 | ||
| 46 | // Read blocks | 46 | // Load the latest block, continue from where we left off |
| 47 | if let Some(block_path) = last_block_content(&db.config.name) { | 47 | if let Some(block_path) = last_block_content(&db.config.name) { |
| 48 | db.populate_with_last_block(block_path); | 48 | db.populate_with_last_block(block_path); |
| 49 | } | 49 | } |
| 50 | 50 | ||
| 51 | // Read users | 51 | // Load the users that had registered themselves |
| 52 | if let Ok(users_path) = read_users(&db.config.name) { | 52 | if let Ok(users_path) = read_users(&db.config.name) { |
| 53 | db.populate_with_users(users_path); | 53 | db.populate_with_users(users_path); |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | return db; | 56 | db |
| 57 | } | 57 | } |
| 58 | 58 | ||
| 59 | fn populate_with_last_block(&mut self, path: String) { | 59 | fn populate_with_last_block(&mut self, path: String) { |
| 60 | debug!("Populating db with the latest block {}", path); | 60 | info!("Populating db with the latest block {}", path); |
| 61 | let file = fs::read(path).unwrap(); | 61 | let file = fs::read(path).unwrap(); |
| 62 | let json = std::str::from_utf8(&file).unwrap(); | 62 | let json = std::str::from_utf8(&file).unwrap(); |
| 63 | let block: Block = serde_json::from_str(json).unwrap(); | 63 | let block: Block = serde_json::from_str(json).unwrap(); |
| @@ -71,7 +71,7 @@ impl Db { | |||
| 71 | String::from_utf8(file_content).expect("we have written a malformed user file"); | 71 | String::from_utf8(file_content).expect("we have written a malformed user file"); |
| 72 | let user_at_rest: UserAtRest = serde_json::from_str(&json).unwrap(); | 72 | let user_at_rest: UserAtRest = serde_json::from_str(&json).unwrap(); |
| 73 | 73 | ||
| 74 | debug!("Populating db with user: {:?}", user_at_rest); | 74 | info!("Populating db with user: {:?}", user_at_rest); |
| 75 | self.users | 75 | self.users |
| 76 | .write() | 76 | .write() |
| 77 | .insert(user_at_rest.fingerprint, user_at_rest.user); | 77 | .insert(user_at_rest.fingerprint, user_at_rest.user); |
| @@ -122,7 +122,7 @@ fn read_block_name(config_name: &str) -> io::Result<Vec<PathBuf>> { | |||
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | fn parse_block(path: &str) -> u64 { | 124 | fn parse_block(path: &str) -> u64 { |
| 125 | let start_pos = path.rfind("/").unwrap() + 1; | 125 | let start_pos = path.rfind('/').unwrap() + 1; |
| 126 | let end_pos = path.find(".block").unwrap(); | 126 | let end_pos = path.find(".block").unwrap(); |
| 127 | let block_str = path[start_pos..end_pos].to_string(); | 127 | let block_str = path[start_pos..end_pos].to_string(); |
| 128 | let block_u64: u64 = block_str.parse().unwrap(); | 128 | let block_u64: u64 = block_str.parse().unwrap(); |
diff --git a/src/handlers.rs b/src/handlers.rs index 01e2c4f..9e1bae1 100644 --- a/src/handlers.rs +++ b/src/handlers.rs | |||
| @@ -30,7 +30,7 @@ use crate::PRIVATE_KEY; | |||
| 30 | type Aes128Cbc = Cbc<Aes128, Pkcs7>; | 30 | type Aes128Cbc = Cbc<Aes128, Pkcs7>; |
| 31 | 31 | ||
| 32 | #[derive(Serialize, Debug)] | 32 | #[derive(Serialize, Debug)] |
| 33 | struct GradeCoinResponse { | 33 | struct UserFeedback { |
| 34 | res: ResponseType, | 34 | res: ResponseType, |
| 35 | message: String, | 35 | message: String, |
| 36 | } | 36 | } |
| @@ -97,7 +97,7 @@ pub async fn authenticate_user( | |||
| 97 | request: InitialAuthRequest, | 97 | request: InitialAuthRequest, |
| 98 | db: Db, | 98 | db: Db, |
| 99 | ) -> Result<impl warp::Reply, warp::Rejection> { | 99 | ) -> Result<impl warp::Reply, warp::Rejection> { |
| 100 | debug!("POST /register, authenticate_user() is handling"); | 100 | debug!("[{}] New user registration attempt", db.config.name); |
| 101 | 101 | ||
| 102 | // In essence PEM files are just base64 encoded versions of the DER encoded data. | 102 | // In essence PEM files are just base64 encoded versions of the DER encoded data. |
| 103 | // ~tls.mbed.org | 103 | // ~tls.mbed.org |
| @@ -113,7 +113,7 @@ pub async fn authenticate_user( | |||
| 113 | &request.key, err | 113 | &request.key, err |
| 114 | ); | 114 | ); |
| 115 | 115 | ||
| 116 | let res_json = warp::reply::json(&GradeCoinResponse { | 116 | let res_json = warp::reply::json(&UserFeedback { |
| 117 | res: ResponseType::Error, | 117 | res: ResponseType::Error, |
| 118 | message: format!( | 118 | message: format!( |
| 119 | "\"key\" field of initial auth request was not base64 encoded: {}, {}", | 119 | "\"key\" field of initial auth request was not base64 encoded: {}, {}", |
| @@ -134,7 +134,7 @@ pub async fn authenticate_user( | |||
| 134 | err, &key_ciphertext | 134 | err, &key_ciphertext |
| 135 | ); | 135 | ); |
| 136 | 136 | ||
| 137 | let res_json = warp::reply::json(&GradeCoinResponse { | 137 | let res_json = warp::reply::json(&UserFeedback { |
| 138 | res: ResponseType::Error, | 138 | res: ResponseType::Error, |
| 139 | message: "Failed to decrypt the 'key_ciphertext' field of the auth request" | 139 | message: "Failed to decrypt the 'key_ciphertext' field of the auth request" |
| 140 | .to_owned(), | 140 | .to_owned(), |
| @@ -153,7 +153,7 @@ pub async fn authenticate_user( | |||
| 153 | &request.iv, err | 153 | &request.iv, err |
| 154 | ); | 154 | ); |
| 155 | 155 | ||
| 156 | let res_json = warp::reply::json(&GradeCoinResponse { | 156 | let res_json = warp::reply::json(&UserFeedback { |
| 157 | res: ResponseType::Error, | 157 | res: ResponseType::Error, |
| 158 | message: format!( | 158 | message: format!( |
| 159 | "\"iv\" field of initial auth request was not base64 encoded: {}, {}", | 159 | "\"iv\" field of initial auth request was not base64 encoded: {}, {}", |
| @@ -174,11 +174,11 @@ pub async fn authenticate_user( | |||
| 174 | &temp_key, &request.iv, err | 174 | &temp_key, &request.iv, err |
| 175 | ); | 175 | ); |
| 176 | 176 | ||
| 177 | let res_json = warp::reply::json(&GradeCoinResponse { | 177 | let res_json = warp::reply::json(&UserFeedback { |
| 178 | res: ResponseType::Error, | 178 | res: ResponseType::Error, |
| 179 | message: format!( | 179 | message: format!( |
| 180 | "Could not create a cipher from given 'temp_key': {:?} and 'IV': {}", | 180 | "Could not create a cipher from given 'temp_key': {:?} and 'IV': {}, {}", |
| 181 | &temp_key, &request.iv | 181 | &temp_key, &request.iv, err |
| 182 | ), | 182 | ), |
| 183 | }); | 183 | }); |
| 184 | 184 | ||
| @@ -195,7 +195,7 @@ pub async fn authenticate_user( | |||
| 195 | &request.c, err | 195 | &request.c, err |
| 196 | ); | 196 | ); |
| 197 | 197 | ||
| 198 | let res_json = warp::reply::json(&GradeCoinResponse { | 198 | let res_json = warp::reply::json(&UserFeedback { |
| 199 | res: ResponseType::Error, | 199 | res: ResponseType::Error, |
| 200 | message: format!( | 200 | message: format!( |
| 201 | "\"c\" field of initial auth request was not base64 encoded: {}, {}", | 201 | "\"c\" field of initial auth request was not base64 encoded: {}, {}", |
| @@ -218,7 +218,7 @@ pub async fn authenticate_user( | |||
| 218 | &buf, err | 218 | &buf, err |
| 219 | ); | 219 | ); |
| 220 | 220 | ||
| 221 | let res_json = warp::reply::json(&GradeCoinResponse { | 221 | let res_json = warp::reply::json(&UserFeedback { |
| 222 | res: ResponseType::Error, | 222 | res: ResponseType::Error, |
| 223 | message: "Failed to decrypt the 'c' field of the auth request, 'iv' and 'k_temp' were valid so far though" | 223 | message: "Failed to decrypt the 'c' field of the auth request, 'iv' and 'k_temp' were valid so far though" |
| 224 | .to_owned(), | 224 | .to_owned(), |
| @@ -237,7 +237,7 @@ pub async fn authenticate_user( | |||
| 237 | &auth_plaintext, err | 237 | &auth_plaintext, err |
| 238 | ); | 238 | ); |
| 239 | 239 | ||
| 240 | let res_json = warp::reply::json(&GradeCoinResponse { | 240 | let res_json = warp::reply::json(&UserFeedback { |
| 241 | res: ResponseType::Error, | 241 | res: ResponseType::Error, |
| 242 | message: "P_AR couldn't get converted to UTF-8, please check your encoding" | 242 | message: "P_AR couldn't get converted to UTF-8, please check your encoding" |
| 243 | .to_owned(), | 243 | .to_owned(), |
| @@ -256,7 +256,7 @@ pub async fn authenticate_user( | |||
| 256 | &utf8_auth_plaintext, err | 256 | &utf8_auth_plaintext, err |
| 257 | ); | 257 | ); |
| 258 | 258 | ||
| 259 | let res_json = warp::reply::json(&GradeCoinResponse { | 259 | let res_json = warp::reply::json(&UserFeedback { |
| 260 | res: ResponseType::Error, | 260 | res: ResponseType::Error, |
| 261 | message: "The P_AR JSON did not serialize correctly, did it include all 3 fields 'student_id', 'passwd' and 'public_key'?".to_owned(), | 261 | message: "The P_AR JSON did not serialize correctly, did it include all 3 fields 'student_id', 'passwd' and 'public_key'?".to_owned(), |
| 262 | }); | 262 | }); |
| @@ -273,7 +273,7 @@ pub async fn authenticate_user( | |||
| 273 | "Someone tried to auth with invalid credentials: {} {}", | 273 | "Someone tried to auth with invalid credentials: {} {}", |
| 274 | &request.student_id, &request.passwd | 274 | &request.student_id, &request.passwd |
| 275 | ); | 275 | ); |
| 276 | let res_json = warp::reply::json(&GradeCoinResponse { | 276 | let res_json = warp::reply::json(&UserFeedback { |
| 277 | res: ResponseType::Error, | 277 | res: ResponseType::Error, |
| 278 | message: | 278 | message: |
| 279 | "The credentials given ('student_id', 'passwd') cannot hold a Gradecoin account" | 279 | "The credentials given ('student_id', 'passwd') cannot hold a Gradecoin account" |
| @@ -289,7 +289,8 @@ pub async fn authenticate_user( | |||
| 289 | 289 | ||
| 290 | for (_, user) in userlist.iter() { | 290 | for (_, user) in userlist.iter() { |
| 291 | if user.user_id == privileged_student_id { | 291 | if user.user_id == privileged_student_id { |
| 292 | let res_json = warp::reply::json(&GradeCoinResponse { | 292 | debug!("{} attempted to authenticate again", user.user_id); |
| 293 | let res_json = warp::reply::json(&UserFeedback { | ||
| 293 | res: ResponseType::Error, | 294 | res: ResponseType::Error, |
| 294 | message: | 295 | message: |
| 295 | "This user is already authenticated, do you think this is a mistake? Contact me" | 296 | "This user is already authenticated, do you think this is a mistake? Contact me" |
| @@ -302,7 +303,7 @@ pub async fn authenticate_user( | |||
| 302 | 303 | ||
| 303 | // We're using this as the validator instead of anything reasonable | 304 | // We're using this as the validator instead of anything reasonable |
| 304 | if DecodingKey::from_rsa_pem(request.public_key.as_bytes()).is_err() { | 305 | if DecodingKey::from_rsa_pem(request.public_key.as_bytes()).is_err() { |
| 305 | let res_json = warp::reply::json(&GradeCoinResponse { | 306 | let res_json = warp::reply::json(&UserFeedback { |
| 306 | res: ResponseType::Error, | 307 | res: ResponseType::Error, |
| 307 | message: "The RSA 'public_key' in 'P_AR' is not in valid PEM format".to_owned(), | 308 | message: "The RSA 'public_key' in 'P_AR' is not in valid PEM format".to_owned(), |
| 308 | }); | 309 | }); |
| @@ -319,7 +320,7 @@ pub async fn authenticate_user( | |||
| 319 | is_bot: false, | 320 | is_bot: false, |
| 320 | }; | 321 | }; |
| 321 | 322 | ||
| 322 | debug!("NEW USER: {:?}", &new_user); | 323 | warn!("A new user has authenticated: {}", &new_user.user_id); |
| 323 | 324 | ||
| 324 | // save the user to disk | 325 | // save the user to disk |
| 325 | let user_at_rest_json = serde_json::to_string(&UserAtRest { | 326 | let user_at_rest_json = serde_json::to_string(&UserAtRest { |
| @@ -335,13 +336,14 @@ pub async fn authenticate_user( | |||
| 335 | 336 | ||
| 336 | fs::write( | 337 | fs::write( |
| 337 | format!("users/{}/{}.guy", db.config.name, new_user.user_id), | 338 | format!("users/{}/{}.guy", db.config.name, new_user.user_id), |
| 338 | user_at_rest_json | 339 | user_at_rest_json, |
| 339 | ).unwrap(); | 340 | ) |
| 341 | .unwrap(); | ||
| 340 | 342 | ||
| 341 | let mut userlist = db.users.write(); | 343 | let mut userlist = db.users.write(); |
| 342 | userlist.insert(fingerprint.clone(), new_user); | 344 | userlist.insert(fingerprint.clone(), new_user); |
| 343 | 345 | ||
| 344 | let res_json = warp::reply::json(&GradeCoinResponse { | 346 | let res_json = warp::reply::json(&UserFeedback { |
| 345 | res: ResponseType::Success, | 347 | res: ResponseType::Success, |
| 346 | message: format!( | 348 | message: format!( |
| 347 | "You have authenticated to use Gradecoin with identifier {}", | 349 | "You have authenticated to use Gradecoin with identifier {}", |
| @@ -396,7 +398,7 @@ pub async fn propose_block( | |||
| 396 | new_block.transaction_list.len(), | 398 | new_block.transaction_list.len(), |
| 397 | block_transaction_count | 399 | block_transaction_count |
| 398 | ); | 400 | ); |
| 399 | let res_json = warp::reply::json(&GradeCoinResponse { | 401 | let res_json = warp::reply::json(&UserFeedback { |
| 400 | res: ResponseType::Error, | 402 | res: ResponseType::Error, |
| 401 | message: format!( | 403 | message: format!( |
| 402 | "There should be at least {} transactions in the block", | 404 | "There should be at least {} transactions in the block", |
| @@ -420,7 +422,7 @@ pub async fn propose_block( | |||
| 420 | new_block.transaction_list[0] | 422 | new_block.transaction_list[0] |
| 421 | ); | 423 | ); |
| 422 | 424 | ||
| 423 | let res_json = warp::reply::json(&GradeCoinResponse { | 425 | let res_json = warp::reply::json(&UserFeedback { |
| 424 | res: ResponseType::Error, | 426 | res: ResponseType::Error, |
| 425 | message: "First transaction in the block is not found in the system".to_owned(), | 427 | message: "First transaction in the block is not found in the system".to_owned(), |
| 426 | }); | 428 | }); |
| @@ -439,7 +441,7 @@ pub async fn propose_block( | |||
| 439 | new_block.transaction_list[0] | 441 | new_block.transaction_list[0] |
| 440 | ); | 442 | ); |
| 441 | 443 | ||
| 442 | let res_json = warp::reply::json(&GradeCoinResponse { | 444 | let res_json = warp::reply::json(&UserFeedback { |
| 443 | res: ResponseType::Error, | 445 | res: ResponseType::Error, |
| 444 | message: "User with that public key signature is not found in the database".to_owned(), | 446 | message: "User with that public key signature is not found in the database".to_owned(), |
| 445 | }); | 447 | }); |
| @@ -455,7 +457,7 @@ pub async fn propose_block( | |||
| 455 | Err(below) => { | 457 | Err(below) => { |
| 456 | debug!("Something went wrong with the JWT {:?}", below); | 458 | debug!("Something went wrong with the JWT {:?}", below); |
| 457 | 459 | ||
| 458 | let res_json = warp::reply::json(&GradeCoinResponse { | 460 | let res_json = warp::reply::json(&UserFeedback { |
| 459 | res: ResponseType::Error, | 461 | res: ResponseType::Error, |
| 460 | message: below, | 462 | message: below, |
| 461 | }); | 463 | }); |
| @@ -470,7 +472,7 @@ pub async fn propose_block( | |||
| 470 | "The Hash of the block {:?} did not match the hash given in jwt {:?}", | 472 | "The Hash of the block {:?} did not match the hash given in jwt {:?}", |
| 471 | new_block.hash, token_payload.claims.tha | 473 | new_block.hash, token_payload.claims.tha |
| 472 | ); | 474 | ); |
| 473 | let res_json = warp::reply::json(&GradeCoinResponse { | 475 | let res_json = warp::reply::json(&UserFeedback { |
| 474 | res: ResponseType::Error, | 476 | res: ResponseType::Error, |
| 475 | message: "The hash of the block did not match the hash given in JWT tha field" | 477 | message: "The hash of the block did not match the hash given in JWT tha field" |
| 476 | .to_owned(), | 478 | .to_owned(), |
| @@ -481,7 +483,7 @@ pub async fn propose_block( | |||
| 481 | 483 | ||
| 482 | if !has_unique_elements(&new_block.transaction_list) { | 484 | if !has_unique_elements(&new_block.transaction_list) { |
| 483 | debug!("Block contains duplicate transactions!"); | 485 | debug!("Block contains duplicate transactions!"); |
| 484 | let res_json = warp::reply::json(&GradeCoinResponse { | 486 | let res_json = warp::reply::json(&UserFeedback { |
| 485 | res: ResponseType::Error, | 487 | res: ResponseType::Error, |
| 486 | message: "Block cannot contain duplicate transactions".to_owned(), | 488 | message: "Block cannot contain duplicate transactions".to_owned(), |
| 487 | }); | 489 | }); |
| @@ -492,7 +494,7 @@ pub async fn propose_block( | |||
| 492 | // Are transactions in the block valid? | 494 | // Are transactions in the block valid? |
| 493 | for transaction_hash in &new_block.transaction_list { | 495 | for transaction_hash in &new_block.transaction_list { |
| 494 | if !pending_transactions.contains_key(transaction_hash) { | 496 | if !pending_transactions.contains_key(transaction_hash) { |
| 495 | let res_json = warp::reply::json(&GradeCoinResponse { | 497 | let res_json = warp::reply::json(&UserFeedback { |
| 496 | res: ResponseType::Error, | 498 | res: ResponseType::Error, |
| 497 | message: "Block contains an unknown transaction".to_owned(), | 499 | message: "Block contains an unknown transaction".to_owned(), |
| 498 | }); | 500 | }); |
| @@ -516,7 +518,7 @@ pub async fn propose_block( | |||
| 516 | // Does the hash claimed in block match with the actual hash? | 518 | // Does the hash claimed in block match with the actual hash? |
| 517 | if hash_string != new_block.hash { | 519 | if hash_string != new_block.hash { |
| 518 | debug!("request was not telling the truth, hash values do not match"); | 520 | debug!("request was not telling the truth, hash values do not match"); |
| 519 | let res_json = warp::reply::json(&GradeCoinResponse { | 521 | let res_json = warp::reply::json(&UserFeedback { |
| 520 | res: ResponseType::Error, | 522 | res: ResponseType::Error, |
| 521 | message: "Given hash value does not match the actual block hash".to_owned(), | 523 | message: "Given hash value does not match the actual block hash".to_owned(), |
| 522 | }); | 524 | }); |
| @@ -525,18 +527,22 @@ pub async fn propose_block( | |||
| 525 | } | 527 | } |
| 526 | 528 | ||
| 527 | // Are the n leftmost characters zero? | 529 | // Are the n leftmost characters zero? |
| 528 | let hash_correct = hash_string.chars() | 530 | let hash_correct = hash_string |
| 531 | .chars() | ||
| 529 | .take(db.config.hash_zeros.into()) | 532 | .take(db.config.hash_zeros.into()) |
| 530 | .all(|x| x == '0'); | 533 | .all(|x| x == '0'); |
| 531 | 534 | ||
| 532 | if !hash_correct { | 535 | if !hash_correct { |
| 533 | debug!("The hash does not have {} leftmost zero characters", db.config.hash_zeros); | 536 | debug!( |
| 534 | let res_json = warp::reply::json(&GradeCoinResponse { | 537 | "The hash does not have {} leftmost zero characters", |
| 538 | db.config.hash_zeros | ||
| 539 | ); | ||
| 540 | let res_json = warp::reply::json(&UserFeedback { | ||
| 535 | res: ResponseType::Error, | 541 | res: ResponseType::Error, |
| 536 | message: format!( | 542 | message: format!( |
| 537 | "Given block hash does not start with {} zero hexadecimal characters", | 543 | "Given block hash does not start with {} zero hexadecimal characters", |
| 538 | db.config.hash_zeros | 544 | db.config.hash_zeros |
| 539 | ), | 545 | ), |
| 540 | }); | 546 | }); |
| 541 | 547 | ||
| 542 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | 548 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); |
| @@ -553,10 +559,15 @@ pub async fn propose_block( | |||
| 553 | // Reward the block proposer | 559 | // Reward the block proposer |
| 554 | // All unwrap calls here are guaranteed to succeed because they are already checked above | 560 | // All unwrap calls here are guaranteed to succeed because they are already checked above |
| 555 | // See: internal_user_fingerprint, internal_user | 561 | // See: internal_user_fingerprint, internal_user |
| 556 | let coinbase = pending_transactions.get(&new_block.transaction_list[0]).unwrap(); | 562 | let coinbase = pending_transactions |
| 563 | .get(&new_block.transaction_list[0]) | ||
| 564 | .unwrap(); | ||
| 557 | let mut coinbase_user = users_store.get_mut(&coinbase.source).unwrap(); | 565 | let mut coinbase_user = users_store.get_mut(&coinbase.source).unwrap(); |
| 558 | coinbase_user.balance += db.config.block_reward; | 566 | coinbase_user.balance += db.config.block_reward; |
| 559 | debug!("{} block reward went to {:?} for mining the block", db.config.block_reward, coinbase_user); | 567 | debug!( |
| 568 | "{} block reward went to {} for mining the block", | ||
| 569 | db.config.block_reward, coinbase_user.user_id | ||
| 570 | ); | ||
| 560 | 571 | ||
| 561 | let mut holding: HashMap<String, Transaction> = HashMap::new(); | 572 | let mut holding: HashMap<String, Transaction> = HashMap::new(); |
| 562 | 573 | ||
| @@ -615,7 +626,11 @@ pub async fn propose_block( | |||
| 615 | let block_json = serde_json::to_string(&new_block).unwrap(); | 626 | let block_json = serde_json::to_string(&new_block).unwrap(); |
| 616 | 627 | ||
| 617 | fs::write( | 628 | fs::write( |
| 618 | format!("blocks/{}/{}.block", db.config.name, new_block.timestamp.timestamp()), | 629 | format!( |
| 630 | "blocks/{}/{}.block", | ||
| 631 | db.config.name, | ||
| 632 | new_block.timestamp.timestamp() | ||
| 633 | ), | ||
| 619 | block_json, | 634 | block_json, |
| 620 | ) | 635 | ) |
| 621 | .unwrap(); | 636 | .unwrap(); |
| @@ -626,7 +641,7 @@ pub async fn propose_block( | |||
| 626 | } | 641 | } |
| 627 | 642 | ||
| 628 | Ok(warp::reply::with_status( | 643 | Ok(warp::reply::with_status( |
| 629 | warp::reply::json(&GradeCoinResponse { | 644 | warp::reply::json(&UserFeedback { |
| 630 | res: ResponseType::Success, | 645 | res: ResponseType::Success, |
| 631 | message: "Block accepted, coinbase reward awarded".to_owned(), | 646 | message: "Block accepted, coinbase reward awarded".to_owned(), |
| 632 | }), | 647 | }), |
| @@ -648,7 +663,10 @@ pub async fn propose_transaction( | |||
| 648 | token: String, | 663 | token: String, |
| 649 | db: Db, | 664 | db: Db, |
| 650 | ) -> Result<impl warp::Reply, warp::Rejection> { | 665 | ) -> Result<impl warp::Reply, warp::Rejection> { |
| 651 | warn!("[{}] New transaction proposal: {:?}", db.config.name, &new_transaction); | 666 | warn!( |
| 667 | "[{}] New transaction proposal: {:?}", | ||
| 668 | db.config.name, &new_transaction | ||
| 669 | ); | ||
| 652 | 670 | ||
| 653 | let users_store = db.users.read(); | 671 | let users_store = db.users.read(); |
| 654 | 672 | ||
| @@ -662,7 +680,7 @@ pub async fn propose_transaction( | |||
| 662 | ); | 680 | ); |
| 663 | 681 | ||
| 664 | return Ok(warp::reply::with_status( | 682 | return Ok(warp::reply::with_status( |
| 665 | warp::reply::json(&GradeCoinResponse { | 683 | warp::reply::json(&UserFeedback { |
| 666 | res: ResponseType::Error, | 684 | res: ResponseType::Error, |
| 667 | message: "User with the given public key signature is not authorized".to_owned(), | 685 | message: "User with the given public key signature is not authorized".to_owned(), |
| 668 | }), | 686 | }), |
| @@ -674,7 +692,7 @@ pub async fn propose_transaction( | |||
| 674 | debug!("Someone tried to send as the bot"); | 692 | debug!("Someone tried to send as the bot"); |
| 675 | 693 | ||
| 676 | return Ok(warp::reply::with_status( | 694 | return Ok(warp::reply::with_status( |
| 677 | warp::reply::json(&GradeCoinResponse { | 695 | warp::reply::json(&UserFeedback { |
| 678 | res: ResponseType::Error, | 696 | res: ResponseType::Error, |
| 679 | message: "Don's send transactions on behalf of bots".to_owned(), | 697 | message: "Don's send transactions on behalf of bots".to_owned(), |
| 680 | }), | 698 | }), |
| @@ -693,7 +711,7 @@ pub async fn propose_transaction( | |||
| 693 | Err(below) => { | 711 | Err(below) => { |
| 694 | debug!("JWT Error: {:?}", below); | 712 | debug!("JWT Error: {:?}", below); |
| 695 | return Ok(warp::reply::with_status( | 713 | return Ok(warp::reply::with_status( |
| 696 | warp::reply::json(&GradeCoinResponse { | 714 | warp::reply::json(&UserFeedback { |
| 697 | res: ResponseType::Error, | 715 | res: ResponseType::Error, |
| 698 | message: below, | 716 | message: below, |
| 699 | }), | 717 | }), |
| @@ -710,7 +728,7 @@ pub async fn propose_transaction( | |||
| 710 | ); | 728 | ); |
| 711 | 729 | ||
| 712 | return Ok(warp::reply::with_status( | 730 | return Ok(warp::reply::with_status( |
| 713 | warp::reply::json(&GradeCoinResponse { | 731 | warp::reply::json(&UserFeedback { |
| 714 | res: ResponseType::Error, | 732 | res: ResponseType::Error, |
| 715 | message: format!( | 733 | message: format!( |
| 716 | "Target of the transaction {} is not found in the system", | 734 | "Target of the transaction {} is not found in the system", |
| @@ -723,8 +741,6 @@ pub async fn propose_transaction( | |||
| 723 | 741 | ||
| 724 | let transaction_id = calculate_transaction_id(&new_transaction.source, &new_transaction.target); | 742 | let transaction_id = calculate_transaction_id(&new_transaction.source, &new_transaction.target); |
| 725 | 743 | ||
| 726 | // OLD: Does this user have a pending transaction? | ||
| 727 | // NEW: Is this source:target pair unqiue? | ||
| 728 | { | 744 | { |
| 729 | let transactions = db.pending_transactions.read(); | 745 | let transactions = db.pending_transactions.read(); |
| 730 | debug!( | 746 | debug!( |
| @@ -739,7 +755,7 @@ pub async fn propose_transaction( | |||
| 739 | ); | 755 | ); |
| 740 | 756 | ||
| 741 | return Ok(warp::reply::with_status( | 757 | return Ok(warp::reply::with_status( |
| 742 | warp::reply::json(&GradeCoinResponse { | 758 | warp::reply::json(&UserFeedback { |
| 743 | res: ResponseType::Error, | 759 | res: ResponseType::Error, |
| 744 | message: "This user already has another pending transaction".to_owned(), | 760 | message: "This user already has another pending transaction".to_owned(), |
| 745 | }), | 761 | }), |
| @@ -752,7 +768,7 @@ pub async fn propose_transaction( | |||
| 752 | debug!("transaction source and target are the same",); | 768 | debug!("transaction source and target are the same",); |
| 753 | 769 | ||
| 754 | return Ok(warp::reply::with_status( | 770 | return Ok(warp::reply::with_status( |
| 755 | warp::reply::json(&GradeCoinResponse { | 771 | warp::reply::json(&UserFeedback { |
| 756 | res: ResponseType::Error, | 772 | res: ResponseType::Error, |
| 757 | message: "transaction to yourself, you had to try didn't you? :)".to_owned(), | 773 | message: "transaction to yourself, you had to try didn't you? :)".to_owned(), |
| 758 | }), | 774 | }), |
| @@ -769,7 +785,7 @@ pub async fn propose_transaction( | |||
| 769 | tx_lower_limit, tx_upper_limit, new_transaction.amount | 785 | tx_lower_limit, tx_upper_limit, new_transaction.amount |
| 770 | ); | 786 | ); |
| 771 | return Ok(warp::reply::with_status( | 787 | return Ok(warp::reply::with_status( |
| 772 | warp::reply::json(&GradeCoinResponse { | 788 | warp::reply::json(&UserFeedback { |
| 773 | res: ResponseType::Error, | 789 | res: ResponseType::Error, |
| 774 | message: format!( | 790 | message: format!( |
| 775 | "Transaction amount should be between {} and {}", | 791 | "Transaction amount should be between {} and {}", |
| @@ -787,7 +803,7 @@ pub async fn propose_transaction( | |||
| 787 | internal_user.balance, new_transaction.amount | 803 | internal_user.balance, new_transaction.amount |
| 788 | ); | 804 | ); |
| 789 | return Ok(warp::reply::with_status( | 805 | return Ok(warp::reply::with_status( |
| 790 | warp::reply::json(&GradeCoinResponse { | 806 | warp::reply::json(&UserFeedback { |
| 791 | res: ResponseType::Error, | 807 | res: ResponseType::Error, |
| 792 | message: "User does not have enough balance in their account for this transaction" | 808 | message: "User does not have enough balance in their account for this transaction" |
| 793 | .to_owned(), | 809 | .to_owned(), |
| @@ -806,7 +822,7 @@ pub async fn propose_transaction( | |||
| 806 | let hashed_transaction = Md5::digest(serd_tx.as_bytes()); | 822 | let hashed_transaction = Md5::digest(serd_tx.as_bytes()); |
| 807 | if token_payload.claims.tha != format!("{:x}", hashed_transaction) { | 823 | if token_payload.claims.tha != format!("{:x}", hashed_transaction) { |
| 808 | return Ok(warp::reply::with_status( | 824 | return Ok(warp::reply::with_status( |
| 809 | warp::reply::json(&GradeCoinResponse { | 825 | warp::reply::json(&UserFeedback { |
| 810 | res: ResponseType::Error, | 826 | res: ResponseType::Error, |
| 811 | message: "The hash of the transaction did not match the hash given in JWT" | 827 | message: "The hash of the transaction did not match the hash given in JWT" |
| 812 | .to_owned(), | 828 | .to_owned(), |
| @@ -815,14 +831,17 @@ pub async fn propose_transaction( | |||
| 815 | )); | 831 | )); |
| 816 | } | 832 | } |
| 817 | 833 | ||
| 818 | warn!("[{}] ACCEPTED TRANSACTION {:?}", db.config.name, new_transaction); | 834 | warn!( |
| 835 | "[{}] ACCEPTED TRANSACTION {:?}", | ||
| 836 | db.config.name, new_transaction | ||
| 837 | ); | ||
| 819 | 838 | ||
| 820 | let mut transactions = db.pending_transactions.write(); | 839 | let mut transactions = db.pending_transactions.write(); |
| 821 | 840 | ||
| 822 | transactions.insert(transaction_id, new_transaction); | 841 | transactions.insert(transaction_id, new_transaction); |
| 823 | 842 | ||
| 824 | Ok(warp::reply::with_status( | 843 | Ok(warp::reply::with_status( |
| 825 | warp::reply::json(&GradeCoinResponse { | 844 | warp::reply::json(&UserFeedback { |
| 826 | res: ResponseType::Success, | 845 | res: ResponseType::Success, |
| 827 | message: "Transaction accepted".to_owned(), | 846 | message: "Transaction accepted".to_owned(), |
| 828 | }), | 847 | }), |
| @@ -835,8 +854,6 @@ pub async fn propose_transaction( | |||
| 835 | /// Cannot fail | 854 | /// Cannot fail |
| 836 | /// Mostly around for debug purposes | 855 | /// Mostly around for debug purposes |
| 837 | pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> { | 856 | pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> { |
| 838 | debug!("GET /block, list_blocks() is handling"); | ||
| 839 | |||
| 840 | let block = db.blockchain.read(); | 857 | let block = db.blockchain.read(); |
| 841 | 858 | ||
| 842 | Ok(reply::with_status(reply::json(&*block), StatusCode::OK)) | 859 | Ok(reply::with_status(reply::json(&*block), StatusCode::OK)) |
diff --git a/src/main.rs b/src/main.rs index 595ce3b..93397fa 100644 --- a/src/main.rs +++ b/src/main.rs | |||
| @@ -46,21 +46,21 @@ | |||
| 46 | #![warn(clippy::all, clippy::pedantic)] | 46 | #![warn(clippy::all, clippy::pedantic)] |
| 47 | #![allow(clippy::unused_async)] | 47 | #![allow(clippy::unused_async)] |
| 48 | 48 | ||
| 49 | mod block; | ||
| 50 | mod config; | ||
| 49 | mod custom_filters; | 51 | mod custom_filters; |
| 50 | mod db; | 52 | mod db; |
| 51 | mod handlers; | 53 | mod handlers; |
| 52 | mod routes; | 54 | mod routes; |
| 53 | mod block; | ||
| 54 | mod student; | 55 | mod student; |
| 55 | mod config; | ||
| 56 | 56 | ||
| 57 | use crate::config::Config; | ||
| 57 | pub use block::{Fingerprint, Id}; | 58 | pub use block::{Fingerprint, Id}; |
| 58 | use db::Db; | 59 | use db::Db; |
| 59 | use lazy_static::lazy_static; | 60 | use lazy_static::lazy_static; |
| 61 | use log::error; | ||
| 60 | use std::fs; | 62 | use std::fs; |
| 61 | use crate::config::Config; | 63 | use warp::Filter; |
| 62 | use warp::{Filter}; | ||
| 63 | use log::{error}; | ||
| 64 | 64 | ||
| 65 | #[tokio::main] | 65 | #[tokio::main] |
| 66 | async fn main() { | 66 | async fn main() { |
| @@ -73,13 +73,11 @@ async fn main() { | |||
| 73 | args.push("config.yaml".to_string()); | 73 | args.push("config.yaml".to_string()); |
| 74 | } | 74 | } |
| 75 | 75 | ||
| 76 | let combined_routes = args.into_iter() | 76 | let combined_routes = args |
| 77 | .into_iter() | ||
| 77 | .skip(1) // Skip the program name | 78 | .skip(1) // Skip the program name |
| 78 | .filter_map(|filename| { | 79 | .filter_map(|filename| { |
| 79 | match Config::read(&filename) { | 80 | Config::read(&filename).map(|config| routes::network(Db::new(config))) |
| 80 | Some(config) => Some(routes::network(Db::new(config))), | ||
| 81 | None => None, | ||
| 82 | } | ||
| 83 | }) | 81 | }) |
| 84 | .reduce(|routes, route| routes.or(route).unify().boxed()); | 82 | .reduce(|routes, route| routes.or(route).unify().boxed()); |
| 85 | 83 | ||
| @@ -89,7 +87,7 @@ async fn main() { | |||
| 89 | // Exit the program if there's no successfully loaded config file. | 87 | // Exit the program if there's no successfully loaded config file. |
| 90 | error!("Failed to load any config files!"); | 88 | error!("Failed to load any config files!"); |
| 91 | return; | 89 | return; |
| 92 | }, | 90 | } |
| 93 | }; | 91 | }; |
| 94 | 92 | ||
| 95 | // gradecoin-site (zola) outputs a public/, we serve it here | 93 | // gradecoin-site (zola) outputs a public/, we serve it here |
diff --git a/src/routes.rs b/src/routes.rs index 651626a..0df3470 100644 --- a/src/routes.rs +++ b/src/routes.rs | |||
| @@ -3,13 +3,16 @@ | |||
| 3 | use crate::custom_filters; | 3 | use crate::custom_filters; |
| 4 | use crate::handlers; | 4 | use crate::handlers; |
| 5 | use crate::Db; | 5 | use crate::Db; |
| 6 | use warp::{Filter, filters::BoxedFilter, Rejection, Reply}; | 6 | use log::info; |
| 7 | use log::{info}; | 7 | use warp::{filters::BoxedFilter, Filter, Rejection, Reply}; |
| 8 | 8 | ||
| 9 | /// Every route combined for a single network | 9 | /// Every route combined for a single network |
| 10 | pub fn network(db: Db) -> BoxedFilter<(impl Reply,)> { | 10 | pub fn network(db: Db) -> BoxedFilter<(impl Reply,)> { |
| 11 | let url_prefix = db.config.url_prefix.clone(); | 11 | let url_prefix = db.config.url_prefix.clone(); |
| 12 | info!("{} will be served at endpoint /{}", db.config.name, url_prefix); | 12 | info!( |
| 13 | "{} will be served at endpoint /{}", | ||
| 14 | db.config.name, url_prefix | ||
| 15 | ); | ||
| 13 | let root = if url_prefix.is_empty() { | 16 | let root = if url_prefix.is_empty() { |
| 14 | // warp::path does not like empty url_prefix | 17 | // warp::path does not like empty url_prefix |
| 15 | // We need to handle this case separately | 18 | // We need to handle this case separately |
| @@ -19,14 +22,14 @@ pub fn network(db: Db) -> BoxedFilter<(impl Reply,)> { | |||
| 19 | }; | 22 | }; |
| 20 | root.and( | 23 | root.and( |
| 21 | transaction_list(db.clone()) | 24 | transaction_list(db.clone()) |
| 22 | .or(get_config_route(db.clone())) | 25 | .or(get_config_route(db.clone())) |
| 23 | .or(register_user(db.clone())) | 26 | .or(register_user(db.clone())) |
| 24 | .or(auth_transaction_propose(db.clone())) | 27 | .or(auth_transaction_propose(db.clone())) |
| 25 | .or(auth_block_propose(db.clone())) | 28 | .or(auth_block_propose(db.clone())) |
| 26 | .or(list_users(db.clone())) | 29 | .or(list_users(db.clone())) |
| 27 | .or(block_list(db)) | 30 | .or(block_list(db)), |
| 28 | ) | 31 | ) |
| 29 | .boxed() | 32 | .boxed() |
| 30 | } | 33 | } |
| 31 | 34 | ||
| 32 | /// GET /config warp route | 35 | /// GET /config warp route |
diff --git a/src/student.rs b/src/student.rs index b6d99cd..5af512e 100644 --- a/src/student.rs +++ b/src/student.rs | |||
| @@ -26,7 +26,13 @@ pub struct User { | |||
| 26 | 26 | ||
| 27 | impl fmt::Display for User { | 27 | impl fmt::Display for User { |
| 28 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 28 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 29 | write!(f, "{}", self.user_id) | 29 | write!(f, "{}", self.user_id.get_id()) |
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | impl fmt::Display for UserAtRest { | ||
| 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| 35 | write!(f, "{}", self.user) | ||
| 30 | } | 36 | } |
| 31 | } | 37 | } |
| 32 | 38 | ||
