Skip to content

Commit

Permalink
A simple mock of L2 logic without proofs or signatures
Browse files Browse the repository at this point in the history
Deposit is a big unanswered question
  • Loading branch information
Avi-D-coder committed Jan 22, 2024
1 parent 33f63eb commit b78dc74
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 54 deletions.
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions kairos-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ name = "kairos-server"

[dependencies]
axum = { version = "0.7", features = ["tracing"]}
thiserror = "1.0"
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
2 changes: 0 additions & 2 deletions kairos-server/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ use axum::{
response::{IntoResponse, Response},
};



#[derive(Debug)]
pub struct AppErr {
error: anyhow::Error,
Expand Down
28 changes: 4 additions & 24 deletions kairos-server/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,16 @@
pub mod errors;
pub mod routes;

use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
pub mod state;

use axum::{routing::post, Router};
use routes::{transfer::Transfer, *};
use tokio::sync::RwLock;
use routes::*;
use state::LockedBatchState;

pub use errors::AppErr;

type PublicKey = String;

pub struct BatchState {
pub balances: HashMap<PublicKey, u64>,
pub batch_epoch: u64,
/// The set of transfers that will be batched in the next epoch.
pub batched_transfers: HashSet<Transfer>,
}
impl BatchState {
pub fn new() -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(Self {
balances: HashMap::new(),
batch_epoch: 0,
batched_transfers: HashSet::new(),
}))
}
}

pub fn app_router(state: Arc<RwLock<BatchState>>) -> Router {
pub fn app_router(state: LockedBatchState) -> Router {
Router::new()
.route("/api/v1/mock/deposit", post(deposit))
.route("/api/v1/mock/withdraw", post(withdraw))
Expand Down
4 changes: 3 additions & 1 deletion kairos-server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::net::SocketAddr;

use kairos_server::state::BatchState;

#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
Expand All @@ -11,7 +13,7 @@ async fn main() {
})
});

let app = kairos_server::app_router(kairos_server::BatchState::new());
let app = kairos_server::app_router(BatchState::new());

let axum_addr = SocketAddr::from(([127, 0, 0, 1], axum_port));
tracing::info!("starting http server");
Expand Down
37 changes: 29 additions & 8 deletions kairos-server/src/routes/deposit.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,41 @@
use std::sync::Arc;
use std::ops::Deref;

use axum::{extract::State, Json};
use anyhow::anyhow;
use axum::{extract::State, http::StatusCode, Json};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;

use crate::{AppErr, BatchState, PublicKey};
use crate::{state::LockedBatchState, AppErr, PublicKey};

#[derive(Serialize, Deserialize)]
pub struct DepositRequest {
pub struct Deposit {
pub public_key: PublicKey,
pub amount: u64,
}

pub async fn deposit(
State(pool): State<Arc<RwLock<BatchState>>>,
Json(proof_request): Json<DepositRequest>,
state: State<LockedBatchState>,
Json(Deposit { public_key, amount }): Json<Deposit>,
) -> Result<(), AppErr> {
todo!("deposit")
tracing::info!("TODO: verifying deposit");

tracing::info!("TODO: adding deposit to batch");

let mut state = state.deref().write().await;
let account = state.balances.entry(public_key.clone());

let prior_balance = account.or_insert(0);
let updated_balance = prior_balance.checked_add(amount).ok_or_else(|| {
AppErr::set_status(
anyhow!("deposit would overflow account"),
StatusCode::CONFLICT,
)
})?;

tracing::info!(
"Updated account public_key={} balance={}",
public_key,
updated_balance
);

Ok(())
}
4 changes: 2 additions & 2 deletions kairos-server/src/routes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub mod deposit;
pub mod withdraw;
pub mod transfer;
pub mod withdraw;

pub use deposit::deposit;
pub use withdraw::withdraw;
pub use transfer::transfer;
pub use withdraw::withdraw;
97 changes: 89 additions & 8 deletions kairos-server/src/routes/transfer.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
use std::sync::Arc;

use axum::{extract::State, Json};
use anyhow::anyhow;
use axum::{extract::State, http::StatusCode, Json};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;

use crate::{AppErr, BatchState, PublicKey};
use crate::{state::LockedBatchState, AppErr, PublicKey};

#[derive(Serialize, Deserialize)]
pub struct Transfer {
Expand All @@ -20,8 +18,91 @@ pub struct TransferRequest {
}

pub async fn transfer(
State(pool): State<Arc<RwLock<BatchState>>>,
Json(proof_request): Json<TransferRequest>,
State(state): State<LockedBatchState>,
Json(TransferRequest {
transfer,
signature: _,
}): Json<TransferRequest>,
) -> Result<(), AppErr> {
todo!()
if transfer.amount == 0 {
return Err(AppErr::set_status(
anyhow!("transfer amount must be greater than 0"),
StatusCode::BAD_REQUEST,
));
}

tracing::info!("TODO: verifying transfer signature");

// 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?;

let mut state = state.write().await;
let from_balance = state.balances.get_mut(&transfer.from).ok_or_else(|| {
AppErr::set_status(
anyhow!(
"Sender no longer has an account.
The sender just removed all their funds."
),
StatusCode::CONFLICT,
)
})?;

*from_balance = from_balance.checked_sub(transfer.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
),
StatusCode::CONFLICT,
)
})?;

