Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate the trie #57

Merged
merged 36 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a277f6d
Model Transaction types
Avi-D-coder Mar 26, 2024
e014db5
trie state updates
Avi-D-coder Apr 2, 2024
a650773
The trie state thread
Avi-D-coder Apr 2, 2024
3dca9e2
Add epoch to kiros-tx schema
Avi-D-coder Apr 2, 2024
1957c20
Connect trie up
Avi-D-coder Apr 2, 2024
ec4ac26
fix kairos-tx tests
Avi-D-coder Apr 2, 2024
333fe94
Refactor kairos-tx helpers
Avi-D-coder Apr 2, 2024
c39d9e0
Make txn logic over any Entry API
Avi-D-coder Apr 13, 2024
4477270
batch_state & Entry API traits
Avi-D-coder Apr 13, 2024
eea8a56
Remove caching and txn precheck
Avi-D-coder Apr 17, 2024
74d0444
Finish up trie integration
Avi-D-coder Apr 24, 2024
372a8e6
Fix tests
Avi-D-coder Apr 24, 2024
abc8c4f
Merge branch 'main' of github.com:cspr-rad/kairos into feature/trie
Avi-D-coder Apr 24, 2024
aabdf04
Add doc comments
Avi-D-coder Apr 24, 2024
deaa882
Cargo.lock update
Avi-D-coder Apr 24, 2024
3e999d4
More doc comments
Avi-D-coder Apr 24, 2024
ba35277
Fixup
Avi-D-coder Apr 24, 2024
f2e9642
Fix nixos end to end test
Avi-D-coder Apr 25, 2024
52702fc
Try to fix nixos tests again
Avi-D-coder Apr 25, 2024
2ba7825
Cargo fmt
Avi-D-coder Apr 25, 2024
6e03992
Revert nixos end-to-end test changes
Avi-D-coder Apr 25, 2024
fbc5e8e
nixos tests again
Avi-D-coder Apr 25, 2024
b2e54a3
Fix nixos again again again
Avi-D-coder Apr 25, 2024
2737a39
nixos test fix withdraw nonce
Avi-D-coder Apr 25, 2024
7ff4c1e
Remove outdated comment about epoch
Avi-D-coder Apr 26, 2024
294775a
Address review
Avi-D-coder Apr 26, 2024
cbf444a
Synchronize changes with master.
koxu1996 Apr 29, 2024
fd958c0
Merge pull request #82 from cspr-rad/feature/trie-sync-master
koxu1996 Apr 29, 2024
ecb3790
Merge branch 'main' into feature/trie
koxu1996 Apr 29, 2024
6874e52
Fixup merge
Avi-D-coder Apr 29, 2024
fa44c2c
Reorder struct fields
Avi-D-coder Apr 29, 2024
a1fb08d
Fixup address review
Avi-D-coder Apr 29, 2024
d653f01
Add docs
Avi-D-coder Apr 29, 2024
90aac5c
Add more docs
Avi-D-coder Apr 29, 2024
4b7b699
Merge branch 'main' into feature/trie
marijanp Apr 29, 2024
4635140
Cargo update
Avi-D-coder Apr 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 193 additions & 114 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions kairos-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
hex = "0.4"
kairos-tx = { path = "../kairos-tx" }
kairos-trie = { git = "https://github.com/cspr-rad/kairos-trie" }
sha2 = "0.10"

[dev-dependencies]
proptest = "1"
Expand Down
9 changes: 9 additions & 0 deletions kairos-server/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,12 @@ impl From<anyhow::Error> for AppErr {
}
}
}

impl From<kairos_trie::TrieError> for AppErr {
fn from(error: kairos_trie::TrieError) -> Self {
Self {
error: anyhow::Error::msg(error.to_string()),
status: Some(StatusCode::INTERNAL_SERVER_ERROR),
}
}
}
5 changes: 3 additions & 2 deletions kairos-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ pub mod state;

mod utils;

use std::sync::Arc;

use axum::Router;
use axum_extra::routing::RouterExt;
use state::LockedBatchState;

pub use errors::AppErr;

type PublicKey = Vec<u8>;
type Signature = Vec<u8>;

