Skip to content

Commit

Permalink
Add transaction tests and fix bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
Avi-D-coder committed Jan 26, 2024
1 parent 3a878e9 commit 7acd1b9
Show file tree
Hide file tree
Showing 9 changed files with 369 additions and 361 deletions.
512 changes: 187 additions & 325 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions kairos-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,20 @@ name = "kairos-server"

[dependencies]
dotenvy = "0.15"
axum = { version = "0.7", features = ["tracing"]}
axum-extra = { version = "0.9", features = ["typed-routing", "typed-header", "json-deserializer"]}
axum = { version = "0.7", features = ["tracing"] }
axum-extra = { version = "0.9", features = [
"typed-routing",
"typed-header",
"json-deserializer",
] }
thiserror = "1.0"
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.35", features = ["full", "tracing", "macros"] }
tracing = "0.1"
tracing-subscriber = "0.3"
tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }

[dev-dependencies]
proptest = "1.4"
axum-test-helper = "0.3"
axum-test = "14"
13 changes: 5 additions & 8 deletions kairos-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,18 @@ pub mod routes;
pub mod state;

use axum::Router;
use axum_extra::routing::{RouterExt, TypedPath};
use axum_extra::routing::RouterExt;
use state::LockedBatchState;

pub use errors::AppErr;

type PublicKey = String;

#[derive(TypedPath)]
#[typed_path("/api/v1")]
pub struct ApiV1Path;
type Signature = String;

pub fn app_router(state: LockedBatchState) -> Router {
Router::new()
.typed_post(routes::deposit::handler)
.typed_post(routes::withdraw::handler)
.typed_post(routes::transfer::handler)
.typed_post(routes::deposit_handler)
.typed_post(routes::withdraw_handler)
.typed_post(routes::transfer_handler)
.with_state(state)
}
16 changes: 10 additions & 6 deletions kairos-server/src/routes/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ use anyhow::anyhow;
use axum::{extract::State, http::StatusCode, Json};
use axum_extra::routing::TypedPath;
use serde::{Deserialize, Serialize};
use tracing::*;

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

#[derive(TypedPath)]
#[typed_path("/deposit")]
#[derive(TypedPath, Debug, Clone, Copy)]
#[typed_path("/api/v1/deposit")]
pub struct DepositPath;

#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Deposit {
pub public_key: PublicKey,
pub amount: u64,
}

