aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.rs3
-rw-r--r--src/handlers.rs153
2 files changed, 108 insertions, 48 deletions
diff --git a/src/config.rs b/src/config.rs
index 9986970..c0b8584 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -49,6 +49,9 @@ pub struct Config {
49 /// Coinbase reward 49 /// Coinbase reward
50 pub block_reward: u16, 50 pub block_reward: u16,
51 51
52 /// Transaction gas fee
53 pub tx_gas_fee: u16,
54
52 /// Transaction amount upper limit 55 /// Transaction amount upper limit
53 pub tx_upper_limit: u16, 56 pub tx_upper_limit: u16,
54 57
diff --git a/src/handlers.rs b/src/handlers.rs
index ae82441..09cd2a5 100644
--- a/src/handlers.rs
+++ b/src/handlers.rs
@@ -664,30 +664,17 @@ pub async fn propose_block(
664 )) 664 ))
665} 665}
666 666
667/// POST /transaction 667async fn deduct_gas_fee(
668/// 668 new_transaction: &Transaction,
669/// Handles the new transaction requests 669 token: &str,
670/// Can reject the block if;
671/// # Arguments
672/// * `new_transaction` - Valid JSON of a [`Transaction`]
673/// * `token` - An Authorization header value such as `Bearer aaa.bbb.ccc`
674/// * `db` - Global [`Db`] instance
675///
676#[allow(clippy::too_many_lines)] // temporary, should be refactored
677pub async fn propose_transaction(
678 new_transaction: Transaction,
679 token: String,
680 db: Db, 670 db: Db,
681) -> Result<impl warp::Reply, warp::Rejection> { 671) -> Option<warp::reply::WithStatus<warp::reply::Json>> {
682 warn!( 672 let mut users_store = db.users.write();
683 "[{}] New transaction proposal: {:?}",
684 db.config.name, &new_transaction
685 );
686
687 let users_store = db.users.read();
688 673
689 // Is this transaction from an authorized source? 674 // Is this transaction from an authorized source?
690 let internal_user = if let Some(existing_user) = users_store.get(&new_transaction.source) { 675 let mut internal_user: &mut User = if let Some(existing_user) =
676 users_store.get_mut(&new_transaction.source)
677 {
691 existing_user 678 existing_user
692 } else { 679 } else {
693 debug!( 680 debug!(
@@ -695,7 +682,7 @@ pub async fn propose_transaction(
695 new_transaction.source 682 new_transaction.source
696 ); 683 );
697 684
698 return Ok(warp::reply::with_status( 685 return Some(warp::reply::with_status(
699 warp::reply::json(&UserFeedback { 686 warp::reply::json(&UserFeedback {
700 res: ResponseType::Error, 687 res: ResponseType::Error,
701 message: "User with the given public key signature is not authorized".to_owned(), 688 message: "User with the given public key signature is not authorized".to_owned(),
@@ -704,29 +691,28 @@ pub async fn propose_transaction(
704 )); 691 ));
705 }; 692 };
706 693
694 // This check is early on because bots don't have public keys, avoiding undefined behaviour
707 if internal_user.is_bot { 695 if internal_user.is_bot {
708 debug!("Someone tried to send as the bot"); 696 debug!("Someone tried to send as a bot");
709 697
710 return Ok(warp::reply::with_status( 698 return Some(warp::reply::with_status(
711 warp::reply::json(&UserFeedback { 699 warp::reply::json(&UserFeedback {
712 res: ResponseType::Error, 700 res: ResponseType::Error,
713 message: "Don's send transactions on behalf of bots".to_owned(), 701 message: "Don't send transactions on behalf of bots".to_owned(),
714 }), 702 }),
715 StatusCode::BAD_REQUEST, 703 StatusCode::BAD_REQUEST,
716 )); 704 ));
717 } 705 }
718 706
719 // `internal_user` is an authenticated student and not a bot, can propose
720
721 // This public key was already written to the database, we can panic if it's not valid at 707 // This public key was already written to the database, we can panic if it's not valid at
722 // *this* point 708 // *this* point
723 let proposer_public_key = &internal_user.public_key; 709 let proposer_public_key = &internal_user.public_key;
724 710
725 let token_payload = match authorize_proposer(&token, proposer_public_key) { 711 let token_payload = match authorize_proposer(token, proposer_public_key) {
726 Ok(data) => data, 712 Ok(data) => data,
727 Err(below) => { 713 Err(below) => {
728 debug!("JWT Error: {:?}", below); 714 debug!("JWT Error: {:?}", below);
729 return Ok(warp::reply::with_status( 715 return Some(warp::reply::with_status(
730 warp::reply::json(&UserFeedback { 716 warp::reply::json(&UserFeedback {
731 res: ResponseType::Error, 717 res: ResponseType::Error,
732 message: below, 718 message: below,
@@ -736,6 +722,96 @@ pub async fn propose_transaction(
736 } 722 }
737 }; 723 };
738 724
725 // this transaction was already checked for correctness at custom_filters we can panic here if
726 // it has been changed since
727 let serd_tx = serde_json::to_string(&new_transaction).unwrap();
728
729 debug!("Taking the hash of {}", serd_tx);
730
731 let hashed_transaction = Md5::digest(serd_tx.as_bytes());
732
733 if token_payload.claims.tha != format!("{:x}", hashed_transaction) {
734 return Some(warp::reply::with_status(
735 warp::reply::json(&UserFeedback {
736 res: ResponseType::Error,
737 message: "The hash of the transaction did not match the hash given in JWT"
738 .to_owned(),
739 }),
740 StatusCode::BAD_REQUEST,
741 ));
742 }
743
744 // At this point we have authorized the user
745 // Deduct gas fee to process the transaction further
746 if internal_user.balance < db.config.tx_gas_fee {
747 debug!(
748 "User does not have enough balance ({}) to pay for the gas fee",
749 internal_user.balance
750 );
751 return Some(warp::reply::with_status(
752 warp::reply::json(&UserFeedback {
753 res: ResponseType::Error,
754 message: "You cannot afford the gas fee for this transaction".to_owned(),
755 }),
756 StatusCode::BAD_REQUEST,
757 ));
758 }
759
760 internal_user.balance -= db.config.tx_gas_fee;
761
762 None
763}
764
765/// POST /transaction
766///
767/// Handles the new transaction requests
768/// Can reject the block if;
769/// # Arguments
770/// * `new_transaction` - Valid JSON of a [`Transaction`]
771/// * `token` - An Authorization header value such as `Bearer aaa.bbb.ccc`
772/// * `db` - Global [`Db`] instance
773///
774#[allow(clippy::too_many_lines)] // temporary, should be refactored
775pub async fn propose_transaction(
776 new_transaction: Transaction,
777 token: String,
778 db: Db,
779) -> Result<impl warp::Reply, warp::Rejection> {
780 warn!(
781 "[{}] New transaction proposal: {:?}",
782 db.config.name, &new_transaction
783 );
784
785 if let Some(error) = deduct_gas_fee(&new_transaction, &token, db.clone()).await {
786 return Ok(error);
787 }
788
789 // Gas fee exists to discourage dumb bots
790 // Checks from this point on will be penalized as they already paid the gas fee but can still
791 // fail
792
793 let users_store = db.users.read();
794
795 // We _can_ get the internal user from deduct_gas_fee but that one is a mutable reference
796 // We only need an unmutable reference from here on out, so unless something better comes along
797 // this is how we get the second internal_user
798 let internal_user = if let Some(existing_user) = users_store.get(&new_transaction.source) {
799 existing_user
800 } else {
801 debug!(
802 "User with public key signature {:?} is not found in the database",
803 new_transaction.source
804 );
805
806 return Ok(warp::reply::with_status(
807 warp::reply::json(&UserFeedback {
808 res: ResponseType::Error,
809 message: "User with the given public key signature is not authorized".to_owned(),
810 }),
811 StatusCode::BAD_REQUEST,
812 ));
813 };
814
739 // is the target of the transaction in the system? 815 // is the target of the transaction in the system?
740 if !users_store.contains_key(&new_transaction.target) { 816 if !users_store.contains_key(&new_transaction.target) {
741 debug!( 817 debug!(
@@ -830,25 +906,6 @@ pub async fn propose_transaction(
830 )); 906 ));
831 } 907 }
832 908
833 // this transaction was already checked for correctness at custom_filters, we can panic here if
834 // it has been changed since
835
836 let serd_tx = serde_json::to_string(&new_transaction).unwrap();
837
838 debug!("Taking the hash of {}", serd_tx);
839
840 let hashed_transaction = Md5::digest(serd_tx.as_bytes());
841 if token_payload.claims.tha != format!("{:x}", hashed_transaction) {
842 return Ok(warp::reply::with_status(
843 warp::reply::json(&UserFeedback {
844 res: ResponseType::Error,
845 message: "The hash of the transaction did not match the hash given in JWT"
846 .to_owned(),
847 }),
848 StatusCode::BAD_REQUEST,
849 ));
850 }
851
852 warn!( 909 warn!(
853 "[{}] ACCEPTED TRANSACTION {:?}", 910 "[{}] ACCEPTED TRANSACTION {:?}",
854 db.config.name, new_transaction 911 db.config.name, new_transaction