pub fn app_router(state: LockedBatchState) -> Router {
pub fn app_router(state: Arc<state::BatchStateManager>) -> Router {
Router::new()
.typed_post(routes::deposit_handler)
.typed_post(routes::withdraw_handler)
Expand Down
4 changes: 2 additions & 2 deletions kairos-server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::net::SocketAddr;

use dotenvy::dotenv;
use kairos_server::{config::ServerConfig, state::BatchState};
use kairos_server::{config::ServerConfig, state::BatchStateManager};

#[tokio::main]
async fn main() {
Expand All @@ -11,7 +11,7 @@ async fn main() {
let config = ServerConfig::from_env()
.unwrap_or_else(|e| panic!("Failed to parse server config from environment: {}", e));

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

let axum_addr = SocketAddr::from(([127, 0, 0, 1], config.port));

Expand Down
52 changes: 21 additions & 31 deletions kairos-server/src/routes/deposit.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::ops::Deref;
use std::sync::Arc;

use anyhow::{anyhow, Context};
use axum::{extract::State, http::StatusCode, Json};
Expand All @@ -7,8 +7,14 @@ use tracing::*;

use kairos_tx::asn::{SigningPayload, TransactionBody};

use crate::routes::PayloadBody;
use crate::{state::LockedBatchState, AppErr};
use crate::{
routes::PayloadBody,
state::{
transactions::{Signed, Transaction},
BatchStateManager,
},
AppErr,
};

#[derive(TypedPath, Debug, Clone, Copy)]
#[typed_path("/api/v1/deposit")]
Expand All @@ -17,46 +23,30 @@ pub struct DepositPath;
#[instrument(level = "trace", skip(state), ret)]
pub async fn deposit_handler(
_: DepositPath,
state: State<LockedBatchState>,
state: State<Arc<BatchStateManager>>,
Json(body): Json<PayloadBody>,
) -> Result<(), AppErr> {
tracing::info!("parsing transaction data");
let signing_payload: SigningPayload =
body.payload.as_slice().try_into().context("payload err")?;
let deposit = match signing_payload.body {
TransactionBody::Deposit(deposit) => deposit,
TransactionBody::Deposit(deposit) => deposit.try_into().context("decoding deposit")?,
_ => {
return Err(AppErr::set_status(
anyhow!("invalid transaction type"),
StatusCode::BAD_REQUEST,
))
}
};
let amount = u64::try_from(deposit.amount).context("invalid amount")?;
let public_key = body.public_key;

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 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,
updated_balance
);

Ok(())
let public_key = body.public_key;
let nonce = signing_payload.nonce.try_into().context("decoding nonce")?;

state
.enqueue_transaction(Signed {
public_key,
nonce,
transaction: Transaction::Deposit(deposit),
})
.await
}
111 changes: 23 additions & 88 deletions kairos-server/src/routes/transfer.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
use std::sync::Arc;

use anyhow::{anyhow, Context};
use axum::{extract::State, http::StatusCode, Json};
use axum_extra::routing::TypedPath;
use tracing::instrument;

use kairos_tx::asn::{SigningPayload, TransactionBody};

use crate::routes::PayloadBody;
use crate::{state::LockedBatchState, AppErr, PublicKey};
use crate::{
routes::PayloadBody,
state::{
transactions::{Signed, Transaction, Transfer},
BatchStateManager,
},
AppErr,
};

#[derive(TypedPath)]
#[typed_path("/api/v1/transfer")]
Expand All @@ -15,106 +23,33 @@ pub struct TransferPath;
#[instrument(level = "trace", skip(state), ret)]
pub async fn transfer_handler(
_: TransferPath,
State(state): State<LockedBatchState>,
State(state): State<Arc<BatchStateManager>>,
Json(body): Json<PayloadBody>,
) -> Result<(), AppErr> {
tracing::info!("parsing transaction data");
let signing_payload: SigningPayload =
body.payload.as_slice().try_into().context("payload err")?;
let transfer = match signing_payload.body {
TransactionBody::Transfer(transfer) => transfer,
let transfer: Transfer = match signing_payload.body {
TransactionBody::Transfer(transfer) => transfer.try_into().context("decoding transfer")?,
_ => {
return Err(AppErr::set_status(
anyhow!("invalid transaction type"),
StatusCode::BAD_REQUEST,
))
}
};
let amount = u64::try_from(transfer.amount).context("invalid amount")?;
let from = body.public_key;
let to = PublicKey::from(transfer.recipient);

if amount == 0 {
return Err(AppErr::set_status(
anyhow!("transfer amount must be greater than 0"),
StatusCode::BAD_REQUEST,
));
}
let public_key = body.public_key;
let nonce = signing_payload.nonce.try_into().context("decoding nonce")?;

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, &from, amount, &to).await?;

let mut state = state.write().await;
let from_balance = state.balances.get_mut(&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(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,
amount
),
StatusCode::CONFLICT,
)
})?;

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(amount).ok_or_else(|| {
AppErr::set_status(anyhow!("Receiver balance overflow"), StatusCode::CONFLICT)
})?;

Ok(())
}

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(from).ok_or_else(|| {
AppErr::set_status(
anyhow!("Sender does not have an account"),
StatusCode::BAD_REQUEST,
)
})?;

from_balance.checked_sub(amount).ok_or_else(|| {
AppErr::set_status(
anyhow!(
"Sender does not have sufficient funds, balance={}, transfer_amount={}",
from_balance,
amount
),
StatusCode::FORBIDDEN,
)
})?;

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,
));
}
tracing::info!("queuing transaction for trie update");

Ok(())
state
.enqueue_transaction(Signed {
public_key,
nonce,
transaction: Transaction::Transfer(transfer),
})
.await
}
Loading
Loading