diff options
| author | Yigit Sever | 2022-04-25 17:21:45 +0300 |
|---|---|---|
| committer | Yigit Sever | 2022-04-25 21:24:00 +0300 |
| commit | 5bc476d41b631a0b416df37d9b1153686f8d465b (patch) | |
| tree | 0fb7ffe77217f9a5aabfccbe39f1fbc0f5e9afb1 | |
| parent | da1b5cb64f0e20ff314b625dd423045268dab82f (diff) | |
| download | gradecoin-5bc476d41b631a0b416df37d9b1153686f8d465b.tar.gz gradecoin-5bc476d41b631a0b416df37d9b1153686f8d465b.tar.bz2 gradecoin-5bc476d41b631a0b416df37d9b1153686f8d465b.zip | |
Implement gas fee
| -rw-r--r-- | Cargo.lock | 2 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | config.yaml | 4 | ||||
| -rw-r--r-- | src/config.rs | 3 | ||||
| -rw-r--r-- | src/handlers.rs | 153 | ||||
| -rw-r--r-- | testnet.yaml | 2 |
6 files changed, 115 insertions, 51 deletions
| @@ -503,7 +503,7 @@ dependencies = [ | |||
| 503 | 503 | ||
| 504 | [[package]] | 504 | [[package]] |
| 505 | name = "gradecoin" | 505 | name = "gradecoin" |
| 506 | version = "0.2.1" | 506 | version = "0.3.0" |
| 507 | dependencies = [ | 507 | dependencies = [ |
| 508 | "aes", | 508 | "aes", |
| 509 | "askama", | 509 | "askama", |
| @@ -1,6 +1,6 @@ | |||
| 1 | [package] | 1 | [package] |
| 2 | name = "gradecoin" | 2 | name = "gradecoin" |
| 3 | version = "0.2.1" | 3 | version = "0.3.0" |
| 4 | authors = ["Yigit Sever <yigit@ceng.metu.edu.tr>", | 4 | authors = ["Yigit Sever <yigit@ceng.metu.edu.tr>", |
| 5 | "İlker Işık (necrashter) <iiilker99@gmail.com>", | 5 | "İlker Işık (necrashter) <iiilker99@gmail.com>", |
| 6 | "Alperen Keleş <alpkeles99@gmail.com>"] | 6 | "Alperen Keleş <alpkeles99@gmail.com>"] |
diff --git a/config.yaml b/config.yaml index 96fc02f..3d4bc72 100644 --- a/config.yaml +++ b/config.yaml | |||
| @@ -19,7 +19,9 @@ block_reward: 2 | |||
| 19 | tx_upper_limit: 4 | 19 | tx_upper_limit: 4 |
| 20 | tx_lower_limit: 1 | 20 | tx_lower_limit: 1 |
| 21 | # Transaction traffic reward | 21 | # Transaction traffic reward |
| 22 | tx_traffic_reward: 1 | 22 | tx_traffic_reward: 2 |
| 23 | # Transaction gas fee | ||
| 24 | tx_gas_fee: 2 | ||
| 23 | # The bots in the network | 25 | # The bots in the network |
| 24 | # Fingerprint: botconfig | 26 | # Fingerprint: botconfig |
| 25 | bots: | 27 | bots: |
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 | 667 | async 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 | ||
| 677 | pub 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 | ||
| 775 | pub 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 |
diff --git a/testnet.yaml b/testnet.yaml index d05ff21..00bc35b 100644 --- a/testnet.yaml +++ b/testnet.yaml | |||
| @@ -20,6 +20,8 @@ tx_upper_limit: 5 | |||
| 20 | tx_lower_limit: 1 | 20 | tx_lower_limit: 1 |
| 21 | # Transaction traffic reward | 21 | # Transaction traffic reward |
| 22 | tx_traffic_reward: 1 | 22 | tx_traffic_reward: 1 |
| 23 | # Transaction gas fee | ||
| 24 | tx_gas_fee: 2 | ||
| 23 | # The bots in the network | 25 | # The bots in the network |
| 24 | # Fingerprint: botconfig | 26 | # Fingerprint: botconfig |
| 25 | bots: | 27 | bots: |
