diff --git a/.env b/.env index ab4ba530..8db726da 100644 --- a/.env +++ b/.env @@ -1,4 +1,5 @@ -KAIROS_SERVER_SOCKET_ADDR="127.0.0.1:7893" +# Make sure this matches the default in the nix module and kairos-cli args +KAIROS_SERVER_SOCKET_ADDR="0.0.0.0:9999" KAIROS_PROVER_SERVER_SOCKET_ADDR="127.0.0.1:7894" # In development the socket and url should match KAIROS_PROVER_SERVER_URL="http://127.0.0.1:7894" @@ -9,4 +10,7 @@ RISC0_DEV_MODE=1; # FIXME this is a dummy value that fixes a regression that prevented server startup KAIROS_SERVER_CASPER_RPC="http://127.0.0.1:11101/rpc" KAIROS_SERVER_CASPER_SSE="http://127.0.0.1:18101/events/main" +KAIROS_SERVER_CASPER_SYNC_INTERVAL=10 + KAIROS_SERVER_DEMO_CONTRACT_HASH="0000000000000000000000000000000000000000000000000000000000000000" +KAIROS_SERVER_SECRET_KEY_FILE="./testdata/users/user-1/secret_key.pem" diff --git a/Cargo.lock b/Cargo.lock index 830cc01f..ba215c4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2495,6 +2495,7 @@ dependencies = [ "casper-hashing 2.0.0", "casper-types 3.0.0", "clap", + "dotenvy", "hex", "kairos-crypto", "kairos-server", diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index 7d85cde4..60755045 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -17,9 +17,10 @@ license.workspace = true default = ["demo"] all-tests = ["cctl-tests"] cctl-tests = [] -demo = ["dep:kairos-test-utils", "dep:tokio"] +demo = ["dep:kairos-test-utils", "dep:tokio", "dep:dotenvy"] [dependencies] +dotenvy = { version = "0.15", optional = true } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } casper-client.workspace = true diff --git a/kairos-cli/bin/main.rs b/kairos-cli/bin/main.rs index b9253099..0b2a7783 100644 --- a/kairos-cli/bin/main.rs +++ b/kairos-cli/bin/main.rs @@ -3,6 +3,9 @@ use std::process; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; fn main() { + #[cfg(feature = "demo")] + let _ = dotenvy::dotenv(); + tracing_subscriber::registry() .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "warn".into())) .with(tracing_subscriber::fmt::layer()) diff --git a/kairos-cli/src/client.rs b/kairos-cli/src/client.rs index c2870428..3c0d5f3c 100644 --- a/kairos-cli/src/client.rs +++ b/kairos-cli/src/client.rs @@ -1,7 +1,10 @@ use axum_extra::routing::TypedPath; use casper_client::types::{DeployBuilder, DeployHash, ExecutableDeployItem, TimeDiff, Timestamp}; 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; @@ -17,7 +20,7 @@ pub enum KairosClientError { ResponseErrorWithCode(u16, String), DecodeError(String), CasperClientError(String), - KairosServerError(String), + KairosServerError(u16, String), } impl std::error::Error for KairosClientError {} @@ -83,14 +86,48 @@ pub fn deposit( .header("Content-Type", "application/json") .json(&deploy) .send() + .map_err(KairosClientError::from)? + .error_for_status(); + + match response { + Err(err) => Err(KairosClientError::from(err)), + Ok(response) => response + .json::() + .map_err(KairosClientError::from), + } +} + +pub fn get_nonce(base_url: &Url, account: &PublicKey) -> Result { + 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::().map_err(KairosClientError::from), + } +} + +pub fn contract_hash(base_url: &Url) -> Result { + let response = reqwest::blocking::Client::new() + .get(base_url.join(ContractHashPath::PATH).unwrap()) + .header("Content-Type", "application/json") + .send() .map_err(KairosClientError::from)?; 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::() + .json::() .map_err(KairosClientError::from) } } diff --git a/kairos-cli/src/commands/deposit.rs b/kairos-cli/src/commands/deposit.rs index 7fc41c1e..cd72f23b 100644 --- a/kairos-cli/src/commands/deposit.rs +++ b/kairos-cli/src/commands/deposit.rs @@ -30,8 +30,13 @@ pub fn run(args: Args, kairos_server_address: Url) -> Result { error: err.to_string(), })?; - let contract_hash_bytes = <[u8; 32]>::from_hex(contract_hash)?; - let contract_hash = ContractHash::new(contract_hash_bytes); + let contract_hash = match contract_hash { + Some(contract_hash_string) => { + let contract_hash_bytes = <[u8; 32]>::from_hex(contract_hash_string)?; + ContractHash::new(contract_hash_bytes) + } + None => client::contract_hash(&kairos_server_address)?, + }; client::deposit( &kairos_server_address, diff --git a/kairos-cli/src/commands/run_cctl.rs b/kairos-cli/src/commands/run_cctl.rs index 0a53cf08..23d95510 100644 --- a/kairos-cli/src/commands/run_cctl.rs +++ b/kairos-cli/src/commands/run_cctl.rs @@ -32,10 +32,10 @@ pub fn run() -> Result { .expect("Expected at least one node after successful network run"); let casper_rpc_url = format!("http://localhost:{}/rpc", node.port.rpc_port); - println!("You can find demo key pairs in `{:?}`", network.working_dir); println!("Before running the Kairos CLI in another terminal, set the following environment variables:"); - println!("export KAIROS_CONTRACT_HASH={}", contract_hash); + println!("export DEMO_KEYS={}/assets/users", network.working_dir.display()); + println!("export KAIROS_SERVER_DEMO_CONTRACT_HASH={}", contract_hash); println!("export KAIROS_SERVER_CASPER_RPC={}", casper_rpc_url); let _ = tokio::signal::ctrl_c().await; diff --git a/kairos-cli/src/commands/transfer.rs b/kairos-cli/src/commands/transfer.rs index c9c7ea0c..1756429a 100644 --- a/kairos-cli/src/commands/transfer.rs +++ b/kairos-cli/src/commands/transfer.rs @@ -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; @@ -30,15 +30,19 @@ pub fn run(args: Args, kairos_server_address: Url) -> Result { 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. - reqwest::blocking::Client::new() + 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(), @@ -47,5 +51,13 @@ pub fn run(args: Args, kairos_server_address: Url) -> Result { .send() .map_err(KairosClientError::from)?; - Ok("ok".to_string()) + if res.status().is_success() { + Ok("Transfer successfully sent to L2".to_string()) + } else { + Err(KairosClientError::ResponseErrorWithCode( + res.status().as_u16(), + res.text().unwrap_or_default(), + ) + .into()) + } } diff --git a/kairos-cli/src/commands/withdraw.rs b/kairos-cli/src/commands/withdraw.rs index 12315b03..031817f4 100644 --- a/kairos-cli/src/commands/withdraw.rs +++ b/kairos-cli/src/commands/withdraw.rs @@ -1,4 +1,4 @@ -use crate::client::KairosClientError; +use crate::client::{self, KairosClientError}; use crate::common::args::{AmountArg, NonceArg, PrivateKeyPathArg}; use crate::error::CliError; @@ -27,15 +27,19 @@ pub fn run(args: Args, kairos_server_address: Url) -> Result { 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. - reqwest::blocking::Client::new() + 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(), @@ -44,5 +48,13 @@ pub fn run(args: Args, kairos_server_address: Url) -> Result { .send() .map_err(KairosClientError::from)?; - Ok("ok".to_string()) + if res.status().is_success() { + Ok("Withdrawal successfully sent to L2".to_string()) + } else { + Err(KairosClientError::ResponseErrorWithCode( + res.status().as_u16(), + res.text().unwrap_or_default(), + ) + .into()) + } } diff --git a/kairos-cli/src/common/args.rs b/kairos-cli/src/common/args.rs index 6c6b2939..50cc5bf5 100644 --- a/kairos-cli/src/common/args.rs +++ b/kairos-cli/src/common/args.rs @@ -21,13 +21,13 @@ pub struct PrivateKeyPathArg { #[derive(Args, Debug)] pub struct NonceArg { #[arg(id = "nonce", long, short, value_name = "NUM")] - pub val: u64, + pub val: Option, } #[derive(Args, Debug)] pub struct ContractHashArg { #[arg(id = "contract-hash", long, short = 'c', value_name = "CONTRACT_HASH")] - pub field: String, + pub field: Option, } #[derive(Args, Debug)] diff --git a/kairos-cli/src/lib.rs b/kairos-cli/src/lib.rs index 6e9e2c30..01d400e9 100644 --- a/kairos-cli/src/lib.rs +++ b/kairos-cli/src/lib.rs @@ -14,6 +14,7 @@ use reqwest::Url; pub struct Cli { #[command(subcommand)] pub command: Command, + // Make sure matches the default in `../.env` and the nix module. #[arg(long, value_name = "URL", default_value = "http://0.0.0.0:9999")] pub kairos_server_address: Url, } diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index 36e27e80..2678e3db 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -68,8 +68,6 @@ async fn deposit_successful_with_ed25519() { cmd.arg("--kairos-server-address") .arg(kairos.url.as_str()) .arg("deposit") - .arg("--contract-hash") - .arg(contract_hash.to_string()) .arg("--amount") .arg("123") .arg("--private-key") @@ -103,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); @@ -122,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); @@ -199,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() @@ -220,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() diff --git a/kairos-prover/kairos-circuit-logic/src/account_trie.rs b/kairos-prover/kairos-circuit-logic/src/account_trie.rs index cbb1bcce..4b3e99f3 100644 --- a/kairos-prover/kairos-circuit-logic/src/account_trie.rs +++ b/kairos-prover/kairos-circuit-logic/src/account_trie.rs @@ -310,6 +310,16 @@ impl> AccountTrie> { 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 { + 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. diff --git a/kairos-server/src/config.rs b/kairos-server/src/config.rs index a9907bfd..51644f51 100644 --- a/kairos-server/src/config.rs +++ b/kairos-server/src/config.rs @@ -14,6 +14,7 @@ pub struct ServerConfig { pub socket_addr: SocketAddr, pub casper_rpc: Url, pub casper_sse: Url, + pub casper_sync_interval: Duration, pub kairos_demo_contract_hash: ContractHash, pub batch_config: BatchConfig, } @@ -21,9 +22,18 @@ pub struct ServerConfig { impl ServerConfig { pub fn from_env() -> Result { let socket_addr = parse_env_as::("KAIROS_SERVER_SOCKET_ADDR")?; + let casper_rpc = parse_env_as::("KAIROS_SERVER_CASPER_RPC")?; let casper_sse = parse_env_as::("KAIROS_SERVER_CASPER_SSE")?; + let casper_sync_interval = + parse_env_as::("KAIROS_SERVER_CASPER_SYNC_INTERVAL").map(Duration::from_secs)?; + + if casper_sync_interval.as_secs() == 0 { + return Err("Casper sync interval must be greater than 0".to_string()); + } + let batch_config = BatchConfig::from_env()?; + let secret_key_file = parse_env_as_opt::("KAIROS_SERVER_SECRET_KEY_FILE")?.map(PathBuf::from); @@ -53,6 +63,7 @@ impl ServerConfig { socket_addr, casper_rpc, casper_sse, + casper_sync_interval, kairos_demo_contract_hash, batch_config, }) diff --git a/kairos-server/src/l1_sync/interval_trigger.rs b/kairos-server/src/l1_sync/interval_trigger.rs deleted file mode 100644 index c310450c..00000000 --- a/kairos-server/src/l1_sync/interval_trigger.rs +++ /dev/null @@ -1,18 +0,0 @@ -use tokio::time::{self, Duration}; - -use std::sync::Arc; - -use super::service::L1SyncService; - -pub async fn run(sync_service: Arc) { - let mut interval = time::interval(Duration::from_secs(30)); - - loop { - interval.tick().await; - - tracing::debug!("Triggering periodic L1 sync"); - let _ = sync_service.trigger_sync().await.map_err(|e| { - tracing::error!("Unable to trigger sync: {}", e); - }); - } -} diff --git a/kairos-server/src/l1_sync/mod.rs b/kairos-server/src/l1_sync/mod.rs index 73076152..0571e8b5 100644 --- a/kairos-server/src/l1_sync/mod.rs +++ b/kairos-server/src/l1_sync/mod.rs @@ -1,5 +1,3 @@ pub mod error; pub mod event_manager; pub mod service; - -pub mod interval_trigger; diff --git a/kairos-server/src/l1_sync/service.rs b/kairos-server/src/l1_sync/service.rs index 16a4fd48..eb661993 100644 --- a/kairos-server/src/l1_sync/service.rs +++ b/kairos-server/src/l1_sync/service.rs @@ -5,8 +5,9 @@ use super::event_manager::EventManager; use tokio::sync::mpsc; use tokio::sync::oneshot; +use tokio::time; -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; pub enum SyncCommand { TriggerSync(oneshot::Sender<()>), @@ -45,6 +46,19 @@ impl L1SyncService { Ok(()) } + + pub async fn run_periodic_sync(&self, interval: Duration) { + let mut interval = time::interval(interval); + + loop { + interval.tick().await; + + tracing::debug!("Triggering periodic L1 sync"); + let _ = self.trigger_sync().await.map_err(|e| { + tracing::error!("Unable to trigger sync: {}", e); + }); + } + } } /// Handles incoming commands and delegates tasks to EventManager. diff --git a/kairos-server/src/lib.rs b/kairos-server/src/lib.rs index 0f26b34c..a83af74f 100644 --- a/kairos-server/src/lib.rs +++ b/kairos-server/src/lib.rs @@ -20,7 +20,7 @@ use crate::state::{BatchStateManager, ServerState, ServerStateInner}; pub use errors::AppErr; /// TODO: support secp256k1 -type PublicKey = Vec; +pub type PublicKey = Vec; type Signature = Vec; #[cfg(not(feature = "deposit-mock"))] @@ -39,11 +39,14 @@ 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) } pub async fn run_l1_sync(server_state: Arc) { // Extra check: make sure the default dummy value of contract hash was changed. + let sync_interval = server_state.server_config.casper_sync_interval; let contract_hash = server_state.server_config.kairos_demo_contract_hash; if contract_hash == ContractHash::default() { tracing::warn!( @@ -60,7 +63,7 @@ pub async fn run_l1_sync(server_state: Arc) { // Run periodic synchronization. // TODO: Add additional SSE trigger. tokio::spawn(async move { - l1_sync::interval_trigger::run(l1_sync_service.into()).await; + l1_sync_service.run_periodic_sync(sync_interval).await; }); } diff --git a/kairos-server/src/routes/contract_hash.rs b/kairos-server/src/routes/contract_hash.rs new file mode 100644 index 00000000..97815086 --- /dev/null +++ b/kairos-server/src/routes/contract_hash.rs @@ -0,0 +1,18 @@ +use axum::{extract::State, Json}; +use axum_extra::routing::TypedPath; +use tracing::*; + +use crate::{state::ServerState, AppErr}; +use casper_client_types::ContractHash; + +#[derive(TypedPath, Debug, Clone, Copy)] +#[typed_path("/api/v1/contract-hash")] +pub struct ContractHashPath; + +#[instrument(level = "trace", skip(state), ret)] +pub async fn contract_hash_handler( + _: ContractHashPath, + state: State, +) -> Result, AppErr> { + Ok(Json(state.server_config.kairos_demo_contract_hash)) +} diff --git a/kairos-server/src/routes/deposit.rs b/kairos-server/src/routes/deposit.rs index 19906b3d..ff3f6c22 100644 --- a/kairos-server/src/routes/deposit.rs +++ b/kairos-server/src/routes/deposit.rs @@ -33,7 +33,7 @@ pub async fn deposit_handler( let response = put_deploy( expected_rpc_id.clone(), state.server_config.casper_rpc.as_str(), - casper_client::Verbosity::High, + casper_client::Verbosity::Low, body, ) .await diff --git a/kairos-server/src/routes/get_nonce.rs b/kairos-server/src/routes/get_nonce.rs new file mode 100644 index 00000000..42177afe --- /dev/null +++ b/kairos-server/src/routes/get_nonce.rs @@ -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, + Json(body): Json, +) -> Result, AppErr> { + let nonce = state.batch_state_manager.get_nonce_for(body).await?; + Ok(Json(nonce)) +} diff --git a/kairos-server/src/routes/mod.rs b/kairos-server/src/routes/mod.rs index 82659da9..b80a206e 100644 --- a/kairos-server/src/routes/mod.rs +++ b/kairos-server/src/routes/mod.rs @@ -1,12 +1,16 @@ +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; diff --git a/kairos-server/src/state.rs b/kairos-server/src/state.rs index dddd3f3c..a12e94cf 100644 --- a/kairos-server/src/state.rs +++ b/kairos-server/src/state.rs @@ -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}; @@ -138,4 +138,21 @@ impl BatchStateManager { .await .expect("Never received response from trie thread") } + + pub async fn get_nonce_for(&self, account: PublicKey) -> Result { + 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) + })? + } } diff --git a/kairos-server/src/state/submit_batch.rs b/kairos-server/src/state/submit_batch.rs index 1676cec6..6563ceba 100644 --- a/kairos-server/src/state/submit_batch.rs +++ b/kairos-server/src/state/submit_batch.rs @@ -30,7 +30,7 @@ pub async fn submit_proof_to_contract( let r = casper_client::put_deploy( casper_client::JsonRpcId::Number(random()), casper_rpc.as_str(), - casper_client::Verbosity::High, + casper_client::Verbosity::Low, deploy, ) .await diff --git a/kairos-server/src/state/trie.rs b/kairos-server/src/state/trie.rs index 76a5a94b..0e269444 100644 --- a/kairos-server/src/state/trie.rs +++ b/kairos-server/src/state/trie.rs @@ -20,12 +20,15 @@ use kairos_trie::{ DigestHasher, NodeHash, TrieRoot, }; +use kairos_circuit_logic::transactions::PublicKey; + pub type Database = MemoryDb; #[derive(Debug)] pub enum TrieStateThreadMsg { Transaction(KairosTransaction, oneshot::Sender>), Commit(oneshot::Sender>), + GetNonce(PublicKey, oneshot::Sender>), } impl TrieStateThreadMsg { @@ -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>) { + let (sender, receiver) = oneshot::channel(); + (Self::GetNonce(account, sender), receiver) + } } pub fn spawn_state_thread( @@ -55,7 +63,11 @@ pub fn spawn_state_thread( tracing::trace!("Trie State Thread received message: {:?}", msg); match msg { TrieStateThreadMsg::Transaction(txn, responder) => { - let res = state.batch_state.execute_transaction(txn); + let res = state.batch_state.execute_transaction(txn).map_err(|e| { + tracing::warn!("Error executing transaction: {:?}", e); + e + }); + responder.send(res).unwrap_or_else(|err| { tracing::warn!( "Transaction submitter hung up before receiving response: {}", @@ -99,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 + ); + } + } } } }) diff --git a/kairos-server/tests/transactions.rs b/kairos-server/tests/transactions.rs index 435b2640..f263b296 100644 --- a/kairos-server/tests/transactions.rs +++ b/kairos-server/tests/transactions.rs @@ -3,6 +3,7 @@ use axum_test::{TestServer, TestServerConfig}; use reqwest::Url; use std::collections::HashSet; use std::sync::{Arc, OnceLock}; +use std::time::Duration; use tokio::sync::RwLock; use tracing_subscriber::{prelude::*, EnvFilter}; @@ -52,6 +53,8 @@ fn new_test_app_with_casper_node(casper_rpc_url: &Url, casper_sse_url: &Url) -> socket_addr: "0.0.0.0:0".parse().unwrap(), casper_rpc: casper_rpc_url.clone(), casper_sse: casper_sse_url.clone(), + // For testing purposes, we set the sync interval to be fast + casper_sync_interval: Duration::from_secs(5), kairos_demo_contract_hash: ContractHash::default(), batch_config: BatchConfig { max_batch_size: None, diff --git a/kairos-test-utils/src/kairos.rs b/kairos-test-utils/src/kairos.rs index 5d65de28..a9ecd240 100644 --- a/kairos-test-utils/src/kairos.rs +++ b/kairos-test-utils/src/kairos.rs @@ -4,6 +4,7 @@ use casper_client_types::ContractHash; use reqwest::Url; use std::io; use std::net::{SocketAddr, TcpListener}; +use std::time::Duration; use tokio::net::TcpStream; use kairos_server::config::{BatchConfig, ServerConfig}; @@ -47,6 +48,8 @@ impl Kairos { socket_addr, casper_rpc: casper_rpc.clone(), casper_sse: casper_sse.clone(), + // We want a short sync interval for tests. + casper_sync_interval: Duration::from_secs(5), kairos_demo_contract_hash: kairos_demo_contract_hash.unwrap_or_default(), batch_config, }; diff --git a/nixos/modules/kairos.nix b/nixos/modules/kairos.nix index b7c14f04..81a9e093 100644 --- a/nixos/modules/kairos.nix +++ b/nixos/modules/kairos.nix @@ -62,6 +62,15 @@ in ''; }; + casperSyncInterval = mkOption { + type = types.int; + default = 10; + example = 10; + description = '' + The interval in seconds to between calls to the casper node to sync the deploys. + ''; + }; + prover = mkOption { description = "Prover server related options"; default = { }; @@ -142,6 +151,7 @@ in KAIROS_SERVER_SOCKET_ADDR = "${cfg.bindAddress}:${builtins.toString cfg.port}"; KAIROS_SERVER_CASPER_RPC = cfg.casperRpcUrl; KAIROS_SERVER_CASPER_SSE = cfg.casperSseUrl; + KAIROS_SERVER_CASPER_SYNC_INTERVAL = builtins.toString cfg.casperSyncInterval; KAIROS_SERVER_DEMO_CONTRACT_HASH = cfg.demoContractHash; KAIROS_PROVER_SERVER_URL = "${cfg.prover.protocol}://${cfg.prover.bindAddress}:${builtins.toString cfg.prover.port}"; } // optionalAttrs (!builtins.isNull cfg.prover.maxBatchSize) { diff --git a/nixos/tests/end-to-end.nix b/nixos/tests/end-to-end.nix index a6433257..edaa8443 100644 --- a/nixos/tests/end-to-end.nix +++ b/nixos/tests/end-to-end.nix @@ -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"; @@ -57,6 +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 + inherit casperSyncInterval; demoContractHash = "0000000000000000000000000000000000000000000000000000000000000000"; }; @@ -81,6 +83,7 @@ nixosTest { testScript = '' import json import backoff + import time # Utils def verify_deploy_success(json_data): @@ -118,23 +121,24 @@ nixosTest { # For more details, see cctl module implementation client.succeed("wget --no-parent -r http://kairos/cctl/users/") - contract_hash = kairos.succeed("cat ${serverContractHashPath}") - kairos.succeed("casper-client get-node-status --node-address ${casperNodeAddress}") # CLI with ed25519 # deposit depositor = client.succeed("cat ${clientUsersDirectory}/user-2/public_key_hex") depositor_private_key = "${clientUsersDirectory}/user-2/secret_key.pem" - deposit_deploy_hash = client.succeed("kairos-cli --kairos-server-address http://kairos deposit --amount 3000000000 --recipient {} --private-key {} --contract-hash {}".format(depositor, depositor_private_key, contract_hash)) + deposit_deploy_hash = client.succeed("kairos-cli --kairos-server-address http://kairos deposit --amount 3000000000 --recipient {} --private-key {}".format(depositor, depositor_private_key)) assert int(deposit_deploy_hash, 16), "The deposit command did not output a hex encoded deploy hash. The output was {}".format(deposit_deploy_hash) wait_for_successful_deploy(deposit_deploy_hash) + # 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)) - assert "ok\n" in transfer_output + 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