Skip to content

Commit

Permalink
Merge branch 'main' into add-kairos-test-utils
Browse files Browse the repository at this point in the history
  • Loading branch information
marijanp authored Mar 26, 2024
2 parents 4e3697e + 5290216 commit 68f3673
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 41 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,16 @@ To run a single check e.g. the format check, run:
```sh
nix build .#checks.<system>.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 this work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
47 changes: 27 additions & 20 deletions kairos-server/src/routes/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ pub struct Transfer {
pub async fn transfer_handler(
_: TransferPath,
State(state): State<LockedBatchState>,
Json(transfer): Json<Transfer>,
Json(Transfer {
from,
signature,
to,
amount,
}): Json<Transfer>,
) -> 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,
Expand All @@ -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.
Expand All @@ -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,
Expand Down
44 changes: 23 additions & 21 deletions kairos-server/src/routes/withdraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,37 @@ pub struct Withdrawal {
pub async fn withdraw_handler(
_: WithdrawPath,
State(state): State<LockedBatchState>,
Json(withdrawal): Json<Withdrawal>,
Json(Withdrawal {
public_key,
signature,
amount,
}): Json<Withdrawal>,
) -> 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,
)
Expand All @@ -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
);

Expand All @@ -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,
));
Expand Down

0 comments on commit 68f3673

Please sign in to comment.