aboutsummaryrefslogtreecommitdiffstats
path: root/src/handlers.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/handlers.rs')
-rw-r--r--src/handlers.rs200
1 files changed, 114 insertions, 86 deletions
diff --git a/src/handlers.rs b/src/handlers.rs
index ca41b61..123f70e 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};
@@ -259,8 +260,7 @@ pub async fn authenticate_user(
259 } 260 }
260 } 261 }
261 262
262 // We're using this as the validator 263 // We're using this as the validator instead of anything reasonable
263 // I hate myself
264 if DecodingKey::from_rsa_pem(request.public_key.as_bytes()).is_err() { 264 if DecodingKey::from_rsa_pem(request.public_key.as_bytes()).is_err() {
265 let res_json = warp::reply::json(&GradeCoinResponse { 265 let res_json = warp::reply::json(&GradeCoinResponse {
266 res: ResponseType::Error, 266 res: ResponseType::Error,
@@ -311,7 +311,6 @@ pub async fn authenticate_user(
311 311
312/// GET /transaction 312/// GET /transaction
313/// Returns JSON array of transactions 313/// Returns JSON array of transactions
314/// Cannot fail
315pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> { 314pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> {
316 debug!("GET /transaction, list_transactions() is handling"); 315 debug!("GET /transaction, list_transactions() is handling");
317 let mut result = HashMap::new(); 316 let mut result = HashMap::new();
@@ -341,8 +340,6 @@ pub async fn propose_block(
341) -> Result<impl warp::Reply, warp::Rejection> { 340) -> Result<impl warp::Reply, warp::Rejection> {
342 debug!("POST /block, propose_block() is handling"); 341 debug!("POST /block, propose_block() is handling");
343 342
344 let users_store = db.users.upgradable_read();
345
346 warn!("New block proposal: {:?}", &new_block); 343 warn!("New block proposal: {:?}", &new_block);
347 344
348 if new_block.transaction_list.len() < BLOCK_TRANSACTION_COUNT as usize { 345 if new_block.transaction_list.len() < BLOCK_TRANSACTION_COUNT as usize {
@@ -363,7 +360,30 @@ pub async fn propose_block(
363 } 360 }
364 361
365 // proposer (first transaction fingerprint) checks 362 // proposer (first transaction fingerprint) checks
366 let internal_user = match users_store.get(&new_block.transaction_list[0]) { 363
364 let pending_transactions = db.pending_transactions.upgradable_read();
365
366 let internal_user_fingerprint = match pending_transactions.get(&new_block.transaction_list[0]) {
367 Some(coinbase) => &coinbase.source,
368 None => {
369 debug!(
370 "User with public key signature {:?} is not found in the database",
371 new_block.transaction_list[0]
372 );
373
374 let res_json = warp::reply::json(&GradeCoinResponse {
375 res: ResponseType::Error,
376 message: "User with that public key signature is not found in the database"
377 .to_owned(),
378 });
379
380 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
381 }
382 };
383
384 let users_store = db.users.upgradable_read();
385
386 let internal_user = match users_store.get(internal_user_fingerprint) {
367 Some(existing_user) => existing_user, 387 Some(existing_user) => existing_user,
368 None => { 388 None => {
369 debug!( 389 debug!(
@@ -433,19 +453,16 @@ pub async fn propose_block(
433 } 453 }
434 454
435 // Scope the RwLocks, there are hashing stuff below 455 // Scope the RwLocks, there are hashing stuff below
436 {
437 let pending_transactions = db.pending_transactions.read();
438 456
439 // Are transactions in the block valid? 457 // Are transactions in the block valid?
440 for transaction_hash in new_block.transaction_list.iter() { 458 for transaction_hash in new_block.transaction_list.iter() {
441 if !pending_transactions.contains_key(transaction_hash) { 459 if !pending_transactions.contains_key(transaction_hash) {
442 let res_json = warp::reply::json(&GradeCoinResponse { 460 let res_json = warp::reply::json(&GradeCoinResponse {
443 res: ResponseType::Error, 461 res: ResponseType::Error,
444 message: "Block contains unknown transaction".to_owned(), 462 message: "Block contains unknown transaction".to_owned(),
445 }); 463 });
446 464
447 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 465 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
448 }
449 } 466 }
450 } 467 }
451 468
@@ -487,9 +504,9 @@ pub async fn propose_block(
487 // All clear, block accepted! 504 // All clear, block accepted!
488 warn!("ACCEPTED BLOCK {:?}", new_block); 505 warn!("ACCEPTED BLOCK {:?}", new_block);
489 506
490 // Scope the pending_transactions 507 // Scope the read guards
491 { 508 {
492 let mut pending_transactions = db.pending_transactions.write(); 509 let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions);
493 let mut users_store = RwLockUpgradableReadGuard::upgrade(users_store); 510 let mut users_store = RwLockUpgradableReadGuard::upgrade(users_store);
494 511
495 for fingerprint in new_block.transaction_list.iter() { 512 for fingerprint in new_block.transaction_list.iter() {
@@ -502,7 +519,22 @@ pub async fn propose_block(
502 } 519 }
503 520
504 if let Some(to) = users_store.get_mut(target) { 521 if let Some(to) = users_store.get_mut(target) {
505 to.balance += transaction.amount; 522 to.balance += transaction.amount + 1;
523 }
524
525 // if the receiver is a bot, they will reciprocate
526 if users_store.get(target).unwrap().is_bot {
527 let transaction_id =
528 calculate_transaction_id(&transaction.target, &transaction.source);
529 pending_transactions.insert(
530 transaction_id,
531 Transaction {
532 source: target.to_owned(),
533 target: source.to_owned(),
534 amount: transaction.amount,
535 timestamp: Utc::now().naive_local(),
536 },
537 );
506 } 538 }
507 } 539 }
508 } 540 }
@@ -557,12 +589,12 @@ pub async fn propose_transaction(
557 let users_store = db.users.read(); 589 let users_store = db.users.read();
558 590
559 // Is this transaction from an authorized source? 591 // Is this transaction from an authorized source?
560 let internal_user = match users_store.get(&new_transaction.by) { 592 let internal_user = match users_store.get(&new_transaction.source) {
561 Some(existing_user) => existing_user, 593 Some(existing_user) => existing_user,
562 None => { 594 None => {
563 debug!( 595 debug!(
564 "User with public key signature {:?} is not found in the database", 596 "User with public key signature {:?} is not found in the database",
565 new_transaction.by 597 new_transaction.source
566 ); 598 );
567 599
568 return Ok(warp::reply::with_status( 600 return Ok(warp::reply::with_status(
@@ -578,11 +610,41 @@ pub async fn propose_transaction(
578 610
579 // `internal_user` is an authenticated student, can propose 611 // `internal_user` is an authenticated student, can propose
580 612
581 // Does this user have a pending transaction? 613 // This public key was already written to the database, we can panic if it's not valid at
614 // *this* point
615 let proposer_public_key = &internal_user.public_key;
616
617 let token_payload = match authorize_proposer(token, &proposer_public_key) {
618 Ok(data) => data,
619 Err(below) => {
620 debug!("JWT Error: {:?}", below);
621 return Ok(warp::reply::with_status(
622 warp::reply::json(&GradeCoinResponse {
623 res: ResponseType::Error,
624 message: below,
625 }),
626 StatusCode::BAD_REQUEST,
627 ));
628 }
629 };
630
631 let transaction_id = calculate_transaction_id(&new_transaction.source, &new_transaction.target);
632
633 // OLD: Does this user have a pending transaction?
634 // NEW: Is this source:target pair unqiue?
582 { 635 {
583 let transactions = db.pending_transactions.read(); 636 let transactions = db.pending_transactions.read();
584 if transactions.contains_key(&*new_transaction.by.to_owned()) { 637 debug!(
585 debug!("{:?} already has a pending transaction", new_transaction.by); 638 "This is a transaction from {} to {}",
639 new_transaction.source, new_transaction.target,
640 );
641
642 if transactions.contains_key(&transaction_id) {
643 debug!(
644 "this source/target combination {} already has a pending transaction",
645 transaction_id
646 );
647
586 return Ok(warp::reply::with_status( 648 return Ok(warp::reply::with_status(
587 warp::reply::json(&GradeCoinResponse { 649 warp::reply::json(&GradeCoinResponse {
588 res: ResponseType::Error, 650 res: ResponseType::Error,
@@ -593,6 +655,18 @@ pub async fn propose_transaction(
593 } 655 }
594 } 656 }
595 657
658 if new_transaction.source == new_transaction.target {
659 debug!("transaction source and target are the same",);
660
661 return Ok(warp::reply::with_status(
662 warp::reply::json(&GradeCoinResponse {
663 res: ResponseType::Error,
664 message: "transaction to yourself, you had to try didn't you? :)".to_owned(),
665 }),
666 StatusCode::BAD_REQUEST,
667 ));
668 }
669
596 // Is transaction amount within bounds 670 // Is transaction amount within bounds
597 if new_transaction.amount > TX_UPPER_LIMIT { 671 if new_transaction.amount > TX_UPPER_LIMIT {
598 debug!( 672 debug!(
@@ -608,76 +682,22 @@ pub async fn propose_transaction(
608 )); 682 ));
609 } 683 }
610 684
611 if new_transaction.by == new_transaction.source { 685 // check if user can afford the transaction
612 // check if user can afford the transaction 686 if internal_user.balance < new_transaction.amount {
613 if internal_user.balance < new_transaction.amount {
614 debug!(
615 "User does not have enough balance ({}) for this TX {}",
616 internal_user.balance, new_transaction.amount
617 );
618 return Ok(warp::reply::with_status(
619 warp::reply::json(&GradeCoinResponse {
620 res: ResponseType::Error,
621 message:
622 "User does not have enough balance in their account for this transaction"
623 .to_owned(),
624 }),
625 StatusCode::BAD_REQUEST,
626 ));
627 }
628 } else if new_transaction.by == new_transaction.target {
629 // Only transactions FROM bank could appear here
630
631 if new_transaction.source
632 != "31415926535897932384626433832795028841971693993751058209749445923"
633 {
634 debug!(
635 "Extortion attempt - between {} and {}",
636 new_transaction.source, new_transaction.target
637 );
638 return Ok(warp::reply::with_status(
639 warp::reply::json(&GradeCoinResponse {
640 res: ResponseType::Error,
641 message: "Transactions cannot extort Gradecoin from unsuspecting users"
642 .to_owned(),
643 }),
644 StatusCode::BAD_REQUEST,
645 ));
646 }
647 } else {
648 debug!( 687 debug!(
649 "Attempt to transact between two unrelated parties - {} and {}", 688 "User does not have enough balance ({}) for this TX {}",
650 new_transaction.source, new_transaction.target 689 internal_user.balance, new_transaction.amount
651 ); 690 );
652 return Ok(warp::reply::with_status( 691 return Ok(warp::reply::with_status(
653 warp::reply::json(&GradeCoinResponse { 692 warp::reply::json(&GradeCoinResponse {
654 res: ResponseType::Error, 693 res: ResponseType::Error,
655 message: "Transactions cannot be proposed on behalf of someone else".to_owned(), 694 message: "User does not have enough balance in their account for this transaction"
695 .to_owned(),
656 }), 696 }),
657 StatusCode::BAD_REQUEST, 697 StatusCode::BAD_REQUEST,
658 )); 698 ));
659 } 699 }
660 700
661 // This public key was already written to the database, we can panic if it's not valid at
662 // *this* point
663 let proposer_public_key = &internal_user.public_key;
664
665 let token_payload = match authorize_proposer(token, &proposer_public_key) {
666 Ok(data) => data,
667 Err(below) => {
668 debug!("Something went wrong at JWT {:?}", below);
669 return Ok(warp::reply::with_status(
670 warp::reply::json(&GradeCoinResponse {
671 res: ResponseType::Error,
672 message: below,
673 }),
674 StatusCode::BAD_REQUEST,
675 ));
676 }
677 };
678
679 // authorized for transaction proposal
680
681 // this transaction was already checked for correctness at custom_filters, we can panic here if 701 // this transaction was already checked for correctness at custom_filters, we can panic here if
682 // it has been changed since 702 // it has been changed since
683 703
@@ -701,7 +721,7 @@ pub async fn propose_transaction(
701 721
702 let mut transactions = db.pending_transactions.write(); 722 let mut transactions = db.pending_transactions.write();
703 723
704 transactions.insert(new_transaction.by.to_owned(), new_transaction); 724 transactions.insert(transaction_id, new_transaction);
705 725
706 Ok(warp::reply::with_status( 726 Ok(warp::reply::with_status(
707 warp::reply::json(&GradeCoinResponse { 727 warp::reply::json(&GradeCoinResponse {
@@ -772,6 +792,12 @@ fn authorize_proposer(jwt_token: String, user_pem: &str) -> Result<TokenData<Cla
772 Ok(token_payload) 792 Ok(token_payload)
773} 793}
774 794
795fn calculate_transaction_id(source: &str, target: &str) -> String {
796 let long_fingerprint = format!("{}{}", source, target);
797 let id = format!("{:x}", Sha256::digest(long_fingerprint.as_bytes()));
798 id
799}
800
775#[derive(Template)] 801#[derive(Template)]
776#[template(path = "list.html")] 802#[template(path = "list.html")]
777struct UserTemplate<'a> { 803struct UserTemplate<'a> {
@@ -781,6 +807,7 @@ struct UserTemplate<'a> {
781struct DisplayUsers { 807struct DisplayUsers {
782 fingerprint: String, 808 fingerprint: String,
783 balance: u16, 809 balance: u16,
810 is_bot: bool,
784} 811}
785 812
786pub async fn user_list_handler(db: Db) -> Result<impl warp::Reply, warp::Rejection> { 813pub async fn user_list_handler(db: Db) -> Result<impl warp::Reply, warp::Rejection> {
@@ -791,6 +818,7 @@ pub async fn user_list_handler(db: Db) -> Result<impl warp::Reply, warp::Rejecti
791 sane_users.push(DisplayUsers { 818 sane_users.push(DisplayUsers {
792 fingerprint: fingerprint.to_owned(), 819 fingerprint: fingerprint.to_owned(),
793 balance: user.balance, 820 balance: user.balance,
821 is_bot: user.is_bot,
794 }); 822 });
795 } 823 }
796 824