From 17d73bb73f4396c22ca24c3839a5449f5e28b4e5 Mon Sep 17 00:00:00 2001 From: Yigit Sever Date: Tue, 13 Apr 2021 04:05:44 +0300 Subject: Housekeeping Moved tests out of routes.rs into their own file Learned how to use lib.rs, now we have cargo doc support as well --- src/bin/main.rs | 25 ++++ src/custom_filters.rs | 18 ++- src/handlers.rs | 128 ++++++++--------- src/lib.rs | 34 +++-- src/main.rs | 26 ---- src/routes.rs | 380 +------------------------------------------------- src/schema.rs | 68 ++++++--- 7 files changed, 178 insertions(+), 501 deletions(-) create mode 100644 src/bin/main.rs delete mode 100644 src/main.rs (limited to 'src') diff --git a/src/bin/main.rs b/src/bin/main.rs new file mode 100644 index 0000000..8d88286 --- /dev/null +++ b/src/bin/main.rs @@ -0,0 +1,25 @@ +use std::env; +use warp::Filter; + +use gradecoin::routes::consensus_routes; +use gradecoin::schema::create_database; + +// mod validators; + +#[tokio::main] +async fn main() { + // Show debug logs by default by setting `RUST_LOG=restful_rust=debug` + if env::var_os("RUST_LOG").is_none() { + env::set_var("RUST_LOG", "gradecoin=debug"); + } + pretty_env_logger::init(); + + let db = create_database(); + + let api = consensus_routes(db); + + let routes = api.with(warp::log("gradecoin")); + + // Start the server + warp::serve(routes).run(([127, 0, 0, 1], 8080)).await; +} diff --git a/src/custom_filters.rs b/src/custom_filters.rs index f93f572..dfdae04 100644 --- a/src/custom_filters.rs +++ b/src/custom_filters.rs @@ -1,30 +1,38 @@ -use gradecoin::schema::{AuthRequest, Block, Db, Transaction}; +/// Functions that extracts Structs to be used in warp routines +use crate::schema::{AuthRequest, Block, Db, Transaction}; use std::convert::Infallible; use warp::{Filter, Rejection}; -// Database context for routes +/// Wraps the database to be used in warp routes pub fn with_db(db: Db) -> impl Filter + Clone { warp::any().map(move || db.clone()) } -// Accept only json encoded User body and reject big payloads +/// Extracts an `AuthRequest` JSON body from the request +/// Accepts only JSON encoded `AuthRequest` body and rejects big payloads +/// // TODO: find a good limit for this, (=e2482057; 8 char String + rsa pem) <11-04-21, yigit> // pub fn auth_request_json_body() -> impl Filter + Clone { warp::body::content_length_limit(1024 * 32).and(warp::body::json()) } -// Accept only json encoded Transaction body and reject big payloads +/// Extracts an `Transaction` JSON body from the request +/// Accepts only JSON encoded `Transaction` body and rejects big payloads // TODO: find a good limit for this <11-04-21, yigit> // pub fn transaction_json_body() -> impl Filter + Clone { warp::body::content_length_limit(1024 * 32).and(warp::body::json()) } +/// Extracts the value of the `Authorization` header field, hopefully a valid JWT +/// Used in Authorization for `Block` and `Transaction` proposals +/// Rejects the request if the Authorization header does not exist pub fn auth_header() -> impl Filter + Clone { warp::header::header::("Authorization") } -// Accept only json encoded Block body and reject big payloads +/// Extracts an `Block` JSON body from the request +/// Accepts only JSON encoded `Block` body and rejects big payloads // TODO: find a good limit for this <11-04-21, yigit> // pub fn block_json_body() -> impl Filter + Clone { warp::body::content_length_limit(1024 * 32).and(warp::body::json()) diff --git a/src/handlers.rs b/src/handlers.rs index 07986f5..80ed1f7 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -1,37 +1,28 @@ +/// API handlers, the ends of each filter chain use blake2::{Blake2s, Digest}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; -/// API handlers, the ends of each filter chain use log::debug; use md5::Md5; use parking_lot::RwLockUpgradableReadGuard; -use serde::{Deserialize, Serialize}; use serde_json; -use serde_json::json; use std::convert::Infallible; use std::fs; -use warp::{http::Response, http::StatusCode, reject, reply}; +use warp::{http::Response, http::StatusCode, reply}; -use gradecoin::schema::{ - AuthRequest, Block, Db, MetuId, NakedBlock, PublicKeySignature, Transaction, User, -}; +use crate::schema::{AuthRequest, Block, Claims, Db, MetuId, NakedBlock, Transaction, User}; const BEARER: &str = "Bearer "; -/// tha: Transaction Hash, String -/// iat: Issued At, Unix Time, epoch -#[derive(Debug, Serialize, Deserialize)] -pub struct Claims { - pub tha: String, - pub iat: usize, -} - -/// POST /register -/// Enables a student to introduce themselves to the system -/// Can fail +/// POST request to /register endpoint +/// +/// Lets a [`User`] (=student) to authenticate themselves to the system +/// This `request` can be rejected if the payload is malformed (= not authenticated properly) or if +/// the [`AuthRequest.user_id`] of the `request` is not in the list of users that can hold a Gradecoin account pub async fn authenticate_user( request: AuthRequest, db: Db, ) -> Result { + debug!("POST request to /register, authenticate_user"); let given_id = request.student_id.clone(); if let Some(priv_student_id) = MetuId::new(request.student_id) { @@ -77,7 +68,7 @@ pub async fn authenticate_user( /// Returns JSON array of transactions /// Cannot fail pub async fn list_transactions(db: Db) -> Result { - debug!("list all transactions"); + debug!("GET request to /transaction, list_transactions"); let mut result = Vec::new(); let transactions = db.pending_transactions.read(); @@ -95,31 +86,13 @@ pub async fn list_transactions(db: Db) -> Result { /// Cannot fail /// Mostly around for debug purposes pub async fn list_blocks(db: Db) -> Result { - debug!("list all block"); + debug!("GET request to /block, list_blocks"); let block = db.blockchain.read(); Ok(reply::with_status(reply::json(&*block), StatusCode::OK)) } -/// POST /transaction -/// Pushes a new transaction for pending transaction pool -/// Can reject the transaction proposal -/// TODO: when is a new transaction rejected <07-04-21, yigit> // -pub async fn propose_transaction( - new_transaction: Transaction, - db: Db, -) -> Result { - debug!("new transaction request {:?}", new_transaction); - - // let mut transactions = db.lock().await; - let mut transactions = db.pending_transactions.write(); - - transactions.insert(new_transaction.source.to_owned(), new_transaction); - - Ok(StatusCode::CREATED) -} - /// POST /block /// Proposes a new block for the next round /// Can reject the block @@ -181,41 +154,64 @@ pub async fn propose_block(new_block: Block, db: Db) -> Result Result { - debug!("new transaction request {:?}", new_transaction); - let raw_jwt = token.trim_start_matches(BEARER).to_owned(); + debug!("POST request to /transaction, propose_transaction"); + debug!("The transaction request: {:?}", new_transaction); - let decoded = jsonwebtoken::decode::( - &token, - &DecodingKey::from_rsa_pem( - db.users - .read() - .get(&new_transaction.by) - .unwrap() - .public_key - .as_bytes(), - ) - .unwrap(), - // todo@keles: If user is not found return user not found error - &Validation::new(Algorithm::PS256), - ) - .unwrap(); - // todo: If user is found but header is not validated, return header not valid - - let hashed_transaction = Md5::digest(&serde_json::to_vec(&new_transaction).unwrap()); - - // let mut transactions = db.lock().await; - if decoded.claims.tha == format!("{:x}", hashed_transaction) { - let mut transactions = db.pending_transactions.write(); - - transactions.insert(new_transaction.source.to_owned(), new_transaction); - - Ok(StatusCode::CREATED) + let raw_jwt = token.trim_start_matches(BEARER).to_owned(); + debug!("raw_jwt: {:?}", raw_jwt); + + if let Some(user) = db.users.read().get(&new_transaction.by) { + // This public key was already written to the database, we can panic if it's not valid at + // *this* point + let by_public_key = &user.public_key; + + if let Ok(decoded) = decode::( + &raw_jwt, + &DecodingKey::from_rsa_pem(by_public_key.as_bytes()).unwrap(), + &Validation::new(Algorithm::RS256), + ) { + // 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()); + + if decoded.claims.tha == format!("{:x}", hashed_transaction) { + let mut transactions = db.pending_transactions.write(); + + transactions.insert(new_transaction.source.to_owned(), new_transaction); + + Ok(StatusCode::CREATED) + } else { + debug!( + "the hash of the request {:x} did not match with the hash given in jwt {:?}", + hashed_transaction, decoded.claims.tha + ); + Ok(StatusCode::BAD_REQUEST) + } + } else { + debug!("raw_jwt was malformed {:?}", raw_jwt); + Ok(StatusCode::BAD_REQUEST) + } } else { + debug!( + "A user with public key signature {:?} is not found in the database", + new_transaction.by + ); Ok(StatusCode::BAD_REQUEST) } } diff --git a/src/lib.rs b/src/lib.rs index aed4591..6e51899 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,27 @@ -pub mod schema; +//! # Gradecoin +//! +//! ## Services +//! ### /register +//! - Student creates their own 2048 bit RSA `keypair` +//! - Downloads `Gradecoin`'s Public Key from Moodle +//! - Encrypts their JSON wrapped `Public Key` and `Student ID` using Gradecoin's Public Key +//! - Their public key is now in our Db under [`schema::User::public_key`] and can be used to sign their JWT's during requests +//! +//! ### /transaction +//! - offer a [`schema::Transaction`] - POST request +//! - The request should have `Authorization` +//! - The request header should be signed by the Public Key of the `by` field in the transaction +//! - fetch the list of `Transaction`s - GET request +//! +//! ### /block +//! - offer a [`schema::Block`] - POST request +//! - The request should have `Authorization` +//! - The [`schema::Block::transaction_list`] of the block should be a subset of [`schema::Db::pending_transactions`] +//! - fetch the last accepted [`schema::Block`] - GET request +//! +//! `Authorization`: The request header should have Bearer JWT.Token signed with Student Public Key -pub use schema::create_database; -pub use schema::AuthRequest; -pub use schema::Block; -pub use schema::Db; -pub use schema::MetuId; -pub use schema::Transaction; -pub use schema::User; +pub mod custom_filters; +pub mod handlers; +pub mod routes; +pub mod schema; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 5683aea..0000000 --- a/src/main.rs +++ /dev/null @@ -1,26 +0,0 @@ -use gradecoin::schema::create_database; -use std::env; -use warp::Filter; - -mod custom_filters; -mod handlers; -mod routes; -// mod validators; - -#[tokio::main] -async fn main() { - // Show debug logs by default by setting `RUST_LOG=restful_rust=debug` - if env::var_os("RUST_LOG").is_none() { - env::set_var("RUST_LOG", "gradecoin=debug"); - } - pretty_env_logger::init(); - - let db = create_database(); - - let api = routes::consensus_routes(db); - - let routes = api.with(warp::log("gradecoin")); - - // Start the server - warp::serve(routes).run(([127, 0, 0, 1], 8080)).await; -} diff --git a/src/routes.rs b/src/routes.rs index ed2acad..e4bdee4 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,10 +1,11 @@ -use gradecoin::schema::Db; +/// Endpoints and their construction use warp::{Filter, Rejection, Reply}; use crate::custom_filters; use crate::handlers; +use crate::schema::Db; -/// Root, all routes combined +/// Every route combined pub fn consensus_routes(db: Db) -> impl Filter + Clone { transaction_list(db.clone()) .or(register_user(db.clone())) @@ -38,15 +39,6 @@ pub fn block_list(db: Db) -> impl Filter impl Filter + Clone { - warp::path!("transaction") - .and(warp::post()) - .and(custom_filters::transaction_json_body()) - .and(custom_filters::with_db(db)) - .and_then(handlers::propose_transaction) -} - /// POST /transaction warp route pub fn auth_transaction_propose( db: Db, @@ -68,369 +60,3 @@ pub fn block_propose(db: Db) -> impl Filter Db { - let db = create_database(); - - db.users.write().insert( - "mock_transaction_source".to_owned(), - User { - user_id: MetuId::new("e254275".to_owned()).unwrap(), - public_key: -"-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4nU0G4WjkmcQUx0hq6LQ -uV5Q+ACmUFL/OjoYMDwC/O/6pCd1UZgCfgHN2xEffDPznzcTn8OiFRxr4oWyBiny -rUpnY4mhy0SQUwoeCw7YkcHAyhCjNT74aR/ohX0MCj0qRRdbt5ZQXM/GC3HJuXE1 -ptSuhFgQxziItamn8maoJ6JUSVEXVO1NOrrjoM3r7Q+BK2B+sX4/bLZ+VG5g1q2n -EbFdTHS6pHqtZNHQndTmEKwRfh0RYtzEzOXuO6e1gQY42Tujkof40dhGCIU7TeIG -GHwdFxy1niLkXwtHNjV7lnIOkTbx6+sSPamRfQAlZqUWM2Lf5o+7h3qWP3ENB138 -sQIDAQAB ------END PUBLIC KEY-----" - .to_owned(), - balance: 0, - }, - ); - db.pending_transactions.write().insert( - "hash_value".to_owned(), - Transaction { - by: "source_account".to_owned(), - source: "source_account".to_owned(), - target: "target_account".to_owned(), - amount: 20, - timestamp: chrono::NaiveDate::from_ymd(2021, 04, 09).and_hms(1, 30, 30), - }, - ); - - *db.blockchain.write() = Block { - transaction_list: vec![ - "old_transaction_hash_1".to_owned(), - "old_transaction_hash_2".to_owned(), - "old_transaction_hash_3".to_owned(), - ], - nonce: 0, - timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30), - hash: "not_a_thing_yet".to_owned(), - }; - - db - } - - fn mocked_jwt() -> String { - - let claims = Claims { - tha: "6692e774eba7fb92dc0fe6cf7347591e".to_owned(), - iat: 1516239022, - }; - let header = Header::new(Algorithm::RS256); - encode(&header, &claims, &EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap()).unwrap() - } - /// Create a mock user that is allowed to be in gradecoin to be used in tests - fn priviliged_mocked_user() -> AuthRequest { - AuthRequest { - student_id: String::from("e254275"), - public_key: "NOT IMPLEMENTED".to_owned(), - } - } - - /// Create a mock user that is NOT allowed to be in gradecoin to be used in tests - fn unpriviliged_mocked_user() -> AuthRequest { - AuthRequest { - student_id: String::from("foobarbaz"), - public_key: "NOT IMPLEMENTED".to_owned(), - } - } - - /// Create a mock transaction to be used in tests - fn mocked_transaction() -> Transaction { - Transaction { - by: "mock_transaction_source".to_owned(), - source: "mock_transaction_source".to_owned(), - target: "mock_transaction_target".to_owned(), - amount: 25, - timestamp: chrono::NaiveDate::from_ymd(2021, 04, 09).and_hms(14, 30, 00), - } - } - - /// Create a mock block with a correct mined hash to be used in tests - fn mocked_block() -> Block { - Block { - transaction_list: vec!["hash_value".to_owned()], - nonce: 3831993, - timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30), - hash: "2b648ffab5d9af1d5d5fc052fc9e51b882fc4fb0c998608c99232f9282000000".to_owned(), - } - } - - /// Create a mock block with a wrong hash and nonce - fn mocked_wrong_block() -> Block { - Block { - transaction_list: vec!["foobarbaz".to_owned(), "dazsaz".to_owned()], - nonce: 1000, // can you imagine - timestamp: chrono::NaiveDate::from_ymd(2021, 04, 12).and_hms(05, 29, 30), - hash: "tnarstnarsuthnarsthlarjstk".to_owned(), - } - } - - /// Test simple GET request to /transaction, resource that exists - /// https://tools.ietf.org/html/rfc7231#section-6.3.1 - /// We should get the only pending transaction available in the database as json - #[tokio::test] - async fn get_pending_transactions() { - let db = mocked_db(); - - let reply = consensus_routes(db); - - let res = warp::test::request() - .method("GET") - .path("/transaction") - .reply(&reply) - .await; - - assert_eq!(res.status(), StatusCode::OK); - - let expected_json_body = r#"[{"by":"source_account","source":"source_account","target":"target_account","amount":20,"timestamp":"2021-04-09T01:30:30"}]"#; - - assert_eq!(res.body(), expected_json_body); - } - - /// Test simple GET request to /block, resource that exists - /// https://tools.ietf.org/html/rfc7231#section-6.3.1 - /// Should return the single block available in the database as json - #[tokio::test] - async fn get_blockchain() { - let db = mocked_db(); - let filter = consensus_routes(db); - - let res = warp::test::request() - .method("GET") - .path("/block") - .reply(&filter) - .await; - - assert_eq!(res.status(), StatusCode::OK); - - let expected_json_body = r#"{"transaction_list":["old_transaction_hash_1","old_transaction_hash_2","old_transaction_hash_3"],"nonce":0,"timestamp":"2021-04-08T12:30:30","hash":"not_a_thing_yet"}"#; - assert_eq!(res.body(), expected_json_body); - } - - /// Test a simple GET request to a nonexisting path - /// https://tools.ietf.org/html/rfc7231#section-6.5.4 - /// Should respond with 404 and stop - #[tokio::test] - async fn get_nonexisting_path_404() { - let db = mocked_db(); - let filter = consensus_routes(db); - - let res = warp::test::request() - .method("GET") - .path("/this_path_does_not_exist") - .reply(&filter) - .await; - - assert_eq!(res.status(), StatusCode::NOT_FOUND); - } - - /// Test a POST request to /transaction, a resource that exists - /// https://tools.ietf.org/html/rfc7231#section-6.3.2 - /// Should accept the json request, create - /// the transaction and add it to pending transactions in the db - #[tokio::test] - async fn post_json_201() { - let db = mocked_db(); - let filter = consensus_routes(db.clone()); - let res = warp::test::request() - .method("POST") - .json(&mocked_transaction()) - .path("/transaction") - .reply(&filter) - .await; - - assert_eq!(res.status(), StatusCode::CREATED); - assert_eq!(db.pending_transactions.read().len(), 2); - } - - /// Test a POST request to /transaction, a resource that exists - /// https://tools.ietf.org/html/rfc7231#section-6.3.2 - /// Should accept the json request, create - /// the transaction and add it to pending transactions in the db - #[tokio::test] - async fn post_auth_json_201() { - let db = mocked_db(); - let filter = consensus_routes(db.clone()); - - let res = warp::test::request() - .method("POST") - .json(&mocked_transaction()) - .header("Authorization", format!("Bearer {}", &mocked_jwt())) - .path("/transaction") - .reply(&filter) - .await; - - assert_eq!(res.status(), StatusCode::CREATED); - assert_eq!(db.pending_transactions.read().len(), 2); - } - - /// Test a POST request to /transaction, a resource that exists - /// https://tools.ietf.org/html/rfc7231#section-6.3.2 - /// Should accept the json request, create - /// the transaction and add it to pending transactions in the db - #[tokio::test] - async fn post_auth_json_400() { - let db = mocked_db(); - let filter = consensus_routes(db.clone()); - - let res = warp::test::request() - .method("POST") - .json(&mocked_transaction()) - .header("Authorization", "Bearer aaaaaaaasdlkjaldkasljdaskjlaaaaaaaaaaaaaa") - .path("/transaction") - .reply(&filter) - .await; - - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - assert_eq!(db.pending_transactions.read().len(), 1); - } - - /// Test a POST request to /block, a resource that exists - /// https://tools.ietf.org/html/rfc7231#section-6.3.2 - /// Should accept the json request, create - /// the block - #[tokio::test] - async fn post_block_201() { - let db = mocked_db(); - let filter = consensus_routes(db.clone()); - - let res = warp::test::request() - .method("POST") - .json(&mocked_block()) - .path("/block") - .reply(&filter) - .await; - - assert_eq!(res.status(), StatusCode::CREATED); - assert_eq!( - *db.blockchain.read().hash, - "2b648ffab5d9af1d5d5fc052fc9e51b882fc4fb0c998608c99232f9282000000".to_owned() - ); - } - - /// Test a POST request to /block, a resource that exists - /// https://tools.ietf.org/html/rfc7231#section-6.3.2 - /// Should reject the block because of the wrong hash - #[tokio::test] - async fn post_block_wrong_hash() { - let db = mocked_db(); - let filter = consensus_routes(db.clone()); - - let res = warp::test::request() - .method("POST") - .json(&mocked_wrong_block()) - .path("/block") - .reply(&filter) - .await; - - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - } - - /// Test a POST request to /register, an endpoint that exists - /// https://tools.ietf.org/html/rfc7231#section-6.3.2 - /// Should accept the json request, create a new user and - /// add it to the user hashmap in the db - #[tokio::test] - async fn post_register_priviliged_user() { - let db = mocked_db(); - let filter = consensus_routes(db.clone()); - - let res = warp::test::request() - .method("POST") - .json(&priviliged_mocked_user()) - .path("/register") - .reply(&filter) - .await; - - println!("{:?}", res.body()); - assert_eq!(res.status(), StatusCode::CREATED); - assert_eq!(db.users.read().len(), 2); - } - - /// Test a POST request to /transaction, an endpoint that exists - /// https://tools.ietf.org/html/rfc7231#section-6.3.2 - /// Should NOT accept the json request as the user is unpriviliged - #[tokio::test] - async fn post_register_unpriviliged_user() { - let db = mocked_db(); - let filter = consensus_routes(db.clone()); - - let res = warp::test::request() - .method("POST") - .json(&unpriviliged_mocked_user()) - .path("/register") - .reply(&filter) - .await; - - println!("{:?}", res.body()); - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - assert_eq!(db.users.read().len(), 1); - } - - /// Test a POST request to /transaction, a resource that exists with a longer than expected - /// payload - /// https://tools.ietf.org/html/rfc7231#section-6.5.11 - /// Should return 413 to user - #[tokio::test] - async fn post_too_long_content_413() { - let db = mocked_db(); - let filter = consensus_routes(db); - - let res = warp::test::request() - .method("POST") - .header("content-length", 1024 * 36) - .path("/transaction") - .reply(&filter) - .await; - - assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE); - } -} - -// TODO: POST block without correct transactions test <09-04-21, yigit> // -// TODO: POST transaction while that source has pending transaction test <09-04-21, yigit> // diff --git a/src/schema.rs b/src/schema.rs index 39921b8..4eb2f0a 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,3 +1,12 @@ +//! # Data Representations +//! +//! We need persistence for [`Block`]s and [`User`]s, not so much for [`Transaction`]s +//! +//! There are around 30 students, a full fledged database would be an overkill (for next year?) +//! +//! Pending transactions are held in memory, these are cleared with every new block +//! Only the last block is held in memory, every block is written to a file +//! Users are held in memory and they're also backed up to text files use chrono::{NaiveDate, NaiveDateTime}; use lazy_static::lazy_static; use parking_lot::RwLock; @@ -9,24 +18,45 @@ use std::sync::Arc; // use crate::validators; -/// We need persistence for blocks and users, not so much for transactions -/// There are around 30 students, a full fledged database would be an overkill (for next year?) -/// Pending transactions are held in memory, these are cleared with every new block -/// Only the last block is held in memory, every block is written to a file -/// Users are held in memory and they're also backed up to text files +pub type PublicKeySignature = String; -/// Creates a new database connection +/// Creates a new database pub fn create_database() -> Db { fs::create_dir_all("blocks").unwrap(); fs::create_dir_all("users").unwrap(); Db::new() } +/// A JWT Payload/Claims representation +/// +/// https://tools.ietf.org/html/rfc7519#section-4.1 +/// +/// - `tha`: Transaction Hash, String (custom field) +/// - `iat`: Issued At, Unix Time, epoch +/// - `exp`: Expiration Time, epoch +#[derive(Debug, Serialize, Deserialize)] +pub struct Claims { + pub tha: String, + pub iat: usize, + pub exp: usize, +} + +/// Global Database representation +/// +/// [`blockchain`] is just the last block that was mined. All the blocks are written to disk as text +/// files whenever they are accepted. +/// +/// [`pending_transactions`] is the in memory representation of the waiting transactions. Every +/// user can have only one outstanding transaction at any given time. +/// +/// [`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 { - // heh. also https://doc.rust-lang.org/std/collections/struct.LinkedList.html says Vec is generally faster pub blockchain: Arc>, - // every proposer can have _one_ pending transaction, a way to enforce this, String is proposer identifier pub pending_transactions: Arc>>, pub users: Arc>>, } @@ -41,8 +71,6 @@ impl Db { } } -pub type PublicKeySignature = String; - /// A transaction between `source` and `target` that moves `amount` #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct Transaction { @@ -53,20 +81,21 @@ pub struct Transaction { pub timestamp: NaiveDateTime, } -/// A block that was proposed with `transaction_list` and `nonce` that made `hash` valid +/// A block that was proposed with `transaction_list` and `nonce` that made `hash` valid, 6 zeroes +/// at the right hand side of the hash (24 bytes) +/// +/// We are mining using blake2s algorithm, which produces 256 bit hashes. Hash/second is roughly +/// 20x10^3. +/// /// https://serde.rs/container-attrs.html might be valuable to normalize the serialize/deserialize /// conventions as these will be hashed +/// #[derive(Serialize, Deserialize, Debug)] pub struct Block { - // TODO: transaction list should hold transaction hash values <09-04-21, yigit> // - // but do we link them somehow? (like a log of old transactions?) - // we can leave this as is and whenever we have a new block we _could_ just log it to file - // somewhere - // I want to keep this as a String vector because it makes things easier elsewhere - pub transaction_list: Vec, // hashes of the transactions (or just "source" for now) + pub transaction_list: Vec, pub nonce: u32, pub timestamp: NaiveDateTime, - pub hash: String, // future proof'd baby + pub hash: String, } /// For prototyping and letting serde handle everything json @@ -89,7 +118,7 @@ impl Block { } } -/// Or simply a Student +/// Simply a Student #[derive(Serialize, Deserialize, Debug)] pub struct User { pub user_id: MetuId, @@ -103,6 +132,7 @@ pub struct MetuId { id: String, } +// TODO: this will arrive encrypted <13-04-21, yigit> // #[derive(Serialize, Deserialize, Debug)] pub struct AuthRequest { pub student_id: String, -- cgit v1.2.3-70-g09d2