diff options
Diffstat (limited to 'src/handlers.rs')
-rw-r--r-- | src/handlers.rs | 200 |
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; | |||
4 | use blake2::{Blake2s, Digest}; | 4 | use blake2::{Blake2s, Digest}; |
5 | use block_modes::block_padding::Pkcs7; | 5 | use block_modes::block_padding::Pkcs7; |
6 | use block_modes::{BlockMode, Cbc}; | 6 | use block_modes::{BlockMode, Cbc}; |
7 | use chrono::Utc; | ||
7 | use jsonwebtoken::errors::ErrorKind; | 8 | use jsonwebtoken::errors::ErrorKind; |
8 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; | 9 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; |
9 | use log::{debug, warn}; | 10 | use 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 | ||
315 | pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> { | 314 | pub 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 | ||
795 | fn 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")] |
777 | struct UserTemplate<'a> { | 803 | struct UserTemplate<'a> { |
@@ -781,6 +807,7 @@ struct UserTemplate<'a> { | |||
781 | struct DisplayUsers { | 807 | struct DisplayUsers { |
782 | fingerprint: String, | 808 | fingerprint: String, |
783 | balance: u16, | 809 | balance: u16, |
810 | is_bot: bool, | ||
784 | } | 811 | } |
785 | 812 | ||
786 | pub async fn user_list_handler(db: Db) -> Result<impl warp::Reply, warp::Rejection> { | 813 | pub 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 | ||