let to_balance = state
.balances
.entry(transfer.to.clone())
.or_insert_with(|| {
tracing::info!("creating new account for receiver");
0
});

*to_balance = to_balance.checked_add(transfer.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> {
let state = state.read().await;
let from_balance = state.balances.get(&transfer.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(|| {
AppErr::set_status(
anyhow!(
"Sender does not have sufficient funds, balance={}, transfer_amount={}",
from_balance,
transfer.amount
),
StatusCode::FORBIDDEN,
)
})?;

let to_balance = state.balances.get(&transfer.to).unwrap_or(&0);
if to_balance.checked_add(transfer.amount).is_none() {
return Err(AppErr::set_status(
anyhow!("Receiver balance overflow"),
StatusCode::CONFLICT,
));
}

Ok(())
}
85 changes: 76 additions & 9 deletions kairos-server/src/routes/withdraw.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,87 @@
use std::sync::Arc;

use axum::{extract::State, Json};
use anyhow::anyhow;
use axum::{extract::State, http::StatusCode, Json};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;

use crate::{AppErr, BatchState, PublicKey};
use crate::{state::LockedBatchState, AppErr, PublicKey};

#[derive(Serialize, Deserialize)]
pub struct WithdrawRequest {
pub struct Withdrawal {
pub public_key: PublicKey,
pub signature: String,
pub amount: u64,
}

pub async fn withdraw(
State(pool): State<Arc<RwLock<BatchState>>>,
Json(proof_request): Json<WithdrawRequest>,
State(state): State<LockedBatchState>,
Json(withdrawal): Json<Withdrawal>,
) -> Result<(), AppErr> {
tracing::info!("TODO: verifying withdrawal signature");

tracing::info!("verifying withdrawal sender has sufficient funds");
check_sender_funds(&state, &withdrawal).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.
The sender just removed all their funds."
),
StatusCode::CONFLICT,
)
})?;

let updated_balance = from_balance.checked_sub(withdrawal.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
),
StatusCode::CONFLICT,
)
})?;

*from_balance = updated_balance;

if updated_balance == 0 {
state.balances.remove(&withdrawal.public_key);
}

tracing::info!(
"Updated account public_key={} balance={}",
withdrawal.public_key,
updated_balance
);

Ok(())
}

async fn check_sender_funds(
state: &LockedBatchState,
withdrawal: &Withdrawal,
) -> Result<(), AppErr> {
todo!()
let state = state.read().await;
let from_balance = state.balances.get(&withdrawal.public_key).ok_or_else(|| {
AppErr::set_status(anyhow!("Withdrawer has no account."), StatusCode::CONFLICT)
})?;

if *from_balance < withdrawal.amount {
return Err(AppErr::set_status(
anyhow!(
"Withdrawer has insufficient funds, balance={}, withdrawal_amount={}.",
from_balance,
withdrawal.amount
),
StatusCode::FORBIDDEN,
));
}

Ok(())
}
Loading

0 comments on commit b78dc74

Please sign in to comment.