From 5bc476d41b631a0b416df37d9b1153686f8d465b Mon Sep 17 00:00:00 2001 From: Yigit Sever Date: Mon, 25 Apr 2022 17:21:45 +0300 Subject: Implement gas fee --- Cargo.lock | 2 +- Cargo.toml | 2 +- config.yaml | 4 +- src/config.rs | 3 ++ src/handlers.rs | 153 ++++++++++++++++++++++++++++++++++++++------------------ testnet.yaml | 2 + 6 files changed, 115 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd1138e..d5ae5f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -503,7 +503,7 @@ dependencies = [ [[package]] name = "gradecoin" -version = "0.2.1" +version = "0.3.0" dependencies = [ "aes", "askama", diff --git a/Cargo.toml b/Cargo.toml index 1e1fcf5..03bfd01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gradecoin" -version = "0.2.1" +version = "0.3.0" authors = ["Yigit Sever ", "İlker Işık (necrashter) ", "Alperen Keleş "] 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 tx_upper_limit: 4 tx_lower_limit: 1 # Transaction traffic reward -tx_traffic_reward: 1 +tx_traffic_reward: 2 +# Transaction gas fee +tx_gas_fee: 2 # The bots in the network # Fingerprint: botconfig 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 { /// Coinbase reward pub block_reward: u16, + /// Transaction gas fee + pub tx_gas_fee: u16, + /// Transaction amount upper limit pub tx_upper_limit: u16, 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( )) } -/// POST /transaction -/// -/// Handles the new transaction requests -/// Can reject the block if; -/// # Arguments -/// * `new_transaction` - Valid JSON of a [`Transaction`] -/// * `token` - An Authorization header value such as `Bearer aaa.bbb.ccc` -/// * `db` - Global [`Db`] instance -/// -#[allow(clippy::too_many_lines)] // temporary, should be refactored -pub async fn propose_transaction( - new_transaction: Transaction, - token: String, +async fn deduct_gas_fee( + new_transaction: &Transaction, + token: &str, db: Db, -) -> Result { - warn!( - "[{}] New transaction proposal: {:?}", - db.config.name, &new_transaction - ); - - let users_store = db.users.read(); +) -> Option> { + let mut users_store = db.users.write(); // Is this transaction from an authorized source? - let internal_user = if let Some(existing_user) = users_store.get(&new_transaction.source) { + let mut internal_user: &mut User = if let Some(existing_user) = + users_store.get_mut(&new_transaction.source) + { existing_user } else { debug!( @@ -695,7 +682,7 @@ pub async fn propose_transaction( new_transaction.source ); - return Ok(warp::reply::with_status( + return Some(warp::reply::with_status( warp::reply::json(&UserFeedback { res: ResponseType::Error, message: "User with the given public key signature is not authorized".to_owned(), @@ -704,29 +691,28 @@ pub async fn propose_transaction( )); }; + // This check is early on because bots don't have public keys, avoiding undefined behaviour if internal_user.is_bot { - debug!("Someone tried to send as the bot"); + debug!("Someone tried to send as a bot"); - return Ok(warp::reply::with_status( + return Some(warp::reply::with_status( warp::reply::json(&UserFeedback { res: ResponseType::Error, - message: "Don's send transactions on behalf of bots".to_owned(), + message: "Don't send transactions on behalf of bots".to_owned(), }), StatusCode::BAD_REQUEST, )); } - // `internal_user` is an authenticated student and not a bot, can propose - // This public key was already written to the database, we can panic if it's not valid at // *this* point let proposer_public_key = &internal_user.public_key; - let token_payload = match authorize_proposer(&token, proposer_public_key) { + let token_payload = match authorize_proposer(token, proposer_public_key) { Ok(data) => data, Err(below) => { debug!("JWT Error: {:?}", below); - return Ok(warp::reply::with_status( + return Some(warp::reply::with_status( warp::reply::json(&UserFeedback { res: ResponseType::Error, message: below, @@ -736,6 +722,96 @@ pub async fn propose_transaction( } }; + // this transaction was already checked for correctness at custom_filters we can panic here if + // it has been changed since + let serd_tx = serde_json::to_string(&new_transaction).unwrap(); + + debug!("Taking the hash of {}", serd_tx); + + let hashed_transaction = Md5::digest(serd_tx.as_bytes()); + + if token_payload.claims.tha != format!("{:x}", hashed_transaction) { + return Some(warp::reply::with_status( + warp::reply::json(&UserFeedback { + res: ResponseType::Error, + message: "The hash of the transaction did not match the hash given in JWT" + .to_owned(), + }), + StatusCode::BAD_REQUEST, + )); + } + + // At this point we have authorized the user + // Deduct gas fee to process the transaction further + if internal_user.balance < db.config.tx_gas_fee { + debug!( + "User does not have enough balance ({}) to pay for the gas fee", + internal_user.balance + ); + return Some(warp::reply::with_status( + warp::reply::json(&UserFeedback { + res: ResponseType::Error, + message: "You cannot afford the gas fee for this transaction".to_owned(), + }), + StatusCode::BAD_REQUEST, + )); + } + + internal_user.balance -= db.config.tx_gas_fee; + + None +} + +/// POST /transaction +/// +/// Handles the new transaction requests +/// Can reject the block if; +/// # Arguments +/// * `new_transaction` - Valid JSON of a [`Transaction`] +/// * `token` - An Authorization header value such as `Bearer aaa.bbb.ccc` +/// * `db` - Global [`Db`] instance +/// +#[allow(clippy::too_many_lines)] // temporary, should be refactored +pub async fn propose_transaction( + new_transaction: Transaction, + token: String, + db: Db, +) -> Result { + warn!( + "[{}] New transaction proposal: {:?}", + db.config.name, &new_transaction + ); + + if let Some(error) = deduct_gas_fee(&new_transaction, &token, db.clone()).await { + return Ok(error); + } + + // Gas fee exists to discourage dumb bots + // Checks from this point on will be penalized as they already paid the gas fee but can still + // fail + + let users_store = db.users.read(); + + // We _can_ get the internal user from deduct_gas_fee but that one is a mutable reference + // We only need an unmutable reference from here on out, so unless something better comes along + // this is how we get the second internal_user + let internal_user = if let Some(existing_user) = users_store.get(&new_transaction.source) { + existing_user + } else { + debug!( + "User with public key signature {:?} is not found in the database", + new_transaction.source + ); + + return Ok(warp::reply::with_status( + warp::reply::json(&UserFeedback { + res: ResponseType::Error, + message: "User with the given public key signature is not authorized".to_owned(), + }), + StatusCode::BAD_REQUEST, + )); + }; + // is the target of the transaction in the system? if !users_store.contains_key(&new_transaction.target) { debug!( @@ -830,25 +906,6 @@ pub async fn propose_transaction( )); } - // this transaction was already checked for correctness at custom_filters, we can panic here if - // it has been changed since - - let serd_tx = serde_json::to_string(&new_transaction).unwrap(); - - debug!("Taking the hash of {}", serd_tx); - - let hashed_transaction = Md5::digest(serd_tx.as_bytes()); - if token_payload.claims.tha != format!("{:x}", hashed_transaction) { - return Ok(warp::reply::with_status( - warp::reply::json(&UserFeedback { - res: ResponseType::Error, - message: "The hash of the transaction did not match the hash given in JWT" - .to_owned(), - }), - StatusCode::BAD_REQUEST, - )); - } - warn!( "[{}] ACCEPTED TRANSACTION {:?}", 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 tx_lower_limit: 1 # Transaction traffic reward tx_traffic_reward: 1 +# Transaction gas fee +tx_gas_fee: 2 # The bots in the network # Fingerprint: botconfig bots: -- cgit v1.2.3-70-g09d2