From dc18956dbf70d35d17eeef9a9234de1893329064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Wed, 20 Mar 2024 12:09:42 +0100 Subject: [PATCH 1/4] Add license clarification. --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 27ad2274..d79dd742 100644 --- a/README.md +++ b/README.md @@ -80,3 +80,16 @@ To run a single check e.g. the format check, run: ```sh nix build .#checks..treefmt ``` + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. From 53d99c4772e993ec0ef1474f74245e3960ce9dfa Mon Sep 17 00:00:00 2001 From: koxu1996 Date: Mon, 25 Mar 2024 18:04:01 +0100 Subject: [PATCH 2/4] Enhance clarity on 'the work' in license. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marijan Petričević --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d79dd742..b4cd0615 100644 --- a/README.md +++ b/README.md @@ -92,4 +92,4 @@ at your option. ### Contribution -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. From 17fcb9a3f898338c2c2a5c0c19d4dea8cbf49f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 26 Mar 2024 13:27:38 +0100 Subject: [PATCH 3/4] Decouple `Transfer` struture from request handling. We follow the pattern from deposit handler, where body type is not tighly coupled with all helper functions. --- kairos-server/src/routes/transfer.rs | 47 ++++++++++++++++------------ 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/kairos-server/src/routes/transfer.rs b/kairos-server/src/routes/transfer.rs index 3c2ba15c..dc18468a 100644 --- a/kairos-server/src/routes/transfer.rs +++ b/kairos-server/src/routes/transfer.rs @@ -22,9 +22,14 @@ pub struct Transfer { pub async fn transfer_handler( _: TransferPath, State(state): State, - Json(transfer): Json, + Json(Transfer { + from, + signature, + to, + amount, + }): Json, ) -> Result<(), AppErr> { - if transfer.amount == 0 { + if amount == 0 { return Err(AppErr::set_status( anyhow!("transfer amount must be greater than 0"), StatusCode::BAD_REQUEST, @@ -36,10 +41,10 @@ pub async fn transfer_handler( // We pre-check this read-only to error early without acquiring the write lock. // This prevents a DoS attack exploiting the write lock. tracing::info!("verifying transfer sender has sufficient funds"); - check_sender_funds(&state, &transfer).await?; + check_sender_funds(&state, &from, amount, &to).await?; let mut state = state.write().await; - let from_balance = state.balances.get_mut(&transfer.from).ok_or_else(|| { + let from_balance = state.balances.get_mut(&from).ok_or_else(|| { AppErr::set_status( anyhow!( "Sender no longer has an account. @@ -49,55 +54,57 @@ pub async fn transfer_handler( ) })?; - *from_balance = from_balance.checked_sub(transfer.amount).ok_or_else(|| { + *from_balance = from_balance.checked_sub(amount).ok_or_else(|| { AppErr::set_status( anyhow!( "Sender no longer has sufficient funds, balance={}, transfer_amount={}. The sender just moved their funds in a concurrent request", from_balance, - transfer.amount + amount ), StatusCode::CONFLICT, ) })?; - let to_balance = state - .balances - .entry(transfer.to.clone()) - .or_insert_with(|| { - tracing::info!("creating new account for receiver"); - 0 - }); + let to_balance = state.balances.entry(to.clone()).or_insert_with(|| { + tracing::info!("creating new account for receiver"); + 0 + }); - *to_balance = to_balance.checked_add(transfer.amount).ok_or_else(|| { + *to_balance = to_balance.checked_add(amount).ok_or_else(|| { AppErr::set_status(anyhow!("Receiver balance overflow"), StatusCode::CONFLICT) })?; Ok(()) } -async fn check_sender_funds(state: &LockedBatchState, transfer: &Transfer) -> Result<(), AppErr> { +async fn check_sender_funds( + state: &LockedBatchState, + from: &PublicKey, + amount: u64, + to: &PublicKey, +) -> Result<(), AppErr> { let state = state.read().await; - let from_balance = state.balances.get(&transfer.from).ok_or_else(|| { + let from_balance = state.balances.get(from).ok_or_else(|| { AppErr::set_status( anyhow!("Sender does not have an account"), StatusCode::BAD_REQUEST, ) })?; - from_balance.checked_sub(transfer.amount).ok_or_else(|| { + from_balance.checked_sub(amount).ok_or_else(|| { AppErr::set_status( anyhow!( "Sender does not have sufficient funds, balance={}, transfer_amount={}", from_balance, - transfer.amount + amount ), StatusCode::FORBIDDEN, ) })?; - let to_balance = state.balances.get(&transfer.to).unwrap_or(&0); - if to_balance.checked_add(transfer.amount).is_none() { + let to_balance = state.balances.get(to).unwrap_or(&0); + if to_balance.checked_add(amount).is_none() { return Err(AppErr::set_status( anyhow!("Receiver balance overflow"), StatusCode::CONFLICT, From cf3b9fc0f0725b0076414e67488553634b629670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 26 Mar 2024 13:32:16 +0100 Subject: [PATCH 4/4] Decouple `Withdraw` structure from request handling. --- kairos-server/src/routes/withdraw.rs | 44 +++++++++++++++------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/kairos-server/src/routes/withdraw.rs b/kairos-server/src/routes/withdraw.rs index 3fb72bc5..7f746331 100644 --- a/kairos-server/src/routes/withdraw.rs +++ b/kairos-server/src/routes/withdraw.rs @@ -21,36 +21,37 @@ pub struct Withdrawal { pub async fn withdraw_handler( _: WithdrawPath, State(state): State, - Json(withdrawal): Json, + Json(Withdrawal { + public_key, + signature, + amount, + }): Json, ) -> Result<(), AppErr> { tracing::info!("TODO: verifying withdrawal signature"); tracing::info!("verifying withdrawal sender has sufficient funds"); - check_sender_funds(&state, &withdrawal).await?; + check_sender_funds(&state, &public_key, amount).await?; tracing::info!("TODO: adding withdrawal to batch"); let mut state = state.write().await; - let from_balance = state - .balances - .get_mut(&withdrawal.public_key) - .ok_or_else(|| { - AppErr::set_status( - anyhow!( - "Sender no longer has an account. + let from_balance = state.balances.get_mut(&public_key).ok_or_else(|| { + AppErr::set_status( + anyhow!( + "Sender no longer has an account. The sender just removed all their funds." - ), - StatusCode::CONFLICT, - ) - })?; + ), + StatusCode::CONFLICT, + ) + })?; - let updated_balance = from_balance.checked_sub(withdrawal.amount).ok_or_else(|| { + let updated_balance = from_balance.checked_sub(amount).ok_or_else(|| { AppErr::set_status( anyhow!( "Sender no longer has sufficient funds, balance={}, withdrawal_amount={}. The sender just moved their funds in a concurrent request", from_balance, - withdrawal.amount + amount ), StatusCode::CONFLICT, ) @@ -59,12 +60,12 @@ pub async fn withdraw_handler( *from_balance = updated_balance; if updated_balance == 0 { - state.balances.remove(&withdrawal.public_key); + state.balances.remove(&public_key); } tracing::info!( "Updated account public_key={:?} balance={}", - withdrawal.public_key, + public_key, updated_balance ); @@ -73,19 +74,20 @@ pub async fn withdraw_handler( async fn check_sender_funds( state: &LockedBatchState, - withdrawal: &Withdrawal, + public_key: &PublicKey, + amount: u64, ) -> Result<(), AppErr> { let state = state.read().await; - let from_balance = state.balances.get(&withdrawal.public_key).ok_or_else(|| { + let from_balance = state.balances.get(public_key).ok_or_else(|| { AppErr::set_status(anyhow!("Withdrawer has no account."), StatusCode::CONFLICT) })?; - if *from_balance < withdrawal.amount { + if *from_balance < amount { return Err(AppErr::set_status( anyhow!( "Withdrawer has insufficient funds, balance={}, withdrawal_amount={}.", from_balance, - withdrawal.amount + amount ), StatusCode::FORBIDDEN, ));