aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYigit Sever2021-04-25 22:23:58 +0300
committerYigit Sever2021-04-25 22:23:58 +0300
commit446cd6bdd6234ffba76a0e440c606686053f0ba0 (patch)
tree5753559b01e1d51e64b5cce8da31f9ca106ef3d5
parent63d08a9f120e842dcc5a34a1db6b39957c643b30 (diff)
downloadgradecoin-446cd6bdd6234ffba76a0e440c606686053f0ba0.tar.gz
gradecoin-446cd6bdd6234ffba76a0e440c606686053f0ba0.tar.bz2
gradecoin-446cd6bdd6234ffba76a0e440c606686053f0ba0.zip
Went over error messages
-rw-r--r--src/handlers.rs188
1 files changed, 127 insertions, 61 deletions
diff --git a/src/handlers.rs b/src/handlers.rs
index 123f70e..7204aa7 100644
--- a/src/handlers.rs
+++ b/src/handlers.rs
@@ -16,13 +16,21 @@ use sha2::Sha256;
16use std::collections::{HashMap, HashSet}; 16use std::collections::{HashMap, HashSet};
17use std::convert::Infallible; 17use std::convert::Infallible;
18use std::fs; 18use std::fs;
19use std::hash::Hash;
19use warp::{http::StatusCode, reply}; 20use warp::{http::StatusCode, reply};
20 21
21use crate::PRIVATE_KEY; 22use crate::PRIVATE_KEY;
22const BLOCK_TRANSACTION_COUNT: u8 = 1; 23
24// Valid blocks should have this many transactions
25const BLOCK_TRANSACTION_COUNT: u8 = 5;
26// Inital registration bonus
23const REGISTER_BONUS: u16 = 40; 27const REGISTER_BONUS: u16 = 40;
28// Coinbase reward
24const BLOCK_REWARD: u16 = 3; 29const BLOCK_REWARD: u16 = 3;
30// Transaction amount limit
25const TX_UPPER_LIMIT: u16 = 2; 31const TX_UPPER_LIMIT: u16 = 2;
32// Transaction traffic reward
33const TX_TRAFFIC_REWARD: u16 = 1;
26 34
27// Encryption primitive 35// Encryption primitive
28type Aes128Cbc = Cbc<Aes128, Pkcs7>; 36type Aes128Cbc = Cbc<Aes128, Pkcs7>;
@@ -108,42 +116,68 @@ pub async fn authenticate_user(
108 116
109 let padding = PaddingScheme::new_oaep::<sha2::Sha256>(); 117 let padding = PaddingScheme::new_oaep::<sha2::Sha256>();
110 118
119 // Peel away the base64 layer from "key" field
111 let key_ciphertext = match base64::decode(&request.key) { 120 let key_ciphertext = match base64::decode(&request.key) {
112 Ok(c) => c, 121 Ok(c) => c,
113 Err(err) => { 122 Err(err) => {
114 debug!( 123 debug!(
115 "The ciphertext of the key was not base64 encoded {}, {}", 124 "\"key\" field of initial auth request was not base64 encoded: {}, {}",
116 &request.key, err 125 &request.key, err
117 ); 126 );
118 127
119 let res_json = warp::reply::json(&GradeCoinResponse { 128 let res_json = warp::reply::json(&GradeCoinResponse {
120 res: ResponseType::Error, 129 res: ResponseType::Error,
121 message: "The ciphertext of the key was not base64 encoded {}, {}".to_owned(), 130 message: format!(
131 "\"key\" field of initial auth request was not base64 encoded: {}, {}",
132 &request.key, err
133 ),
122 }); 134 });
123 135
124 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 136 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
125 } 137 }
126 }; 138 };
127 139
140 // Decrypt the "key" field using Gradecoin's private key
128 let temp_key = match gradecoin_private_key.decrypt(padding, &key_ciphertext) { 141 let temp_key = match gradecoin_private_key.decrypt(padding, &key_ciphertext) {
129 Ok(k) => k, 142 Ok(k) => k,
130 Err(err) => { 143 Err(err) => {
131 debug!( 144 debug!(
132 "Failed to decrypt ciphertext {:?}, {}", 145 "Failed to decrypt ciphertext of the key with Gradecoin's public key: {}. Key was {:?}",
133 &key_ciphertext, err 146 err, &key_ciphertext
134 ); 147 );
135 148
136 let res_json = warp::reply::json(&GradeCoinResponse { 149 let res_json = warp::reply::json(&GradeCoinResponse {
137 res: ResponseType::Error, 150 res: ResponseType::Error,
138 message: "Failed to decrypt the ciphertext of the temporary key".to_owned(), 151 message: "Failed to decrypt the 'key_ciphertext' field of the auth request"
152 .to_owned(),
139 }); 153 });
140 154
141 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 155 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
142 } 156 }
143 }; 157 };
144 158
145 let byte_iv = base64::decode(&request.iv).unwrap(); 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
166 );
146 167
168 let res_json = warp::reply::json(&GradeCoinResponse {
169 res: ResponseType::Error,
170 message: format!(
171 "\"iv\" field of initial auth request was not base64 encoded: {}, {}",
172 &request.iv, err
173 ),
174 });
175
176 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
177 }
178 };
179
180 // we have key and iv, time to decrypt the "c" field, first prepare the decryptor
147 let cipher = match Aes128Cbc::new_var(&temp_key, &byte_iv) { 181 let cipher = match Aes128Cbc::new_var(&temp_key, &byte_iv) {
148 Ok(c) => c, 182 Ok(c) => c,
149 Err(err) => { 183 Err(err) => {
@@ -161,42 +195,49 @@ pub async fn authenticate_user(
161 } 195 }
162 }; 196 };
163 197
198 // peel away the base64 from the auth packet
164 let auth_packet = match base64::decode(&request.c) { 199 let auth_packet = match base64::decode(&request.c) {
165 Ok(a) => a, 200 Ok(a) => a,
166
167 Err(err) => { 201 Err(err) => {
168 debug!( 202 debug!(
169 "The auth_packet (c field) did not base64 decode {} {}", 203 "\"c\" field of initial auth request was not base64 encoded: {}, {}",
170 &request.c, err 204 &request.c, err
171 ); 205 );
172 206
173 let res_json = warp::reply::json(&GradeCoinResponse { 207 let res_json = warp::reply::json(&GradeCoinResponse {
174 res: ResponseType::Error, 208 res: ResponseType::Error,
175 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 ),
176 }); 213 });
177 214
178 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 215 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
179 } 216 }
180 }; 217 };
181 218
219 // c field was properly base64 encoded, now available in auth_packet
220 // decryptor was setup properly, with the correct lenght key
182 let mut buf = auth_packet.to_vec(); 221 let mut buf = auth_packet.to_vec();
183 let auth_plaintext = match cipher.decrypt(&mut buf) { 222 let auth_plaintext = match cipher.decrypt(&mut buf) {
184 Ok(p) => p, 223 Ok(p) => p,
185 Err(err) => { 224 Err(err) => {
186 debug!( 225 println!(
187 "Base64 decoded auth request did not decrypt correctly {:?} {}", 226 "auth request (c) did not decrypt correctly {:?} {}",
188 &auth_packet, err 227 &auth_packet, err
189 ); 228 );
190 229
191 let res_json = warp::reply::json(&GradeCoinResponse { 230 let res_json = warp::reply::json(&GradeCoinResponse {
192 res: ResponseType::Error, 231 res: ResponseType::Error,
193 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(),
194 }); 234 });
195 235
196 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 236 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
197 } 237 }
198 }; 238 };
199 239
240 // we have a decrypted c field, create a string from the bytes mess
200 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()) {
201 Ok(text) => text, 242 Ok(text) => text,
202 Err(err) => { 243 Err(err) => {
@@ -207,13 +248,15 @@ pub async fn authenticate_user(
207 248
208 let res_json = warp::reply::json(&GradeCoinResponse { 249 let res_json = warp::reply::json(&GradeCoinResponse {
209 res: ResponseType::Error, 250 res: ResponseType::Error,
210 message: "Auth plaintext couldn't 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(),
211 }); 253 });
212 254
213 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 255 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
214 } 256 }
215 }; 257 };
216 258
259 // finally create an AuthRequest object from the plaintext
217 let request: AuthRequest = match serde_json::from_str(&utf8_auth_plaintext) { 260 let request: AuthRequest = match serde_json::from_str(&utf8_auth_plaintext) {
218 Ok(req) => req, 261 Ok(req) => req,
219 Err(err) => { 262 Err(err) => {
@@ -224,24 +267,32 @@ pub async fn authenticate_user(
224 267
225 let res_json = warp::reply::json(&GradeCoinResponse { 268 let res_json = warp::reply::json(&GradeCoinResponse {
226 res: ResponseType::Error, 269 res: ResponseType::Error,
227 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(),
228 }); 271 });
229 272
230 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 273 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
231 } 274 }
232 }; 275 };
233 276
234 let privileged_student_id = match MetuId::new(request.student_id, request.passwd) { 277 // is the student in AuthRequest privileged?
235 Some(id) => id, 278 let privileged_student_id =
236 None => { 279 match MetuId::new(request.student_id.clone(), request.passwd.clone()) {
237 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 {
238 res: ResponseType::Error, 287 res: ResponseType::Error,
239 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(),
240 }); 291 });
241 292
242 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 293 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
243 } 294 }
244 }; 295 };
245 296
246 // Students should be able to authenticate once 297 // Students should be able to authenticate once
247 { 298 {
@@ -264,7 +315,7 @@ pub async fn authenticate_user(
264 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() {
265 let res_json = warp::reply::json(&GradeCoinResponse { 316 let res_json = warp::reply::json(&GradeCoinResponse {
266 res: ResponseType::Error, 317 res: ResponseType::Error,
267 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(),
268 }); 319 });
269 320
270 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 321 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
@@ -279,23 +330,23 @@ pub async fn authenticate_user(
279 is_bot: false, 330 is_bot: false,
280 }; 331 };
281 332
282 debug!("New user authenticated themselves! {:?}", &new_user); 333 debug!("NEW USER: {:?}", &new_user);
283 334
335 // save the user to disk
284 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(),
285 user: User { 338 user: User {
286 user_id: new_user.user_id.clone(), 339 user_id: new_user.user_id.clone(),
287 public_key: new_user.public_key.clone(), 340 public_key: new_user.public_key.clone(),
288 balance: 0, 341 balance: new_user.balance,
289 is_bot: false, 342 is_bot: false,
290 }, 343 },
291 fingerprint: fingerprint.clone(),
292 }) 344 })
293 .unwrap(); 345 .unwrap();
294 346
295 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();
296 348
297 let mut userlist = db.users.write(); 349 let mut userlist = db.users.write();
298
299 userlist.insert(fingerprint.clone(), new_user); 350 userlist.insert(fingerprint.clone(), new_user);
300 351
301 let res_json = warp::reply::json(&GradeCoinResponse { 352 let res_json = warp::reply::json(&GradeCoinResponse {
@@ -312,7 +363,6 @@ pub async fn authenticate_user(
312/// GET /transaction 363/// GET /transaction
313/// Returns JSON array of transactions 364/// Returns JSON array of transactions
314pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> { 365pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> {
315 debug!("GET /transaction, list_transactions() is handling");
316 let mut result = HashMap::new(); 366 let mut result = HashMap::new();
317 367
318 let transactions = db.pending_transactions.read(); 368 let transactions = db.pending_transactions.read();
@@ -338,10 +388,9 @@ pub async fn propose_block(
338 token: String, 388 token: String,
339 db: Db, 389 db: Db,
340) -> Result<impl warp::Reply, warp::Rejection> { 390) -> Result<impl warp::Reply, warp::Rejection> {
341 debug!("POST /block, propose_block() is handling");
342
343 warn!("New block proposal: {:?}", &new_block); 391 warn!("New block proposal: {:?}", &new_block);
344 392
393 // Check if there are enough transactions in the block
345 if new_block.transaction_list.len() < BLOCK_TRANSACTION_COUNT as usize { 394 if new_block.transaction_list.len() < BLOCK_TRANSACTION_COUNT as usize {
346 debug!( 395 debug!(
347 "{} transactions offered, needed {}", 396 "{} transactions offered, needed {}",
@@ -360,21 +409,19 @@ pub async fn propose_block(
360 } 409 }
361 410
362 // proposer (first transaction fingerprint) checks 411 // proposer (first transaction fingerprint) checks
363
364 let pending_transactions = db.pending_transactions.upgradable_read(); 412 let pending_transactions = db.pending_transactions.upgradable_read();
365 413
366 let internal_user_fingerprint = match pending_transactions.get(&new_block.transaction_list[0]) { 414 let internal_user_fingerprint = match pending_transactions.get(&new_block.transaction_list[0]) {
367 Some(coinbase) => &coinbase.source, 415 Some(coinbase) => &coinbase.source,
368 None => { 416 None => {
369 debug!( 417 debug!(
370 "User with public key signature {:?} is not found in the database", 418 "Block proposer with public key signature {:?} is not found in the database",
371 new_block.transaction_list[0] 419 new_block.transaction_list[0]
372 ); 420 );
373 421
374 let res_json = warp::reply::json(&GradeCoinResponse { 422 let res_json = warp::reply::json(&GradeCoinResponse {
375 res: ResponseType::Error, 423 res: ResponseType::Error,
376 message: "User with that public key signature is not found in the database" 424 message: "Proposer of the first transaction is not found in the system".to_owned(),
377 .to_owned(),
378 }); 425 });
379 426
380 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 427 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
@@ -383,6 +430,7 @@ pub async fn propose_block(
383 430
384 let users_store = db.users.upgradable_read(); 431 let users_store = db.users.upgradable_read();
385 432
433 // this probably cannot fail, if the transaction is valid then it must've been checked already
386 let internal_user = match users_store.get(internal_user_fingerprint) { 434 let internal_user = match users_store.get(internal_user_fingerprint) {
387 Some(existing_user) => existing_user, 435 Some(existing_user) => existing_user,
388 None => { 436 None => {
@@ -407,7 +455,7 @@ pub async fn propose_block(
407 let token_payload = match authorize_proposer(token, &proposer_public_key) { 455 let token_payload = match authorize_proposer(token, &proposer_public_key) {
408 Ok(data) => data, 456 Ok(data) => data,
409 Err(below) => { 457 Err(below) => {
410 debug!("Something went wrong below {:?}", below); 458 debug!("Something went wrong with the JWT {:?}", below);
411 459
412 let res_json = warp::reply::json(&GradeCoinResponse { 460 let res_json = warp::reply::json(&GradeCoinResponse {
413 res: ResponseType::Error, 461 res: ResponseType::Error,
@@ -426,46 +474,36 @@ pub async fn propose_block(
426 ); 474 );
427 let res_json = warp::reply::json(&GradeCoinResponse { 475 let res_json = warp::reply::json(&GradeCoinResponse {
428 res: ResponseType::Error, 476 res: ResponseType::Error,
429 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(),
430 }); 479 });
431 480
432 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 481 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
433 } 482 }
434 483
435 // scope the HashSet 484 if !has_unique_elements(&new_block.transaction_list) {
436 { 485 debug!("Block contains duplicate transactions!");
437 let mut proposed_transactions = HashSet::new(); 486 let res_json = warp::reply::json(&GradeCoinResponse {
438 for tx in new_block.transaction_list.iter() { 487 res: ResponseType::Error,
439 proposed_transactions.insert(tx); 488 message: "Block cannot contain duplicate transactions".to_owned(),
440 } 489 });
441
442 if proposed_transactions.len() < BLOCK_TRANSACTION_COUNT as usize {
443 let res_json = warp::reply::json(&GradeCoinResponse {
444 res: ResponseType::Error,
445 message: format!(
446 "Block cannot contain less than {} unique transaction(s).",
447 BLOCK_TRANSACTION_COUNT
448 ),
449 });
450 490
451 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 491 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
452 }
453 } 492 }
454 493
455 // Scope the RwLocks, there are hashing stuff below
456
457 // Are transactions in the block valid? 494 // Are transactions in the block valid?
458 for transaction_hash in new_block.transaction_list.iter() { 495 for transaction_hash in new_block.transaction_list.iter() {
459 if !pending_transactions.contains_key(transaction_hash) { 496 if !pending_transactions.contains_key(transaction_hash) {
460 let res_json = warp::reply::json(&GradeCoinResponse { 497 let res_json = warp::reply::json(&GradeCoinResponse {
461 res: ResponseType::Error, 498 res: ResponseType::Error,
462 message: "Block contains unknown transaction".to_owned(), 499 message: "Block contains an unknown transaction".to_owned(),
463 }); 500 });
464 501
465 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 502 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
466 } 503 }
467 } 504 }
468 505
506 // hash the block ourselves to double check
469 let naked_block = NakedBlock { 507 let naked_block = NakedBlock {
470 transaction_list: new_block.transaction_list.clone(), 508 transaction_list: new_block.transaction_list.clone(),
471 nonce: new_block.nonce, 509 nonce: new_block.nonce,
@@ -509,6 +547,7 @@ pub async fn propose_block(
509 let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions); 547 let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions);
510 let mut users_store = RwLockUpgradableReadGuard::upgrade(users_store); 548 let mut users_store = RwLockUpgradableReadGuard::upgrade(users_store);
511 549
550 // Play out the transactions
512 for fingerprint in new_block.transaction_list.iter() { 551 for fingerprint in new_block.transaction_list.iter() {
513 if let Some(transaction) = pending_transactions.remove(fingerprint) { 552 if let Some(transaction) = pending_transactions.remove(fingerprint) {
514 let source = &transaction.source; 553 let source = &transaction.source;
@@ -519,7 +558,7 @@ pub async fn propose_block(
519 } 558 }
520 559
521 if let Some(to) = users_store.get_mut(target) { 560 if let Some(to) = users_store.get_mut(target) {
522 to.balance += transaction.amount + 1; 561 to.balance += transaction.amount + TX_TRAFFIC_REWARD;
523 } 562 }
524 563
525 // if the receiver is a bot, they will reciprocate 564 // if the receiver is a bot, they will reciprocate
@@ -539,6 +578,7 @@ pub async fn propose_block(
539 } 578 }
540 } 579 }
541 580
581 // Reward the block proposer
542 let coinbase_fingerprint = new_block.transaction_list.get(0).unwrap(); 582 let coinbase_fingerprint = new_block.transaction_list.get(0).unwrap();
543 583
544 if let Some(coinbase_user) = users_store.get_mut(coinbase_fingerprint) { 584 if let Some(coinbase_user) = users_store.get_mut(coinbase_fingerprint) {
@@ -562,7 +602,7 @@ pub async fn propose_block(
562 Ok(warp::reply::with_status( 602 Ok(warp::reply::with_status(
563 warp::reply::json(&GradeCoinResponse { 603 warp::reply::json(&GradeCoinResponse {
564 res: ResponseType::Success, 604 res: ResponseType::Success,
565 message: "Block accepted coinbase reward awarded".to_owned(), 605 message: "Block accepted, coinbase reward awarded".to_owned(),
566 }), 606 }),
567 StatusCode::CREATED, 607 StatusCode::CREATED,
568 )) 608 ))
@@ -582,8 +622,6 @@ pub async fn propose_transaction(
582 token: String, 622 token: String,
583 db: Db, 623 db: Db,
584) -> Result<impl warp::Reply, warp::Rejection> { 624) -> Result<impl warp::Reply, warp::Rejection> {
585 debug!("POST /transaction, propose_transaction() is handling");
586
587 warn!("New transaction proposal: {:?}", &new_transaction); 625 warn!("New transaction proposal: {:?}", &new_transaction);
588 626
589 let users_store = db.users.read(); 627 let users_store = db.users.read();
@@ -628,6 +666,25 @@ pub async fn propose_transaction(
628 } 666 }
629 }; 667 };
630 668
669 // is the target of the transaction in the system?
670 if !users_store.contains_key(&new_transaction.target) {
671 debug!(
672 "Target of the transaction is not in the system {}",
673 new_transaction.target
674 );
675
676 return Ok(warp::reply::with_status(
677 warp::reply::json(&GradeCoinResponse {
678 res: ResponseType::Error,
679 message: format!(
680 "Target of the transaction {} is not found in the system",
681 new_transaction.target
682 ),
683 }),
684 StatusCode::BAD_REQUEST,
685 ));
686 }
687
631 let transaction_id = calculate_transaction_id(&new_transaction.source, &new_transaction.target); 688 let transaction_id = calculate_transaction_id(&new_transaction.source, &new_transaction.target);
632 689
633 // OLD: Does this user have a pending transaction? 690 // OLD: Does this user have a pending transaction?
@@ -826,3 +883,12 @@ pub async fn user_list_handler(db: Db) -> Result<impl warp::Reply, warp::Rejecti
826 let res = template.render().unwrap(); 883 let res = template.render().unwrap();
827 Ok(warp::reply::html(res)) 884 Ok(warp::reply::html(res))
828} 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}