aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock47
-rw-r--r--Cargo.toml1
-rw-r--r--TODO.md6
-rw-r--r--examples/serdeser.rs5
-rw-r--r--src/handlers.rs386
-rw-r--r--src/schema.rs58
-rw-r--r--templates/css.html21
-rw-r--r--templates/header.html11
-rw-r--r--templates/list.html4
-rw-r--r--tests/route_tests.rs78
-rw-r--r--tests/schema_tests.rs608
11 files changed, 701 insertions, 524 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 82a3df2..22990ad 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -41,6 +41,15 @@ dependencies = [
41] 41]
42 42
43[[package]] 43[[package]]
44name = "ansi_term"
45version = "0.12.1"
46source = "registry+https://github.com/rust-lang/crates.io-index"
47checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
48dependencies = [
49 "winapi 0.3.9",
50]
51
52[[package]]
44name = "anyhow" 53name = "anyhow"
45version = "1.0.40" 54version = "1.0.40"
46source = "registry+https://github.com/rust-lang/crates.io-index" 55source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -283,6 +292,16 @@ dependencies = [
283] 292]
284 293
285[[package]] 294[[package]]
295name = "ctor"
296version = "0.1.20"
297source = "registry+https://github.com/rust-lang/crates.io-index"
298checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
299dependencies = [
300 "quote",
301 "syn",
302]
303
304[[package]]
286name = "derivative" 305name = "derivative"
287version = "2.2.0" 306version = "2.2.0"
288source = "registry+https://github.com/rust-lang/crates.io-index" 307source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -294,6 +313,12 @@ dependencies = [
294] 313]
295 314
296[[package]] 315[[package]]
316name = "diff"
317version = "0.1.12"
318source = "registry+https://github.com/rust-lang/crates.io-index"
319checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
320
321[[package]]
297name = "digest" 322name = "digest"
298version = "0.9.0" 323version = "0.9.0"
299source = "registry+https://github.com/rust-lang/crates.io-index" 324source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -457,6 +482,7 @@ dependencies = [
457 "log4rs", 482 "log4rs",
458 "md-5", 483 "md-5",
459 "parking_lot 0.10.2", 484 "parking_lot 0.10.2",
485 "pretty_assertions",
460 "rsa", 486 "rsa",
461 "serde", 487 "serde",
462 "serde_json", 488 "serde_json",
@@ -985,6 +1011,15 @@ dependencies = [
985] 1011]
986 1012
987[[package]] 1013[[package]]
1014name = "output_vt100"
1015version = "0.1.2"
1016source = "registry+https://github.com/rust-lang/crates.io-index"
1017checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
1018dependencies = [
1019 "winapi 0.3.9",
1020]
1021
1022[[package]]
988name = "parking_lot" 1023name = "parking_lot"
989version = "0.10.2" 1024version = "0.10.2"
990source = "registry+https://github.com/rust-lang/crates.io-index" 1025source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1115,6 +1150,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1115checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 1150checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
1116 1151
1117[[package]] 1152[[package]]
1153name = "pretty_assertions"
1154version = "0.7.2"
1155source = "registry+https://github.com/rust-lang/crates.io-index"
1156checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b"
1157dependencies = [
1158 "ansi_term",
1159 "ctor",
1160 "diff",
1161 "output_vt100",
1162]
1163
1164[[package]]
1118name = "proc-macro2" 1165name = "proc-macro2"
1119version = "1.0.26" 1166version = "1.0.26"
1120source = "registry+https://github.com/rust-lang/crates.io-index" 1167source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 45fda30..77efd25 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,3 +30,4 @@ askama = "0.10.5"
30 30
31[dev-dependencies] 31[dev-dependencies]
32serde_test = "1.0.117" 32serde_test = "1.0.117"
33pretty_assertions = "0.7.2"
diff --git a/TODO.md b/TODO.md
index 2b3e21a..590ef50 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,11 +1,7 @@
1# TODO 1# TODO
2 2
3## Issues 3## Issues
4- [ ] Students can authenticate twice 4- [X] Students can authenticate twice
5
6## Good ideas
7- [ ] Add the hash of previous block to the next block
8
9 5
10## Tests 6## Tests
11- [ ] Route Tests 7- [ ] Route Tests
diff --git a/examples/serdeser.rs b/examples/serdeser.rs
index 60d90b9..4fdfdc2 100644
--- a/examples/serdeser.rs
+++ b/examples/serdeser.rs
@@ -4,9 +4,8 @@ use serde_json;
4pub fn main() { 4pub fn main() {
5 5
6 let tx = Transaction { 6 let tx = Transaction {
7 by: "fingerprint_of_some_guy".to_owned(), 7 source: "fingerprint_of_some_guy".to_owned(),
8 source: "31415926535897932384626433832795028841971693993751058209749445923".to_owned(), 8 target: "31415926535897932384626433832795028841971693993751058209749445923".to_owned(),
9 target: "fingerprint_of_some_guy".to_owned(),
10 amount: 2, 9 amount: 2,
11 timestamp: chrono::NaiveDate::from_ymd(2021, 04, 13).and_hms(20, 55, 30), 10 timestamp: chrono::NaiveDate::from_ymd(2021, 04, 13).and_hms(20, 55, 30),
12 }; 11 };
diff --git a/src/handlers.rs b/src/handlers.rs
index 7e022c3..e37cb40 100644
--- a/src/handlers.rs
+++ b/src/handlers.rs
@@ -4,6 +4,7 @@ use askama::Template;
4use blake2::{Blake2s, Digest}; 4use blake2::{Blake2s, Digest};
5use block_modes::block_padding::Pkcs7; 5use block_modes::block_padding::Pkcs7;
6use block_modes::{BlockMode, Cbc}; 6use block_modes::{BlockMode, Cbc};
7use chrono::Utc;
7use jsonwebtoken::errors::ErrorKind; 8use jsonwebtoken::errors::ErrorKind;
8use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; 9use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation};
9use log::{debug, warn}; 10use log::{debug, warn};
@@ -15,12 +16,21 @@ use sha2::Sha256;
15use std::collections::{HashMap, HashSet}; 16use std::collections::{HashMap, HashSet};
16use std::convert::Infallible; 17use std::convert::Infallible;
17use std::fs; 18use std::fs;
19use std::hash::Hash;
18use warp::{http::StatusCode, reply}; 20use warp::{http::StatusCode, reply};
19 21
20use crate::PRIVATE_KEY; 22use crate::PRIVATE_KEY;
21const BLOCK_TRANSACTION_COUNT: u8 = 1; 23
24// Valid blocks should have this many transactions
25const BLOCK_TRANSACTION_COUNT: u8 = 5;
26// Inital registration bonus
27const REGISTER_BONUS: u16 = 40;
28// Coinbase reward
22const BLOCK_REWARD: u16 = 3; 29const BLOCK_REWARD: u16 = 3;
30// Transaction amount limit
23const TX_UPPER_LIMIT: u16 = 2; 31const TX_UPPER_LIMIT: u16 = 2;
32// Transaction traffic reward
33const TX_TRAFFIC_REWARD: u16 = 1;
24 34
25// Encryption primitive 35// Encryption primitive
26type Aes128Cbc = Cbc<Aes128, Pkcs7>; 36type Aes128Cbc = Cbc<Aes128, Pkcs7>;
@@ -106,19 +116,20 @@ pub async fn authenticate_user(
106 116
107 let padding = PaddingScheme::new_oaep::<sha2::Sha256>(); 117 let padding = PaddingScheme::new_oaep::<sha2::Sha256>();
108 118
119 // Peel away the base64 layer from "key" field
109 let key_ciphertext = match base64::decode(&request.key) { 120 let key_ciphertext = match base64::decode(&request.key) {
110 Ok(c) => c, 121 Ok(c) => c,
111 Err(err) => { 122 Err(err) => {
112 debug!( 123 debug!(
113 "The ciphertext of the key was not base64 encoded {}, {}", 124 "\"key\" field of initial auth request was not base64 encoded: {}, {}",
114 &request.key, err 125 &request.key, err
115 ); 126 );
116 127
117 let res_json = warp::reply::json(&GradeCoinResponse { 128 let res_json = warp::reply::json(&GradeCoinResponse {
118 res: ResponseType::Error, 129 res: ResponseType::Error,
119 message: format!( 130 message: format!(
120 "The ciphertext of the key was not base64 encoded: {}", 131 "\"key\" field of initial auth request was not base64 encoded: {}, {}",
121 request.key 132 &request.key, err
122 ), 133 ),
123 }); 134 });
124 135
@@ -126,19 +137,39 @@ pub async fn authenticate_user(
126 } 137 }
127 }; 138 };
128 139
140 // Decrypt the "key" field using Gradecoin's private key
129 let temp_key = match gradecoin_private_key.decrypt(padding, &key_ciphertext) { 141 let temp_key = match gradecoin_private_key.decrypt(padding, &key_ciphertext) {
130 Ok(k) => k, 142 Ok(k) => k,
131 Err(err) => { 143 Err(err) => {
132 debug!( 144 debug!(
133 "Failed to decrypt ciphertext {:?}, {}", 145 "Failed to decrypt ciphertext of the key with Gradecoin's public key: {}. Key was {:?}",
134 &key_ciphertext, err 146 err, &key_ciphertext
147 );
148
149 let res_json = warp::reply::json(&GradeCoinResponse {
150 res: ResponseType::Error,
151 message: "Failed to decrypt the 'key_ciphertext' field of the auth request"
152 .to_owned(),
153 });
154
155 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
156 }
157 };
158
159 // Peel away the base64 from the iv field as well
160 let byte_iv = match base64::decode(&request.iv) {
161 Ok(iv) => iv,
162 Err(err) => {
163 debug!(
164 "\"iv\" field of initial auth request was not base64 encoded: {}, {}",
165 &request.iv, err
135 ); 166 );
136 167
137 let res_json = warp::reply::json(&GradeCoinResponse { 168 let res_json = warp::reply::json(&GradeCoinResponse {
138 res: ResponseType::Error, 169 res: ResponseType::Error,
139 message: format!( 170 message: format!(
140 "Failed to decrypt the ciphertext of the temporary key: {:?}", 171 "\"iv\" field of initial auth request was not base64 encoded: {}, {}",
141 &key_ciphertext 172 &request.iv, err
142 ), 173 ),
143 }); 174 });
144 175
@@ -146,8 +177,7 @@ pub async fn authenticate_user(
146 } 177 }
147 }; 178 };
148 179
149 let byte_iv = base64::decode(&request.iv).unwrap(); 180 // we have key and iv, time to decrypt the "c" field, first prepare the decryptor
150
151 let cipher = match Aes128Cbc::new_var(&temp_key, &byte_iv) { 181 let cipher = match Aes128Cbc::new_var(&temp_key, &byte_iv) {
152 Ok(c) => c, 182 Ok(c) => c,
153 Err(err) => { 183 Err(err) => {
@@ -165,42 +195,49 @@ pub async fn authenticate_user(
165 } 195 }
166 }; 196 };
167 197
198 // peel away the base64 from the auth packet
168 let auth_packet = match base64::decode(&request.c) { 199 let auth_packet = match base64::decode(&request.c) {
169 Ok(a) => a, 200 Ok(a) => a,
170
171 Err(err) => { 201 Err(err) => {
172 debug!( 202 debug!(
173 "The auth_packet (c field) did not base64 decode {} {}", 203 "\"c\" field of initial auth request was not base64 encoded: {}, {}",
174 &request.c, err 204 &request.c, err
175 ); 205 );
176 206
177 let res_json = warp::reply::json(&GradeCoinResponse { 207 let res_json = warp::reply::json(&GradeCoinResponse {
178 res: ResponseType::Error, 208 res: ResponseType::Error,
179 message: "The c field was not correctly base64 encoded".to_owned(), 209 message: format!(
210 "\"c\" field of initial auth request was not base64 encoded: {}, {}",
211 &request.c, err
212 ),
180 }); 213 });
181 214
182 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 215 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
183 } 216 }
184 }; 217 };
185 218
219 // c field was properly base64 encoded, now available in auth_packet
220 // decryptor was setup properly, with the correct lenght key
186 let mut buf = auth_packet.to_vec(); 221 let mut buf = auth_packet.to_vec();
187 let auth_plaintext = match cipher.decrypt(&mut buf) { 222 let auth_plaintext = match cipher.decrypt(&mut buf) {
188 Ok(p) => p, 223 Ok(p) => p,
189 Err(err) => { 224 Err(err) => {
190 debug!( 225 println!(
191 "Base64 decoded auth request did not decrypt correctly {:?} {}", 226 "auth request (c) did not decrypt correctly {:?} {}",
192 &auth_packet, err 227 &buf, err
193 ); 228 );
194 229
195 let res_json = warp::reply::json(&GradeCoinResponse { 230 let res_json = warp::reply::json(&GradeCoinResponse {
196 res: ResponseType::Error, 231 res: ResponseType::Error,
197 message: "The base64 decoded auth request did not decrypt correctly".to_owned(), 232 message: "Failed to decrypt the 'c' field of the auth request, 'iv' and 'k_temp' were valid so far though"
233 .to_owned(),
198 }); 234 });
199 235
200 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 236 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
201 } 237 }
202 }; 238 };
203 239
240 // we have a decrypted c field, create a string from the bytes mess
204 let utf8_auth_plaintext = match String::from_utf8(auth_plaintext.to_vec()) { 241 let utf8_auth_plaintext = match String::from_utf8(auth_plaintext.to_vec()) {
205 Ok(text) => text, 242 Ok(text) => text,
206 Err(err) => { 243 Err(err) => {
@@ -211,13 +248,15 @@ pub async fn authenticate_user(
211 248
212 let res_json = warp::reply::json(&GradeCoinResponse { 249 let res_json = warp::reply::json(&GradeCoinResponse {
213 res: ResponseType::Error, 250 res: ResponseType::Error,
214 message: "Auth plaintext could not get converted to UTF-8".to_owned(), 251 message: "P_AR couldn't get converted to UTF-8, please check your encoding"
252 .to_owned(),
215 }); 253 });
216 254
217 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 255 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
218 } 256 }
219 }; 257 };
220 258
259 // finally create an AuthRequest object from the plaintext
221 let request: AuthRequest = match serde_json::from_str(&utf8_auth_plaintext) { 260 let request: AuthRequest = match serde_json::from_str(&utf8_auth_plaintext) {
222 Ok(req) => req, 261 Ok(req) => req,
223 Err(err) => { 262 Err(err) => {
@@ -228,24 +267,32 @@ pub async fn authenticate_user(
228 267
229 let res_json = warp::reply::json(&GradeCoinResponse { 268 let res_json = warp::reply::json(&GradeCoinResponse {
230 res: ResponseType::Error, 269 res: ResponseType::Error,
231 message: "The auth request JSON did not serialize correctly".to_owned(), 270 message: "The P_AR JSON did not serialize correctly, did it include all 3 fields 'student_id', 'passwd' and 'public_key'?".to_owned(),
232 }); 271 });
233 272
234 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 273 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
235 } 274 }
236 }; 275 };
237 276
238 let privileged_student_id = match MetuId::new(request.student_id, request.passwd) { 277 // is the student in AuthRequest privileged?
239 Some(id) => id, 278 let privileged_student_id =
240 None => { 279 match MetuId::new(request.student_id.clone(), request.passwd.clone()) {
241 let res_json = warp::reply::json(&GradeCoinResponse { 280 Some(id) => id,
281 None => {
282 debug!(
283 "Someone tried to auth with invalid credentials: {} {}",
284 &request.student_id, &request.passwd
285 );
286 let res_json = warp::reply::json(&GradeCoinResponse {
242 res: ResponseType::Error, 287 res: ResponseType::Error,
243 message: "This user cannot have a gradecoin account".to_owned(), 288 message:
289 "The credentials given ('student_id', 'passwd') cannot hold a Gradecoin account"
290 .to_owned(),
244 }); 291 });
245 292
246 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 293 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
247 } 294 }
248 }; 295 };
249 296
250 // Students should be able to authenticate once 297 // Students should be able to authenticate once
251 { 298 {
@@ -264,12 +311,11 @@ pub async fn authenticate_user(
264 } 311 }
265 } 312 }
266 313
267 // We're using this as the validator 314 // We're using this as the validator instead of anything reasonable
268 // I hate myself
269 if DecodingKey::from_rsa_pem(request.public_key.as_bytes()).is_err() { 315 if DecodingKey::from_rsa_pem(request.public_key.as_bytes()).is_err() {
270 let res_json = warp::reply::json(&GradeCoinResponse { 316 let res_json = warp::reply::json(&GradeCoinResponse {
271 res: ResponseType::Error, 317 res: ResponseType::Error,
272 message: "The supplied RSA public key is not in valid PEM format".to_owned(), 318 message: "The RSA 'public_key' in 'P_AR' is not in valid PEM format".to_owned(),
273 }); 319 });
274 320
275 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 321 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
@@ -280,25 +326,27 @@ pub async fn authenticate_user(
280 let new_user = User { 326 let new_user = User {
281 user_id: privileged_student_id, 327 user_id: privileged_student_id,
282 public_key: request.public_key, 328 public_key: request.public_key,
283 balance: 0, 329 balance: REGISTER_BONUS,
330 is_bot: false,
284 }; 331 };
285 332
286 debug!("New user authenticated themselves! {:?}", &new_user); 333 debug!("NEW USER: {:?}", &new_user);
287 334
335 // save the user to disk
288 let user_at_rest_json = serde_json::to_string(&UserAtRest { 336 let user_at_rest_json = serde_json::to_string(&UserAtRest {
337 fingerprint: fingerprint.clone(),
289 user: User { 338 user: User {
290 user_id: new_user.user_id.clone(), 339 user_id: new_user.user_id.clone(),
291 public_key: new_user.public_key.clone(), 340 public_key: new_user.public_key.clone(),
292 balance: 0, 341 balance: new_user.balance,
342 is_bot: false,
293 }, 343 },
294 fingerprint: fingerprint.clone(),
295 }) 344 })
296 .unwrap(); 345 .unwrap();
297 346
298 fs::write(format!("users/{}.guy", new_user.user_id), user_at_rest_json).unwrap(); 347 fs::write(format!("users/{}.guy", new_user.user_id), user_at_rest_json).unwrap();
299 348
300 let mut userlist = db.users.write(); 349 let mut userlist = db.users.write();
301
302 userlist.insert(fingerprint.clone(), new_user); 350 userlist.insert(fingerprint.clone(), new_user);
303 351
304 let res_json = warp::reply::json(&GradeCoinResponse { 352 let res_json = warp::reply::json(&GradeCoinResponse {
@@ -314,9 +362,7 @@ pub async fn authenticate_user(
314 362
315/// GET /transaction 363/// GET /transaction
316/// Returns JSON array of transactions 364/// Returns JSON array of transactions
317/// Cannot fail
318pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> { 365pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> {
319 debug!("GET /transaction, list_transactions() is handling");
320 let mut result = HashMap::new(); 366 let mut result = HashMap::new();
321 367
322 let transactions = db.pending_transactions.read(); 368 let transactions = db.pending_transactions.read();
@@ -342,12 +388,9 @@ pub async fn propose_block(
342 token: String, 388 token: String,
343 db: Db, 389 db: Db,
344) -> Result<impl warp::Reply, warp::Rejection> { 390) -> Result<impl warp::Reply, warp::Rejection> {
345 debug!("POST /block, propose_block() is handling");
346
347 let users_store = db.users.upgradable_read();
348
349 warn!("New block proposal: {:?}", &new_block); 391 warn!("New block proposal: {:?}", &new_block);
350 392
393 // Check if there are enough transactions in the block
351 if new_block.transaction_list.len() < BLOCK_TRANSACTION_COUNT as usize { 394 if new_block.transaction_list.len() < BLOCK_TRANSACTION_COUNT as usize {
352 debug!( 395 debug!(
353 "{} transactions offered, needed {}", 396 "{} transactions offered, needed {}",
@@ -366,7 +409,29 @@ pub async fn propose_block(
366 } 409 }
367 410
368 // proposer (first transaction fingerprint) checks 411 // proposer (first transaction fingerprint) checks
369 let internal_user = match users_store.get(&new_block.transaction_list[0]) { 412 let pending_transactions = db.pending_transactions.upgradable_read();
413
414 let internal_user_fingerprint = match pending_transactions.get(&new_block.transaction_list[0]) {
415 Some(coinbase) => &coinbase.source,
416 None => {
417 debug!(
418 "Block proposer with public key signature {:?} is not found in the database",
419 new_block.transaction_list[0]
420 );
421
422 let res_json = warp::reply::json(&GradeCoinResponse {
423 res: ResponseType::Error,
424 message: "Proposer of the first transaction is not found in the system".to_owned(),
425 });
426
427 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
428 }
429 };
430
431 let users_store = db.users.upgradable_read();
432
433 // this probably cannot fail, if the transaction is valid then it must've been checked already
434 let internal_user = match users_store.get(internal_user_fingerprint) {
370 Some(existing_user) => existing_user, 435 Some(existing_user) => existing_user,
371 None => { 436 None => {
372 debug!( 437 debug!(
@@ -390,7 +455,7 @@ pub async fn propose_block(
390 let token_payload = match authorize_proposer(token, &proposer_public_key) { 455 let token_payload = match authorize_proposer(token, &proposer_public_key) {
391 Ok(data) => data, 456 Ok(data) => data,
392 Err(below) => { 457 Err(below) => {
393 debug!("Something went wrong below {:?}", below); 458 debug!("Something went wrong with the JWT {:?}", below);
394 459
395 let res_json = warp::reply::json(&GradeCoinResponse { 460 let res_json = warp::reply::json(&GradeCoinResponse {
396 res: ResponseType::Error, 461 res: ResponseType::Error,
@@ -409,49 +474,36 @@ pub async fn propose_block(
409 ); 474 );
410 let res_json = warp::reply::json(&GradeCoinResponse { 475 let res_json = warp::reply::json(&GradeCoinResponse {
411 res: ResponseType::Error, 476 res: ResponseType::Error,
412 message: "The hash of the block did not match the hash given in JWT".to_owned(), 477 message: "The hash of the block did not match the hash given in JWT tha field"
478 .to_owned(),
413 }); 479 });
414 480
415 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 481 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
416 } 482 }
417 483
418 // scope the HashSet 484 if !has_unique_elements(&new_block.transaction_list) {
419 { 485 debug!("Block contains duplicate transactions!");
420 let mut proposed_transactions = HashSet::new(); 486 let res_json = warp::reply::json(&GradeCoinResponse {
421 for tx in new_block.transaction_list.iter() { 487 res: ResponseType::Error,
422 proposed_transactions.insert(tx); 488 message: "Block cannot contain duplicate transactions".to_owned(),
423 } 489 });
424 490
425 if proposed_transactions.len() < BLOCK_TRANSACTION_COUNT as usize { 491 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
492 }
493
494 // Are transactions in the block valid?
495 for transaction_hash in new_block.transaction_list.iter() {
496 if !pending_transactions.contains_key(transaction_hash) {
426 let res_json = warp::reply::json(&GradeCoinResponse { 497 let res_json = warp::reply::json(&GradeCoinResponse {
427 res: ResponseType::Error, 498 res: ResponseType::Error,
428 message: format!( 499 message: "Block contains an unknown transaction".to_owned(),
429 "Block cannot contain less than {} unique transaction(s).",
430 BLOCK_TRANSACTION_COUNT
431 ),
432 }); 500 });
433 501
434 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 502 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
435 } 503 }
436 } 504 }
437 505
438 // Scope the RwLocks, there are hashing stuff below 506 // hash the block ourselves to double check
439 {
440 let pending_transactions = db.pending_transactions.read();
441
442 // Are transactions in the block valid?
443 for transaction_hash in new_block.transaction_list.iter() {
444 if !pending_transactions.contains_key(transaction_hash) {
445 let res_json = warp::reply::json(&GradeCoinResponse {
446 res: ResponseType::Error,
447 message: "Block contains unknown transaction".to_owned(),
448 });
449
450 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
451 }
452 }
453 }
454
455 let naked_block = NakedBlock { 507 let naked_block = NakedBlock {
456 transaction_list: new_block.transaction_list.clone(), 508 transaction_list: new_block.transaction_list.clone(),
457 nonce: new_block.nonce, 509 nonce: new_block.nonce,
@@ -490,15 +542,14 @@ pub async fn propose_block(
490 // All clear, block accepted! 542 // All clear, block accepted!
491 warn!("ACCEPTED BLOCK {:?}", new_block); 543 warn!("ACCEPTED BLOCK {:?}", new_block);
492 544
493 // Scope the pending_transactions 545 // Scope the read guards
494 { 546 {
495 let pending_transactions = db.pending_transactions.read(); 547 let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions);
496 let mut users_store = RwLockUpgradableReadGuard::upgrade(users_store); 548 let mut users_store = RwLockUpgradableReadGuard::upgrade(users_store);
497 549
498 let coinbase_fingerprint = new_block.transaction_list.get(0).unwrap(); 550 // Play out the transactions
499
500 for fingerprint in new_block.transaction_list.iter() { 551 for fingerprint in new_block.transaction_list.iter() {
501 if let Some(transaction) = pending_transactions.get(fingerprint) { 552 if let Some(transaction) = pending_transactions.remove(fingerprint) {
502 let source = &transaction.source; 553 let source = &transaction.source;
503 let target = &transaction.target; 554 let target = &transaction.target;
504 555
@@ -507,21 +558,34 @@ pub async fn propose_block(
507 } 558 }
508 559
509 if let Some(to) = users_store.get_mut(target) { 560 if let Some(to) = users_store.get_mut(target) {
510 to.balance += transaction.amount; 561 to.balance += transaction.amount + TX_TRAFFIC_REWARD;
562 }
563
564 // if the receiver is a bot, they will reciprocate
565 if users_store.get(target).unwrap().is_bot {
566 let transaction_id =
567 calculate_transaction_id(&transaction.target, &transaction.source);
568 pending_transactions.insert(
569 transaction_id,
570 Transaction {
571 source: target.to_owned(),
572 target: source.to_owned(),
573 amount: transaction.amount,
574 timestamp: Utc::now().naive_local(),
575 },
576 );
511 } 577 }
512 } 578 }
513 } 579 }
514 580
581 // Reward the block proposer
582 let coinbase_fingerprint = new_block.transaction_list.get(0).unwrap();
583
515 if let Some(coinbase_user) = users_store.get_mut(coinbase_fingerprint) { 584 if let Some(coinbase_user) = users_store.get_mut(coinbase_fingerprint) {
516 coinbase_user.balance += BLOCK_REWARD; 585 coinbase_user.balance += BLOCK_REWARD;
517 } 586 }
518 } 587 }
519 588
520 {
521 let mut pending_transactions = db.pending_transactions.write();
522 pending_transactions.clear();
523 }
524
525 let block_json = serde_json::to_string(&new_block).unwrap(); 589 let block_json = serde_json::to_string(&new_block).unwrap();
526 590
527 fs::write( 591 fs::write(
@@ -538,7 +602,7 @@ pub async fn propose_block(
538 Ok(warp::reply::with_status( 602 Ok(warp::reply::with_status(
539 warp::reply::json(&GradeCoinResponse { 603 warp::reply::json(&GradeCoinResponse {
540 res: ResponseType::Success, 604 res: ResponseType::Success,
541 message: "Block accepted coinbase reward awarded".to_owned(), 605 message: "Block accepted, coinbase reward awarded".to_owned(),
542 }), 606 }),
543 StatusCode::CREATED, 607 StatusCode::CREATED,
544 )) 608 ))
@@ -558,19 +622,17 @@ pub async fn propose_transaction(
558 token: String, 622 token: String,
559 db: Db, 623 db: Db,
560) -> Result<impl warp::Reply, warp::Rejection> { 624) -> Result<impl warp::Reply, warp::Rejection> {
561 debug!("POST /transaction, propose_transaction() is handling");
562
563 warn!("New transaction proposal: {:?}", &new_transaction); 625 warn!("New transaction proposal: {:?}", &new_transaction);
564 626
565 let users_store = db.users.read(); 627 let users_store = db.users.read();
566 628
567 // Is this transaction from an authorized source? 629 // Is this transaction from an authorized source?
568 let internal_user = match users_store.get(&new_transaction.by) { 630 let internal_user = match users_store.get(&new_transaction.source) {
569 Some(existing_user) => existing_user, 631 Some(existing_user) => existing_user,
570 None => { 632 None => {
571 debug!( 633 debug!(
572 "User with public key signature {:?} is not found in the database", 634 "User with public key signature {:?} is not found in the database",
573 new_transaction.by 635 new_transaction.source
574 ); 636 );
575 637
576 return Ok(warp::reply::with_status( 638 return Ok(warp::reply::with_status(
@@ -586,105 +648,112 @@ pub async fn propose_transaction(
586 648
587 // `internal_user` is an authenticated student, can propose 649 // `internal_user` is an authenticated student, can propose
588 650
589 // Does this user have a pending transaction? 651 // This public key was already written to the database, we can panic if it's not valid at
590 { 652 // *this* point
591 let transactions = db.pending_transactions.read(); 653 let proposer_public_key = &internal_user.public_key;
592 if transactions.contains_key(&*new_transaction.by.to_owned()) { 654
593 debug!("{:?} already has a pending transaction", new_transaction.by); 655 let token_payload = match authorize_proposer(token, &proposer_public_key) {
656 Ok(data) => data,
657 Err(below) => {
658 debug!("JWT Error: {:?}", below);
594 return Ok(warp::reply::with_status( 659 return Ok(warp::reply::with_status(
595 warp::reply::json(&GradeCoinResponse { 660 warp::reply::json(&GradeCoinResponse {
596 res: ResponseType::Error, 661 res: ResponseType::Error,
597 message: "This user already has another pending transaction".to_owned(), 662 message: below,
598 }), 663 }),
599 StatusCode::BAD_REQUEST, 664 StatusCode::BAD_REQUEST,
600 )); 665 ));
601 } 666 }
602 } 667 };
603 668
604 // Is transaction amount within bounds 669 // is the target of the transaction in the system?
605 if new_transaction.amount > TX_UPPER_LIMIT { 670 if !users_store.contains_key(&new_transaction.target) {
606 debug!( 671 debug!(
607 "Transaction amount cannot exceed {}, was {}", 672 "Target of the transaction is not in the system {}",
608 TX_UPPER_LIMIT, new_transaction.amount 673 new_transaction.target
609 ); 674 );
675
610 return Ok(warp::reply::with_status( 676 return Ok(warp::reply::with_status(
611 warp::reply::json(&GradeCoinResponse { 677 warp::reply::json(&GradeCoinResponse {
612 res: ResponseType::Error, 678 res: ResponseType::Error,
613 message: format!("Transaction amount cannot exceed {}", TX_UPPER_LIMIT), 679 message: format!(
680 "Target of the transaction {} is not found in the system",
681 new_transaction.target
682 ),
614 }), 683 }),
615 StatusCode::BAD_REQUEST, 684 StatusCode::BAD_REQUEST,
616 )); 685 ));
617 } 686 }
618 687
619 if new_transaction.by == new_transaction.source { 688 let transaction_id = calculate_transaction_id(&new_transaction.source, &new_transaction.target);
620 // check if user can afford the transaction
621 if internal_user.balance < new_transaction.amount {
622 debug!(
623 "User does not have enough balance ({}) for this TX {}",
624 internal_user.balance, new_transaction.amount
625 );
626 return Ok(warp::reply::with_status(
627 warp::reply::json(&GradeCoinResponse {
628 res: ResponseType::Error,
629 message:
630 "User does not have enough balance in their account for this transaction"
631 .to_owned(),
632 }),
633 StatusCode::BAD_REQUEST,
634 ));
635 }
636 } else if new_transaction.by == new_transaction.target {
637 // Only transactions FROM bank could appear here
638 689
639 if new_transaction.source 690 // OLD: Does this user have a pending transaction?
640 != "31415926535897932384626433832795028841971693993751058209749445923" 691 // NEW: Is this source:target pair unqiue?
641 { 692 {
693 let transactions = db.pending_transactions.read();
694 debug!(
695 "This is a transaction from {} to {}",
696 new_transaction.source, new_transaction.target,
697 );
698
699 if transactions.contains_key(&transaction_id) {
642 debug!( 700 debug!(
643 "Extortion attempt - between {} and {}", 701 "this source/target combination {} already has a pending transaction",
644 new_transaction.source, new_transaction.target 702 transaction_id
645 ); 703 );
704
646 return Ok(warp::reply::with_status( 705 return Ok(warp::reply::with_status(
647 warp::reply::json(&GradeCoinResponse { 706 warp::reply::json(&GradeCoinResponse {
648 res: ResponseType::Error, 707 res: ResponseType::Error,
649 message: "Transactions cannot extort Gradecoin from unsuspecting users" 708 message: "This user already has another pending transaction".to_owned(),
650 .to_owned(),
651 }), 709 }),
652 StatusCode::BAD_REQUEST, 710 StatusCode::BAD_REQUEST,
653 )); 711 ));
654 } 712 }
655 } else { 713 }
714
715 if new_transaction.source == new_transaction.target {
716 debug!("transaction source and target are the same",);
717
718 return Ok(warp::reply::with_status(
719 warp::reply::json(&GradeCoinResponse {
720 res: ResponseType::Error,
721 message: "transaction to yourself, you had to try didn't you? :)".to_owned(),
722 }),
723 StatusCode::BAD_REQUEST,
724 ));
725 }
726
727 // Is transaction amount within bounds
728 if new_transaction.amount > TX_UPPER_LIMIT {
656 debug!( 729 debug!(
657 "Attempt to transact between two unrelated parties - {} and {}", 730 "Transaction amount cannot exceed {}, was {}",
658 new_transaction.source, new_transaction.target 731 TX_UPPER_LIMIT, new_transaction.amount
659 ); 732 );
660 return Ok(warp::reply::with_status( 733 return Ok(warp::reply::with_status(
661 warp::reply::json(&GradeCoinResponse { 734 warp::reply::json(&GradeCoinResponse {
662 res: ResponseType::Error, 735 res: ResponseType::Error,
663 message: "Transactions cannot be proposed on behalf of someone else".to_owned(), 736 message: format!("Transaction amount cannot exceed {}", TX_UPPER_LIMIT),
664 }), 737 }),
665 StatusCode::BAD_REQUEST, 738 StatusCode::BAD_REQUEST,
666 )); 739 ));
667 } 740 }
668 741
669 // This public key was already written to the database, we can panic if it's not valid at 742 // check if user can afford the transaction
670 // *this* point 743 if internal_user.balance < new_transaction.amount {
671 let proposer_public_key = &internal_user.public_key; 744 debug!(
672 745 "User does not have enough balance ({}) for this TX {}",
673 let token_payload = match authorize_proposer(token, &proposer_public_key) { 746 internal_user.balance, new_transaction.amount
674 Ok(data) => data, 747 );
675 Err(below) => { 748 return Ok(warp::reply::with_status(
676 debug!("Something went wrong at JWT {:?}", below); 749 warp::reply::json(&GradeCoinResponse {
677 return Ok(warp::reply::with_status( 750 res: ResponseType::Error,
678 warp::reply::json(&GradeCoinResponse { 751 message: "User does not have enough balance in their account for this transaction"
679 res: ResponseType::Error, 752 .to_owned(),
680 message: below, 753 }),
681 }), 754 StatusCode::BAD_REQUEST,
682 StatusCode::BAD_REQUEST, 755 ));
683 )); 756 }
684 }
685 };
686
687 // authorized for transaction proposal
688 757
689 // this transaction was already checked for correctness at custom_filters, we can panic here if 758 // this transaction was already checked for correctness at custom_filters, we can panic here if
690 // it has been changed since 759 // it has been changed since
@@ -709,7 +778,7 @@ pub async fn propose_transaction(
709 778
710 let mut transactions = db.pending_transactions.write(); 779 let mut transactions = db.pending_transactions.write();
711 780
712 transactions.insert(new_transaction.by.to_owned(), new_transaction); 781 transactions.insert(transaction_id, new_transaction);
713 782
714 Ok(warp::reply::with_status( 783 Ok(warp::reply::with_status(
715 warp::reply::json(&GradeCoinResponse { 784 warp::reply::json(&GradeCoinResponse {
@@ -780,6 +849,12 @@ fn authorize_proposer(jwt_token: String, user_pem: &str) -> Result<TokenData<Cla
780 Ok(token_payload) 849 Ok(token_payload)
781} 850}
782 851
852fn calculate_transaction_id(source: &str, target: &str) -> String {
853 let long_fingerprint = format!("{}{}", source, target);
854 let id = format!("{:x}", Sha256::digest(long_fingerprint.as_bytes()));
855 id
856}
857
783#[derive(Template)] 858#[derive(Template)]
784#[template(path = "list.html")] 859#[template(path = "list.html")]
785struct UserTemplate<'a> { 860struct UserTemplate<'a> {
@@ -789,6 +864,7 @@ struct UserTemplate<'a> {
789struct DisplayUsers { 864struct DisplayUsers {
790 fingerprint: String, 865 fingerprint: String,
791 balance: u16, 866 balance: u16,
867 is_bot: bool,
792} 868}
793 869
794pub async fn user_list_handler(db: Db) -> Result<impl warp::Reply, warp::Rejection> { 870pub async fn user_list_handler(db: Db) -> Result<impl warp::Reply, warp::Rejection> {
@@ -799,6 +875,7 @@ pub async fn user_list_handler(db: Db) -> Result<impl warp::Reply, warp::Rejecti
799 sane_users.push(DisplayUsers { 875 sane_users.push(DisplayUsers {
800 fingerprint: fingerprint.to_owned(), 876 fingerprint: fingerprint.to_owned(),
801 balance: user.balance, 877 balance: user.balance,
878 is_bot: user.is_bot,
802 }); 879 });
803 } 880 }
804 881
@@ -806,3 +883,12 @@ pub async fn user_list_handler(db: Db) -> Result<impl warp::Reply, warp::Rejecti
806 let res = template.render().unwrap(); 883 let res = template.render().unwrap();
807 Ok(warp::reply::html(res)) 884 Ok(warp::reply::html(res))
808} 885}
886
887fn has_unique_elements<T>(iter: T) -> bool
888where
889 T: IntoIterator,
890 T::Item: Eq + Hash,
891{
892 let mut uniq = HashSet::new();
893 iter.into_iter().all(move |x| uniq.insert(x))
894}
diff --git a/src/schema.rs b/src/schema.rs
index 19b7fd8..537e0a5 100644
--- a/src/schema.rs
+++ b/src/schema.rs
@@ -20,9 +20,9 @@ use std::path::PathBuf;
20use std::string::String; 20use std::string::String;
21use std::sync::Arc; 21use std::sync::Arc;
22use std::vec::Vec; 22use std::vec::Vec;
23// use crate::validators;
24 23
25pub type Fingerprint = String; 24pub type Fingerprint = String;
25pub type Id = String;
26 26
27fn block_parser(path: String) -> u64 { 27fn block_parser(path: String) -> u64 {
28 let end_pos = path.find(".block").unwrap(); 28 let end_pos = path.find(".block").unwrap();
@@ -146,7 +146,7 @@ pub struct Claims {
146#[derive(Debug, Clone)] 146#[derive(Debug, Clone)]
147pub struct Db { 147pub struct Db {
148 pub blockchain: Arc<RwLock<Block>>, 148 pub blockchain: Arc<RwLock<Block>>,
149 pub pending_transactions: Arc<RwLock<HashMap<Fingerprint, Transaction>>>, 149 pub pending_transactions: Arc<RwLock<HashMap<Id, Transaction>>>,
150 pub users: Arc<RwLock<HashMap<Fingerprint, User>>>, 150 pub users: Arc<RwLock<HashMap<Fingerprint, User>>>,
151} 151}
152 152
@@ -154,14 +154,51 @@ impl Db {
154 pub fn new() -> Self { 154 pub fn new() -> Self {
155 let mut users: HashMap<Fingerprint, User> = HashMap::new(); 155 let mut users: HashMap<Fingerprint, User> = HashMap::new();
156 156
157 let bank_acc = MetuId::new("bank".to_owned(), "P7oxDm30g1jeIId".to_owned()).unwrap(); 157 let friendly_1 = MetuId::new("friend_1".to_owned(), "not_used".to_owned()).unwrap();
158 158
159 users.insert( 159 users.insert(
160 "31415926535897932384626433832795028841971693993751058209749445923".to_owned(), 160 "cde48537ca2c28084ff560826d0e6388b7c57a51497a6cb56f397289e52ff41b".to_owned(),
161 User { 161 User {
162 user_id: bank_acc, 162 user_id: friendly_1,
163 public_key: "null".to_owned(), 163 public_key: "not_used".to_owned(),
164 balance: 27 * 80, 164 balance: 0,
165 is_bot: true,
166 },
167 );
168
169 let friendly_2 = MetuId::new("friend_2".to_owned(), "not_used".to_owned()).unwrap();
170
171 users.insert(
172 "a1a38b5bae5866d7d998a9834229ec2f9db7a4fc8fb6f58b1115a96a446875ff".to_owned(),
173 User {
174 user_id: friendly_2,
175 public_key: "not_used".to_owned(),
176 balance: 0,
177 is_bot: true,
178 },
179 );
180
181 let friendly_3 = MetuId::new("friend_4".to_owned(), "not_used".to_owned()).unwrap();
182
183 users.insert(
184 "4e048fd2a62f1307866086e803e9be43f78a702d5df10831fbf434e7663ae0e7".to_owned(),
185 User {
186 user_id: friendly_3,
187 public_key: "not_used".to_owned(),
188 balance: 0,
189 is_bot: true,
190 },
191 );
192
193 let friendly_4 = MetuId::new("friend_4".to_owned(), "not_used".to_owned()).unwrap();
194
195 users.insert(
196 "60e77101e76950a9b1830fa107fd2f8fc545255b3e0f14b6a7797cf9ee005f07".to_owned(),
197 User {
198 user_id: friendly_4,
199 public_key: "not_used".to_owned(),
200 balance: 0,
201 is_bot: true,
165 }, 202 },
166 ); 203 );
167 204
@@ -182,7 +219,6 @@ impl Default for Db {
182/// A transaction between `source` and `target` that moves `amount` 219/// A transaction between `source` and `target` that moves `amount`
183#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 220#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
184pub struct Transaction { 221pub struct Transaction {
185 pub by: Fingerprint,
186 pub source: Fingerprint, 222 pub source: Fingerprint,
187 pub target: Fingerprint, 223 pub target: Fingerprint,
188 pub amount: u16, 224 pub amount: u16,
@@ -244,6 +280,8 @@ pub struct User {
244 pub user_id: MetuId, 280 pub user_id: MetuId,
245 pub public_key: String, 281 pub public_key: String,
246 pub balance: u16, 282 pub balance: u16,
283 #[serde(skip, default = "bool::default")]
284 pub is_bot: bool,
247} 285}
248 286
249/// The values are hard coded in [`OUR_STUDENTS`] so MetuId::new() can accept/reject values based on that 287/// The values are hard coded in [`OUR_STUDENTS`] so MetuId::new() can accept/reject values based on that
@@ -308,6 +346,10 @@ lazy_static! {
308 ("e223715", "1H5QuOYI1b2r9ET"), 346 ("e223715", "1H5QuOYI1b2r9ET"),
309 ("e181932", "THANKYOUHAVEFUN"), 347 ("e181932", "THANKYOUHAVEFUN"),
310 ("bank", "P7oxDm30g1jeIId"), 348 ("bank", "P7oxDm30g1jeIId"),
349 ("friend_1", "not_used"),
350 ("friend_2", "not_used"),
351 ("friend_3", "not_used"),
352 ("friend_4", "not_used"),
311 ] 353 ]
312 .iter() 354 .iter()
313 .cloned() 355 .cloned()
diff --git a/templates/css.html b/templates/css.html
index a918a4b..4a2ac7b 100644
--- a/templates/css.html
+++ b/templates/css.html
@@ -1,23 +1,30 @@
1<style> 1<style>
2
3body {
4 font-family: monospace, Times, serif;
5 margin: 2em auto;
6 max-width: 800px;
7}
8
2table, th, td { 9table, th, td {
3 border: 1px solid black; 10 border: 1px solid black;
4 border-collapse: collapse; 11 border-collapse: collapse;
5} 12}
6th, td { 13th, td {
7 padding: 15px; 14 padding: 10px;
8} 15}
9#t01 { 16#t01 {
10 width: 100%; 17 width: 100%;
11 background-color: #fbf1c7; 18 background-color: #fbf1c7;
12} 19}
13#t01 tr:nth-child(even) { 20#t01 tr:nth-child(even) {
14 background-color: #a89984; 21 background-color: #a89984;
15} 22}
16#t01 tr:nth-child(odd) { 23#t01 tr:nth-child(odd) {
17 background-color: #f9f5d7; 24 background-color: #f9f5d7;
18} 25}
19#t01 th { 26#t01 th {
20 color: #fbf1c7; 27 color: #fbf1c7;
21 background-color: #282828; 28 background-color: #282828;
22} 29}
23</style> 30</style>
diff --git a/templates/header.html b/templates/header.html
index a142fad..1c9136e 100644
--- a/templates/header.html
+++ b/templates/header.html
@@ -1,10 +1,11 @@
1<html> 1<html>
2 <head> 2 <head>
3 <title>Gradecoin</title> 3 <meta name="viewport" content="width=device-width, initial-scale=1.0">
4 <title>Gradecoin | Users</title>
4 {% include "css.html" %} 5 {% include "css.html" %}
5 </head> 6 </head>
6 <body> 7 <body>
7 <div> 8 <div>
8 <h1>Registered Users</h1> 9 <h1>Registered Users</h1>
9 </div> 10 </div>
10 <hr /> 11 <hr />
diff --git a/templates/list.html b/templates/list.html
index 083e0e8..0f19107 100644
--- a/templates/list.html
+++ b/templates/list.html
@@ -7,9 +7,9 @@
7 </tr> 7 </tr>
8 {% for user in users %} 8 {% for user in users %}
9 <tr> 9 <tr>
10 <td>{{ user.fingerprint }}</td> 10 <td>{{ user.fingerprint }} {% if user.is_bot %} <span title="I'm a bot!">👋🤖</span> {% endif %}</td>
11 <td>{{ user.balance }}</td> 11 <td>{{ user.balance }}</td>
12 </tr> 12 </tr>
13 {% endfor %} 13 {% endfor %}
14</table> 14</table>
15{% include "footer.html" %} 15{% include "footer.html" %}
diff --git a/tests/route_tests.rs b/tests/route_tests.rs
index decc712..a449b0b 100644
--- a/tests/route_tests.rs
+++ b/tests/route_tests.rs
@@ -1,6 +1,7 @@
1#[cfg(test)] 1#[cfg(test)]
2mod tests { 2mod tests {
3 use gradecoin::schema::{Block, Db, InitialAuthRequest, MetuId, Transaction, User}; 3 use gradecoin::schema::{Block, Db, InitialAuthRequest, MetuId, Transaction, User};
4 use pretty_assertions::assert_eq;
4 5
5 use gradecoin::routes::consensus_routes; 6 use gradecoin::routes::consensus_routes;
6 use warp::http::StatusCode; 7 use warp::http::StatusCode;
@@ -24,6 +25,7 @@ FQIDAQAB
24-----END PUBLIC KEY-----" 25-----END PUBLIC KEY-----"
25 .to_owned(), 26 .to_owned(),
26 balance: 30, 27 balance: 30,
28 is_bot: false,
27 }, 29 },
28 ); 30 );
29 31
@@ -33,45 +35,44 @@ FQIDAQAB
33 user_id: MetuId::new("e223715".to_owned(), "1H5QuOYI1b2r9ET".to_owned()).unwrap(), 35 user_id: MetuId::new("e223715".to_owned(), "1H5QuOYI1b2r9ET".to_owned()).unwrap(),
34 public_key: "NOT_USED_FOR_THIS_USER".to_owned(), 36 public_key: "NOT_USED_FOR_THIS_USER".to_owned(),
35 balance: 0, 37 balance: 0,
38 is_bot: false,
36 }, 39 },
37 ); 40 );
38 41
39 /* 42 /*
40-----BEGIN RSA PRIVATE KEY----- 43 -----BEGIN RSA PRIVATE KEY-----
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
66-----END RSA PRIVATE KEY----- 69 -----END RSA PRIVATE KEY-----
67*/ 70 */
68 71
69 db.pending_transactions.write().insert( 72 db.pending_transactions.write().insert(
70 "fingerprint_of_foo".to_owned(), 73 "fingerprint_of_foo".to_owned(),
71 Transaction { 74 Transaction {
72 by: "fingerprint_of_foo".to_owned(), 75 source: "fingerprint_of_foo".to_owned(),
73 source: "31415926535897932384626433832795028841971693993751058209749445923"
74 .to_owned(),
75 target: "fingerprint_of_foo".to_owned(), 76 target: "fingerprint_of_foo".to_owned(),
76 amount: 2, 77 amount: 2,
77 timestamp: chrono::NaiveDate::from_ymd(2021, 04, 13).and_hms(20, 55, 30), 78 timestamp: chrono::NaiveDate::from_ymd(2021, 04, 13).and_hms(20, 55, 30),
@@ -109,7 +110,7 @@ MRArEl4y68+jZLRu74TVG0lXi6ht6KhNHF6GiWKU9FHZ4B+btLicsg==
109 110
110 assert_eq!(res.status(), StatusCode::OK); 111 assert_eq!(res.status(), StatusCode::OK);
111 112
112 let expected_json_body = r#"{"fingerprint_of_foo":{"by":"fingerprint_of_foo","source":"31415926535897932384626433832795028841971693993751058209749445923","target":"fingerprint_of_foo","amount":2,"timestamp":"2021-04-13T20:55:30"}}"#; 113 let expected_json_body = r#"{"fingerprint_of_foo":{"source":"fingerprint_of_foo","target":"fingerprint_of_foo","amount":2,"timestamp":"2021-04-13T20:55:30"}}"#;
113 114
114 assert_eq!(res.body(), expected_json_body); 115 assert_eq!(res.body(), expected_json_body);
115 } 116 }
@@ -167,9 +168,8 @@ MRArEl4y68+jZLRu74TVG0lXi6ht6KhNHF6GiWKU9FHZ4B+btLicsg==
167 let res = warp::test::request() 168 let res = warp::test::request()
168 .method("POST") 169 .method("POST")
169 .json(&Transaction { 170 .json(&Transaction {
170 by: "fingerprint_of_some_guy".to_owned(), 171 source: "fingerprint_of_some_guy".to_owned(),
171 source: "31415926535897932384626433832795028841971693993751058209749445923".to_owned(), 172 target: "31415926535897932384626433832795028841971693993751058209749445923".to_owned(),
172 target: "fingerprint_of_some_guy".to_owned(),
173 amount: 2, 173 amount: 2,
174 timestamp: chrono::NaiveDate::from_ymd(2021, 04, 13).and_hms(20, 55, 30), 174 timestamp: chrono::NaiveDate::from_ymd(2021, 04, 13).and_hms(20, 55, 30),
175 }) 175 })
@@ -200,7 +200,6 @@ MRArEl4y68+jZLRu74TVG0lXi6ht6KhNHF6GiWKU9FHZ4B+btLicsg==
200 let res = warp::test::request() 200 let res = warp::test::request()
201 .method("POST") 201 .method("POST")
202 .json(&Transaction { 202 .json(&Transaction {
203 by: "some_fingerprint".to_owned(),
204 source: "some_fingerprint".to_owned(), 203 source: "some_fingerprint".to_owned(),
205 target: "some_other_fingerprint".to_owned(), 204 target: "some_other_fingerprint".to_owned(),
206 amount: 2, 205 amount: 2,
@@ -232,10 +231,9 @@ MRArEl4y68+jZLRu74TVG0lXi6ht6KhNHF6GiWKU9FHZ4B+btLicsg==
232 db.pending_transactions.write().insert( 231 db.pending_transactions.write().insert(
233 "fingerprint_of_some_guy".to_owned(), 232 "fingerprint_of_some_guy".to_owned(),
234 Transaction { 233 Transaction {
235 by: "fingerprint_of_some_guy".to_owned(), 234 source: "fingerprint_of_some_guy".to_owned(),
236 source: "31415926535897932384626433832795028841971693993751058209749445923" 235 target: "31415926535897932384626433832795028841971693993751058209749445923"
237 .to_owned(), 236 .to_owned(),
238 target: "fingerprint_of_some_guy".to_owned(),
239 amount: 2, 237 amount: 2,
240 timestamp: chrono::NaiveDate::from_ymd(2021, 04, 13).and_hms(20, 55, 30), 238 timestamp: chrono::NaiveDate::from_ymd(2021, 04, 13).and_hms(20, 55, 30),
241 }, 239 },
@@ -422,7 +420,7 @@ MRArEl4y68+jZLRu74TVG0lXi6ht6KhNHF6GiWKU9FHZ4B+btLicsg==
422 .json(&InitialAuthRequest { 420 .json(&InitialAuthRequest {
423 c: "D9OKSp4XD+niltqhoiTEyz3pTxGm5ZKYVNFPofW40M6Km7wE7FgIpfTkurBZ6tQsG/rYPRsd6C/Qo+o3HrgOYC8BDprwpnYb7UnJdL2pe44ZMEsPAmDAdwTP9WozY0lr+bjEjtTM1mVQnIdfknychFek/FNi3l8MrapeFTxFaTMGxWuS1+wEuAkcz4AR4+jooaXVAEpKrPiSXqbywF9OQ41tk0kRiXn234dj40ndND+GlfMgghITuBJrJx6tzLppAZNIIGwUjQDt5Oib5dEGrPOe+rran1D26YNhZOtrfYEGyUSN+/58HbItQlLrgFhL6zRT7ojw/Eg4jYXndK0xNgYGyhAn5UI/qnI2NPpZU7Wd3sJKlWc7HfrjNnKVKlcrhHtYy3FXfN/hLg7SFmuSfXqqvVbNVT6pEDU6Y5NahOYaE/vkL0no7F7lz0UjAlgQCmn5yN7mKs3yLSnlx6hmsK/fVoqGBcOIbYY5gzYMlAQ3E+lq0p2MPEoWC8NYxStSeo9M8uLYT6Jl3hYVf8aLgd1l0HEiCyT+kWxvcR5hw42I7gqaoUcnr53Zm1mYK30/fvZ6lxsrb4FphldgQC5fx6nwEgjaLUeB4n0oZTSRLbrd9ZXCjUG4FNmM+sOklhIXyTYUj4VcBSwZuAvJZEFf2em68e7ySJs/ysz+TGu3eVeRc+voAvI9mGLxWnSEjWx64po7PO61uG6ikadHZH+wIw==".to_owned(), 421 c: "D9OKSp4XD+niltqhoiTEyz3pTxGm5ZKYVNFPofW40M6Km7wE7FgIpfTkurBZ6tQsG/rYPRsd6C/Qo+o3HrgOYC8BDprwpnYb7UnJdL2pe44ZMEsPAmDAdwTP9WozY0lr+bjEjtTM1mVQnIdfknychFek/FNi3l8MrapeFTxFaTMGxWuS1+wEuAkcz4AR4+jooaXVAEpKrPiSXqbywF9OQ41tk0kRiXn234dj40ndND+GlfMgghITuBJrJx6tzLppAZNIIGwUjQDt5Oib5dEGrPOe+rran1D26YNhZOtrfYEGyUSN+/58HbItQlLrgFhL6zRT7ojw/Eg4jYXndK0xNgYGyhAn5UI/qnI2NPpZU7Wd3sJKlWc7HfrjNnKVKlcrhHtYy3FXfN/hLg7SFmuSfXqqvVbNVT6pEDU6Y5NahOYaE/vkL0no7F7lz0UjAlgQCmn5yN7mKs3yLSnlx6hmsK/fVoqGBcOIbYY5gzYMlAQ3E+lq0p2MPEoWC8NYxStSeo9M8uLYT6Jl3hYVf8aLgd1l0HEiCyT+kWxvcR5hw42I7gqaoUcnr53Zm1mYK30/fvZ6lxsrb4FphldgQC5fx6nwEgjaLUeB4n0oZTSRLbrd9ZXCjUG4FNmM+sOklhIXyTYUj4VcBSwZuAvJZEFf2em68e7ySJs/ysz+TGu3eVeRc+voAvI9mGLxWnSEjWx64po7PO61uG6ikadHZH+wIw==".to_owned(),
424 iv: "bmV2ZXJtaW5kdGhlbmZ1aw==".to_owned(), 422 iv: "bmV2ZXJtaW5kdGhlbmZ1aw==".to_owned(),
425 key: "s4cn9BSmuForX6PxJAa55Es4t2puXuDtdII1lxEArqVlP+uYd5jDKofFtn9PCAoY7jyTgBIhQW7Ah5MGCcufWTaKHAjFVfSZ+qGwbGbBcklbNGH/F7cJ0Pe7kOCddUpIvLG6WH6+mnvyPs8PwDyagsx1Jc2PSSOYLAwkECvPbjiUjQiBixguTRNsU2eKaqzLimPE0w2ztvdA+IgCv94UPhjQfQrnMGK+Ppn3oK7IfKQJ7v2DLVNuz4d/BpwuqD+lYYAu4B4qn3daNR32a/mqAAlPg/RbPlH69N44Qh/NYux90FOY0XKxUskEwsAUw8dHFzzdKPcGx4C0s5e4KSLGkw==".to_owned(), 423 key: "Xd6/VSuFKqayNHspcFJSm+PAHNoTmcR4SsMijSyuyEh6PS5rdvO4W98AhxW4VBrRO1ljfEMeFq835NEDame511D2pim00Xv0HPIYSDW6pIJA1hy+Np/WyC7PCxvKy0hPzTmHMpFmM+aF43BknJdYlPUhY4cww/xScU6WxuKIsEQNORRhQds8CHOO0EGcOjHVvR2xqnOda1g/rI7mfNMATHj9ZRsB9GH6QG5WTUbo9/71cDAILF+28TG40jSKvY2KzO9vr668tgqoMV2vLnXQa1AD9ZWmdHHdjiXuiH3X0uXxHrfjH7HeXi/HOj/pgCX12jKsEsRwkBTGL4koObH6pQ==".to_owned(),
426 }) 424 })
427 .path("/register") 425 .path("/register")
428 .reply(&filter) 426 .reply(&filter)
diff --git a/tests/schema_tests.rs b/tests/schema_tests.rs
index 4240a5f..072cb2b 100644
--- a/tests/schema_tests.rs
+++ b/tests/schema_tests.rs
@@ -1,310 +1,310 @@
1#[cfg(test)] 1// #[cfg(test)]
2mod tests { 2// mod tests {
3 use gradecoin::schema::*; 3// use gradecoin::schema::*;
4 use serde_test::{assert_tokens, Token}; 4// use serde_test::{assert_tokens, Token};
5 use chrono::NaiveDate; 5// use chrono::NaiveDate;
6 6
7 #[test] 7// #[test]
8 fn claims_serialize_correctly() { 8// fn claims_serialize_correctly() {
9 let claims = Claims { 9// let claims = Claims {
10 tha: "hashed_string".to_owned(), 10// tha: "hashed_string".to_owned(),
11 iat: 0, 11// iat: 0,
12 exp: 100, 12// exp: 100,
13 }; 13// };
14 assert_tokens( 14// assert_tokens(
15 &claims, 15// &claims,
16 &[ 16// &[
17 Token::Struct{name: "Claims", len: 3}, 17// Token::Struct{name: "Claims", len: 3},
18 Token::String("tha"), 18// Token::String("tha"),
19 Token::String("hashed_string"), 19// Token::String("hashed_string"),
20 Token::String("iat"), 20// Token::String("iat"),
21 Token::U64(0), 21// Token::U64(0),
22 Token::String("exp"), 22// Token::String("exp"),
23 Token::U64(100), 23// Token::U64(100),
24 Token::StructEnd, 24// Token::StructEnd,
25 ] 25// ]
26 ) 26// )
27 } 27// }
28 28
29 #[test] 29// #[test]
30 fn claims_deserialize_correctly() { 30// fn claims_deserialize_correctly() {
31 let data = r#"{"tha":"hashed_string","iat":0,"exp":100}"#; 31// let data = r#"{"tha":"hashed_string","iat":0,"exp":100}"#;
32 let claims: Claims = serde_json::from_str(data).unwrap(); 32// let claims: Claims = serde_json::from_str(data).unwrap();
33 let expected_claims = Claims { 33// let expected_claims = Claims {
34 tha: "hashed_string".to_owned(), 34// tha: "hashed_string".to_owned(),
35 iat: 0, 35// iat: 0,
36 exp: 100, 36// exp: 100,
37 }; 37// };
38 assert_eq!(claims, expected_claims); 38// assert_eq!(claims, expected_claims);
39 } 39// }
40 40
41 #[test] 41// #[test]
42 fn transaction_serialize_correctly() { 42// fn transaction_serialize_correctly() {
43 let transaction = Transaction { 43// let transaction = Transaction {
44 by: "source".to_owned(), 44// by: "source".to_owned(),
45 source: "source".to_owned(), 45// source: "source".to_owned(),
46 target: "target".to_owned(), 46// target: "target".to_owned(),
47 amount: 0, 47// amount: 0,
48 timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42), 48// timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42),
49 }; 49// };
50 50
51 assert_tokens( 51// assert_tokens(
52 &transaction, 52// &transaction,
53 &[ 53// &[
54 Token::Struct{name: "Transaction", len: 5}, 54// Token::Struct{name: "Transaction", len: 5},
55 Token::String("by"), 55// Token::String("by"),
56 Token::String("source"), 56// Token::String("source"),
57 Token::String("source"), 57// Token::String("source"),
58 Token::String("source"), 58// Token::String("source"),
59 Token::String("target"), 59// Token::String("target"),
60 Token::String("target"), 60// Token::String("target"),
61 Token::String("amount"), 61// Token::String("amount"),
62 Token::I32(0), 62// Token::I32(0),
63 Token::String("timestamp"), 63// Token::String("timestamp"),
64 Token::String("2021-04-02T04:02:42"), 64// Token::String("2021-04-02T04:02:42"),
65 Token::StructEnd, 65// Token::StructEnd,
66 ] 66// ]
67 ) 67// )
68 } 68// }
69 69
70 #[test] 70// #[test]
71 fn transaction_deserialize_correctly() { 71// fn transaction_deserialize_correctly() {
72 let data = r#"{"by":"source","source":"source","target":"target","amount":0,"timestamp":"2021-04-02T04:02:42"}"#; 72// let data = r#"{"by":"source","source":"source","target":"target","amount":0,"timestamp":"2021-04-02T04:02:42"}"#;
73 let transaction: Transaction = serde_json::from_str(data).unwrap(); 73// let transaction: Transaction = serde_json::from_str(data).unwrap();
74 let expected_transaction = Transaction { 74// let expected_transaction = Transaction {
75 by: "source".to_owned(), 75// by: "source".to_owned(),
76 source: "source".to_owned(), 76// source: "source".to_owned(),
77 target: "target".to_owned(), 77// target: "target".to_owned(),
78 amount: 0, 78// amount: 0,
79 timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42), 79// timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42),
80 }; 80// };
81 81
82 assert_eq!(transaction, expected_transaction); 82// assert_eq!(transaction, expected_transaction);
83 } 83// }
84 84
85 #[test] 85// #[test]
86 fn block_serialize_correctly() { 86// fn block_serialize_correctly() {
87 let block = Block { 87// let block = Block {
88 transaction_list: vec!["transaction1".to_owned()], 88// transaction_list: vec!["transaction1".to_owned()],
89 nonce: 0, 89// nonce: 0,
90 timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42), 90// timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42),
91 hash: "hash".to_owned() 91// hash: "hash".to_owned()
92 }; 92// };
93 93
94 assert_tokens( 94// assert_tokens(
95 &block, 95// &block,
96 &[ 96// &[
97 Token::Struct{name: "Block", len: 4}, 97// Token::Struct{name: "Block", len: 4},
98 Token::String("transaction_list"), 98// Token::String("transaction_list"),
99 Token::Seq {len: Some(1)}, 99// Token::Seq {len: Some(1)},
100 Token::String("transaction1"), 100// Token::String("transaction1"),
101 Token::SeqEnd, 101// Token::SeqEnd,
102 Token::String("nonce"), 102// Token::String("nonce"),
103 Token::U32(0), 103// Token::U32(0),
104 Token::String("timestamp"), 104// Token::String("timestamp"),
105 Token::String("2021-04-02T04:02:42"), 105// Token::String("2021-04-02T04:02:42"),
106 Token::String("hash"), 106// Token::String("hash"),
107 Token::String("hash"), 107// Token::String("hash"),
108 Token::StructEnd, 108// Token::StructEnd,
109 ] 109// ]
110 ) 110// )
111 } 111// }
112 112
113 #[test] 113// #[test]
114 fn block_deserialize_correctly() { 114// fn block_deserialize_correctly() {
115 let expected_block = Block { 115// let expected_block = Block {
116 transaction_list: vec!["transaction1".to_owned()], 116// transaction_list: vec!["transaction1".to_owned()],
117 nonce: 0, 117// nonce: 0,
118 timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42), 118// timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42),
119 hash: "hash".to_owned() 119// hash: "hash".to_owned()
120 }; 120// };
121 let data = r#"{"transaction_list":["transaction1"],"nonce":0,"timestamp":"2021-04-02T04:02:42","hash":"hash"}"#; 121// let data = r#"{"transaction_list":["transaction1"],"nonce":0,"timestamp":"2021-04-02T04:02:42","hash":"hash"}"#;
122 let block: Block = serde_json::from_str(data).unwrap(); 122// let block: Block = serde_json::from_str(data).unwrap();
123 123
124 assert_eq!(block, expected_block); 124// assert_eq!(block, expected_block);
125 125
126 } 126// }
127 127
128 #[test] 128// #[test]
129 fn block_serialize_when_vec_emptpy() { 129// fn block_serialize_when_vec_emptpy() {
130 let block = Block { 130// let block = Block {
131 transaction_list: vec![], 131// transaction_list: vec![],
132 nonce: 0, 132// nonce: 0,
133 timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42), 133// timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42),
134 hash: "hash".to_owned() 134// hash: "hash".to_owned()
135 }; 135// };
136 136
137 let json = serde_json::to_string(&block).unwrap(); 137// let json = serde_json::to_string(&block).unwrap();
138 assert_eq!(json, r#"{"nonce":0,"timestamp":"2021-04-02T04:02:42","hash":"hash"}"#) 138// assert_eq!(json, r#"{"nonce":0,"timestamp":"2021-04-02T04:02:42","hash":"hash"}"#)
139 } 139// }
140 140
141 #[test] 141// #[test]
142 fn naked_block_serialize_correctly() { 142// fn naked_block_serialize_correctly() {
143 let naked_block = NakedBlock { 143// let naked_block = NakedBlock {
144 transaction_list: vec!["transaction1".to_owned()], 144// transaction_list: vec!["transaction1".to_owned()],
145 nonce: 0, 145// nonce: 0,
146 timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42), 146// timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42),
147 }; 147// };
148 148
149 assert_tokens( 149// assert_tokens(
150 &naked_block, 150// &naked_block,
151 &[ 151// &[
152 Token::Struct{name: "NakedBlock", len: 3}, 152// Token::Struct{name: "NakedBlock", len: 3},
153 Token::String("transaction_list"), 153// Token::String("transaction_list"),
154 Token::Seq {len: Some(1)}, 154// Token::Seq {len: Some(1)},
155 Token::String("transaction1"), 155// Token::String("transaction1"),
156 Token::SeqEnd, 156// Token::SeqEnd,
157 Token::String("nonce"), 157// Token::String("nonce"),
158 Token::U32(0), 158// Token::U32(0),
159 Token::String("timestamp"), 159// Token::String("timestamp"),
160 Token::String("2021-04-02T04:02:42"), 160// Token::String("2021-04-02T04:02:42"),
161 Token::StructEnd, 161// Token::StructEnd,
162 ] 162// ]
163 ) 163// )
164 } 164// }
165 165
166 #[test] 166// #[test]
167 fn naked_block_deserialize_correctly() { 167// fn naked_block_deserialize_correctly() {
168 let expected_naked_block = NakedBlock { 168// let expected_naked_block = NakedBlock {
169 transaction_list: vec!["transaction1".to_owned()], 169// transaction_list: vec!["transaction1".to_owned()],
170 nonce: 0, 170// nonce: 0,
171 timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42), 171// timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42),
172 }; 172// };
173 let data = r#"{"transaction_list":["transaction1"],"nonce":0,"timestamp":"2021-04-02T04:02:42"}"#; 173// let data = r#"{"transaction_list":["transaction1"],"nonce":0,"timestamp":"2021-04-02T04:02:42"}"#;
174 let naked_block: NakedBlock = serde_json::from_str(data).unwrap(); 174// let naked_block: NakedBlock = serde_json::from_str(data).unwrap();
175 175
176 assert_eq!(naked_block, expected_naked_block); 176// assert_eq!(naked_block, expected_naked_block);
177 177
178 } 178// }
179 179
180 #[test] 180// #[test]
181 fn naked_block_serialize_when_vec_emptpy() { 181// fn naked_block_serialize_when_vec_emptpy() {
182 let naked_block = NakedBlock { 182// let naked_block = NakedBlock {
183 transaction_list: vec![], 183// transaction_list: vec![],
184 nonce: 0, 184// nonce: 0,
185 timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42), 185// timestamp: NaiveDate::from_ymd(2021, 4, 2).and_hms(4, 2, 42),
186 }; 186// };
187 187
188 let json = serde_json::to_string(&naked_block).unwrap(); 188// let json = serde_json::to_string(&naked_block).unwrap();
189 assert_eq!(json, r#"{"nonce":0,"timestamp":"2021-04-02T04:02:42"}"#) 189// assert_eq!(json, r#"{"nonce":0,"timestamp":"2021-04-02T04:02:42"}"#)
190 } 190// }
191 191
192 #[test] 192// #[test]
193 fn user_serialize_correctly() { 193// fn user_serialize_correctly() {
194 let user = User { 194// let user = User {
195 user_id: MetuId::new("e254275".to_owned(), "DtNX1qk4YF4saRH".to_owned()).unwrap(), 195// user_id: MetuId::new("e254275".to_owned(), "DtNX1qk4YF4saRH".to_owned()).unwrap(),
196 public_key: "public_key".to_owned(), 196// public_key: "public_key".to_owned(),
197 balance: 0 197// balance: 0
198 }; 198// };
199 199
200 assert_tokens( 200// assert_tokens(
201 &user, 201// &user,
202 &[ 202// &[
203 Token::Struct{name: "User", len: 3}, 203// Token::Struct{name: "User", len: 3},
204 Token::String("user_id"), 204// Token::String("user_id"),
205 Token::Struct {name: "MetuId", len: 2}, 205// Token::Struct {name: "MetuId", len: 2},
206 Token::String("id"), 206// Token::String("id"),
207 Token::String("e254275"), 207// Token::String("e254275"),
208 Token::String("passwd"), 208// Token::String("passwd"),
209 Token::String("DtNX1qk4YF4saRH"), 209// Token::String("DtNX1qk4YF4saRH"),
210 Token::StructEnd, 210// Token::StructEnd,
211 Token::String("public_key"), 211// Token::String("public_key"),
212 Token::String("public_key"), 212// Token::String("public_key"),
213 Token::String("balance"), 213// Token::String("balance"),
214 Token::I32(0), 214// Token::I32(0),
215 Token::StructEnd, 215// Token::StructEnd,
216 ] 216// ]
217 ) 217// )
218 } 218// }
219 219
220 #[test] 220// #[test]
221 fn user_deserialize_correctly() { 221// fn user_deserialize_correctly() {
222 let expected_user = User { 222// let expected_user = User {
223 user_id: MetuId::new("e254275".to_owned(), "DtNX1qk4YF4saRH".to_owned()).unwrap(), 223// user_id: MetuId::new("e254275".to_owned(), "DtNX1qk4YF4saRH".to_owned()).unwrap(),
224 public_key: "public_key".to_owned(), 224// public_key: "public_key".to_owned(),
225 balance: 0 225// balance: 0
226 }; 226// };
227 let data = r#"{"user_id":{"id":"e254275","passwd":"DtNX1qk4YF4saRH"},"public_key":"public_key","balance":0}"#; 227// let data = r#"{"user_id":{"id":"e254275","passwd":"DtNX1qk4YF4saRH"},"public_key":"public_key","balance":0}"#;
228 let user: User = serde_json::from_str(data).unwrap(); 228// let user: User = serde_json::from_str(data).unwrap();
229 229
230 assert_eq!(user, expected_user); 230// assert_eq!(user, expected_user);
231 231
232 } 232// }
233 233
234 #[test] 234// #[test]
235 fn metu_id_serialize_correctly() { 235// fn metu_id_serialize_correctly() {
236 let metu_id = MetuId::new ("e254275".to_owned(), "DtNX1qk4YF4saRH".to_owned()).unwrap(); 236// let metu_id = MetuId::new ("e254275".to_owned(), "DtNX1qk4YF4saRH".to_owned()).unwrap();
237 237
238 assert_tokens( 238// assert_tokens(
239 &metu_id, 239// &metu_id,
240 &[ 240// &[
241 Token::Struct{name: "MetuId", len: 2}, 241// Token::Struct{name: "MetuId", len: 2},
242 Token::String("id"), 242// Token::String("id"),
243 Token::String("e254275"), 243// Token::String("e254275"),
244 Token::String("passwd"), 244// Token::String("passwd"),
245 Token::String("DtNX1qk4YF4saRH"), 245// Token::String("DtNX1qk4YF4saRH"),
246 Token::StructEnd, 246// Token::StructEnd,
247 ] 247// ]
248 ) 248// )
249 } 249// }
250 250
251 #[test] 251// #[test]
252 fn metu_id_deserialize_correctly() { 252// fn metu_id_deserialize_correctly() {
253 let expected_metu_id = MetuId::new ("e254275".to_owned(), "DtNX1qk4YF4saRH".to_owned()).unwrap(); 253// let expected_metu_id = MetuId::new ("e254275".to_owned(), "DtNX1qk4YF4saRH".to_owned()).unwrap();
254 let data = r#"{"id":"e254275","passwd":"DtNX1qk4YF4saRH"}"#; 254// let data = r#"{"id":"e254275","passwd":"DtNX1qk4YF4saRH"}"#;
255 let metu_id: MetuId = serde_json::from_str(data).unwrap(); 255// let metu_id: MetuId = serde_json::from_str(data).unwrap();
256 256
257 assert_eq!(metu_id, expected_metu_id); 257// assert_eq!(metu_id, expected_metu_id);
258 } 258// }
259 259
260 #[test] 260// #[test]
261 fn auth_request_serialize_correctly() { 261// fn auth_request_serialize_correctly() {
262 let auth_request = AuthRequest { 262// let auth_request = AuthRequest {
263 student_id: "e254275".to_owned(), 263// student_id: "e254275".to_owned(),
264 passwd: "DtNX1qk4YF4saRH".to_owned(), 264// passwd: "DtNX1qk4YF4saRH".to_owned(),
265 public_key: "public_key".to_owned() 265// public_key: "public_key".to_owned()
266 }; 266// };
267 267
268 assert_tokens( 268// assert_tokens(
269 &auth_request, 269// &auth_request,
270 &[ 270// &[
271 Token::Struct{name: "AuthRequest", len: 3}, 271// Token::Struct{name: "AuthRequest", len: 3},
272 Token::String("student_id"), 272// Token::String("student_id"),
273 Token::String("e254275"), 273// Token::String("e254275"),
274 Token::String("passwd"), 274// Token::String("passwd"),
275 Token::String("DtNX1qk4YF4saRH"), 275// Token::String("DtNX1qk4YF4saRH"),
276 Token::String("public_key"), 276// Token::String("public_key"),
277 Token::String("public_key"), 277// Token::String("public_key"),
278 Token::StructEnd, 278// Token::StructEnd,
279 ] 279// ]
280 ) 280// )
281 } 281// }
282 282
283 #[test] 283// #[test]
284 fn auth_request_deserialize_correctly() { 284// fn auth_request_deserialize_correctly() {
285 let expected_auth_request = AuthRequest { 285// let expected_auth_request = AuthRequest {
286 student_id: "e254275".to_owned(), 286// student_id: "e254275".to_owned(),
287 passwd: "DtNX1qk4YF4saRH".to_owned(), 287// passwd: "DtNX1qk4YF4saRH".to_owned(),
288 public_key: "public_key".to_owned() 288// public_key: "public_key".to_owned()
289 }; 289// };
290 let data = r#"{"student_id":"e254275","passwd":"DtNX1qk4YF4saRH","public_key":"public_key"}"#; 290// let data = r#"{"student_id":"e254275","passwd":"DtNX1qk4YF4saRH","public_key":"public_key"}"#;
291 let auth_request: AuthRequest = serde_json::from_str(data).unwrap(); 291// let auth_request: AuthRequest = serde_json::from_str(data).unwrap();
292 292
293 assert_eq!(auth_request, expected_auth_request); 293// assert_eq!(auth_request, expected_auth_request);
294 294
295 } 295// }
296 296
297 297
298 298
299 299
300 300
301 301
302 302
303 303
304 304
305 305
306 306
307 307
308 308
309 309
310} \ No newline at end of file 310// }