pub async fn handler(
#[instrument(level = "trace", skip(state), ret)]
pub async fn deposit_handler(
_: DepositPath,
state: State<LockedBatchState>,
Json(Deposit { public_key, amount }): Json<Deposit>,
Expand All @@ -29,14 +31,16 @@ pub async fn handler(
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(|| {
let balance = account.or_insert(0);
let updated_balance = balance.checked_add(amount).ok_or_else(|| {
AppErr::set_status(
anyhow!("deposit would overflow account"),
StatusCode::CONFLICT,
)
})?;

*balance = updated_balance;

tracing::info!(
"Updated account public_key={} balance={}",
public_key,
Expand Down
4 changes: 4 additions & 0 deletions kairos-server/src/routes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
pub mod deposit;
pub mod transfer;
pub mod withdraw;

pub use deposit::deposit_handler;
pub use transfer::transfer_handler;
pub use withdraw::withdraw_handler;
22 changes: 8 additions & 14 deletions kairos-server/src/routes/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,27 @@ use anyhow::anyhow;
use axum::{extract::State, http::StatusCode, Json};
use axum_extra::routing::TypedPath;
use serde::{Deserialize, Serialize};
use tracing::instrument;

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

#[derive(TypedPath)]
#[typed_path("/transfer")]
#[typed_path("/api/v1/transfer")]
pub struct TransferPath;

#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Transfer {
pub from: PublicKey,
pub signature: Signature,
pub to: PublicKey,
pub amount: u64,
}

#[derive(Serialize, Deserialize)]
pub struct TransferRequest {
transfer: Transfer,
signature: String,
}

pub async fn handler(
#[instrument(level = "trace", skip(state), ret)]
pub async fn transfer_handler(
_: TransferPath,
State(state): State<LockedBatchState>,
Json(TransferRequest {
transfer,
signature: _,
}): Json<TransferRequest>,
Json(transfer): Json<Transfer>,
) -> Result<(), AppErr> {
if transfer.amount == 0 {
return Err(AppErr::set_status(
Expand Down
10 changes: 6 additions & 4 deletions kairos-server/src/routes/withdraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ use anyhow::anyhow;
use axum::{extract::State, http::StatusCode, Json};
use axum_extra::routing::TypedPath;
use serde::{Deserialize, Serialize};
use tracing::*;

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

#[derive(TypedPath)]
#[typed_path("/withdraw")]
#[derive(Debug, TypedPath)]
#[typed_path("/api/v1/withdraw")]
pub struct WithdrawPath;

#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Withdrawal {
pub public_key: PublicKey,
pub signature: String,
pub amount: u64,
}

pub async fn handler(
#[instrument(level = "trace", skip(state), ret)]
pub async fn withdraw_handler(
_: WithdrawPath,
State(state): State<LockedBatchState>,
Json(withdrawal): Json<Withdrawal>,
Expand Down
1 change: 1 addition & 0 deletions kairos-server/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{

pub type LockedBatchState = Arc<RwLock<BatchState>>;

#[derive(Debug)]
pub struct BatchState {
pub balances: HashMap<PublicKey, u64>,
pub batch_epoch: u64,
Expand Down
140 changes: 140 additions & 0 deletions kairos-server/tests/transactions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use std::sync::OnceLock;

use axum_extra::routing::TypedPath;
use axum_test::{TestServer, TestServerConfig};
use kairos_server::{
routes::{
deposit::{Deposit, DepositPath},
transfer::{Transfer, TransferPath},
withdraw::{WithdrawPath, Withdrawal},
},
state::BatchState,
};
use tracing_subscriber::{prelude::*, EnvFilter};

static TEST_ENVIRONMENT: OnceLock<()> = OnceLock::new();

fn new_test_app() -> TestServer {
TEST_ENVIRONMENT.get_or_init(|| {
tracing_subscriber::registry()
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "trace".into()))
.with(tracing_subscriber::fmt::layer())
.init();
});
let config = TestServerConfig::builder().mock_transport().build();

TestServer::new_with_config(kairos_server::app_router(BatchState::new()), config).unwrap()
}

#[tokio::test]
async fn test_deposit_withdraw() {
let server = new_test_app();

let deposit = Deposit {
public_key: "alice_key".into(),
amount: 100,
};

// no arguments
server
.post(DepositPath.to_uri().path())
.await
.assert_status_failure();

// deposit
server
.post(DepositPath.to_uri().path())
.json(&deposit)
.await
.assert_status_success();

// wrong arguments
server
.post(WithdrawPath.to_uri().path())
.json(&deposit)
.await
.assert_status_failure();

let withdrawal = Withdrawal {
public_key: "alice_key".into(),
signature: "TODO".into(),
amount: 50,
};

// first withdrawal
server
.post(WithdrawPath.to_uri().path())
.json(&withdrawal)
.await
.assert_status_success();

// withdrawal with insufficient funds
server
.post(WithdrawPath.to_uri().path())
.json(&Withdrawal {
amount: 51,
..withdrawal.clone()
})
.await
.assert_status_failure();

// second withdrawal
server
.post(WithdrawPath.to_uri().path())
.json(&Withdrawal {
amount: 50,
..withdrawal.clone()
})
.await
.assert_status_success();

server
.post(WithdrawPath.to_uri().path())
.json(&withdrawal)
.await
.assert_status_failure();
}

#[tokio::test]
async fn test_deposit_transfer_withdraw() {
let server = new_test_app();

let deposit = Deposit {
public_key: "alice_key".into(),
amount: 100,
};

let transfer = Transfer {
from: "alice_key".into(),
signature: "TODO".into(),
to: "bob_key".into(),
amount: 50,
};

let withdrawal = Withdrawal {
public_key: "bob_key".into(),
signature: "TODO".into(),
amount: 50,
};

// deposit
server
.post(DepositPath.to_uri().path())
.json(&deposit)
.await
.assert_status_success();

// transfer
server
.post(TransferPath.to_uri().path())
.json(&transfer)
.await
.assert_status_success();

// withdraw
server
.post(WithdrawPath.to_uri().path())
.json(&withdrawal)
.await
.assert_status_success();
}

0 comments on commit 7acd1b9

Please sign in to comment.