Skip to content

Commit

Permalink
Merge pull request #146 from cspr-rad/add-get-nonce
Browse files Browse the repository at this point in the history
Add get-nonce endpoint
  • Loading branch information
Avi-D-coder authored Jul 16, 2024
2 parents 374ca97 + 8683d29 commit abda197
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 33 deletions.
37 changes: 28 additions & 9 deletions kairos-cli/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use casper_client::types::{DeployBuilder, DeployHash, ExecutableDeployItem, Time
use casper_client_types::{crypto::SecretKey, runtime_args, ContractHash, RuntimeArgs, U512};
use kairos_server::routes::contract_hash::ContractHashPath;
use kairos_server::routes::deposit::DepositPath;
use kairos_server::routes::get_nonce::GetNoncePath;
use kairos_server::PublicKey;
use reqwest::Url;
use serde::{Deserialize, Serialize};
use std::fmt;
Expand All @@ -18,7 +20,7 @@ pub enum KairosClientError {
ResponseErrorWithCode(u16, String),
DecodeError(String),
CasperClientError(String),
KairosServerError(String),
KairosServerError(u16, String),
}

impl std::error::Error for KairosClientError {}
Expand Down Expand Up @@ -84,15 +86,29 @@ pub fn deposit(
.header("Content-Type", "application/json")
.json(&deploy)
.send()
.map_err(KairosClientError::from)?;
.map_err(KairosClientError::from)?
.error_for_status();

let status = response.status();
if !status.is_success() {
Err(KairosClientError::KairosServerError(status.to_string()))
} else {
response
match response {
Err(err) => Err(KairosClientError::from(err)),
Ok(response) => response
.json::<DeployHash>()
.map_err(KairosClientError::from)
.map_err(KairosClientError::from),
}
}

pub fn get_nonce(base_url: &Url, account: &PublicKey) -> Result<u64, KairosClientError> {
let response = reqwest::blocking::Client::new()
.post(base_url.join(GetNoncePath::PATH).unwrap())
.header("Content-Type", "application/json")
.json(&account)
.send()
.map_err(KairosClientError::from)?
.error_for_status();

match response {
Err(err) => Err(KairosClientError::from(err)),
Ok(response) => response.json::<u64>().map_err(KairosClientError::from),
}
}

Expand All @@ -105,7 +121,10 @@ pub fn contract_hash(base_url: &Url) -> Result<ContractHash, KairosClientError>

let status = response.status();
if !status.is_success() {
Err(KairosClientError::KairosServerError(status.to_string()))
Err(KairosClientError::KairosServerError(
status.as_u16(),
status.to_string(),
))
} else {
response
.json::<ContractHash>()
Expand Down
10 changes: 7 additions & 3 deletions kairos-cli/src/commands/transfer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::client::KairosClientError;
use crate::client::{self, KairosClientError};
use crate::common::args::{AmountArg, NonceArg, PrivateKeyPathArg, RecipientArg};
use crate::error::CliError;

Expand Down Expand Up @@ -30,15 +30,19 @@ pub fn run(args: Args, kairos_server_address: Url) -> Result<String, CliError> {
let amount: u64 = args.amount.field;
let signer =
Signer::from_private_key_file(args.private_key_path.field).map_err(CryptoError::from)?;
let nonce = args.nonce.val;
let signer_public_key = signer.to_public_key()?;
let nonce = match args.nonce.val {
None => client::get_nonce(&kairos_server_address, &signer_public_key)?,
Some(nonce) => nonce,
};

// TODO: Create transaction and sign it with `signer`.

// TODO: Send transaction to the network, using Rust SDK.
let res = reqwest::blocking::Client::new()
.post(kairos_server_address.join(TransferPath::PATH).unwrap())
.json(&PayloadBody {
public_key: signer.to_public_key()?,
public_key: signer_public_key,
payload: SigningPayload::new(nonce, Transfer::new(recipient, amount))
.try_into()
.unwrap(),
Expand Down
10 changes: 7 additions & 3 deletions kairos-cli/src/commands/withdraw.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::client::KairosClientError;
use crate::client::{self, KairosClientError};
use crate::common::args::{AmountArg, NonceArg, PrivateKeyPathArg};
use crate::error::CliError;

Expand Down Expand Up @@ -27,15 +27,19 @@ pub fn run(args: Args, kairos_server_address: Url) -> Result<String, CliError> {
let amount: u64 = args.amount.field;
let signer =
Signer::from_private_key_file(args.private_key_path.field).map_err(CryptoError::from)?;
let nonce = args.nonce.val;
let signer_public_key = signer.to_public_key()?;
let nonce = match args.nonce.val {
None => client::get_nonce(&kairos_server_address, &signer_public_key)?,
Some(nonce) => nonce,
};

// TODO: Create transaction and sign it with `signer`.

// TODO: Send transaction to the network, using Rust SDK.
let res = reqwest::blocking::Client::new()
.post(kairos_server_address.join(WithdrawPath::PATH).unwrap())
.json(&PayloadBody {
public_key: signer.to_public_key()?,
public_key: signer_public_key,
payload: SigningPayload::new(nonce, Withdrawal::new(amount))
.try_into()
.unwrap(),
Expand Down
2 changes: 1 addition & 1 deletion kairos-cli/src/common/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct PrivateKeyPathArg {
#[derive(Args, Debug)]
pub struct NonceArg {
#[arg(id = "nonce", long, short, value_name = "NUM")]
pub val: u64,
pub val: Option<u64>,
}

#[derive(Args, Debug)]
Expand Down
12 changes: 2 additions & 10 deletions kairos-cli/tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ fn transfer_successful_with_secp256k1() {
.arg(recipient)
.arg("--amount")
.arg("123")
.arg("--nonce")
.arg("0")
.arg("--private-key")
.arg(secret_key_path);

Expand All @@ -120,8 +118,6 @@ fn withdraw_successful_with_ed25519() {
cmd.arg("withdraw")
.arg("--amount")
.arg("123")
.arg("--nonce")
.arg("0")
.arg("--private-key")
.arg(secret_key_path);

Expand Down Expand Up @@ -197,9 +193,7 @@ fn transfer_invalid_recipient() {
.arg("--amount")
.arg("123")
.arg("--private-key")
.arg(secret_key_path)
.arg("--nonce")
.arg("0");
.arg(secret_key_path);

cmd.assert()
.failure()
Expand All @@ -218,9 +212,7 @@ fn transfer_valid_recipient() {
.arg("--amount")
.arg("123")
.arg("--private-key")
.arg(secret_key_path)
.arg("--nonce")
.arg("0");
.arg(secret_key_path);

cmd.assert()
.failure()
Expand Down
10 changes: 10 additions & 0 deletions kairos-prover/kairos-circuit-logic/src/account_trie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,16 @@ impl<Db: DatabaseGet<Account>> AccountTrie<SnapshotBuilder<Db, Account>> {
Ok(())
}
}

/// Returns the nonce for an accounts public key if it's known, returns an error if unknown.
pub fn get_nonce_for(&self, account: &PublicKey) -> Result<u64, TxnErr> {
let [account_hash] = hash_buffers([account]);

let account = self.txn.get_exclude_from_txn(&account_hash)?;
let account = account.ok_or("Unknown account")?;

Ok(account.nonce)
}
}

/// An account in the trie.
Expand Down
3 changes: 2 additions & 1 deletion kairos-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::state::{BatchStateManager, ServerState, ServerStateInner};
pub use errors::AppErr;

/// TODO: support secp256k1
type PublicKey = Vec<u8>;
pub type PublicKey = Vec<u8>;
type Signature = Vec<u8>;

#[cfg(not(feature = "deposit-mock"))]
Expand All @@ -39,6 +39,7 @@ pub fn app_router(state: ServerState) -> Router {
.typed_post(routes::withdraw_handler)
.typed_post(routes::transfer_handler)
.typed_post(routes::deposit_mock_handler)
.typed_post(routes::get_nonce_handler)
.typed_get(routes::contract_hash_handler)
.with_state(state)
}
Expand Down
19 changes: 19 additions & 0 deletions kairos-server/src/routes/get_nonce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use axum::{extract::State, Json};
use axum_extra::routing::TypedPath;
use tracing::*;

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

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

#[instrument(level = "trace", skip(state), ret)]
pub async fn get_nonce_handler(
_: GetNoncePath,
state: State<ServerState>,
Json(body): Json<PublicKey>,
) -> Result<Json<u64>, AppErr> {
let nonce = state.batch_state_manager.get_nonce_for(body).await?;
Ok(Json(nonce))
}
2 changes: 2 additions & 0 deletions kairos-server/src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ pub mod contract_hash;
pub mod deposit;
#[cfg(feature = "deposit-mock")]
pub mod deposit_mock;
pub mod get_nonce;
pub mod transfer;
pub mod withdraw;

pub use contract_hash::contract_hash_handler;
pub use deposit::deposit_handler;
#[cfg(feature = "deposit-mock")]
pub use deposit_mock::deposit_mock_handler;
pub use get_nonce::get_nonce_handler;
pub use transfer::transfer_handler;
pub use withdraw::withdraw_handler;

Expand Down
19 changes: 18 additions & 1 deletion kairos-server/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use tokio::{
use casper_client::types::DeployHash;

pub use self::trie::TrieStateThreadMsg;
use crate::{config::ServerConfig, state::submit_batch::submit_proof_to_contract};
use crate::{config::ServerConfig, state::submit_batch::submit_proof_to_contract, PublicKey};
use kairos_circuit_logic::transactions::KairosTransaction;
use kairos_trie::{stored::memory_db::MemoryDb, NodeHash, TrieRoot};

Expand Down Expand Up @@ -138,4 +138,21 @@ impl BatchStateManager {
.await
.expect("Never received response from trie thread")
}

pub async fn get_nonce_for(&self, account: PublicKey) -> Result<u64, crate::AppErr> {
let (msg, response) = TrieStateThreadMsg::get_nonce_for(account);

self.queued_transactions.send(msg).await.map_err(|err| {
tracing::error!("Could not send get-nonce request to trie thread {:?}", err);
crate::AppErr::new(err)
})?;

response.await.map_err(|err| {
tracing::error!(
"Never received response from the trie thread for the get-nonce-request {:?}",
err
);
crate::AppErr::new(err)
})?
}
}
25 changes: 25 additions & 0 deletions kairos-server/src/state/trie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ use kairos_trie::{
DigestHasher, NodeHash, TrieRoot,
};

use kairos_circuit_logic::transactions::PublicKey;

pub type Database = MemoryDb<Account>;

#[derive(Debug)]
pub enum TrieStateThreadMsg {
Transaction(KairosTransaction, oneshot::Sender<Result<(), AppErr>>),
Commit(oneshot::Sender<Result<BatchOutput, AppErr>>),
GetNonce(PublicKey, oneshot::Sender<Result<u64, AppErr>>),
}

impl TrieStateThreadMsg {
Expand All @@ -38,6 +41,11 @@ impl TrieStateThreadMsg {
let (sender, receiver) = oneshot::channel();
(Self::Commit(sender), receiver)
}

pub fn get_nonce_for(account: PublicKey) -> (Self, oneshot::Receiver<Result<u64, AppErr>>) {
let (sender, receiver) = oneshot::channel();
(Self::GetNonce(account, sender), receiver)
}
}

pub fn spawn_state_thread(
Expand Down Expand Up @@ -103,6 +111,23 @@ pub fn spawn_state_thread(
tracing::error!("failed to send commit result: {:?}", err);
}
}
TrieStateThreadMsg::GetNonce(account, responder) => {
let res = state
.batch_state
.account_trie
.get_nonce_for(&account)
.map_err(|err| {
AppErr::new(anyhow::anyhow!(err))
.set_status(axum::http::StatusCode::NOT_FOUND)
});
if let Err(err) = responder.send(res) {
tracing::error!(
"Failed to get the nonce for account '{:?}': {:?}",
account,
err
);
}
}
}
}
})
Expand Down
10 changes: 5 additions & 5 deletions nixos/tests/end-to-end.nix
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ let
contractHashName = "kairos_contract_package_hash";
# The path where cctl will write the deployed contract hash on the servers filesystem
serverContractHashPath = "${cctlWorkingDirectory}/contracts/${contractHashName}";
casperSyncInterval = 5;
in
nixosTest {
name = "kairos e2e test";
Expand Down Expand Up @@ -57,7 +58,7 @@ nixosTest {
services.kairos = {
casperRpcUrl = "http://localhost:${builtins.toString config.services.cctl.port}/rpc";
casperSseUrl = "http://localhost:18101/events/main"; # has to be hardcoded since it's not configurable atm
casperSyncInterval = 5;
inherit casperSyncInterval;
demoContractHash = "0000000000000000000000000000000000000000000000000000000000000000";
};

Expand Down Expand Up @@ -131,13 +132,12 @@ nixosTest {
wait_for_successful_deploy(deposit_deploy_hash)
# wait for l2 to sync with l1 every 10 seconds
time.sleep(12)
# wait for l2 to sync with l1 every 5 seconds
time.sleep(${builtins.toString (casperSyncInterval * 2)})
# transfer
beneficiary = client.succeed("cat ${clientUsersDirectory}/user-3/public_key_hex")
transfer_output = client.succeed("kairos-cli --kairos-server-address http://kairos transfer --nonce 0 --amount 1000 --recipient {} --private-key {}".format(beneficiary, depositor_private_key))
transfer_output = client.succeed("kairos-cli --kairos-server-address http://kairos transfer --amount 1000 --recipient {} --private-key {}".format(beneficiary, depositor_private_key))
assert "Transfer successfully sent to L2\n" in transfer_output, "The transfer command was not successful: {}".format(transfer_output)
# TODO test withdraw
Expand Down

0 comments on commit abda197

Please sign in to comment.