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

Add get-nonce endpoint #146

Merged
merged 11 commits into from
Jul 16, 2024
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
Loading