Skip to content

Commit

Permalink
Merge branch 'main' into add-coverage-report
Browse files Browse the repository at this point in the history
  • Loading branch information
marijanp authored Mar 27, 2024
2 parents 0008b8c + 5fd9c57 commit 4f79d17
Show file tree
Hide file tree
Showing 13 changed files with 1,683 additions and 113 deletions.
1,186 changes: 1,118 additions & 68 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"kairos-cli",
"kairos-server",
"kairos-tx",
"kairos-test-utils",
]

[workspace.package]
Expand Down
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.
78 changes: 78 additions & 0 deletions flake.lock

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

18 changes: 18 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
advisory-db.flake = false;
risc0pkgs.url = "github:cspr-rad/risc0pkgs";
risc0pkgs.inputs.nixpkgs.follows = "nixpkgs";
csprpkgs.url = "github:cspr-rad/csprpkgs/add-cctl";
csprpkgs.inputs.nixpkgs.follows = "nixpkgs";
};

outputs = inputs@{ self, flake-parts, treefmt-nix, ... }:
Expand Down Expand Up @@ -58,6 +60,11 @@
openssl.dev
] ++ lib.optionals stdenv.isDarwin [
libiconv
darwin.apple_sdk.frameworks.Security
darwin.apple_sdk.frameworks.SystemConfiguration
];
checkInputs = [
inputs'.csprpkgs.packages.cctl
];
meta.mainProgram = "kairos-server";
};
Expand Down Expand Up @@ -93,11 +100,22 @@

coverage-report = craneLib.cargoTarpaulin (kairosNodeAttrs // {
cargoArtifacts = self'.packages.kairos-deps;
# Default values from https://crane.dev/API.html?highlight=tarpau#cranelibcargotarpaulin
# --avoid-cfg-tarpaulin fixes nom/bitvec issue https://github.com/xd009642/tarpaulin/issues/756#issuecomment-838769320
cargoTarpaulinExtraArgs = "--skip-clean --out xml --output-dir $out --avoid-cfg-tarpaulin";
# For some reason cargoTarpaulin runs the tests in the buildPhase
buildInputs = kairosNodeAttrs.buildInputs ++ [
inputs'.csprpkgs.packages.cctl
];
});

audit = craneLib.cargoAudit {
inherit (kairosNodeAttrs) src;
advisory-db = inputs.advisory-db;
# Default values from https://crane.dev/API.html?highlight=cargoAudit#cranelibcargoaudit
# FIXME --ignore RUSTSEC-2022-0093 ignores ed25519-dalek 1.0.1 vulnerability caused by introducing casper-client 2.0.0
# FIXME --ignore RUSTSEC-2024-0013 ignores libgit2-sys 0.14.2+1.5.1 vulnerability caused by introducing casper-client 2.0.0
cargoAuditExtraArgs = "--ignore yanked --ignore RUSTSEC-2022-0093 --ignore RUSTSEC-2024-0013";
};
};

Expand Down
1 change: 0 additions & 1 deletion kairos-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ axum-extra = { version = "0.9", features = [
"typed-header",
"json-deserializer",
] }
thiserror = "1"
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
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
19 changes: 19 additions & 0 deletions kairos-test-utils/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "kairos-test-utils"
version.workspace = true
edition.workspace = true
license.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]

[dependencies]

anyhow = "1"
backoff = { version = "0.4", features = ["tokio", "futures"]}
casper-client = "2"
nom = "7"
tokio = { version = "1", features = [ "full", "tracing", "macros" ] }
tempfile = "3"
tracing = "0.1"

Loading

0 comments on commit 4f79d17

Please sign in to comment.