diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/custom_filters.rs | 4 | ||||
| -rw-r--r-- | src/handlers.rs | 60 | ||||
| -rw-r--r-- | src/routes.rs | 132 | ||||
| -rw-r--r-- | src/schema.rs | 14 |
4 files changed, 189 insertions, 21 deletions
diff --git a/src/custom_filters.rs b/src/custom_filters.rs index 315ba4a..f93f572 100644 --- a/src/custom_filters.rs +++ b/src/custom_filters.rs | |||
| @@ -20,6 +20,10 @@ pub fn transaction_json_body() -> impl Filter<Extract = (Transaction,), Error = | |||
| 20 | warp::body::content_length_limit(1024 * 32).and(warp::body::json()) | 20 | warp::body::content_length_limit(1024 * 32).and(warp::body::json()) |
| 21 | } | 21 | } |
| 22 | 22 | ||
| 23 | pub fn auth_header() -> impl Filter<Extract = (String,), Error = Rejection> + Clone { | ||
| 24 | warp::header::header::<String>("Authorization") | ||
| 25 | } | ||
| 26 | |||
| 23 | // Accept only json encoded Block body and reject big payloads | 27 | // Accept only json encoded Block body and reject big payloads |
| 24 | // TODO: find a good limit for this <11-04-21, yigit> // | 28 | // TODO: find a good limit for this <11-04-21, yigit> // |
| 25 | pub fn block_json_body() -> impl Filter<Extract = (Block,), Error = Rejection> + Clone { | 29 | pub fn block_json_body() -> impl Filter<Extract = (Block,), Error = Rejection> + Clone { |
diff --git a/src/handlers.rs b/src/handlers.rs index 38bd459..07986f5 100644 --- a/src/handlers.rs +++ b/src/handlers.rs | |||
| @@ -1,16 +1,29 @@ | |||
| 1 | use blake2::{Blake2s, Digest}; | ||
| 2 | use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; | ||
| 1 | /// API handlers, the ends of each filter chain | 3 | /// API handlers, the ends of each filter chain |
| 2 | use log::debug; | 4 | use log::debug; |
| 5 | use md5::Md5; | ||
| 3 | use parking_lot::RwLockUpgradableReadGuard; | 6 | use parking_lot::RwLockUpgradableReadGuard; |
| 7 | use serde::{Deserialize, Serialize}; | ||
| 4 | use serde_json; | 8 | use serde_json; |
| 5 | use serde_json::json; | 9 | use serde_json::json; |
| 6 | use std::convert::Infallible; | 10 | use std::convert::Infallible; |
| 7 | use warp::{http::Response, http::StatusCode, reply}; | 11 | use std::fs; |
| 12 | use warp::{http::Response, http::StatusCode, reject, reply}; | ||
| 8 | 13 | ||
| 9 | use blake2::{Blake2s, Digest}; | 14 | use gradecoin::schema::{ |
| 15 | AuthRequest, Block, Db, MetuId, NakedBlock, PublicKeySignature, Transaction, User, | ||
| 16 | }; | ||
| 10 | 17 | ||
| 11 | use std::fs; | 18 | const BEARER: &str = "Bearer "; |
| 12 | 19 | ||
| 13 | use gradecoin::schema::{AuthRequest, Block, Db, MetuId, NakedBlock, Transaction, User}; | 20 | /// tha: Transaction Hash, String |
| 21 | /// iat: Issued At, Unix Time, epoch | ||
| 22 | #[derive(Debug, Serialize, Deserialize)] | ||
| 23 | pub struct Claims { | ||
| 24 | pub tha: String, | ||
| 25 | pub iat: usize, | ||
| 26 | } | ||
| 14 | 27 | ||
| 15 | /// POST /register | 28 | /// POST /register |
| 16 | /// Enables a student to introduce themselves to the system | 29 | /// Enables a student to introduce themselves to the system |
| @@ -167,3 +180,42 @@ pub async fn propose_block(new_block: Block, db: Db) -> Result<impl warp::Reply, | |||
| 167 | Ok(StatusCode::BAD_REQUEST) | 180 | Ok(StatusCode::BAD_REQUEST) |
| 168 | } | 181 | } |
| 169 | } | 182 | } |
| 183 | |||
| 184 | pub async fn auth_propose_transaction( | ||
| 185 | new_transaction: Transaction, | ||
| 186 | token: String, | ||
| 187 | db: Db, | ||
| 188 | ) -> Result<impl warp::Reply, warp::Rejection> { | ||
| 189 | debug!("new transaction request {:?}", new_transaction); | ||
| 190 | let raw_jwt = token.trim_start_matches(BEARER).to_owned(); | ||
| 191 | |||
| 192 | let decoded = jsonwebtoken::decode::<Claims>( | ||
| 193 | &token, | ||
| 194 | &DecodingKey::from_rsa_pem( | ||
| 195 | db.users | ||
| 196 | .read() | ||
| 197 | .get(&new_transaction.by) | ||
| 198 | .unwrap() | ||
| 199 | .public_key | ||
| 200 | .as_bytes(), | ||
| 201 | ) | ||
| 202 | .unwrap(), | ||
| 203 | // todo@keles: If user is not found return user not found error | ||
| 204 | &Validation::new(Algorithm::PS256), | ||
| 205 | ) | ||
| 206 | .unwrap(); | ||
| 207 | // todo: If user is found but header is not validated, return header not valid | ||
| 208 | |||
| 209 | let hashed_transaction = Md5::digest(&serde_json::to_vec(&new_transaction).unwrap()); | ||
| 210 | |||
| 211 | // let mut transactions = db.lock().await; | ||
| 212 | if decoded.claims.tha == format!("{:x}", hashed_transaction) { | ||
| 213 | let mut transactions = db.pending_transactions.write(); | ||
| 214 | |||
| 215 | transactions.insert(new_transaction.source.to_owned(), new_transaction); | ||
| 216 | |||
| 217 | Ok(StatusCode::CREATED) | ||
| 218 | } else { | ||
| 219 | Ok(StatusCode::BAD_REQUEST) | ||
| 220 | } | ||
| 221 | } | ||
diff --git a/src/routes.rs b/src/routes.rs index 03a2569..ed2acad 100644 --- a/src/routes.rs +++ b/src/routes.rs | |||
| @@ -1,14 +1,14 @@ | |||
| 1 | use gradecoin::schema::Db; | ||
| 1 | use warp::{Filter, Rejection, Reply}; | 2 | use warp::{Filter, Rejection, Reply}; |
| 2 | 3 | ||
| 3 | use crate::custom_filters; | 4 | use crate::custom_filters; |
| 4 | use crate::handlers; | 5 | use crate::handlers; |
| 5 | use gradecoin::schema::Db; | ||
| 6 | 6 | ||
| 7 | /// Root, all routes combined | 7 | /// Root, all routes combined |
| 8 | pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | 8 | pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { |
| 9 | transaction_list(db.clone()) | 9 | transaction_list(db.clone()) |
| 10 | .or(register_user(db.clone())) | 10 | .or(register_user(db.clone())) |
| 11 | .or(transaction_propose(db.clone())) | 11 | .or(auth_transaction_propose(db.clone())) |
| 12 | .or(block_propose(db.clone())) | 12 | .or(block_propose(db.clone())) |
| 13 | .or(block_list(db.clone())) | 13 | .or(block_list(db.clone())) |
| 14 | } | 14 | } |
| @@ -47,6 +47,18 @@ pub fn transaction_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = | |||
| 47 | .and_then(handlers::propose_transaction) | 47 | .and_then(handlers::propose_transaction) |
| 48 | } | 48 | } |
| 49 | 49 | ||
| 50 | /// POST /transaction warp route | ||
| 51 | pub fn auth_transaction_propose( | ||
| 52 | db: Db, | ||
| 53 | ) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | ||
| 54 | warp::path!("transaction") | ||
| 55 | .and(warp::post()) | ||
| 56 | .and(custom_filters::transaction_json_body()) | ||
| 57 | .and(custom_filters::auth_header()) | ||
| 58 | .and(custom_filters::with_db(db)) | ||
| 59 | .and_then(handlers::auth_propose_transaction) | ||
| 60 | } | ||
| 61 | |||
| 50 | /// POST /block warp route | 62 | /// POST /block warp route |
| 51 | pub fn block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | 63 | pub fn block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { |
| 52 | warp::path!("block") | 64 | warp::path!("block") |
| @@ -58,22 +70,69 @@ pub fn block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Reject | |||
| 58 | 70 | ||
| 59 | #[cfg(test)] | 71 | #[cfg(test)] |
| 60 | mod tests { | 72 | mod tests { |
| 61 | use super::*; | 73 | use gradecoin::schema::{create_database, AuthRequest, Block, MetuId, Transaction, User}; |
| 62 | 74 | use handlers::Claims; | |
| 63 | // use chrono::prelude::*; | 75 | // use chrono::prelude::*; |
| 64 | // use parking_lot::RwLock; | 76 | // use parking_lot::RwLock; |
| 65 | // use std::sync::Arc; | 77 | // use std::sync::Arc; |
| 66 | use warp::http::StatusCode; | 78 | use warp::http::StatusCode; |
| 67 | 79 | ||
| 68 | use gradecoin::schema::{create_database, AuthRequest, Block, Transaction}; | 80 | use super::*; |
| 69 | 81 | ||
| 82 | use jsonwebtoken::{Header, encode, EncodingKey, Algorithm}; | ||
| 83 | const private_key_pem: &str = "-----BEGIN RSA PRIVATE KEY----- | ||
| 84 | MIIEpAIBAAKCAQEA4nU0G4WjkmcQUx0hq6LQuV5Q+ACmUFL/OjoYMDwC/O/6pCd1 | ||
| 85 | UZgCfgHN2xEffDPznzcTn8OiFRxr4oWyBinyrUpnY4mhy0SQUwoeCw7YkcHAyhCj | ||
| 86 | NT74aR/ohX0MCj0qRRdbt5ZQXM/GC3HJuXE1ptSuhFgQxziItamn8maoJ6JUSVEX | ||
| 87 | VO1NOrrjoM3r7Q+BK2B+sX4/bLZ+VG5g1q2nEbFdTHS6pHqtZNHQndTmEKwRfh0R | ||
| 88 | YtzEzOXuO6e1gQY42Tujkof40dhGCIU7TeIGGHwdFxy1niLkXwtHNjV7lnIOkTbx | ||
| 89 | 6+sSPamRfQAlZqUWM2Lf5o+7h3qWP3ENB138sQIDAQABAoIBAD23nYTmrganag6M | ||
| 90 | wPFrBSGP79c3Lhx0EjUHQjJbGKFgsdltG48qM3ut+DF9ACy0Z+/7bbC7+39vaIOq | ||
| 91 | 1jLR2d6aiYTaLKseO4s2FawD1sgamvU3BZPsXn0gAhnnU5Gyy8Nas1dccvhoc9wI | ||
| 92 | neaZUPrvucQ90AzLfo6r9yacDbYHB1lOyomApUvpJxOgHISGEtc9qGPDrdH19aF0 | ||
| 93 | 8fCv2bbQRh+TChgN3IB0o5w0wXaI7YAyAouAv/AzHCoEMpt7OGjFTkjh/ujlPL9O | ||
| 94 | +FLuJNsQRHDN0gJo2pcvwGwDCsioMixQ9bZ7ZrUu2BNpEQygyeSbj9ZI1iRvhosO | ||
| 95 | JU3rwEECgYEA9MppTYA6A9WQbCCwPH1QMpUAmPNVSWVhUVag4lGOEhdCDRcz9ook | ||
| 96 | DohQMKctiEB1luKuvDokxo0uMOfMO9/YwjsRB7qjQip7Th1zMJIjD+A+juLzHK4r | ||
| 97 | /RiRtWYGAnF8mptDvE+93JsPb3C/lQLvIhio5GQYWBqPJu6SpeosIskCgYEA7NPi | ||
| 98 | Gbffzr2UQhW8BNKmctEEh8yFRVojFo3wwwWxSNUVXGSmSm31CL+Q8h817R+2OkPV | ||
| 99 | 1ZMUOBU4UJiqFt28kIvTDFqbAJlJQGCpY2mY7OLQiD2A+TVLcFrHmoCaPfCAK1Qd | ||
| 100 | hQ0PmFK7Mf8qClpA3E5chop/WfKQfiu46sZv1qkCgYAhGdXPcw1lQ1W6KVlrdI6J | ||
| 101 | qHhiNlVMDXdxZkNvFxQdAiQeXQrbxaZGiMw/J/wSNpUwCAsUzM/4QVMDrfSCDCzl | ||
| 102 | ZtNQtj4pTlFKKNVQthIjrXEIJUw2jp7IJLBfVSJu5iWxSlmId0f3MsiNizN81N69 | ||
| 103 | P5Rm/doE3+KHoy8VXGsHcQKBgQCkNh62enqjHWypjex6450qS6f6iWN3PRLLVsw0 | ||
| 104 | TcQpniZblCaBwVCAKmRUnjOEIdL2/4ZLutnwMTaFG/YEOOfAylMiY8jKV38lNmD9 | ||
| 105 | X4D78CFr9klxgvS2CRwSE03f2NzmLkLxuKaxldvaxPTfjMkgeO1LFMlNExYBhkuH | ||
| 106 | 7uQpUQKBgQCKX6qMNh2gSdgG7qyxfTFZ4y5EGOBoKe/dE+IcVF3Vnh6DZVbCAbBL | ||
| 107 | 5EdFWZSrCnDjA4xiKW55mwp95Ud9EZsZAb13L8V9t82eK+UDBoWlb7VRNYpda/x1 | ||
| 108 | 5/i4qQJ28x2UNJDStpYFpnp4Ba1lvXjKngIbDPkjU+hbBJ+BNGAIeg== | ||
| 109 | -----END RSA PRIVATE KEY-----"; | ||
| 70 | /// Create a mock database to be used in tests | 110 | /// Create a mock database to be used in tests |
| 71 | fn mocked_db() -> Db { | 111 | fn mocked_db() -> Db { |
| 72 | let db = create_database(); | 112 | let db = create_database(); |
| 73 | 113 | ||
| 114 | db.users.write().insert( | ||
| 115 | "mock_transaction_source".to_owned(), | ||
| 116 | User { | ||
| 117 | user_id: MetuId::new("e254275".to_owned()).unwrap(), | ||
| 118 | public_key: | ||
| 119 | "-----BEGIN PUBLIC KEY----- | ||
| 120 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4nU0G4WjkmcQUx0hq6LQ | ||
| 121 | uV5Q+ACmUFL/OjoYMDwC/O/6pCd1UZgCfgHN2xEffDPznzcTn8OiFRxr4oWyBiny | ||
| 122 | rUpnY4mhy0SQUwoeCw7YkcHAyhCjNT74aR/ohX0MCj0qRRdbt5ZQXM/GC3HJuXE1 | ||
| 123 | ptSuhFgQxziItamn8maoJ6JUSVEXVO1NOrrjoM3r7Q+BK2B+sX4/bLZ+VG5g1q2n | ||
| 124 | EbFdTHS6pHqtZNHQndTmEKwRfh0RYtzEzOXuO6e1gQY42Tujkof40dhGCIU7TeIG | ||
| 125 | GHwdFxy1niLkXwtHNjV7lnIOkTbx6+sSPamRfQAlZqUWM2Lf5o+7h3qWP3ENB138 | ||
| 126 | sQIDAQAB | ||
| 127 | -----END PUBLIC KEY-----" | ||
| 128 | .to_owned(), | ||
| 129 | balance: 0, | ||
| 130 | }, | ||
| 131 | ); | ||
| 74 | db.pending_transactions.write().insert( | 132 | db.pending_transactions.write().insert( |
| 75 | "hash_value".to_owned(), | 133 | "hash_value".to_owned(), |
| 76 | Transaction { | 134 | Transaction { |
| 135 | by: "source_account".to_owned(), | ||
| 77 | source: "source_account".to_owned(), | 136 | source: "source_account".to_owned(), |
| 78 | target: "target_account".to_owned(), | 137 | target: "target_account".to_owned(), |
| 79 | amount: 20, | 138 | amount: 20, |
| @@ -95,6 +154,15 @@ mod tests { | |||
| 95 | db | 154 | db |
| 96 | } | 155 | } |
| 97 | 156 | ||
| 157 | fn mocked_jwt() -> String { | ||
| 158 | |||
| 159 | let claims = Claims { | ||
| 160 | tha: "6692e774eba7fb92dc0fe6cf7347591e".to_owned(), | ||
| 161 | iat: 1516239022, | ||
| 162 | }; | ||
| 163 | let header = Header::new(Algorithm::RS256); | ||
| 164 | encode(&header, &claims, &EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap()).unwrap() | ||
| 165 | } | ||
| 98 | /// Create a mock user that is allowed to be in gradecoin to be used in tests | 166 | /// Create a mock user that is allowed to be in gradecoin to be used in tests |
| 99 | fn priviliged_mocked_user() -> AuthRequest { | 167 | fn priviliged_mocked_user() -> AuthRequest { |
| 100 | AuthRequest { | 168 | AuthRequest { |
| @@ -114,6 +182,7 @@ mod tests { | |||
| 114 | /// Create a mock transaction to be used in tests | 182 | /// Create a mock transaction to be used in tests |
| 115 | fn mocked_transaction() -> Transaction { | 183 | fn mocked_transaction() -> Transaction { |
| 116 | Transaction { | 184 | Transaction { |
| 185 | by: "mock_transaction_source".to_owned(), | ||
| 117 | source: "mock_transaction_source".to_owned(), | 186 | source: "mock_transaction_source".to_owned(), |
| 118 | target: "mock_transaction_target".to_owned(), | 187 | target: "mock_transaction_target".to_owned(), |
| 119 | amount: 25, | 188 | amount: 25, |
| @@ -125,9 +194,9 @@ mod tests { | |||
| 125 | fn mocked_block() -> Block { | 194 | fn mocked_block() -> Block { |
| 126 | Block { | 195 | Block { |
| 127 | transaction_list: vec!["hash_value".to_owned()], | 196 | transaction_list: vec!["hash_value".to_owned()], |
| 128 | nonce: 560108, | 197 | nonce: 3831993, |
| 129 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30), | 198 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30), |
| 130 | hash: "c7d053f3e5b056ba948db3f5c0d30408fb0c29a328a0c3c1cf435fb68d700000".to_owned(), | 199 | hash: "2b648ffab5d9af1d5d5fc052fc9e51b882fc4fb0c998608c99232f9282000000".to_owned(), |
| 131 | } | 200 | } |
| 132 | } | 201 | } |
| 133 | 202 | ||
| @@ -158,7 +227,7 @@ mod tests { | |||
| 158 | 227 | ||
| 159 | assert_eq!(res.status(), StatusCode::OK); | 228 | assert_eq!(res.status(), StatusCode::OK); |
| 160 | 229 | ||
| 161 | let expected_json_body = r#"[{"source":"source_account","target":"target_account","amount":20,"timestamp":"2021-04-09T01:30:30"}]"#; | 230 | let expected_json_body = r#"[{"by":"source_account","source":"source_account","target":"target_account","amount":20,"timestamp":"2021-04-09T01:30:30"}]"#; |
| 162 | 231 | ||
| 163 | assert_eq!(res.body(), expected_json_body); | 232 | assert_eq!(res.body(), expected_json_body); |
| 164 | } | 233 | } |
| @@ -208,10 +277,30 @@ mod tests { | |||
| 208 | async fn post_json_201() { | 277 | async fn post_json_201() { |
| 209 | let db = mocked_db(); | 278 | let db = mocked_db(); |
| 210 | let filter = consensus_routes(db.clone()); | 279 | let filter = consensus_routes(db.clone()); |
| 280 | let res = warp::test::request() | ||
| 281 | .method("POST") | ||
| 282 | .json(&mocked_transaction()) | ||
| 283 | .path("/transaction") | ||
| 284 | .reply(&filter) | ||
| 285 | .await; | ||
| 286 | |||
| 287 | assert_eq!(res.status(), StatusCode::CREATED); | ||
| 288 | assert_eq!(db.pending_transactions.read().len(), 2); | ||
| 289 | } | ||
| 290 | |||
| 291 | /// Test a POST request to /transaction, a resource that exists | ||
| 292 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
| 293 | /// Should accept the json request, create | ||
| 294 | /// the transaction and add it to pending transactions in the db | ||
| 295 | #[tokio::test] | ||
| 296 | async fn post_auth_json_201() { | ||
| 297 | let db = mocked_db(); | ||
| 298 | let filter = consensus_routes(db.clone()); | ||
| 211 | 299 | ||
| 212 | let res = warp::test::request() | 300 | let res = warp::test::request() |
| 213 | .method("POST") | 301 | .method("POST") |
| 214 | .json(&mocked_transaction()) | 302 | .json(&mocked_transaction()) |
| 303 | .header("Authorization", format!("Bearer {}", &mocked_jwt())) | ||
| 215 | .path("/transaction") | 304 | .path("/transaction") |
| 216 | .reply(&filter) | 305 | .reply(&filter) |
| 217 | .await; | 306 | .await; |
| @@ -220,6 +309,27 @@ mod tests { | |||
| 220 | assert_eq!(db.pending_transactions.read().len(), 2); | 309 | assert_eq!(db.pending_transactions.read().len(), 2); |
| 221 | } | 310 | } |
| 222 | 311 | ||
| 312 | /// Test a POST request to /transaction, a resource that exists | ||
| 313 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
| 314 | /// Should accept the json request, create | ||
| 315 | /// the transaction and add it to pending transactions in the db | ||
| 316 | #[tokio::test] | ||
| 317 | async fn post_auth_json_400() { | ||
| 318 | let db = mocked_db(); | ||
| 319 | let filter = consensus_routes(db.clone()); | ||
| 320 | |||
| 321 | let res = warp::test::request() | ||
| 322 | .method("POST") | ||
| 323 | .json(&mocked_transaction()) | ||
| 324 | .header("Authorization", "Bearer aaaaaaaasdlkjaldkasljdaskjlaaaaaaaaaaaaaa") | ||
| 325 | .path("/transaction") | ||
| 326 | .reply(&filter) | ||
| 327 | .await; | ||
| 328 | |||
| 329 | assert_eq!(res.status(), StatusCode::BAD_REQUEST); | ||
| 330 | assert_eq!(db.pending_transactions.read().len(), 1); | ||
| 331 | } | ||
| 332 | |||
| 223 | /// Test a POST request to /block, a resource that exists | 333 | /// Test a POST request to /block, a resource that exists |
| 224 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | 334 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 |
| 225 | /// Should accept the json request, create | 335 | /// Should accept the json request, create |
| @@ -239,7 +349,7 @@ mod tests { | |||
| 239 | assert_eq!(res.status(), StatusCode::CREATED); | 349 | assert_eq!(res.status(), StatusCode::CREATED); |
| 240 | assert_eq!( | 350 | assert_eq!( |
| 241 | *db.blockchain.read().hash, | 351 | *db.blockchain.read().hash, |
| 242 | "c7d053f3e5b056ba948db3f5c0d30408fb0c29a328a0c3c1cf435fb68d700000".to_owned() | 352 | "2b648ffab5d9af1d5d5fc052fc9e51b882fc4fb0c998608c99232f9282000000".to_owned() |
| 243 | ); | 353 | ); |
| 244 | } | 354 | } |
| 245 | 355 | ||
| @@ -279,7 +389,7 @@ mod tests { | |||
| 279 | 389 | ||
| 280 | println!("{:?}", res.body()); | 390 | println!("{:?}", res.body()); |
| 281 | assert_eq!(res.status(), StatusCode::CREATED); | 391 | assert_eq!(res.status(), StatusCode::CREATED); |
| 282 | assert_eq!(db.users.read().len(), 1); | 392 | assert_eq!(db.users.read().len(), 2); |
| 283 | } | 393 | } |
| 284 | 394 | ||
| 285 | /// Test a POST request to /transaction, an endpoint that exists | 395 | /// Test a POST request to /transaction, an endpoint that exists |
| @@ -299,7 +409,7 @@ mod tests { | |||
| 299 | 409 | ||
| 300 | println!("{:?}", res.body()); | 410 | println!("{:?}", res.body()); |
| 301 | assert_eq!(res.status(), StatusCode::BAD_REQUEST); | 411 | assert_eq!(res.status(), StatusCode::BAD_REQUEST); |
| 302 | assert_eq!(db.users.read().len(), 0); | 412 | assert_eq!(db.users.read().len(), 1); |
| 303 | } | 413 | } |
| 304 | 414 | ||
| 305 | /// Test a POST request to /transaction, a resource that exists with a longer than expected | 415 | /// Test a POST request to /transaction, a resource that exists with a longer than expected |
diff --git a/src/schema.rs b/src/schema.rs index 98291d7..39921b8 100644 --- a/src/schema.rs +++ b/src/schema.rs | |||
| @@ -41,18 +41,20 @@ impl Db { | |||
| 41 | } | 41 | } |
| 42 | } | 42 | } |
| 43 | 43 | ||
| 44 | pub type PublicKeySignature = String; | ||
| 45 | |||
| 44 | /// A transaction between `source` and `target` that moves `amount` | 46 | /// A transaction between `source` and `target` that moves `amount` |
| 45 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] | 47 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] |
| 46 | pub struct Transaction { | 48 | pub struct Transaction { |
| 47 | // TODO: new field by <11-04-21, yigit> // | 49 | pub by: PublicKeySignature, |
| 48 | pub source: String, | 50 | pub source: PublicKeySignature, |
| 49 | pub target: String, | 51 | pub target: PublicKeySignature, |
| 50 | pub amount: i32, | 52 | pub amount: i32, |
| 51 | pub timestamp: NaiveDateTime, | 53 | pub timestamp: NaiveDateTime, |
| 52 | } | 54 | } |
| 53 | 55 | ||
| 54 | /// A block that was proposed with `transaction_list` and `nonce` that made `hash` valid | 56 | /// A block that was proposed with `transaction_list` and `nonce` that made `hash` valid |
| 55 | /// https://serde.rs/container-attrs.html might be valueable to normalize the serialize/deserialize | 57 | /// https://serde.rs/container-attrs.html might be valuable to normalize the serialize/deserialize |
| 56 | /// conventions as these will be hashed | 58 | /// conventions as these will be hashed |
| 57 | #[derive(Serialize, Deserialize, Debug)] | 59 | #[derive(Serialize, Deserialize, Debug)] |
| 58 | pub struct Block { | 60 | pub struct Block { |
| @@ -61,7 +63,7 @@ pub struct Block { | |||
| 61 | // we can leave this as is and whenever we have a new block we _could_ just log it to file | 63 | // we can leave this as is and whenever we have a new block we _could_ just log it to file |
| 62 | // somewhere | 64 | // somewhere |
| 63 | // I want to keep this as a String vector because it makes things easier elsewhere | 65 | // I want to keep this as a String vector because it makes things easier elsewhere |
| 64 | pub transaction_list: Vec<String>, // hashes of the transactions (or just "source" for now) | 66 | pub transaction_list: Vec<PublicKeySignature>, // hashes of the transactions (or just "source" for now) |
| 65 | pub nonce: u32, | 67 | pub nonce: u32, |
| 66 | pub timestamp: NaiveDateTime, | 68 | pub timestamp: NaiveDateTime, |
| 67 | pub hash: String, // future proof'd baby | 69 | pub hash: String, // future proof'd baby |
| @@ -70,7 +72,7 @@ pub struct Block { | |||
| 70 | /// For prototyping and letting serde handle everything json | 72 | /// For prototyping and letting serde handle everything json |
| 71 | #[derive(Serialize, Deserialize, Debug)] | 73 | #[derive(Serialize, Deserialize, Debug)] |
| 72 | pub struct NakedBlock { | 74 | pub struct NakedBlock { |
| 73 | pub transaction_list: Vec<String>, | 75 | pub transaction_list: Vec<PublicKeySignature>, |
| 74 | pub nonce: u32, | 76 | pub nonce: u32, |
| 75 | pub timestamp: NaiveDateTime, | 77 | pub timestamp: NaiveDateTime, |
| 76 | } | 78 | } |
